eefocus_3914144 发表于 2026-9-11 15:30:27

DA16200 WiFi 模块接入 RA4M2:MQTT 订阅查询 → 应答当前温湿

# DA16200 WiFi 模块接入 RA4M2:MQTT 订阅查询 → 应答当前温湿度(移植分享)

!(https://www.eefocus.com/forum/data/attachment/forum/202609/11/152700gn8lvv68878vl2ns.png)

!(https://www.eefocus.com/forum/data/attachment/forum/202609/11/153019nnznndb5nnceen5n.png)



> 目标:在上一篇(小米温湿度上屏)的基础上,把 DA16200 WiFi 模块插到 **PMOD1**,实现**远程查询-应答**:任何人向 MQTT 主题 `sensor/ra4m2/query` 发一条消息,板子立刻把当前读到的小米温湿度以 JSON 发到 `sensor/ra4m2/temperature`。
> 本文是移植记录:驱动来自我之前的 RA6M4 工程(`D:\RA\ra6m4\ra6m4_sht30_da16200`),但这次 RA4M2 用的是 **PMOD1/SCI4**(RA6M4 用的是 PMOD2/SCI0),并且**新增了 MQTT 订阅接收**功能(RA6M4 版本只会定时发布)。
>
> 环境:Zephyr 4.4.0 / RA-Eco-RA4M2-100PIN / DA16200MOD PMOD 模块 / J-Link RTT 日志。

实测效果(PC 端 paho-mqtt 测试脚本视角):

--> publish query #0            # PC 向 sensor/ra4m2/query 发 "get"
RECV sensor/ra4m2/temperature: {"temperature":22.5,"humidity":66.8,"battery":0}

```
板端 RTT 日志(同一时刻):
```

MQTT query: get
MQTT publish {"temperature":22.5,"humidity":66.8,"battery":0} rc=0

```

## 1. 总体结构
```

小米温湿度计 ))) BLE 广播 ((( DA14531(PMOD2,HCI 固件)
│ SCI0 UART
RA4M2(Zephyr:BLE observer + MiBeacon 解密 + 本应用)
│   │
SCI3 I2C ▼   ▼ SCI4 UART(115200,AT 指令)
OLED          DA16200(PMOD1)── MQTT broker(epiwendo.com:1883)
▲            ▲
订阅 sensor/ra4m2/query ┘            └ 发布 sensor/ra4m2/temperature

```
三个模块各占一个 SCI,互不冲突:BLE=SCI0(PMOD2)、OLED=SCI3(I2C 插座)、WiFi=SCI4(PMOD1)。

## 2. 硬件连接(PMOD1)

模块是资料包里"版本 B"仿板(交错编号),与 RA6M4 时一样**用杜邦线跳接**,不是直插:

| PMOD1 引脚 | MCU 端口 | 跳线到模块引脚 | 信号 |
|---|---|---|---|
| 2 | P205(TXD4) | 模块 3 | TXD_HOST(主机 TX → 模块 UART RX) |
| 3 | P206(RXD4) | 模块 5 | RXD_HOST(主机 RX ← 模块 UART TX) |
| 8 | P403 | 模块 4 | RESETn(低有效,注意有 0.47µF 电容,脉冲要够长) |
| 9 | P402 | 模块 6 | RTC_WAKE_UP(高有效脉冲,唤醒 DPM 睡眠) |
| 5/11 | GND | 模块 9/10/11 | GND |
| 6/12 | 3.3V | 模块 12 | VCC |

⚠️ 资料包里有两版原理图,TX/RX 位置不同(版本 A 在 2/3,版本 B 在 3/5),接线前用万用表核对实物,详见 `docs/04-da16200-wifi-module.md`。

## 3. 从 RA6M4 移植:改了什么

| 项 | RA6M4 版本 | RA4M2 本版 |
|---|---|---|
| UART | sci0/uart0(P410/P411,PMOD2) | **sci4/uart4(P205/P206,PMOD1)** |
| 控制 GPIO | P608/P609/P414(PMOD2) | **P403(RESETn)/P402(WAKEUP,PMOD1 pin8/9)** |
| SNTP 对时 | 有(滚动时钟用) | 删掉(本应用不需要) |
| MQTT 订阅 | 无 | **新增:`AT+NWMQTS` + `+NWMQMSG` 异步上报扫描** |
| UART 并发 | 裸奔(主线程发布 + 管理线程巡检共用) | **加互斥锁 `at_mutex` 串行化所有 AT 事务** |

驱动文件:`src/drivers/da16200.{c,h}`,加上一个极简 devicetree binding(`dts/bindings/wifi-da16200.yaml`,只描述 reset/wakeup 两个 GPIO)。

## 4. 驱动架构(Zephyr 侧)
```

```
┌── UART ISR → ring buffer(512B)──┐
```

DA16200 ═UART╡                                  ├─ read_resp()(等 AT 应答)
└─────────────────────────────────┘
│ 所有取出的字节都过 scan_bytes()

行扫描器:抓 "+NWMQMSG:<payload>,<topic>,<len>"

incoming_pending 标志 → da16200_mqtt_incoming()

conn_mgr 线程(优先级 12):
离线 → 复位+握手+连 AP+MQTT 配置(含订阅)→ 在线
在线 → 每 100ms 清一次 ring buffer(抓异步 +NWMQMSG),每 5s AT+WFJAP=? 巡检,掉线自动重来

```
关键点:

- **订阅要在连接前**:`AT+NWMQTS=1,<topic>` 必须在 `AT+NWMQCL=1` 之前下发,模块是在打开 broker 连接那一刻读取订阅列表的(UM-WI-003 §9.1.1)。
- **异步消息会被 rx_flush 丢掉**:收到订阅消息的时机不可控,如果它恰好在某条 AT 命令发出前的清空阶段到达,朴素实现会直接丢弃。本驱动把 ring buffer 的**每一个出口**(read_resp、rx_flush、在线空闲轮询)都接上同一个行扫描器,保证 `+NWMQMSG` 一定被捕获。
- **互斥锁**:主线程(发布)和 conn_mgr 线程(巡检)都会碰 UART、ring buffer 和 resp 缓冲,`at_mutex` 把所有 AT 事务串行化。RA6M4 版本没加锁,靠时序侥幸没撞车,这次补上。
- 回复格式:订阅消息上报为 `+NWMQMSG:<消息>,<主题>,<长度>`,消息本身可能含逗号,所以**从右往左**取最后两个逗号字段(主题、长度),前面整体是 payload。

## 5. AT 指令序列(连 AP + MQTT 订阅)

| 步骤 | 指令 | 说明 |
|---|---|---|
| 唤醒+硬复位 | RTC_WAKE_UP 高脉冲 → RESETn 拉低 200ms | 模块没事干会进 DPM 睡眠(UART 关闭) |
| 握手 | `AT` | 不要用 `ATZ`(会重启,重启窗口内发命令必乱码) |
| 清 NVRAM | `AT+DEFAP` | 模块重启进 Soft-AP 默认模式 |
| Station | `AT+WFMODE=0` → `AT+RESTART` | |
| 连 AP | `AT+WFJAPA='<ssid>','<psk>'` | 等 `+WFJAP:1`;IP 用 `AT+NWIP=?` 另查 |
| MQTT 配置 | `AT+NWMQBR=host,port` `AT+NWMQQOS=0` `AT+NWMQLI=user,pw` `AT+NWMQCID=id` | |
| **订阅** | `AT+NWMQTS=1,sensor/ra4m2/query` | **必须在 NWMQCL 之前** |
| 连接 | `AT+NWMQCL=1` | |
| 发布 | `AT+NWMQMSG='<json>',<topic>` | JSON 含逗号/双引号,**整条必须单引号包裹** |

## 6. Devicetree(overlay 新增部分)

```dts
/* DA16200 WiFi 模块插 PMOD1(SCI4):pin2 P205(TXD4)-> 模块 RXD,
* pin3 P206(RXD4)<- 模块 TXD,115200 8N1,无流控。
* 控制脚用杜邦线:pin8 P403 -> RESETn(低有效),pin9 P402 -> RTC_WAKE_UP。 */
&pinctrl {
        sci4_default: sci4_default {
                group1 {
                        psels = <RA_PSEL(RA_PSEL_SCI_4, 2, 5)>,
                                <RA_PSEL(RA_PSEL_SCI_4, 2, 6)>;
                };
        };
};

&sci4 {
        pinctrl-0 = <&sci4_default>;
        pinctrl-names = "default";
        status = "okay";

        uart4: uart {
                current-speed = <115200>;
                status = "okay";
        };
};

/ {
        wifi_da16200: wifi-da16200 {
                compatible = "renesas,da16200";
                reset-gpios = <&ioport4 3 GPIO_ACTIVE_LOW>;
                wakeup-gpios = <&ioport4 2 GPIO_ACTIVE_HIGH>;
        };
};
```

`prj.conf` 只需在原有 BLE 配置上追加:

```
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
```

## 7. 应用层:收到查询 → 发布当前温湿度

`src/main.c` 主循环里新增(温湿度是 BLE 侧解密后缓存在 `last` 里的):

```c
/* WiFi 后台任务:连 AP、起 MQTT(含订阅),掉线自动重连 */
        da16200_start();

        while (1) {
                ...
                char query;

                if (da16200_mqtt_incoming(query, sizeof(query))) {
                        printk("MQTT query: %s\n", query);
                        if (last.has_temp || last.has_hum) {
                                char payload;
                                snprintf(payload, sizeof(payload),
                                       "{\"temperature\":%d.%d,\"humidity\":%u.%u,\"battery\":%u}",
                                       ...);
                                int rc = da16200_mqtt_publish(DA16200_MQTT_TOPIC, payload);
                                printk("MQTT publish %s rc=%d\n", payload, rc);
                        }
                }
        }
```

WiFi 账号、broker、主题都在 `src/drivers/da16200.h` 顶部的宏里,换环境只改那里。

## 8. 验证方法

PC 端测试脚本 `tools/mqtt_query_test.py`(paho-mqtt):订阅 `sensor/ra4m2/temperature`,然后往 `sensor/ra4m2/query` 发 "get",8 秒内等到回复即成功。

启动日志(RTT):

```
*** Booting Zephyr OS build v4.4.0-24-g58069e646a84 ***
app start
oled init ret=0
i2c scan: 0x3c ACK
mibeacon init ret=0
scan start ret=0
da16200: connected, IP 192.168.3.233
da16200: MQTT ready (sub sensor/ra4m2/query)
```

## 9. 踩坑记录

1. **环境里邻居的 MiBeacon 设备会捣乱**:调试时发现一台 MAC `08:7C:BE:84:BF:05` 的未知小米设备(product 0x0083,**不加密**,事件 ID 0x1005)一直在广播,被观察者照单全收。对策:`scan_cb` 里加目标 MAC 过滤(只收 `A4:C1:38:99:9A:07`)。
2. **小米原厂固件的数据帧很稀疏**:温湿度事件帧有时十来分钟才来一帧(抠电池重启后的一段时间内会频繁得多)。所以应用层**缓存最近读数**(`last`),查询应答永远发最近一次值,而不是等下一帧。
3. **烧录后首次启动偶发 HCI Reset(0x0c03)超时断言**(hci_core.c "Controller unresponsive"),按一次复位即恢复——DA14531 模块就绪时序问题,已知待根治。
4. **复位 DA16200 要"先唤醒再复位"**:直接拉 RESETn 可能叫不醒 DPM 睡眠中的模块,先给 RTC_WAKE_UP 高脉冲再复位,100% 可靠。
5. **`AT+NWMQMSG` 的 JSON 引号规则**:payload 含逗号/双引号时整条用单引号包,否则参数被逗号截断。

## 10. 复用指南

`src/drivers/da16200.{c,h}` + `dts/bindings/wifi-da16200.yaml` 是自包含的,搬到其它 Zephyr 工程只要三步:

1. overlay 里指一个 115200 的 UART(nodelabel `uart4`,改 `DA16200_UART_NODE` 即可换)+ 两个控制 GPIO;
2. `prj.conf` 加 `CONFIG_SERIAL=y`、`CONFIG_UART_INTERRUPT_DRIVEN=y`;
3. `da16200.h` 里填 WiFi/broker/主题。

API 就四个:`da16200_start()`(非阻塞,后台线程管生命周期)、`da16200_is_connected()`、`da16200_mqtt_publish()`、`da16200_mqtt_incoming()`。

`


页: [1]
查看完整版本: DA16200 WiFi 模块接入 RA4M2:MQTT 订阅查询 → 应答当前温湿