上一篇把 RA4M2 的板子定义从头抄了一遍,栽在晶振上(以为 12MHz 实际 24MHz,超频到 192MHz 直接 LOCKUP),最后 DA16200 的 WiFi 从 AT 握手跑到 TCP 收发。这篇换蓝牙模块。

本来以为这是最轻松的一篇——板子定义现成、Pmod 座现成、串口现成,把 WiFi 模块拔下来插上 BLE 模块就完事。结果是目前为止最曲折的一次
接线不用动,两个模块都是 Pmod 插同一个座。先翻 Zephyr 对这颗芯片的支持:
$ grep -rn "DA1453" zephyr/drivers/bluetooth/hci/Kconfig 208:config BT_DA1453X 209: bool "DA1453x HCI driver" 211: depends on DT_HAS_RENESAS_BT_HCI_DA1453X_ENABLED $ grep -rln "bt-hci-da1453x" zephyr/boards/ boards/shields/renesas_us159_da14531evz/renesas_us159_da14531evz.overlay
驱动有,连 shield 定义都有——瑞萨官方的 US159-DA14531EVZ,Zephyr 上游直接支持。我当时很高兴,觉得这篇五分钟就能写完:挂 shield,CONFIG_BT=y,跑 beacon 示例,收工。然后看到 shield 文档里这句:
The DA14531 Module contained on the shield must be programmed with a binary file that supports the HCI interface over UART, with hardware flow control enabled.
出厂固件不是 HCI,得自己用 SmartBond Flash Programmer 经 SWD 重烧。那就先探一探模块现在跑的到底是什么。两种可能一次问清:AT 固件会回 OK(ASCII),HCI 固件会回 Command Complete 事件(二进制)。写个探测程序,发 AT\r\n,再发 HCI Reset 的 01 03 0C 00,收到什么全按 hex + ASCII 打出来:
[1] boot output (module talking on its own, 3s) 2 bytes (framing/overrun errors: 1) 0000 00 00 |..| [2] CodeLess AT probe: "AT\r\n" <nothing> [3] HCI probe: 01 03 0c 00 (HCI_Reset) <nothing>
两个都静默。开机那 2 个字节 00 00 还伴随 1 个 framing error,是复位释放瞬间电平跳变造出来的假字节,不是数据。
引脚电平:一个我自己挖的坑
协议层问不出东西,往下走一层直接量引脚。UART 空闲时 TX 必须是高电平,所以只要模块 TX 脚是高的,至少说明串口初始化过了。读 PIDR(端口输入数据寄存器,即使引脚被复用成外设功能也反映真实电平):
40080086: ffff
Port4 全高,包括模块 TX 所在的 P410。但这个读数一文不值——悬空的引脚配上 MCU 内部上拉也是高,「高电平」这个事实同时兼容「模块在驱动它」和「根本没人管它」两种情况。要分开得用内部上下拉去逼它:外部有低阻驱动的话内部上下拉都拗不过,pd 和 pu 读数相同;没有外部驱动的话引脚老实跟着内部走,pd=0 pu=1。
gpio_pin_configure(port4, 10, GPIO_INPUT | GPIO_PULL_DOWN); k_msleep(5); vd = gpio_pin_get_raw(port4, 10); gpio_pin_configure(port4, 10, GPIO_INPUT | GPIO_PULL_UP); k_msleep(5); vu = gpio_pin_get_raw(port4, 10);
跑出来:
P410 as input, no pull : 0 P410 with pull-down : 0 P410 with pull-up : 1
悬空特征,上拉能拉高、下拉能拉低,对端一个驱动源都没有,第三行还顺手证明了引脚本身是好的、方法有效。我以为模块 UART 从未初始化,TX 高阻,和「flash 里没有固件」完全吻合,需要重烧。
这个结论是错的。
后来翻别人的测评帖,看到这么一句:BLE 模块中需要将 LPR 引脚拉高才能正常工作。同时找到模块原理图,J1 是 2x6 排针:
1 BOOT 7 CONNECT 2 RXD 8 RESET(经 SS8550) 3 TXD 9 LPR 4 NC 10 NC 5 GND 11 GND 6 GND 12 VCC
Pin9 = LPR。LPR 不拉高模块根本不跑,不跑 UART 就不初始化,不初始化 TX 就是高阻。我量到的一切都对,只是那个「因」我压根没想到:
LPR 悬空 → 模块不启动 → UART 不初始化 → TXD 高阻 → 我量到悬空 → 判定"没烧固件" → 冤案
「引脚没被驱动」是个现象不是原因,它至少有没供电、没使能、没固件三种成因,我当时只想到最后一种,因为那是文档里刚提醒过我的那种。刚读过的东西最容易成为默认答案。
接下来把 LPR 映射到 RA4M2 的脚上。我的 pmod2 映射从 my_ra6m4 继承:Pin2(RXD)=P411 正好是 sci0 的 TXD0,Pin3(TXD)=P410 正好是 RXD0,Pin8(RESET)=P608 正好是 DA16200 那个复位脚,三处独立吻合映射可信;LPR 落在 P609,正是我一直空着没接的那根。overlay 补上,串口一根线都不用改:
zephyr,user {
lpr-gpios = <&ioport6 9 GPIO_ACTIVE_HIGH>; /* Pmod Pin 9, P609 */
};拉高 LPR,重新扫一遍全部 Pmod 脚:
[diag] which Pmod pins is the module driving? LPR Pin9 P609 (we drive it high): reads 1 method check on P411: forced low=0 high=1 -> GPIO owns the pin, pull results valid Pin1 P413 expect BOOT pd=0 pu=1 -> floating Pin2 P411 expect mod RXD pd=1 pu=1 -> DRIVEN HIGH Pin3 P410 expect mod TXD pd=1 pu=1 -> DRIVEN HIGH Pin4 P412 expect NC pd=0 pu=1 -> floating Pin7 P414 expect CONNECT pd=0 pu=0 -> DRIVEN LOW Pin8 P608 expect RESET pd=1 pu=1 -> DRIVEN HIGH Pin10 P610 expect NC pd=0 pu=1 -> floating
P410 从「悬空」变成「DRIVEN HIGH」,同一块板子、同一根线、同一段测试代码,只多拉高了一个脚。
CONNECT 被驱动低(未连接)、RESET 被 R1 上拉到高、TXD 空闲高,模块通电、固件在跑、UART 初始化完毕。然后我满怀信心发了 AT,没有回应。
从空口那边看
AT 不回,波特率全扫一遍(115200 / 230400 / 460800 / 57600 / 38400 / 19200 / 9600)也不回。卡住了。这时候想起来这是个蓝牙模块,它有第二条链路——串口问不出来的事可以从空口问。电脑上就有蓝牙:
$ timeout 30 bluetoothctl --timeout 22 scan le [NEW] Device E0:E8:F6:3F:A4:C1 superflyBLE
superflyBLE 是 DA14531 出厂默认设备名,模块在正常广播。到这一步嫌疑范围瞬间从「整个模块」缩到「UART 这一层」——一条命令砍掉一半可能性。顺手把 GATT 也枚举了:
service0001 00001800 (GAP) service0006 00001801 (GATT) service000a 866d3b04-e674-40dc-9c05-b7f91bec6e83 ← CodeLess char000b 914f8fb9-e8cd-411d-b7d1-14594de45425 (write) char000d 3bb535aa-50b2-4fbe-aa09-6b06dc59a404 (notify) service0010 0000fd00
CodeLess 服务在,而且有一对读写特征。CodeLess 是双向的,BLE 那头也能灌数据进去
先做个验证。CodeLess 在 BLE 连上时会把 CONNECT 脚拉起来,我让 MCU 监控这几个脚,同时用电脑去连它:
[ 9] P410 edges=0 P411 edges=0 CONN=0 [ 10] P410 edges=0 P411 edges=0 CONN=1 ← 电脑连上 [ 11] P410 edges=0 P411 edges=0 CONN=1 [ 14] P410 edges=0 P411 edges=0 CONN=1 [ 15] P410 edges=0 P411 edges=0 CONN=0 ← 断开
CONNECT 脚精确跟随 BLE 连接状态。空口上那个 superflyBLE 就是我手上这块、Pmod 映射 Pin7=P414 正确、模块固件功能完全正常
但 P410 和 P411 在上面那轮里 edges 全是 0,而前面 pd/pu 测试说这两个脚都是 DRIVEN HIGH。这里发现 pd/pu 这把尺子有个盲区:模块的输出脚空闲高,读数 pd=1 pu=1;模块的输入脚带 10K 上拉,读数也是 pd=1 pu=1,两者完全一样。Pin8 那个 DRIVEN HIGH 就是 R1 的 10K 上拉造出来的,已经现身说法了。UART 引脚带上拉太常见,所以我根本分不清 Pin2/Pin3 谁是输出。
从 BLE 那头灌数据,CodeLess 是透传,BLE 写进去的字节会从 UART 吐出来。于是 MCU 把 P410、P411 都配成普通输入死循环采样 PIDR 数异或跳变,电脑连上 BLE 往 914f8fb9 这个 write 特征里写 AT\r\n:
[ 92] P410 edges=0 P411 edges=0 CONN=1 ← 连上 [ 93] P410 edges=26 P411 edges=0 CONN=1 ← ★ 我写入的瞬间 [ 94] P410 edges=0 P411 edges=0 CONN=1 [ 97] P410 edges=0 P411 edges=0 CONN=0 ← 断开
P410 翻转 26 次,P411 纹丝不动。P410 就是模块的 TXD,原理图标注正确、映射从头到尾都对,而且接收方向是通的
用一段已知的字节去量波特率
既然模块能按我的指令从 TX 吐出我指定内容的数据,那就有了一个标准信号源。拿它标波特率:让 BLE 那头反复写 AT\r\n,MCU 这头每 6 秒换一个波特率接收,波特率对解出来就是干净的 AT\r\n,不对就是一堆带 framing error 的垃圾。
--- now listening at 460800 baud --- [ 19] 27 bytes, 18 errors | ascii: x....x.x....x.x.x.x.x....x. | hex: 78 00 f8 00 1e 78 fe ... --- now listening at 115200 baud --- [ 21] 8 bytes, 0 errors | ascii: AT\r\nAT\r\n | hex: 41 54 0d 0a 41 54 0d 0a [ 22] 4 bytes, 0 errors | ascii: AT\r\n | hex: 41 54 0d 0a [ 23] 8 bytes, 0 errors | ascii: AT\r\nAT\r\n --- now listening at 57600 baud --- [ 24] 2 bytes, 0 errors | ascii: .. | hex: 06 c2 --- now listening at 38400 baud --- [ 27] 2 bytes, 1 errors | ascii: j. | hex: 6a ff --- now listening at 230400 baud --- [ 36] 8 bytes, 6 errors | ascii: ..`f.... | hex: 06 98 60 66 1e 18 06 fe
115200 零错误完美解码,其余全是垃圾。这比查手册可靠,波特率是量出来的不是查出来的。
这里有个 RA 特有的坑必须交代。RA 的 SCI 一旦 ORER(溢出)置位接收器会直接停摆,而 Zephyr 的 uart_ra_sci_poll_in() 只看 RDRF、只读 RDR,从来不清 ORER/FER/PER:
static int uart_ra_sci_poll_in(const struct device *dev, unsigned char *c)
{
...
if (... cfg->regs->SSR_b.RDRF == 0U) {
return -1;
}
*c = ... cfg->regs->RDR;
return 0;
}波特率错的时候 framing error 成片来,不清标志的话采集几毫秒就彻底聋了,恰好在诊断最需要它的时候。得自己按硬件要求的「先读后写 0」清掉:
#define SCI0_SSR (*(volatile uint8_t *)(DT_REG_ADDR(DT_NODELABEL(sci0)) + 0x04))
#define SSR_ERR_MASK 0x38U /* ORER | FER | PER */
static void clear_rx_errors(void)
{
uint8_t s = SCI0_SSR;
if (s & SSR_ERR_MASK) {
SCI0_SSR = (uint8_t)(s & ~SSR_ERR_MASK);
}
}另一个坑:CONFIG_UART_INTERRUPT_DRIVEN 必须关。板子 defconfig 默认开着,它会把 SCI0 的 ERI(错误中断)挂进 NVIC,一个 framing error 进来 ERI 触发,驱动因为没注册回调不清标志,中断立刻重入,CPU 卡死在 _isr_wrapper 里回不到 main。第一次遇到时现象是「程序跑到一半不动了」,halt 下来 PC 在 _isr_wrapper,NVIC 的 IABR0 是 0x08 正好是 SCI0 的第 4 个中断也就是 ERI。中断风暴的排查路子就是读 ICSR 的 VECTACTIVE 和 NVIC 的 active 位,能直接点名。
反向再验一次
接收通了,发送呢?这个也能用 BLE 验,CodeLess 双向,UART 灌进去的数据会转发到 BLE。让 MCU 每轮从 UART 发一句自带标识的 MCU2BLE\r\n,电脑这头订阅 notify 特征等着:
[CHG] Attribute .../service000a/char000d Value: 4d 43 55 32 42 4c 45 0d 0a MCU2BLE.. 4d 43 55 32 42 4c 45 0d 0a MCU2BLE.. 4d 43 55 32 42 4c 45 0d 0a MCU2BLE.. ...(连收 10 次)
ASCII 完整无损连收十次。到这里 UART 两个方向、波特率、引脚映射、模块健康状况全部有实测证据,而我发的 AT 它就是不回。
它根本不认 AT
最后一个嫌疑犯是我自己的代码:前面几轮 AT 全是在波特率扫描里发的,每次发之前都调过 uart_configure(),而那次成功的 MCU2BLE 用的是 DT 默认配置没碰过 uart_configure。会不会是它把什么搞坏了?把扫描拆掉,用 DT 默认的 115200 直接发,顺便把能想到的格式都试一遍:
[ 1] >>> AT\r\n - [ 2] >>> ATI\r\n - [ 3] >>> AT+GAPSTATUS\r\n - [ 4] >>> ATE1\r\n - [ 5] >>> AT\r - [ 6] >>> AT\r\n -
全部无响应,uart_configure 洗清嫌疑。这块模块出厂固件不解析 UART 侧的 AT 命令,它就是个纯透传。BLE 服务里确实枚举出了 CodeLess,但 UART 进去的字节只做转发不当命令。别人的帖子里能发 AT+NAME= 改名,说明存在响应 AT 的固件版本,只是和我手上这块烧的不是同一个。要 AT 控制,得用 SmartBond Flash Programmer 经 Pmod 板上的 SWD 头重烧。
最终测试结果:
| 设备名 / 地址 | superflyBLE / E0:E8:F6:3F:A4:C1 |
| 串口参数 | 115200 8N1(实测标定) |
| BLE 广播 | ✅ |
| BLE 连接 / 断开 | ✅ CONNECT 脚同步翻转 |
| GATT 服务 | ✅ GAP / GATT / CodeLess / 0xFD00 |
| 透传 BLE→UART | ✅ 零 framing error |
| 透传 UART→BLE | ✅ 连收 10 次无损 |
| UART 侧 AT 指令 | ❌ 全格式无响应 |
我要赚赏金
