eefocus_3914144 发表于 2026-9-11 15:24:31

【瑞萨BLE/WIFI模块测评】无线家居智能监测

本帖最后由 eefocus_3914144 于 2026-9-11 16:42 编辑

# 基于RA4M2的无线家居智能监测

> 此次提供了RA4M2开发板一块,蓝牙开发板DA14531一块,wifi开发板DA16200一块。我将使用DA14531获取家里的小米温湿度计的信息,通过DA16200连接到家里的无线路由器,通过MQTT实现家里的温度湿度智能监测。

## 1. 开发板介绍

### 1.1 RA-Eco-RA4M2-100PIN-V2

![](https://ramcu-cn.oss-cn-shenzhen.aliyuncs.com/11ae363ad578a7a18630be113cdaae48_1773732655_1773732686.jpg)

**型号** R7FA4M2AD3CFP,LQFP-100,Cortex-M33,512KB Flash / 128KB RAM。

### 1.2 DA14531蓝牙开发板

![](https://ramcu-cn.oss-cn-shenzhen.aliyuncs.com/20648974a0ea175c9614c986cc12a2a9_1784615293.jpg)

### 1.3 DA16200 wifi开发板

![](https://ramcu-cn.oss-cn-shenzhen.aliyuncs.com/eef5b2688587a9e8858cd8441319c845_1784614986.jpg)

### 1.4 总览

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

## 2. 开发环境

### 2.1 操作系统

* Zephyr RTOS 4.4.0
* Zephyr SDK 1.0.1

### 2.2 调试下载

Jlink Ultra +
!(https://www.eefocus.com/forum/data/attachment/forum/202609/11/151904h920nwx9mmis2vm4.jpg)

## 3. 已发布的文章

由于我是阶段开发,因此前面已经将环境、OLED、蓝牙的部分做了详细的分享

* [【瑞萨BLE/WIFI模块测评】Zephyr 驱动OLED - 瑞萨电子 - 与非网](https://www.eefocus.com/forum/thread-236382-1-1.html)
* [【瑞萨BLE/WIFI模块测评】Zephyr 驱动OLED - 瑞萨电子 - 与非网](https://www.eefocus.com/forum/thread-236382-1-1.html)
* [【瑞萨BLE/WIFI模块测评】小米温湿度计接入 RA4M2 全记录 - 瑞萨电子 - 与非网](https://www.eefocus.com/forum/thread-236389-1-1.html)
* (https://www.eefocus.com/forum/thread-236396-1-1.html)

## 4. 程序框架

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

## 5. 主要的技术要点

此次我的项目可以说是一个大工程,集成了zephyr rtos 、BLE、WIFI、MQTT、显示等三个大环路。按子系统梳理如下:

### 5.1 外置 HCI 蓝牙控制器(DA14531)—— 全项目最难的一环

* ​**MCU 无蓝牙射频**​,蓝牙能力完全靠 DA14531 模块刷 2-wire UART HCI 固件后充当 Zephyr BLE host 的外置控制器(`<span>renesas,bt-hci-da1453x</span>`,SCI0 P410/P411 @115200,无流控)。
* ​**固件要自行裁剪编译**​(SDK6 + e2studio headless):必须 `<span>ARCH_SLEEP_OFF</span>`、关闭 `<span>CFG_TRNG</span>` —— 空白 OTP 没有出厂 trim 数据,否则 `<span>TRNG_ACQUIRE</span>` 直接断言。
* ​**烧录格式坑**​:ROM booter 要的是 8 字节头(`<span>70 50 00 00 00 00</span>` + 大端长度)+ raw bin,而不是 AN-B-001 文档里的 64 字节二级引导头。
* **自研烧录工具** `<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">tools/spi_flash_program.py</button>`:J-Link SWD 寄存器级操作,ClrRESET 让芯片停在 POR 保证连接可靠,先发 0xAB 唤醒 P25Q40 flash 再页编程。

### 5.2. BLE 扫描与 MiBeacon 解密

* ​**线程解耦**​:扫描回调运行在中断/BT RX 线程上下文,栈有限,而 AES-CCM 解密栈消耗大 —— 用 `<span>k_mem_slab</span>`(4 块帧池)+ `<span>k_fifo</span>` 把广播帧转交主线程处理,回调里只做解析和投递(`<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">src/main.c</button><span>:90-120</span>`)。
* ​**MAC 白名单过滤**​:环境里有邻居的 0xFE95 设备,必须按目标 MAC(A4:C1:38:99:9A:07)过滤,否则日志和显示被污染- ​**MiBeacon V4 协议 + PSA 加密**​:帧解析、bindkey 导入 PSA、AES-CCM-128 解密(AAD=0x11,tag=4,nonce = MAC+PID+帧计数+扩展计数器),从 STM32L5 工程移植(`<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">src/mibeacon.c</button>`)。
* ​**数据分帧合并**​:小米把温度/湿度/电量分散在不同事件帧里广播,需要缓存 `<span>last</span>` 跨帧合并,OLED 显示和 MQTT 应答都依赖这份。

### 5.3. OLED 显示(SSD1306)

* 走 RA 的 SCI3 simple-I2C 控制器(`<span>renesas,ra-i2c-sci</span>`,P408/P409),不是原生 I2C 外设。
* ​**驱动坑**​:该 I2C 驱动对零长度写会挂死,总线扫描必须改用 1 字节读(`<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">src/main.c</button><span>:51-65</span>`)。
* 自研可复用驱动模块 `<span>oled_ssd1306.c</span>`,提供 init/clear/print/flush 的 4 行文本接口。

### 5.4. WiFi + MQTT(DA16200)

* ​**AT 指令并发管理**​:主线程(发布)和 conn-mgr 线程(连接管理)都会发 AT,所有事务用 `<span>at_mutex</span>` 串行化(`<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">src/drivers/da16200.c</button><span>:48</span>`)。
* ​**非请求响应处理**​:UART 接收进 ring buffer,每次排空时做行,捕捉 `<span>+NWMQMSG:<payload>,<topic>,<len></span>` 实现订阅消息的异步接收。
* ​**连接状态机**​:conn-mgr 线程独占模块生命周期 —— 复位、AT 握手、加 AP、起 MQTT(​**必须先 `<span>AT+NWMQTS</span>` 订阅再 `<span>AT+NWMQCL</span>` 连接**​)、轮询链路状态、掉线自动重连。
* ​**查询-应答模式**​: `<span>sensor/ra4m2/query</span>` 消息 → 把缓存的小米读数组成 JSON 发布到 `<span>sensor/ra4m2/temperature</span>`(已端到端验证)。
* 模块 RESETn(P403)/RTC\_WAKE\_UP(P402用自定义 dts binding(`<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">dts/bindings/wifi-da16200.yaml</button>`)管理。

### 5.5. 系统集成与工程层面

* ​**三个执行上下文**​:BT RX 线程、main 主线程、da16200 conn-mgr 线程,靠 FIFO/互斥锁/状态标志协作,没有共享数据裸奔。
* ​**第三方板卡适配**​:RA-Eco-RA4M2 与官方 EK-RA4M2 的 LED 引脚不同,overlay 定义;24MHz 晶振确认后直接沿用官方时钟配置。
* ​**调试手段**​:控制台走 SEGGER RTT(J-Link),UART 全部留给模组;还有 PC 端测试工具 `<button type="button" class="hover:text-zinc-900 dark:hover:text-white hover:underline cursor-pointer break-all text-left">tools/mqtt_query_test.py</button>`。
* ​**构建**​:Zephyr 4.4 + SDK 1.0.1,west workspace 共享 `<span>build/</span>` 目录,切换应用必须 `<span>-p</span>` pristine。

## 6. 工程代码简介

> 本次工程大约1100行 我分别介绍如下:

### 6.1 prj.con

```
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_TICKLESS_KERNEL=y

# DA16200 WiFi 模块(UART AT 指令,中断驱动接收)
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y

# 蓝牙 host(外接 DA14531 HCI 控制器,observer 角色)
CONFIG_BT=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_DEVICE_NAME="ra4m2-ble"

# AES-CCM(PSA crypto)用于 MiBeacon bindkey 解密
CONFIG_PSA_WANT_ALG_CCM=y
CONFIG_PSA_WANT_KEY_TYPE_AES=y
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_HEAP_MEM_POOL_SIZE=2048

# RTT 调试输出(经 J-Link SWD,不占串口;SCI0 留给 BLE 模块)
CONFIG_PRINTK=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=n
```

配置文件,我们按模块进行加载,特别是如何加裁wifi ble host RTT调试等。

### 6.2 板级设备树

我是使用ek_ra4m2官方开发板进行开发板,由于与官方的一些不同,我在ek_ra4m2.overlay中按照原理图进行了定义,这一块最花时间的。

```
/*
* RA-Eco-RA4M2-100PIN(第三方板)适配。
*
* 时钟:本板已焊 24MHz 主晶振,直接用官方 ek_ra4m2.dts 的默认时钟配置
* (xtal 24MHz -> PLL -> ICLK 100MHz),无需改动。
* (注意:同系列 RA-Eco-RA4L1 等板子未贴晶振,那种情况需关闭 xtal/PLL
* 改 HOCO,参考 app/RA4L1_led/boards/ek_ra4l1.overlay。)
*
* LED 引脚与官方 EK-RA4M2 不同,按 RA-Eco 板原理图重定义:
* LED1=P405, LED2=P404, LED3=P002,均高电平点亮。
* 官方板 led1=P415、led2=P404、led3=P405,先删再建,alias led0..2 不变。
*/

#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/pinctrl/renesas/pinctrl-ra.h>

/* I2C 插座(SCI3 简易 I2C):SCL3=P408, SDA3=P409,板载 1.5K 上拉,
* 挂 SSD1306 OLED(0x3C)。驱动:i2c_renesas_ra_sci(FSP r_sci_i2c)。 */
&pinctrl {
        sci3_i2c_default: sci3_i2c_default {
                group1 {
                        /* SCL3 SDA3 */
                        psels = <RA_PSEL(RA_PSEL_SCI_3, 4, 8)>,
                                <RA_PSEL(RA_PSEL_SCI_3, 4, 9)>;
                        drive-open-drain;
                };
        };
};

&sci3 {
        pinctrl-0 = <&sci3_i2c_default>;
        pinctrl-names = "default";
        status = "okay";

        i2c {
                clock-frequency = <I2C_BITRATE_FAST>;
                status = "okay";
        };
};

/ {
        aliases {
                oled-i2c = &{/soc/sci3@40118300/i2c};
        };

        chosen {
                zephyr,bt-hci = &bt_hci_uart;
        };
};

/* DA14531 HCI 控制器:SCI0(P410 RX / P411 TX),115200 8N1,无流控。
* 模块复位脚悬空、上电自启,renesas,bt-hci-da1453x 的 reset-gpios 可选,不接。 */
&uart0 {
        bt_hci_uart: bt_hci_uart {
                compatible = "zephyr,bt-hci-uart";
                status = "okay";

                da1453x {
                        compatible = "renesas,bt-hci-da1453x";
                        status = "okay";
                };
        };
};

/delete-node/ &led1;
/delete-node/ &led2;
/delete-node/ &led3;

/ {
        leds {
                led1: led1 {
                        gpios = <&ioport4 5 GPIO_ACTIVE_HIGH>;
                        label = "LED1";
                };

                led2: led2 {
                        gpios = <&ioport4 4 GPIO_ACTIVE_HIGH>;
                        label = "LED2";
                };

                led3: led3 {
                        gpios = <&ioport0 2 GPIO_ACTIVE_HIGH>;
                        label = "LED3";
                };
        };
};

&ioport0 {
        status = "okay";
};

&ioport2 {
        status = "okay";
};

&ioport4 {
        status = "okay";
};

/* 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 {
                        /* TXD4 RXD4 */
                        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";
                /* PMOD1 pin8P403 -> module RESETn (active low) */
                reset-gpios = <&ioport4 3 GPIO_ACTIVE_LOW>;
                /* PMOD1 pin9P402 -> module RTC_WAKE_UP (active high pulse) */
                wakeup-gpios = <&ioport4 2 GPIO_ACTIVE_HIGH>;
        };
};
```

### 6.3 da16200 绑定文件

为了配置wifi模块的复位、中断、唤醒,我在项目dts\bindings下面创建了wif-da16200.yaml 规定的PMOD1的规则

```
# SPDX-License-Identifier: Apache-2.0

description: DA16200 WiFi module control GPIOs (jumper-wired to PMOD1)

compatible: "renesas,da16200"

properties:
reset-gpios:
    type: phandle-array
    required: true
    description: MCU -> module RESETn (active low)

wakeup-gpios:
    type: phandle-array
    required: true
    description: MCU -> module RTC_WAKE_UP (active high pulse)

irq-gpios:
    type: phandle-array
    required: false
    description: module INTn -> MCU (active low, input only)
```

### 6.4 oled显示

由于zephyr的display在驱动ssd1306上有问题,因此我使用i2c_write来直接写

```
/* ctrl: 0x00=命令,0x40=显存数据 */
static int oled_write(uint8_t ctrl, const uint8_t *data, size_t len)
{
        uint8_t buf;

        if (len > OLED_WIDTH) {
                return -EINVAL;
        }
        buf = ctrl;
        memcpy(&buf, data, len);
        return i2c_write(i2c_bus, buf, len + 1, SSD1306_ADDR);
}
```

### 6.5 wifi驱动

我在src/drivers下面创建da16200.c,来实现AT命令的打包与解包,这是由官方提供的代码,修改为适配zephyr而来的。

```
/* SPDX-License-Identifier: Apache-2.0 */

/*
* Minimal DA16200 AT-command driver (UART host interface, no flow control).
*
* Ported from the RA6M4 project (app ra6m4_sht30_da16200), which in turn
* ported the AT sequence, response matching and retry/timeout policy from
* the Renesas FSP example RA6M4_DA16200_MQTT_CLIENT (da16200_AT.c). The RX
* path uses the Zephyr UART interrupt-driven API (ISR -> ring buffer).
*
* RA4M2 differences vs the RA6M4 original:
* - UART moved to sci4/uart4 (PMOD1: P205 TXD4 / P206 RXD4 @115200).
* - SNTP time sync dropped (not needed here).
* - MQTT subscription added: AT+NWMQTS before AT+NWMQCL, and every byte
*   pulled from the RX ring buffer is passed through a line scanner that
*   catches the unsolicited +NWMQMSG:<payload>,<topic>,<len> response.
* - All UART transactions are serialized with a mutex: the connection
*   manager thread and the application thread (publish) share the module.
*/

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>
#include <stdio.h>
#include <string.h>

#include "da16200.h"

#define DA16200_UART_NODEDT_NODELABEL(uart4)   /* sci4: P206 RX / P205 TX @115200 */
#define DA16200_GPIO_NODEDT_NODELABEL(wifi_da16200)

#define RX_RB_SIZE   512
#define RESP_SIZE    256

static const struct device *uart_dev = DEVICE_DT_GET(DA16200_UART_NODE);
static const struct gpio_dt_spec reset_gpio =
        GPIO_DT_SPEC_GET(DA16200_GPIO_NODE, reset_gpios);
static const struct gpio_dt_spec wakeup_gpio =
        GPIO_DT_SPEC_GET(DA16200_GPIO_NODE, wakeup_gpios);

RING_BUF_DECLARE(rx_rb, RX_RB_SIZE);

/* Serializes every UART transaction (send_cmd / link_check / RX polling):
* the connection manager thread and the application thread both talk to
* the module, and the ring buffer plus resp buffer are not thread-safe. */
static K_MUTEX_DEFINE(at_mutex);

/* Last response, kept so da16200_connect_ap() can parse the IP address. */
static char resp;

/* --------------------------------------------------------------------------
* Unsolicited +NWMQMSG scanning
*
* Every byte taken out of the RX ring buffer (by read_resp, rx_flush or
* the idle poll) is fed through scan_bytes(), which assembles lines and
* catches "+NWMQMSG:<payload>,<topic>,<len>". Only one message is kept;
* the application polls it with da16200_mqtt_incoming().
* ------------------------------------------------------------------------ */

static char scan_line;
static size_t scan_line_len;

static volatile bool incoming_pending;
static char incoming_payload;

static void scan_line_process(const char *line, size_t len)
{
        static const char prefix[] = "+NWMQMSG:";
        const char *p, *end, *c1, *c2, *q;
        size_t plen;

        if (len <= sizeof(prefix) - 1 ||
          strncmp(line, prefix, sizeof(prefix) - 1) != 0) {
                return;
        }
        p = line + sizeof(prefix) - 1;
        end = line + len;

        /* The payload itself may contain commas; topic and length are the
       * last two comma-separated fields. */
        c1 = NULL;
        c2 = NULL;
        for (q = end; q > p; q--) {
                if (q[-1] == ',') {
                        if (c2 == NULL) {
                                c2 = q - 1;
                        } else {
                                c1 = q - 1;
                                break;
                        }
                }
        }
        if (c1 == NULL || c2 == NULL) {
                return;
        }

        plen = c1 - p;
        if (plen >= sizeof(incoming_payload)) {
                plen = sizeof(incoming_payload) - 1;
        }
        memcpy(incoming_payload, p, plen);
        incoming_payload = '\0';
        incoming_pending = true;
}

static void scan_bytes(const uint8_t *data, size_t len)
{
        for (size_t i = 0; i < len; i++) {
                char c = (char)data;

                if (c == '\r') {
                        continue;
                }
                if (c == '\n') {
                        if (scan_line_len > 0) {
                                scan_line_process(scan_line, scan_line_len);
                        }
                        scan_line_len = 0;
                        continue;
                }
                if (scan_line_len < sizeof(scan_line) - 1) {
                        scan_line = c;
                } else {
                        scan_line_len = 0;   /* overlong line: drop it */
                }
        }
}

bool da16200_mqtt_incoming(char *payload, size_t cap)
{
        bool pending;

        k_mutex_lock(&at_mutex, K_FOREVER);
        pending = incoming_pending;
        if (pending) {
                incoming_pending = false;
                if (payload != NULL && cap > 0) {
                        strncpy(payload, incoming_payload, cap - 1);
                        payload = '\0';
                }
        }
        k_mutex_unlock(&at_mutex);
        return pending;
}

/* --------------------------------------------------------------------------
* UART low level
* ------------------------------------------------------------------------ */

static void uart_isr(const struct device *dev, void *user_data)
{
        ARG_UNUSED(user_data);

        while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
                if (uart_irq_rx_ready(dev)) {
                        uint8_t buf;
                        int len = uart_fifo_read(dev, buf, sizeof(buf));

                        if (len > 0) {
                                ring_buf_put(&rx_rb, buf, len);
                        }
                }
        }
}

static void rx_flush(void)
{
        uint8_t tmp;
        uint32_t got;

        while ((got = ring_buf_get(&rx_rb, tmp, sizeof(tmp))) > 0) {
                scan_bytes(tmp, got);   /* don't lose a +NWMQMSG in the flush */
        }
}

/* Collect a response into buf. Returns as soon as both expected substrings
* have been seen (early exit keeps fast commands fast), or when the timeout
* expires. expect2 may be NULL. */
static size_t read_resp(char *buf, size_t cap, uint32_t timeout_ms,
                        const char *expect1, const char *expect2)
{
        size_t len = 0;
        int64_t deadline = k_uptime_get() + timeout_ms;

        buf = '\0';
        while (len < cap - 1) {
                uint32_t got = ring_buf_get(&rx_rb, (uint8_t *)buf + len, cap - 1 - len);

                if (got > 0) {
                        scan_bytes((uint8_t *)buf + len, got);
                }
                len += got;
                buf = '\0';
                if ((expect1 == NULL || strstr(buf, expect1) != NULL) &&
                  (expect2 == NULL || strstr(buf, expect2) != NULL)) {
                        break;
                }
                if (k_uptime_get() >= deadline) {
                        break;
                }
                if (got == 0) {
                        k_msleep(2);
                }
        }
        buf = '\0';
        return len;
}

/*
* Send one AT command and wait for the expected response substring(s).
* Ported from the FSP AT_cmd_send_ok / AT_cmd_send_data pair: up to
* `retries` attempts, `timeout_ms` of RX collection per attempt and
* `retry_delay_ms` between attempts. expect2 may be NULL.
*/
static int send_cmd(const char *cmd, const char *expect1, const char *expect2,
                  uint32_t timeout_ms, int retries, uint32_t retry_delay_ms)
{
        int rc = -ETIMEDOUT;

        k_mutex_lock(&at_mutex, K_FOREVER);
        for (int i = 0; i < retries; i++) {
                rx_flush();

                for (const char *p = cmd; *p != '\0'; p++) {
                        uart_poll_out(uart_dev, *p);
                }

                read_resp(resp, sizeof(resp), timeout_ms, expect1, expect2);

                bool ok1 = (expect1 == NULL) || (strstr(resp, expect1) != NULL);
                bool ok2 = (expect2 == NULL) || (strstr(resp, expect2) != NULL);

                if (ok1 && ok2) {
                        rc = 0;
                        break;
                }
                k_msleep(retry_delay_ms);
        }
        k_mutex_unlock(&at_mutex);

        if (rc < 0) {
                printf("da16200: no match for %s", cmd);
                printf("da16200: got: %s\n", resp);
        }
        return rc;
}

static int da16200_init(void)
{
        if (!device_is_ready(uart_dev)) {
                printf("da16200: uart4 not ready\n");
                return -ENODEV;
        }
        if (!device_is_ready(reset_gpio.port) || !device_is_ready(wakeup_gpio.port)) {
                printf("da16200: control GPIOs not ready\n");
                return -ENODEV;
        }

        /* Wake + hardware reset. The module auto-enters DPM sleep (UART off)
       * when it has nothing to do; RTC_WAKE_UP pulls it out of sleep, and
       * the RESETn pulse then guarantees a clean boot from any state. */
        gpio_pin_configure_dt(&wakeup_gpio, GPIO_OUTPUT_ACTIVE);
        gpio_pin_configure_dt(&reset_gpio, GPIO_OUTPUT_ACTIVE);
        k_msleep(200);
        gpio_pin_set_dt(&reset_gpio, 0);   /* release reset */
        k_msleep(50);
        gpio_pin_set_dt(&wakeup_gpio, 0);/* end wake-up pulse */
        k_msleep(1500);

        uart_irq_callback_set(uart_dev, uart_isr);
        uart_irq_rx_enable(uart_dev);

        /* Give the boot banner (+INIT:DONE,0) time to arrive, then drop it. */
        k_msleep(500);
        rx_flush();

        /* Handshake with plain "AT" - NOT "ATZ": ATZ reboots the module, and
       * any command sent during the reboot window arrives as garbled bytes
       * (module answers ERROR:-1). The hardware reset above already gave us
       * a clean boot. */
        int rc = send_cmd("AT\r\n", "OK", NULL, 500, 10, 500);

        if (rc < 0) {
                return rc;
        }
        /* Echo toggle. DA16200 only accepts bare "ATE" (ATE0/ATE1 are rejected
       * with ERROR:-1), and failure is harmless anyway: response parsing
       * uses strstr, so the echo state does not matter. */
        (void)send_cmd("ATE\r\n", "OK", NULL, 500, 3, 200);
        return 0;
}

static int da16200_connect_ap(const char *ssid, const char *pw, char *ip, size_t ip_len)
{
        char cmd;
        int rc;

        /* Clear NVRAM profiles; module reboots into Soft-AP default mode. */
        rc = send_cmd("AT+DEFAP\r\n", "OK", "+INIT:DONE,1", 4000, 5, 200);
        if (rc < 0) {
                return rc;
        }

        /* Country code step removed: this module's firmware rejects codes
       * outside its regulatory table ("CN" -> ERROR:-4), and the default
       * covers 2.4 GHz channels 1-11 anyway. */

        rc = send_cmd("AT+WFMODE=0\r\n", "OK", NULL, 200, 5, 200); /* station mode */
        if (rc < 0) {
                return rc;
        }

        rc = send_cmd("AT+RESTART\r\n", "OK", "+INIT:DONE,0", 3000, 5, 1000);
        if (rc < 0) {
                return rc;
        }

        /* Restart turns echo back on; toggle it off again (non-fatal). */
        (void)send_cmd("ATE\r\n", "OK", NULL, 200, 3, 200);

        /* DPM keepalive every 5 s (default is 30 s): the module wakes more
       * often to keep the AP association alive, so the AP does not age out
       * the sleeping station. Non-fatal if the firmware rejects it. */
        if (send_cmd("AT+DPMKA=500000\r\n", "OK", NULL, 500, 3, 200) < 0) {
                printf("da16200: DPMKA not accepted, continue anyway\n");
        }

        rc = send_cmd("AT+NWDHC=1\r\n", "OK", NULL, 200, 5, 200); /* DHCP client */
        if (rc < 0) {
                return rc;
        }

        snprintf(cmd, sizeof(cmd), "AT+WFJAPA='%s','%s'\r\n", ssid, pw);
        rc = send_cmd(cmd, "OK", "+WFJAP:1", 4000, 5, 1000);
        if (rc < 0) {
                return rc;
        }

        /* Query the assigned IP via AT+NWIP=? -> +NWIP:0,<ip>,<netmask>,<gw>.
       * The +WFJAP reply format varies between firmware builds (this module
       * answers +WFJAP:'<ssid>',<sec>,<enc>,'<key>' with no IP field), so
       * never parse the IP from the join response. */
        if (ip != NULL && ip_len > 0) {
                ip = '\0';
                if (send_cmd("AT+NWIP=?\r\n", "+NWIP:", "OK", 500, 3, 200) == 0) {
                        const char *p = strstr(resp, "+NWIP:");

                        if (p != NULL) {
                                p = strchr(p, ',');   /* skip the interface id */
                        }
                        if (p != NULL) {
                                p++;
                                size_t n = strcspn(p, ",\r\n");

                                if (n >= ip_len) {
                                        n = ip_len - 1;
                                }
                                memcpy(ip, p, n);
                                ip = '\0';
                        }
                }
        }
        return 0;
}

/* --------------------------------------------------------------------------
* Connection manager thread
*
* State machine: while offline -> reset + handshake + join AP (retry with
* backoff); while online -> poll the link state and drop back to offline
* on loss. The application only reads the state via da16200_is_connected().
* ------------------------------------------------------------------------ */

#define CHECK_PERIOD_MS   5000   /* link-state poll interval when online */
#define POLL_SLICE_MS   100    /* unsolicited-message poll slice when online */
#define RETRY_DELAY_MS    10000/* backoff between join attempts when offline */

static volatile bool link_up;
static volatile bool mqtt_ready;

bool da16200_is_connected(void)
{
        return link_up;
}

/* Link check: AT+WFJAP=? answers with a +WFJAP: line. The connected format
* varies by firmware: the manual says +WFJAP:1,'<ssid>',<ip>, this module
* answers +WFJAP:'<ssid>',<sec>,<enc>,'<key>'. The only reliable common
* rule is: +WFJAP: present and NOT +WFJAP:0 (disconnected). Two attempts
* so a single transient UART glitch doesn't drop the link. */
static bool link_check(void)
{
        static const char query[] = "AT+WFJAP=?\r\n";
        bool up = false;

        k_mutex_lock(&at_mutex, K_FOREVER);
        for (int i = 0; i < 2; i++) {
                rx_flush();

                for (const char *p = query; *p != '\0'; p++) {
                        uart_poll_out(uart_dev, *p);
                }
                read_resp(resp, sizeof(resp), 1000, "OK", NULL);

                if (strstr(resp, "+WFJAP:") != NULL &&
                  strstr(resp, "+WFJAP:0") == NULL) {
                        up = true;
                        break;
                }
                k_msleep(200);
        }
        k_mutex_unlock(&at_mutex);

        if (!up) {
                printf("da16200: link check failed, got: %s\n", resp);
        }
        return up;
}

/* Bring up the MQTT client on the module: broker address, QoS 0, login,
* client ID, subscription topic, then enable. Mirrors the FSP example's
* mqtt_con_routine() plus AT+NWMQTS from UM-WI-003 9.1.1.
* MQTT config is not auto-started by the module, so this runs after every
* (re)connect. */
static int mqtt_setup(void)
{
        char cmd;
        int rc;

        snprintf(cmd, sizeof(cmd), "AT+NWMQBR=%s,%s\r\n",
               DA16200_MQTT_BROKER, DA16200_MQTT_PORT);
        rc = send_cmd(cmd, "OK", NULL, 500, 3, 200);
        if (rc < 0) {
                return rc;
        }

        rc = send_cmd("AT+NWMQQOS=0\r\n", "OK", NULL, 200, 3, 200);
        if (rc < 0) {
                return rc;
        }

        snprintf(cmd, sizeof(cmd), "AT+NWMQLI=%s,%s\r\n",
               DA16200_MQTT_USER, DA16200_MQTT_PW);
        rc = send_cmd(cmd, "OK", NULL, 200, 3, 200);
        if (rc < 0) {
                return rc;
        }

        snprintf(cmd, sizeof(cmd), "AT+NWMQCID=%s\r\n", DA16200_MQTT_CLIENT_ID);
        rc = send_cmd(cmd, "OK", NULL, 200, 3, 200);
        if (rc < 0) {
                return rc;
        }

        /* Subscribe BEFORE enabling the client: the topic list is read by
       * the module when AT+NWMQCL=1 opens the broker connection. */
        snprintf(cmd, sizeof(cmd), "AT+NWMQTS=1,%s\r\n", DA16200_MQTT_SUB_TOPIC);
        rc = send_cmd(cmd, "OK", NULL, 500, 3, 200);
        if (rc < 0) {
                return rc;
        }

        /* Connecting to the broker can take a moment - allow longer waits. */
        return send_cmd("AT+NWMQCL=1\r\n", "OK", NULL, 1000, 5, 500);
}

int da16200_mqtt_publish(const char *topic, const char *payload)
{
        if (!link_up || !mqtt_ready) {
                return -ENOTCONN;
        }

        char cmd;

        /* JSON payloads contain commas/double quotes: the AT spec requires
       * the message to be wrapped in single quotes. */
        snprintf(cmd, sizeof(cmd), "AT+NWMQMSG='%s',%s\r\n", payload, topic);
        return send_cmd(cmd, "OK", NULL, 1000, 2, 200);
}

static void conn_mgr_thread(void *p1, void *p2, void *p3)
{
        ARG_UNUSED(p1);
        ARG_UNUSED(p2);
        ARG_UNUSED(p3);

        char ip;

        for (;;) {
                if (!link_up) {
                        if (da16200_init() == 0 &&
                          da16200_connect_ap(DA16200_WIFI_SSID, DA16200_WIFI_PW,
                                             ip, sizeof(ip)) == 0) {
                                link_up = true;
                                printf("da16200: connected, IP %s\n", ip);
                                if (mqtt_setup() == 0) {
                                        mqtt_ready = true;
                                        printf("da16200: MQTT ready (sub %s)\n",
                                             DA16200_MQTT_SUB_TOPIC);
                                } else {
                                        /* Retry the whole cycle (with backoff). */
                                        printf("da16200: MQTT setup failed\n");
                                        link_up = false;
                                }
                        } else {
                                printf("da16200: offline, retry in %d s\n",
                                     RETRY_DELAY_MS / 1000);
                                k_msleep(RETRY_DELAY_MS);
                        }
                } else {
                        /* Sleep in slices and drain the RX ring buffer in
                       * between, so an unsolicited +NWMQMSG is seen within
                       * ~POLL_SLICE_MS instead of waiting for the next
                       * link_check(). */
                        for (int t = 0; t < CHECK_PERIOD_MS / POLL_SLICE_MS; t++) {
                                uint8_t tmp;
                                uint32_t got;

                                k_msleep(POLL_SLICE_MS);
                                k_mutex_lock(&at_mutex, K_FOREVER);
                                while ((got = ring_buf_get(&rx_rb, tmp,
                                                           sizeof(tmp))) > 0) {
                                        scan_bytes(tmp, got);
                                }
                                k_mutex_unlock(&at_mutex);
                        }
                        if (!link_check()) {
                                link_up = false;
                                mqtt_ready = false;
                                printf("da16200: connection lost\n");
                        }
                }
        }
}

K_THREAD_DEFINE(da16200_mgr, 2048, conn_mgr_thread, NULL, NULL, NULL,
                12, 0, -1);

int da16200_start(void)
{
        k_thread_start(da16200_mgr);
        return 0;
}
```

### 6.6 小米温湿度计解包

根据网上的资料,我编写了mibeacon.c用来解包

```
/*
* 小米 MiBeacon 帧解析 + AES-CCM 解密,接口见 mibeacon.h
*/

#include <errno.h>
#include <string.h>

#include <psa/crypto.h>
#include <zephyr/sys/printk.h>

#include "mibeacon.h"

#define MI_PAYLOAD_MAX 32

/* 小米 MiBeacon 事件对象 ID(小端) */
#define MI_EVT_TEMP      0x1004U /* s16, 0.1 ℃ */
#define MI_EVT_HUM       0x1006U /* u16, 0.1 % */
#define MI_EVT_BATT_PCT0x100AU /* u8, % */
#define MI_EVT_TEMP_HUM0x100DU /* s16 0.1℃ + u16 0.1% */

/* FC 首字节标志位 */
#define MI_FC_ENCRYPTED0x08U
#define MI_FC_CAPABILITY 0x20U
#define MI_FC_HAS_DATA   0x40U

#define MI_TAG_LEN       4U
#define MI_AAD         0x11U

/* 设备 bindkey:A4:C1:38:99:9A:07(TelinkMiFlasher 读取,
* 注意 flasher 页面每次"登录/激活"都会轮换密钥,轮换后必须更新这里) */
static const uint8_t mi_bindkey = {
        0x93, 0xbb, 0x3b, 0x15, 0xd5, 0x90, 0x82, 0xe3,
        0x34, 0xe4, 0x28, 0x32, 0x7f, 0x9a, 0x63, 0x69,
};

static psa_key_id_t mi_key_id;

int mibeacon_init(void)
{
        psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
        psa_status_t st;

        psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
        psa_set_key_bits(&attr, 128);
        psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DECRYPT);
        psa_set_key_algorithm(&attr,
                              PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM,
                                                              MI_TAG_LEN));
        st = psa_import_key(&attr, mi_bindkey, sizeof(mi_bindkey), &mi_key_id);
        if (st != PSA_SUCCESS) {
                printk("mibeacon: bindkey import failed %d\n", st);
                return (int)st;
        }
        return 0;
}

static void decode_events(const uint8_t *p, uint8_t len,
                          struct mi_sensor_data *sd)
{
        uint8_t off = 0;

        while (off + 3 <= len) {
                uint16_t eid = (uint16_t)p | ((uint16_t)p << 8);
                uint8_t elen = p;

                off += 3;
                if (off + elen > len) {
                        return;
                }

                switch (eid) {
                case MI_EVT_TEMP:
                        if (elen == 2) {
                                sd->temp_x10 = (int16_t)((uint16_t)p |
                                                       ((uint16_t)p << 8));
                                sd->has_temp = true;
                        }
                        break;
                case MI_EVT_HUM:
                        if (elen == 2) {
                                sd->hum_x10 = (uint16_t)p |
                                              ((uint16_t)p << 8);
                                sd->has_hum = true;
                        }
                        break;
                case MI_EVT_BATT_PCT:
                        if (elen == 1) {
                                sd->batt_pct = p;
                                sd->has_batt = true;
                        }
                        break;
                case MI_EVT_TEMP_HUM:
                        if (elen == 4) {
                                sd->temp_x10 = (int16_t)((uint16_t)p |
                                                       ((uint16_t)p << 8));
                                sd->hum_x10 = (uint16_t)p |
                                              ((uint16_t)p << 8);
                                sd->has_temp = true;
                                sd->has_hum = true;
                        }
                        break;
                default:
                        break;
                }

                off = (uint8_t)(off + elen);
        }
}

static int decrypt_frame(const uint8_t *payload, uint8_t payload_len,
                       struct mi_sensor_data *sd)
{
        uint8_t enc_off = 11;
        uint8_t ct_len;
        uint8_t nonce;
        uint8_t ct_tag;
        uint8_t plain;
        size_t plain_len = 0;
        static const uint8_t aad = MI_AAD;
        psa_status_t st;

        if (payload & MI_FC_CAPABILITY) {
                enc_off++;
        }
        if (payload_len < enc_off + MI_TAG_LEN + 3 + 1) {
                return -EINVAL;
        }

        ct_len = (uint8_t)(payload_len - enc_off - 3 - MI_TAG_LEN);

        /* nonce = MAC(广播序) + PID + frame_cnt + ext_counter */
        memcpy(nonce, payload + 5, 6);
        memcpy(nonce + 6, payload + 2, 2);
        nonce = payload;
        memcpy(nonce + 9, payload + payload_len - 7, 3);

        /* PSA AEAD 输入为 ciphertext || tag */
        memcpy(ct_tag, payload + enc_off, ct_len);
        memcpy(ct_tag + ct_len, payload + payload_len - MI_TAG_LEN,
             MI_TAG_LEN);

        st = psa_aead_decrypt(mi_key_id,
                              PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM,
                                                              MI_TAG_LEN),
                              nonce, sizeof(nonce), &aad, sizeof(aad),
                              ct_tag, (size_t)ct_len + MI_TAG_LEN,
                              plain, sizeof(plain), &plain_len);
        if (st != PSA_SUCCESS) {
                return (int)st;
        }

        decode_events(plain, (uint8_t)plain_len, sd);
        return 0;
}

int mibeacon_process(const uint8_t *payload, uint8_t len,
                     struct mi_sensor_data *sd)
{
        uint8_t off = 11;

        memset(sd, 0, sizeof(*sd));

        if (len < 11) {
                return -EINVAL;
        }
        if (!(payload & (MI_FC_ENCRYPTED | MI_FC_HAS_DATA))) {
                return -ENODATA; /* 周期无数据信标帧,忽略 */
        }

        if (payload & MI_FC_ENCRYPTED) {
                return decrypt_frame(payload, len, sd);
        }

        if (payload & MI_FC_CAPABILITY) {
                off++;
        }
        if (off < len) {
                decode_events(payload + off, (uint8_t)(len - off), sd);
        }
        return 0;
}
```

### 6.7 主程序

做为总的调度 main.c 主要实现各个功能模块的总装

```
/*
* RA-Eco-RA4M2-100PIN:BLE observer 读小米温湿度计(LYWSD03MMC,MiBeacon)
* - DA14531 HCI 控制器挂 SCI0(P410/P411,115200,无流控)
* - 被动扫描;MiBeacon(0xFE95)帧经 FIFO 交主线程解密(STACK 消耗大)
* - 温湿度/电量显示在 SSD1306 OLED(SCI3 I2C)上,LED1 心跳
* 调试输出走 SEGGER RTT(J-Link)
*/

#include <stdio.h>
#include <string.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/sys/byteorder.h>

#include "oled_ssd1306.h"
#include "mibeacon.h"
#include "drivers/da16200.h"

#define MI_SVC_UUID   0xFE95U
#define MI_PAYLOAD_MAX 32

static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);

struct mi_frame {
        void *fifo_reserved;
        int8_t rssi;
        uint8_t len;
        uint8_t payload;
};

static K_FIFO_DEFINE(mi_frame_fifo);
K_MEM_SLAB_DEFINE_STATIC(mi_frame_slab, sizeof(struct mi_frame), 4, 4);

struct mi_ctx {
        bool found;
        uint8_t len;
        uint8_t payload;
};

static uint32_t adv_count;
static uint32_t mi_count;

/* 保留最近读数:小米把温/湿/电分散在不同事件帧里;MQTT 查询应答也用它 */
static struct mi_sensor_data last;

/* I2C 总线扫描:打印所有应答的 7 位地址(诊断用;注意该驱动不支持零长度写) */
static void i2c_scan(const struct device *bus)
{
        uint8_t dummy;
        bool found = false;

        for (uint16_t addr = 0x08; addr < 0x78; addr++) {
                if (i2c_read(bus, &dummy, 1, addr) == 0) {
                        printk("i2c scan: 0x%02x ACK\n", addr);
                        found = true;
                }
        }
        if (!found) {
                printk("i2c scan: no device found\n");
        }
}

static bool parse_ad(struct bt_data *data, void *user_data)
{
        struct mi_ctx *ctx = user_data;
        uint16_t uuid;
        uint8_t len;

        if (data->type != BT_DATA_SVC_DATA16 || data->data_len < 3) {
                return true;
        }
        uuid = sys_get_le16(data->data);
        if (uuid != MI_SVC_UUID) {
                return true;
        }
        len = (uint8_t)(data->data_len - 2);
        if (len > sizeof(ctx->payload)) {
                return true;
        }
        memcpy(ctx->payload, data->data + 2, len);
        ctx->len = len;
        ctx->found = true;
        return true;
}

static void scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
                  struct net_buf_simple *buf)
{
        struct mi_ctx ctx = {0};
        struct mi_frame *frame;

        adv_count++;
        ARG_UNUSED(addr);
        ARG_UNUSED(adv_type);

        bt_data_parse(buf, parse_ad, &ctx);
        if (!ctx.found) {
                return;
        }

        mi_count++;
        /* 只处理目标传感器,过滤环境中其它 0xFE95 设备 */
        if (addr->a.val != 0xa4 || addr->a.val != 0xc1 ||
          addr->a.val != 0x38 || addr->a.val != 0x99 ||
          addr->a.val != 0x9a || addr->a.val != 0x07) {
                return;
        }
        if (k_mem_slab_alloc(&mi_frame_slab, (void **)&frame, K_NO_WAIT) != 0) {
                printk("frame pool exhausted, dropped\n");
                return;
        }
        frame->rssi = rssi;
        frame->len = ctx.len;
        memcpy(frame->payload, ctx.payload, ctx.len);
        k_fifo_put(&mi_frame_fifo, frame);
}

static void show_frame(const struct mi_frame *frame)
{
        struct mi_sensor_data sd;
        char line;
        int rc = mibeacon_process(frame->payload, frame->len, &sd);

        if (rc != 0) {
                if (rc != -ENODATA) {
                        printk("mibeacon: process failed %d(wrong bindkey?)\n", rc);
                }
                return;
        }

        printk("PKT #%u RSSI=%d", frame->payload, frame->rssi);

        int frac;

        if (sd.has_temp) {
                last.temp_x10 = sd.temp_x10;
                last.has_temp = true;
                printk(" TEMP=%d.%dC", sd.temp_x10 / 10,
                     sd.temp_x10 % 10 < 0 ? -(sd.temp_x10 % 10) : sd.temp_x10 % 10);
        }
        if (sd.has_hum) {
                last.hum_x10 = sd.hum_x10;
                last.has_hum = true;
                printk(" HUM=%u.%u%%", sd.hum_x10 / 10, sd.hum_x10 % 10);
        }
        if (sd.has_batt) {
                last.batt_pct = sd.batt_pct;
                last.has_batt = true;
                printk(" BATT=%u%%", sd.batt_pct);
        }
        printk("\n");

        oled_clear();
        oled_print(da16200_is_connected() ? "RA4M2 MiBeacon W" : "RA4M2 MiBeacon", 0, 0);
        if (last.has_temp) {
                frac = last.temp_x10 % 10;
                if (frac < 0) {
                        frac = -frac;
                }
                snprintf(line, sizeof(line), "T: %d.%d C",
                       (int)(last.temp_x10 / 10), frac);
                oled_print(line, 0, 1);
        }
        if (last.has_hum) {
                snprintf(line, sizeof(line), "H: %u.%u %%",
                       (unsigned)(last.hum_x10 / 10), (unsigned)(last.hum_x10 % 10));
                oled_print(line, 0, 2);
        }
        if (last.has_batt) {
                snprintf(line, sizeof(line), "B:%u%% %ddBm #%u",
                       last.batt_pct, frame->rssi, frame->payload);
        } else {
                snprintf(line, sizeof(line), "%ddBm #%u",
                       frame->rssi, frame->payload);
        }
        oled_print(line, 0, 3);
        oled_flush();
}

int main(void)
{
        bool bt_ok = false;

        printk("app start\n");

        if (!gpio_is_ready_dt(&led)) {
                printk("led device not ready\n");
                return 0;
        }
        if (gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE) != 0) {
                printk("led config failed\n");
                return 0;
        }

        int ret = oled_init();

        printk("oled init ret=%d\n", ret);
        i2c_scan(oled_i2c_bus());

        ret = mibeacon_init();
        printk("mibeacon init ret=%d\n", ret);

        /* 蓝牙初始化:模块没刷好 HCI 固件时会超时断言,属预期,先看 OLED */
        ret = bt_enable(NULL);
        if (ret != 0) {
                printk("bt enable failed ret=%d(模块未就绪或未刷 HCI 固件)\n", ret);
        } else {
                struct bt_le_scan_param scan_param = {
                        .type = BT_LE_SCAN_TYPE_PASSIVE,
                        .options = BT_LE_SCAN_OPT_NONE,
                        .interval = BT_GAP_SCAN_FAST_INTERVAL,
                        .window = BT_GAP_SCAN_FAST_WINDOW,
                };

                bt_ok = true;
                ret = bt_le_scan_start(&scan_param, scan_cb);
                printk("scan start ret=%d\n", ret);
        }

        oled_clear();
        oled_print("RA4M2 MiBeacon", 0, 0);
        oled_print(bt_ok ? "scanning..." : "BT INIT FAIL", 0, 3);
        oled_flush();

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

        while (1) {
                struct mi_frame *frame = k_fifo_get(&mi_frame_fifo, K_MSEC(10));

                if (frame != NULL) {
                        show_frame(frame);
                        k_mem_slab_free(&mi_frame_slab, (void *)frame);
                }

                /* 收到订阅主题(查询)消息 -> 把当前温湿度发布出去 */
                char query;

                if (da16200_mqtt_incoming(query, sizeof(query))) {
                        printk("MQTT query: %s\n", query);
                        if (last.has_temp || last.has_hum || last.has_batt) {
                                char payload;
                                int len = 0;

                                /* 只发已收到的字段(小米把温/湿/电分散在不同帧里) */
                                len += snprintf(payload + len, sizeof(payload) - len, "{");
                                if (last.has_temp) {
                                        int tfrac = last.temp_x10 % 10;

                                        if (tfrac < 0) {
                                                tfrac = -tfrac;
                                        }
                                        len += snprintf(payload + len, sizeof(payload) - len,
                                                        "\"temperature\":%d.%d",
                                                        (int)(last.temp_x10 / 10), tfrac);
                                }
                                if (last.has_hum) {
                                        len += snprintf(payload + len, sizeof(payload) - len,
                                                        "%s\"humidity\":%u.%u",
                                                        last.has_temp ? "," : "",
                                                        (unsigned)(last.hum_x10 / 10),
                                                        (unsigned)(last.hum_x10 % 10));
                                }
                                if (last.has_batt) {
                                        len += snprintf(payload + len, sizeof(payload) - len,
                                                        "%s\"battery\":%u",
                                                        (last.has_temp || last.has_hum) ? "," : "",
                                                        last.batt_pct);
                                }
                                snprintf(payload + len, sizeof(payload) - len, "}");
                                int rc = da16200_mqtt_publish(DA16200_MQTT_TOPIC, payload);

                                printk("MQTT publish %s rc=%d\n", payload, rc);
                        } else {
                                printk("MQTT skip: no sensor data yet\n");
                        }
                }

                gpio_pin_toggle_dt(&led);
        }

        return 0;
}
```

## 工程总结

经过一个星期的工作,终于是把整个工程给完成了,这里主要是我用zephyr 可以轻松的把我原来在ra6m4上的oled、da16200的工程给移植过来。因此体会到了zephyr rtos的功能强大,相比使用e2studio或者keil,不需要进行复杂的fsp工程配置,依据官方开发板ek-ra4m2的工程包进行少量人修改即可以快速驱动外设。
当然这次试用,官方提供了驱动资料包,找资料也非常的方便。再次感谢与非网与瑞萨电子!

页: [1]
查看完整版本: 【瑞萨BLE/WIFI模块测评】无线家居智能监测