基于 NXP FRDM-MCXW71 + Zephyr RTOS,使用 DHT11 传感器采集温湿度数据,通过 BLE 自定义 GATT 服务实时透传至手机 App。双核架构(CM33 主核 + NBU 无线核),全程无需配对加密,开箱即连。
一、硬件介绍
1.1 主控 — NXP FRDM-MCXW71
项目参数
| 主控芯片 | NXP MCXW716C |
| 主核 | ARM Cortex-M33F @ 96MHz |
| 无线子核 NBU | 独立 CM3 核,专用于 BLE / 802.15.4 Controller |
| Flash | 1MB(主核)+ NBU 独立闪存 |
| SRAM | 128KB(主核)+ NBU 独立内存 |
| 蓝牙 | BLE 5.x,主核与 NBU 通过共享内存 RPMSG 通信(非 UART HCI) |
1.2 板载外设与本项目用途
外设引脚本项目中用途
| 蓝色 LED | GPIOC Pin 1 | BLE 连接状态指示(快闪 100ms = 已连接 / 慢闪 500ms = 未连接) |
| MCU-LINK 调试器 | 板载 CMSIS-DAP | 一键烧录 + 串口日志 |
| Arduino R3 接口 | GPIO/I2C/SPI | 引出 PA19 接 DHT11 数据线 |
| TPM0 PWM | PA19~21 | 本项目中禁用(Device Tree overlay),释放 PA19 给 DHT11 |
1.3 传感器 — DHT11
参数规格
| 温度范围 / 精度 | 0~50°C / ±2°C |
| 湿度范围 / 精度 | 20~90%RH / ±5%RH |
| 通信协议 | 单总线(Single-Wire),仅需 1 根 GPIO |
| 采样间隔要求 | ≥1 秒 |
1.4 应用方向与场景
MCXW716C 双核架构 + BLE/15.4 双模射频,典型场景:
智能家居传感器 — BLE + 低功耗传感器,手机直连查看(本项目方向)
工业无线网络 — 802.15.4 Thread 组网 + CAN 工业总线
可穿戴设备 — 小封装 BLE 心率/温湿度监测
楼宇自动化 — 电池供电 + BLE Mesh 组网
二、设计思路
2.1 模块功能映射
传感器采集: GPIOA PA19 → DHT11(bit-bang 单总线)
无线透传: NBU 子系统 → BLE GATT Notify 推送至手机
状态指示: GPIOC PC1 → 蓝色 LED 连接状态
调试日志: UART 串口 → printk 温湿度数值
2.2 分层架构

2.3 数据流
DHT11 ─(单总线)─→ GPIOA PA19 ─→ DHT 驱动 ─→ sensor_value
├─→ printk 串口输出
└─→ 转换为 BLE 标准单位
├─ Temperature: sint16 × 0.01°C
└─ Humidity: uint16 × 0.01%
└─→ GATT Notify ─→ 手机 App
2.4 关键设计决策
决策理由
| 禁用 TPM0 PWM | PA19 默认被 PWM 占用,通过 Device Tree overlay 禁掉释放 GPIO |
| 不启用 BLE 配对 (SMP=n) | 纯透传场景无需加密,降低复杂度与 Flash 占用 |
| 温湿度用标准 UUID (0x2A6E / 0x2A6F) | 兼容 nRF Connect 等通用 BLE 工具 |
| 16-bit 通知值 (sint16 / uint16) | BLE 标准数据格式,分辨率 0.01°C / 0.01%RH |
| 采样间隔 2 秒 | 满足 DHT11 ≥1s 约束,兼顾实时性与功耗 |
三、代码流程

