这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【瑞萨BLE/WIFI模块测评】环境搭建

共1条 1/1 1 跳转至

【瑞萨BLE/WIFI模块测评】环境搭建

菜鸟
2026-09-19 17:31:53     打赏

申请的瑞萨无线模块到了,RA4M2 板子 + WiFi 模块 + 蓝牙模块一共三个。

image.png

环境 Zephyr 4.4.99,out-of-tree 的 board 定义在 my_boards/,应用在 my_app/。起点 RA6M4,目标接一块 DA16200 WiFi 模组,先跑通最基础的 AT 握手,通了再往 RA4M2 迁。

DA16200 的接口就是普通 UART 加一个 RESET 脚,插 pmod2 上,UART 和 RESET 在同一排针,一插就完事。dts 里 pmod2 映射现成,sci0 也配好了,TXD0 走 P411、RXD0 走 P410。RESET 用 zephyr,user 节点声明成 DA16200 的 P608、低有效:

reset-gpios = <&ioport6 8 GPIO_ACTIVE_LOW>;

测试逻辑:复位模组 → 等半秒 → 发 AT → 死循环把收到的字节从 console 打出来。console 走 uart9,模组走 uart0,两路分开。

编译烧录,插上模组,开串口。结果串口时好时坏,纯随机,OK 有时出来有时一个字节都没有。软件侧波特率、日志子系统、模组启动时间挨个排查都不是根因,最后拿板子时手碰到排针,一按就有输出、一松就不一定。起点板子排针虚焊。不纠缠,直接换 RA4M2 来测——芯片丝印看着就差一个型号,照现成的 my_ra6m4 抄一份 my_ra4m2,改改 SoC 相关、引脚对一下就行。当时是真这么想的。

抄一份 my_ra4m2

新板子芯片 R7FA4M2AD3CFP,LQFP100,512KB flash / 128KB RAM。两块板子外形基本一样,似乎连引脚和走线都一样?所以我直接复用,board.yml 改 soc 名,board.cmake 改 pyOCD target,my_ra4m2.yaml 里 RAM/flash 从 256/1024 改成 128/512。

image.png

时钟这块是后面所有痛苦的源头。看板上晶振,判断是 12MHz

image.png

RA4M2 的时钟约束不用翻 PDF,FSP 的 bsp_feature.h 里全是明文宏:

#define BSP_FEATURE_CGC_PLL_INPUT_POST_DIV_MAX_HZ  (24000000UL)
#define BSP_FEATURE_CGC_PLL_INPUT_POST_DIV_MIN_HZ  (8000000UL)
#define BSP_FEATURE_CGC_PLL_OUT_MAX_HZ             (200000000UL)
#define BSP_FEATURE_CGC_PLL_OUT_MIN_HZ             (100000000UL)

拿 12MHz 去凑,想凑到正好 100MHz 需要非整数倍频,凑不出来,于是给自己的结论是 96MHz 是 12MHz 晶振能跑到的最高干净频率。推理链条一点毛病没有,前提错了而已。写进 dts:

&pll {
    clocks = <&xtal>;
    div = <1>;
    mul = <16 0>;
    status = "okay";
};

这里有个 SYS_CLOCK_HW_CYCLES_PER_SEC 的坑躲过去了。抄 defconfig 时看到 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=192000000,直觉是改成 96000000。但翻 SoC 层 Kconfig:

DT_ICLK_PATH := $(dt_nodelabel_path,iclk)
config SYS_CLOCK_HW_CYCLES_PER_SEC
    default $(dt_node_int_prop_int,$(DT_ICLK_PATH),clock-frequency)

这个值是从 devicetree 的 iclk 节点 clock-frequency 自动推导的。在 defconfig 硬写就是埋雷:哪天改了 PLL 忘了同步,两边对不上,不会有任何报错,只会所有基于 tick 的延时全部跑偏。正确做法是 defconfig 一个字不写,在 dts 里覆盖 &iclk(SoC dtsi 把它写死成 100000000,不覆盖就错):

&iclk {
    clock-frequency = <96000000>;
    div = <2>;
    status = "okay";
};

值本身后面被证明是错的,但机制对——后面改晶振只需动 dts 一处。

Flash 分区也要重算。RA 的 code flash 是两段不同 block 大小拼起来的,两颗芯片结构一样只是 32K 那段短了:

RA6M4 (1MB):   8×8K = 64K @ 0x00000   +  30×32K = 960K @ 0x10000
RA4M2 (512K):  8×8K = 64K @ 0x00000   +  14×32K = 448K @ 0x10000

MCUboot 的 swap-using-move 要求两个 slot 落在均匀 block 区,所以 mcuboot 塞进前 64K 小块区,剩下 448K 给两个 slot 平分:

