本文分享实现FRDM_RW612的 BLE 温湿度数据 → 以太网 → MQTT 转发链路的移植过程与关键要点,重点在 BLE central 部分。基于 Zephyr 4.4.0。
1. 总体架构


MCXW71 工程:app/mcx_w71_dht11(未改动),BLE peripheral,设备名 LWX_demo;
RW612 工程:本工程,新增 src/custom/ble_dht11.c(BLE central)和 src/custom/mqtt_pub.c(MQTT 发布),均从 custom_init() 启动,与 LVGL 界面、以太网配置逻辑(eth_config.c)互不干扰。
2. GATT 服务约定(与 MCXW71 侧的接口)
MCXW71 侧(peripheral)定义的自定义服务:
项目UUID说明
| Service | 12345678-1234-5678-1234-56789abcdef0 | 主服务 |
| Characteristic | ...-ef1 | 温度,uint8,°C,read + notify |
| Characteristic | ...-ef2 | 湿度,uint8,%RH,read + notify |
广播数据(adv)带 128-bit 服务 UUID(BT_DATA_UUID128_ALL),扫描响应带设备名 LWX_demo。无配对/加密要求。
注意一个坑:MCXW71 广播里的 UUID 字节流与其服务 UUID 并不完全一致 (...9abcdef0 vs 广播里的 456789abcdef)。central 端扫描匹配时应使用 广播原文的 16 字节做 memcmp,而不是用 BT_UUID_128_ENCODE 编码后的值:
/* 与 peripheral 广播中 BT_DATA_UUID128_ALL 完全一致的字节序列 */
static const uint8_t peer_svc_adv_uuid[16] = {
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
};
同时用设备名 LWX_demo 做兜底匹配,两者命中其一即发起连接。
3. RW612 BLE central 实现要点(src/custom/ble_dht11.c)
3.1 整体结构
独立线程(栈 4096,优先级 8)跑状态机,避免 bt_enable() 阻塞 LVGL 主线程:
bt_enable() → 扫描 → (命中) 连接 → 按 UUID 发现两个特征 → 订阅 CCC
→ 等断开 → 重新扫描(循环)
线程与 BT 协议栈回调之间用 3 个信号量同步:
conn_sem:connected 回调给出;
disconn_sem:disconnected / 连接失败回调给出;
disc_sem:一轮 GATT discovery 完成时给出。
3.2 三个容易踩的协议栈坑(Zephyr 4.4 实测)
坑 1:discovery 回调返回 BT_GATT_ITER_STOP 后,协议栈不再回调 attr == NULL。
subsys/bluetooth/host/gatt.c 在回调返回 STOP 时直接 return,跳过结尾的 params->func(conn, NULL, params)。如果用"attr == NULL 表示本轮发现结束" 来释放信号量,线程会永远卡死(现象:日志停在 Connected)。 正确做法:在找到目标属性的分支里也要释放信号量。
坑 2:特征发现回调里 attr->handle 是"声明"句柄,不是值句柄。
BT_GATT_DISCOVER_CHARACTERISTIC 的回调中,attr->handle 是特征声明 (declaration)的句柄,真正的 value handle 要从 attr->user_data (struct bt_gatt_chrc)里取:
const struct bt_gatt_chrc *chrc = attr->user_data;
*handle_out = chrc->value_handle; /* 不要用 attr->handle */
本例中声明句柄是 17/20,值句柄是 18/21。把声明句柄当值句柄订阅,CCC 会写错位置 (写到只读的值属性上),对端返回 ATT 错误,协议栈随即本地移除订阅——现象是 subscribed 后 90ms 立刻 unsubscribed。
坑 3:bt_gatt_subscribe_params.value 必须显式设为 BT_GATT_CCC_NOTIFY。
Zephyr 4.4 的 subscribe 参数有 value 字段,它是要写进对端 CCC 的值。 memset(0) 后忘了赋值 = 往 CCC 写 0x0000(关闭通知),同样导致"刚订阅就退订":
temp_sub.notify = notify_func;
temp_sub.value_handle = temp_handle; /* 值句柄,如 18 */
temp_sub.ccc_handle = temp_handle + 1; /* 本 peripheral 中 CCC 紧跟值属性 */
temp_sub.value = BT_GATT_CCC_NOTIFY; /* 关键! */
另外 bt_gatt_subscribe() 在前一个 GATT 操作未完成时返回 -EBUSY, central 线程里用 50ms 间隔重试即可。
3.3 通知接收
两个特征共用一个 notify 回调,用 params 指针区分温度/湿度; data == NULL 表示订阅被移除。收到的单字节值存入 volatile 全局变量, 由 ble_dht11_latest() 供 MQTT 线程读取(单字节读写无竞争问题)。
4. MQTT 发布(src/custom/mqtt_pub.c)
独立线程(栈 4096,优先级 8),逻辑:等网络就绪 → 连 broker → zsock_poll + mqtt_live() 保活 + 每秒发布 → 出错/断线 5 秒重连。
网络就绪用 net_mgmt 事件跟踪:NET_EVENT_IPV4_ADDR_ADD 置位, NET_EVENT_ETHERNET_CARRIER_OFF(拔网线)清除。注意不同层的事件要用 不同的 net_mgmt_event_callback 注册(L3 IPv4 与 L2 Ethernet 不能合并 在一个掩码里,dispatcher 按 layer 精确匹配)。
载荷 JSON:{"temperature":25,"humidity":60},主题 rw612/dht11,QoS 0。
只有收到过 BLE 数据才开始发布(ble_dht11_latest() 返回 false 时跳过)。
5. 配置与环境(比代码更容易漏的部分)
5.1 prj.conf
# 以太网协议栈
CONFIG_NETWORKING=y
CONFIG_NET_IPV6=n
CONFIG_NET_IPV4=y
CONFIG_NET_ARP=y
CONFIG_NET_UDP=y
CONFIG_NET_DHCPV4=y
CONFIG_DNS_RESOLVER=y # 自动 select NET_SOCKETS
CONFIG_NET_L2_ETHERNET_MGMT=y # 关键:否则载波(拔插网线)事件根本不发
CONFIG_NET_LOG=y
CONFIG_NET_SHELL=y
# BLE central
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y # 关键:4.4 中不随 BT_CENTRAL 自动打开
# MQTT
CONFIG_NET_TCP=y
CONFIG_MQTT_LIB=y
5.2 BLE NBU 固件 blob
RW612 的 BLE 协议栈跑在 NBU 核上,需要签名固件 blob,否则 CMake 直接报 Couldn't find signed firmware:
cd D:\luglZephyrproject
west blobs -a fetch hal_nxp # 注意 -a 在子命令前;会拉取 rw61x_sb_ble_15d4_combo_a2.bin
非交互环境必须加 -a(自动接受许可),否则脚本读 stdin 报 EOFError。
5.3 PHY 链路检测改轮询(boards/frdm_rw612.overlay)
板级 dts 给 KSZ8081 配了 int-gpios,驱动因此关闭周期轮询改用中断,但这根 中断线在 FRDM-RW612 上不产生 link-down 事件——插网线能识别、拔网线无感知。 应用 overlay 删除该属性即可回到 500ms 轮询:
&phy {
/delete-property/ int-gpios;
};
注意:新增 overlay 文件后必须 west build -p always 一次(pristine), 增量构建不会发现新 overlay。
5.4 显示屏 shield
本板显示来自 lcd_par_s035_spi shield(ST7796S),编译必须带:
west build -p auto -b frdm_rw612 . -- -DSHIELD=lcd_par_s035_spi
6. 编译烧录与验证
# MCXW71 侧(peripheral,工程已就绪) D:\luglZephyrproject\app\mcx_w71_dht11\build_and_flash.bat # RW612 侧(本工程) cd D:\luglZephyrproject\app\frmd_rw612_ethernet west build -p auto -b frdm_rw612 . -- -DSHIELD=lcd_par_s035_spi west flash
RW612 串口正常日志序列:
ble_dht11: Bluetooth initialized ble_dht11: Scanning for "LWX_demo"... ble_dht11: peer found: 00:60:37:D3:EF:84 (public), connecting ble_dht11: Connected ble_dht11: char found: declaration 17, value 18 ble_dht11: char found: declaration 20, value 21 ble_dht11: temperature subscribed ble_dht11: humidity subscribed ble_dht11: Humidity: 60 %RH ← 每 2 秒 ble_dht11: Temperature: 25 C mqtt_pub: MQTT connected to 39.99.195.81:1883
浏览器打开 web/dht11_monitor.html(broker 需开 WebSocket 监听,默认 ws://39.99.195.81:8083/mqtt),即可看到温度/湿度实时曲线。
7. 文件清单
文件作用
| src/custom/ble_dht11.c/h | BLE central:扫描/连接/发现/订阅/通知接收 |
| src/custom/mqtt_pub.c/h | MQTT 客户端:连 broker、每秒发布 JSON |
| src/custom/eth_config.c/h | 以太网配置 UI 逻辑(DHCP/静态 IP、载波状态) |
| boards/frdm_rw612.overlay | 删除 PHY int-gpios,改轮询链路检测 |
| web/dht11_monitor.html | 浏览器端 MQTT 监测页 |
| prj.conf |
8. 工程效果
打开网页监测系统,可以实现实时的观察到环境的温度湿度:

我要赚赏金