事件回调(BT 子系统中断上下文)
手机连接 ──→ connected() ──→ 保存 conn 句柄 + 打印日志
手机断开 ──→ disconnected() ──→ 释放 conn 句柄
手机订阅 ──→ ccc_cfg_changed() ──→ 置位 notify 标志
三、核心代码 (main.c)
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#define SLEEP_TIME_MS 2000
#define LED0_NODE DT_ALIAS(led0)
#if DT_NODE_EXISTS(LED0_NODE)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
#endif
/* ── BLE GATT 服务:温湿度 ──────────────────── */
#define BT_UUID_TH_SERVICE BT_UUID_DECLARE_128( \
BT_UUID_128_ENCODE(0x12340001, 0x5678, 0x9abc, 0xdef0, 0x123456789abc))
#define BT_UUID_TH_TEMP BT_UUID_DECLARE_16(0x2A6E) /* 0.01°C */
#define BT_UUID_TH_HUMID BT_UUID_DECLARE_16(0x2A6F) /* 0.01% */
static int16_t th_temp = 0;
static uint16_t th_humid = 0;
static struct bt_conn *current_conn;
static bool notify_temp_enabled, notify_humid_enabled;
static void temp_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
notify_temp_enabled = (value == BT_GATT_CCC_NOTIFY);
}
static void humid_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
notify_humid_enabled = (value == BT_GATT_CCC_NOTIFY);
}
static ssize_t read_temp(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&th_temp, sizeof(th_temp));
}
static ssize_t read_humid(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&th_humid, sizeof(th_humid));
}
BT_GATT_SERVICE_DEFINE(th_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_TH_SERVICE),
BT_GATT_CHARACTERISTIC(BT_UUID_TH_TEMP,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, read_temp, NULL, NULL),
BT_GATT_CCC(temp_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(BT_UUID_TH_HUMID,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, read_humid, NULL, NULL),
BT_GATT_CCC(humid_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);
/* ── 广播 & 连接 ────────────────────────────── */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
BT_UUID_128_ENCODE(0x12340001, 0x5678, 0x9abc, 0xdef0, 0x123456789abc)),
};
static const struct bt_data sd[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME,
sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};
static void connected(struct bt_conn *conn, uint8_t err)
{
if (!err) { current_conn = bt_conn_ref(conn); printk("Connected\n"); }
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %u)\n", reason);
if (current_conn) { bt_conn_unref(current_conn); current_conn = NULL; }
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected, .disconnected = disconnected,
};
/* ── BLE 初始化 & 通知 ──────────────────────── */
static void ble_init(void)
{
bt_enable(NULL);
bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad),
sd, ARRAY_SIZE(sd));
printk("BLE advertising as \"%s\"\n", CONFIG_BT_DEVICE_NAME);
}
static void ble_notify(void)
{
if (!current_conn) return;
if (notify_temp_enabled)
bt_gatt_notify(current_conn, &th_svc.attrs[2], &th_temp, sizeof(th_temp));
if (notify_humid_enabled)
bt_gatt_notify(current_conn, &th_svc.attrs[5], &th_humid, sizeof(th_humid));
}
/* ── 主函数 ────────────────────────────────── */
int main(void)
{
#if DT_NODE_EXISTS(LED0_NODE)
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
#endif
const struct device *const dht = DEVICE_DT_GET_OR_NULL(DT_ALIAS(dht0));
if (!device_is_ready(dht)) { printk("DHT11 not ready\n"); return 0; }
ble_init();
while (1) {
if (sensor_sample_fetch(dht) == 0) {
struct sensor_value temp, humidity;
sensor_channel_get(dht, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(dht, SENSOR_CHAN_HUMIDITY, &humidity);
th_temp = (int16_t)(temp.val1 * 100 + temp.val2 / 10000);
th_humid = (uint16_t)(humidity.val1 * 100 + humidity.val2 / 10000);
printk("DHT11: %d.%d C %d.%d %%RH\n",
temp.val1, temp.val2 / 100000,
humidity.val1, humidity.val2 / 100000);
ble_notify();
}
#if DT_NODE_EXISTS(LED0_NODE)
int ms = current_conn ? 100 : 500; /* 已连接快闪 / 未连接慢闪 */
gpio_pin_toggle_dt(&led); k_msleep(ms);
gpio_pin_toggle_dt(&led); k_msleep(SLEEP_TIME_MS - ms);
#else
k_msleep(SLEEP_TIME_MS);
#endif
}
return 0;
}
四、关键配置
4.1 Device Tree Overlay
blinky/boards/frdm_mcxw71.overlay:
/ {
aliases { dht0 = &dht11; };
dht11: dht11 {
compatible = "aosong,dht";
dio-gpios = <&gpioa 19 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
&gpioa { status = "okay"; };
&tpm0 { status = "disabled"; }; /* 释放 PA19 */
4.2 Kconfig
blinky/prj.conf:
CONFIG_GPIO=y
CONFIG_SENSOR=y
CONFIG_DHT=y
CONFIG_PRINTK=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="DHT11-TH-Sensor"
CONFIG_BT_DEVICE_APPEARANCE=0
CONFIG_MBEDTLS=n
CONFIG_BT_SMP=n
SoC 级 defconfig 自动处理 BLE 栈大小、HCI 缓冲区、NBU 核间通信等底层参数。
五、BLE GATT 服务
Service: 12340001-5678-9abc-def0-123456789abc (自定义 128-bit)
├─ Temperature (0x2A6E) READ | NOTIFY → sint16, 单位 0.01°C
│ └─ CCC Descriptor(手机订阅后自动推送)
└─ Humidity (0x2A6F) READ | NOTIFY → uint16, 单位 0.01%
└─ CCC Descriptor(手机订阅后自动推送)
手机端使用 nRF Connect 连接 "DHT11-TH-Sensor",展开 Unknown Service,点击 ↓ 订阅温湿度特征即可收到实时数据。弹配对时直接取消,不影响数据传输。
六、烧录步骤
6.1 拉取 NBU 固件
west blobs fetch hal_nxp


6.2 烧录 NBU 无线核固件
使用 :
进入 ISP 模式:断开电源 → 按住 SW3 → 接 USB → 松开 SW3
执行烧录:
blhost.exe -p COM18 -- receive-sb-file ^ E:\Zephyr\external\modules\hal\nxp\zephyr\blobs\mcxw71\mcxw71_nbu_ble.sb3


6.3 编译烧录应用
cd blinky
west build -b frdm_mcxw71
west flash
七、效果展示
视频链接:<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=116762796361032&bvid=BV1ZBjL6JEKU&cid=39174406327&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
输出方式内容
| 串口日志 | DHT11: 25.0°C 60.0%RH(每 2 秒) |
| LED | 未连接慢闪 500ms / 已连接快闪 100ms |
| 手机 App | nRF Connect 展开 GATT 服务,订阅后温湿度数值自动刷新 |




八、总结
本项目充分利用了 FRDM-MCXW71 的双核无线架构:
CM33 主核运行 Zephyr Sensor 框架 + BLE Host 协议栈,驱动 DHT11 单总线传感器
NBU 无线核独立运行 BLE Controller 固件,通过共享内存 RPMSG 与 Host 高效通信
Device Tree Overlay 解决了 PA19 引脚复用冲突(TPM0 PWM)
自定义 BLE GATT 服务 + 标准温湿度 UUID,兼容主流 BLE 调试 App
全程无需配对加密,开箱即连即用,适合作为智能家居无线传感器节点的原型参考。
我要赚赏金