boot_partition:  reg = <0x0      DT_SIZE_K(64)>;   /* mcuboot */
slot0_partition: reg = <0x10000  DT_SIZE_K(224)>;  /* image-0, 7×32K */
slot1_partition: reg = <0x48000  DT_SIZE_K(224)>;  /* image-1, 7×32K */

ioport 我把 0~7 全 enable 了(上游 ek_ra4m2 只开 0 和 4),想着开着不占资源以后接东西方便——这个决定后面救了我一次。

编过了,烧进去,一片死寂

先拿 hello_world 试水:

west build -p always -b my_ra4m2 zephyr/samples/hello_world \
    -- -DBOARD_ROOT=/home/l/zephyrproject/my_boards

编过,FLASH 4.19%,回读确认 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=96000000 和 dts 一致,console 挂 sci9,分区都对。烧录成功没有任何报错,打开串口,什么都没有——不是乱码,是一个字节都没有,连 Zephyr 那行 banner 都没有。刚从虚焊板子逃出来,新板子直接一个字不吐。

串口没输出时最怕分不清「没跑起来」和「跑起来了但串口不通」,这两个方向差十万八千里。好在 SWD 通着(烧录都成功),直接从调试口看 MCU 在干什么:

pyocd commander -t r7fa4m2ad -c "halt" -c "reg pc"
# pc = 0xeffffffe

pc = 0xeffffffe 不是随机垃圾,Cortex-M 进入 LOCKUP 状态时 PC 就读作这个哨兵值。status 也直说 Lockup [Secure]。LOCKUP 的意思是程序出错触发 fault → 升级成 HardFault → handler 里又出错 → 内核彻底放弃停止取指。所以不是没跑,是跑了几步撞死了,而且死在 console 初始化之前,连 banner 都没有。

读 fault 状态寄存器(SCB 这几个地址是死的,不随芯片变):

pyocd commander -t r7fa4m2ad -c "halt" \
  -c "read32 0xE000ED28" -c "read32 0xE000ED2C"
# e000ed28: 01000001   CFSR
# e000ed2c: 40000000   HFSR

CFSR = 0x01000001,bit0 是 IACCVIOL(取指访问违例),bit24 是 UNALIGNED;HFSR bit30 是 FORCED,说明是从某个 fault 升级来的。这里差点被带沟里:MMFAR/BFAR 都读出 0x20001c14,离 SP 很近,特别像栈溢出。但 CFSR 的 MMARVALID/BFARVALID 位都是 0,这两个地址寄存器里是陈旧残留值,压根不该看。真正的线索只有一条:IACCVIOL,取指失败。

顺着 SWD 又查了三条线索,全是死路:TrustZone 跨域取指(查 DSCSR/SAU 发现 CPU 就在 Secure 域、SAU 没使能,不存在跨域)、固件没烧到 0x0(hex 里初始 MSP 0x20001C30 和实测 SP 0x20001C00 正好差一个栈深,向量表确实生效了)、option bytes 设错(OFS0 全 1、无 block 保护,构不成 IACCVIOL)。猜了三轮全错。

停止猜测,二分

改做实验,每步只回答一个二选一。先烧 Zephyr 原版空 prj.conf 的 hello_world,判断标准直接用 SWD 读 PC 不依赖串口:

# pc = 0xeffffffe, CFSR = 01000001 —— 和我的 app 一模一样

和 app 无关,锅在 board 定义上。范围缩一半。

决定性一步:烧上游的 ek_ra4m2 到我这块板子。上游是官方维护的 board 定义,能跑就证明硬件、工具链、芯片状态都没问题,问题 100% 在我的 dts;也挂就是硬件层面的事。一步同时排除三个方向。

west build -p always -b ek_ra4m2 zephyr/samples/hello_world -d .../b_ek
pyocd flash -t r7fa4m2ad .../b_ek/zephyr/zephyr.hex
# Core 0 (Cortex-M33): Sleeping [Secure]
# pc = 0x00003d2a, CFSR = 0

Sleeping,CFSR 干净,进 idle 了。硬件、工具链、芯片都没问题,就是我写的 dts 有问题。

范围缩到「我的 dts 和上游的差异」里。ioport/console/分区都很难和取指失败扯上关系,盯着时钟看。两边差异:我的 xtal 12MHz、pll div=1 mul=16,上游 xtal 24MHz、pll div=3 mul=25。先读 PLLCCR 确认两边配置都真写进了硬件:

我的:   PLLCCR = 0x1F00  ->  mul=16, div=1   (和我写的对上)
上游:   PLLCCR = 0x3102  ->  mul=25, div=3   (和上游对上)

两边配置都正确写进了硬件,那问题只可能出在唯一没法直接测的量上——输入频率。把「晶振到底多少」当未知数,代进去看哪种假设能同时解释两个实验结果:

假设晶振上游 ÷3 ×25我的 ÷1 ×16
24 MHz输入 8MHz→输出 200MHz→ICLK 100MHz ✔能跑输入 24MHz→输出 384MHz→ICLK 192MHz ✘超频 92%
12 MHz输入 4MHz✘低于下限→ICLK 50MHz 超规格输入 12MHz→输出 192MHz→ICLK 96MHz ✔应该能跑

实测事实是上游能跑、我的 LOCKUP。24MHz 那行预测「上游能跑、我的爆炸」和现实完全一致,12MHz 那行完全相反。晶振是 24MHz,一开始看图看错了。真相也就清楚了:24MHz 配我那套 ÷1 ×16,PLL 输出 384MHz、ICLK 192MHz,CPU 跑在近两倍额定频率,从 flash 取指失败,表现为 IACCVIOL → HardFault → LOCKUP,发生在任何 console 输出之前。

dts 改成和上游一致:

&xtal {
    clock-frequency = <DT_FREQ_M(24)>;
    status = "okay";
};
&pll {
    clocks = <&xtal>;
    div = <3>;
    mul = <25 0>;
    status = "okay";
};
&iclk {
    clock-frequency = <100000000>;
    div = <2>;
    status = "okay";
};

这个时钟树同时卡在三条限制线上(PLL 输入 8MHz 下限、输出 200MHz 上限、ICLK 100MHz 器件最大值),一点余量没有但每条都合规,这就是 RA4M2 配 24MHz 晶振的标准解。重编,CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC 自动从 96000000 变成 100000000,defconfig 一个字没改。烧进去:

# Core 0 (Cortex-M33): Sleeping [Secure]
# pc = 0x00003a46
# e000ed28: 00000000   CFSR 一个 fault 都没有

LOCKUP 没了。

编译真正的 app,又炸了

hello_world 通了,烧我自己的 DA16200 程序,一屏 gcc 宏展开报错:

error: '__device_dts_ord_..._reset_gpios_IDX_0_PH_ORD' undeclared
error: 'DT_N_S_zephyr_user_P_reset_gpios_IDX_0_VAL_pin' undeclared

翻译成人话就一句:zephyr,user 节点里没有 reset-gpios 属性。抄 dts 时把这个节点留空了。引脚不用猜,白纸黑字写在我自己的注释里:RESET = P608、低有效。加进 dts:

zephyr,user {
    /* DA16200 WiFi module RESET, P608, active low. */
    reset-gpios = <&ioport6 8 GPIO_ACTIVE_LOW>;
};

这时候前面「ioport 0~7 全 enable」的决定救了我一次——上游只开了 0 和 4,要是照抄,这里还得再补一处 &ioport6 { status = "okay"; };,而且大概率要再编一轮才发现。重编过了,Sleeping,正常。

AT 通了,但又时好时坏

打开串口,终于看到东西:

app start
*** Booting Zephyr OS build v4.4.0-8295-g0bd807ceeb75 ***
OK

OK 出来了,AT 回了。然后按一下复位,没输出;再烧一遍还是没有;过一会儿又有了。又是时好时坏。

但这次手里有 RA6M4 那次没有的东西——完整的 SWD 体检报告。时钟对、引脚 mux 对、UART 使能对、波特率误差 0.014%,而且刚才 OK 真出来过。代码是对的、硬件刚体检过,问题只能在运行时序和观测方式上。看主循环就明白了:

while (1) {
    if (uart_poll_in(uart, &c) == 0) printk("%c", c);
    k_msleep(1);
}

只在 DA16200 发数据时才打印。模组回完一次 OK 就不再主动说话,屏幕当然停住。「程序死了」和「程序活着但没新数据」这两种在屏幕上长得一模一样。加一行无条件的心跳打印,一秒钟就能分清是哪种。

真正的原因是这条开机消息暴露的:

*** Booting Zephyr OS build v4.4.0-8295-g0bd807ceeb75 ***
+INIT:DONE,0

+INIT:DONE,0 不是我发 AT 换来的,是 DA16200 开机完成后主动吐的,,0 表示正常上电。MCU 上电几十毫秒就跑到 main 发 AT,而 DA16200 要几百毫秒到一秒多才启动完,AT 发过去时它还没醒,自然不回;之后模组也不主动说话,屏幕就空白。偶尔能看到 OK 是时序碰巧对上了。那个 k_msleep(500) 是拍脑袋写的,对 DA16200 有时够有时不够。

解法是先等它说完话再问:上电后先把模组主动吐的东西全收出来,等它稳定(简单起见等 2 秒收完开机信息)再发 AT。

printk("waiting for DA16200 boot...\n");
int64_t end = k_uptime_get() + 2000;
unsigned char c;
while (k_uptime_get() < end) {
    if (uart_poll_in(uart, &c) == 0) printk("%c", c);
}
/* 收完开机信息,模组已稳定,再发 AT */
send_str("AT\r\n");

至此环境搭通,RA4M2 上 DA16200 的 AT 握手稳定了。



共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]