2026 M-Design设计竞赛-用FRDM-RW612实现多合一远程环境监测
该项目使用了FRDM-RW612,实现了环境信息采集和传输的设计,它的主要功能为:通过IIC接口连接SEN66多合一环境采集,并在OLED屏上显示,通过蓝牙传输到微信小程序。
标签
嵌入式系统
BLE
OLED
M-Design
FRDM_RW612
SEN66
Batman9527
更新2026-06-09
32

一、项目介绍和创意介绍

1.1 项目背景


2026贸泽电子M-Design创意设计大赛(第二季),我实现的是任务一智能家居,使用 FRDM_RW612 开发板、SEN66多合一传感器以及OLED显示屏制作了多合一远程环境监测项目。


室内环境质量(PM2.5、CO₂、温湿度等)直接影响人体健康,目前市面多数方案只测一两个参数,数据显示也只停留在设备本地,缺少远程查看和历史追溯的能力。本项目的目标是做一套低成本、高集成度的环境监测系统,支持本地 OLED 和手机小程序双向展示。

1.2 核心创意

一颗芯片测9种参数。 选 SEN66 这颗多合一传感器,同时输出 PM1.0/2.5/4.0/10、温度、湿度、CO₂、VOC、NOx,省掉多个分立传感器带来的布线复杂度和成本。

本地显示 + 远程查看。 本地用 OLED 翻页显示9种数据,远程通过 BLE 5.0 推送到微信小程序,小程序里还能看历史曲线。

上位机用微信小程序。 不要装 App,微信扫码直接用。Android / iOS 都支持,微信本身就带 BLE API,存数据用 wx.setStorageSync 就行。

1.3 系统组成

部分

方案

职责

下位机

FRDM-RW612 + Zephyr RTOS

采集传感器、驱动 OLED、走 BLE

传感器

SEN66

9种环境参数,I2C 接口

显示屏

SSD1306 OLED 128×64

本地展示数据

通信

BLE 5.0 GATT Notify

下位机 → 手机

上位机

微信小程序

实时显示 + 历史曲线


二、硬件介绍

2.1 FRDM-RW612 开发板

NXP 官方的低成本评估板,主控是 RW612 双核(Cortex-M33 @ 260MHz + DSP),板载 2MB Flash、768KB SRAM,集成 2.4GHz BLE 5.0 射频,自带 J-Link 调试器。

2.2 SEN66 多合一传感器

Sensirion 出品,一颗芯片集成四种传感技术:

  • 激光散射 → PM1.0 / PM2.5 / PM4.0 / PM10
  • 电容式 → 温湿度
  • NDIR 非色散红外 → CO₂
  • MOx 金属氧化物 → VOC / NOx 指数

I²C 接口,地址 0x6b。

2.3 SSD1306 OLED 显示屏

0.96 寸,128×64 像素,驱动芯片 SSD1306,I²C 接口,地址 0x3c。

2.4 硬件连接

FRDM-RW612 的 flexcomm2 配成 I²C 主模式(100kHz),挂两个从设备:



图2-1: 硬件连接示意图

两路外设的 SCL 和 SDA 引脚并联,靠地址区分。按钮 SW0 和 RGB LED 走 GPIO,跟 I²C 没关系。


三、方案框图和项目设计思路

3.1 系统架构

分四层:传感器采集 → 主控处理 → BLE 通信 → 应用展示。



3.2 数据流

本地路径:SEN66 → I²C → sen66_thread 采集 → sensor_data 共享内存 → user_ui_thread → OLED

远程路径:SEN66 → I²C → sen66_thread → sensor_data → ble_thread → BLE Notify → 微信小程序

两个路径共用同一份数据源,通过 sensor_data 模块的 k_mutex 保证线程安全。

3.3 多线程架构

线程

优先级

栈大小

干什么

sen66_thread

7

4096

每秒读一次 SEN66

ble_thread

6

4096

BLE 广播、连手机、每5秒推数据

user_ui_thread

8

2048

OLED 显示,2秒刷新一次

hmi_thread

8

1024

按钮中断 + LED 控制

线程间不走消息队列,直接用 k_mutex 保护共享内存,避免拷贝开销。

3.4 BLE 数据包协议

22 字节定长包,小端序,一次 Notify 发完:

偏移

字节数

字段

类型

物理值换算

0

2

PM1.0

uint16

÷10 = μg/m³

2

2

PM2.5

uint16

÷10

4

2

PM4.0

uint16

÷10

6

2

PM10

uint16

÷10

8

2

湿度

int16

÷100 = %RH

10

2

温度

int16

÷200 = °C

12

2

VOC

int16

÷10

14

2

NOx

int16

÷10

16

2

CO₂

uint16

ppm

18

4

时间戳

uint32

系统启动秒数

用整型传原始值,接收端再除,避免传浮点数。

3.5 历史存储和 OLED 优化

  • 下位机环形缓冲区 1440 条(约24小时,每分钟记1条)
  • 上位机 wx.setStorageSync 存 10000 条
  • OLED 帧缓冲 + 变化检测:新旧 buffer 对比,只刷变化页,I²C 通信省 75%+

四、原理图介绍

没有自制 PCB,跑在 FRDM-RW612 官方开发板上。原理图层面就只有 flexcomm2 的 I²C 接口接两个外设。


image.png


image.png



flexcomm2 配成 I²C 主模式,频率 100kHz。SEN66 和 OLED 的 SCL/SDA 并一起,从机地址分别是 0x6b 和 0x3c。板上自带 I²C 上拉电阻。


五、软件流程图和关键代码介绍

5.1 整体软件架构

下位机跑 Zephyr RTOS v4.3.0。main() 只打了个编译时间就进空循环睡大觉,真正干活的是四个独立线程:

// main.c — 就这12行
int main(void)
{
printk("Zephyr Application Build Time: %s %s\r\n", __DATE__, __TIME__);
while (1) {
k_msleep(1000);
}
return 0;
}

四个线程通过 K_THREAD_DEFINE 宏定义,编译时自动创建。

线程调度流程


图5-1: 四大线程的软件流程图


5.2 SEN66 采集线程

线程入口在 sen66_thread.c,做了这几件事:

  1. 初始化 I²C HAL
  2. 初始化 SEN66 驱动
  3. 复位芯片(等 1.2s)
  4. 读序列号(打印到日志)
  5. 启动连续测量模式
  6. 每秒循环:读9种数据 → 写共享内存 → 每60次写一条历史

以下是完整的主循环代码(直接从源文件复制):

while (1) {
/* 6. 读取测量值 */
error = sen66_read_measured_values_as_integers(&pm1p0, &pm2p5, &pm4p0, &pm10p0, &humidity, &temperature,
&voc_index, &nox_index, &co2);
if (error != NO_ERROR) {
LOG_WRN("Faild to read measurements: %i", error);
continue;
}

/* 更新共享数据 */
m_sensor_data.pm1_0 = pm1p0;
m_sensor_data.pm2_5 = pm2p5;
m_sensor_data.pm4_0 = pm4p0;
m_sensor_data.pm10 = pm10p0;
m_sensor_data.humidity = humidity;
m_sensor_data.temperature = temperature;
m_sensor_data.voc_index = voc_index;
m_sensor_data.nox_index = nox_index;
m_sensor_data.co2 = co2;
m_sensor_data.timestamp = k_uptime_get() / 1000;
m_sensor_data.valid = true;

sensor_data_update(&m_sensor_data);

/* 每分钟添加一次历史记录 */
history_counter++;
if (history_counter >= 60) {
sensor_data_add_history(&m_sensor_data);
history_counter = 0;
}

row_count++;

k_msleep(1000);
}

说明:sen66_read_measured_values_as_integers 是 Sensirion 官方驱动库的接口,一次 I²C 读操作取回9个值。取回来先填到栈上的 m_sensor_data,再调 sensor_data_update 写进全局共享内存(内部有 k_mutex 保护)。历史记录是每60次采(约1分钟)记一条,环形缓冲区 1440 条。

5.3 传感器数据共享模块

这是线程间通信的核心,代码在 sensor_data.c

写端(SEN66 线程调):

void sensor_data_update(const struct sensor_data *data)
{
k_mutex_lock(&data_mutex, K_FOREVER);
memcpy(&current_data, data, sizeof(*data));
current_data.valid = true;
k_mutex_unlock(&data_mutex);
}

读端(BLE 线程和 UI 线程调):

int sensor_data_get(struct sensor_data *data)
{
if (!data) {
return -EINVAL;
}
k_mutex_lock(&data_mutex, K_FOREVER);
if (!current_data.valid) {
k_mutex_unlock(&data_mutex);
return -ENODATA;
}
memcpy(data, &current_data, sizeof(*data));
k_mutex_unlock(&data_mutex);
return 0;
}

说明:这两个接口都很短。写的时候 lock→memcpy→unlock,读的时候 lock→copy out→unlock。valid 标志用来区分"还没采过数据"和"数据已更新",SEN66 线程第一次写时才设为 true,读端发现 false 就直接返回 -ENODATA

历史记录的环形缓冲区逻辑:

int sensor_data_add_history(const struct sensor_data *data)
{
k_mutex_lock(&history_mutex, K_FOREVER);
struct history_record *record = &history_buffer[history_write_index];
record->timestamp = data->timestamp;
record->temperature = data->temperature;
/* ... 其他字段赋值 ... */
history_write_index = (history_write_index + 1) % MAX_HISTORY_RECORDS;
if (history_count < MAX_HISTORY_RECORDS) {
history_count++;
}
k_mutex_unlock(&history_mutex);
return 0;
}

history_write_index 从0走到1439再绕回0,满了就覆盖老数据。

5.4 BLE GATT 服务和通信线程

GATT 服务用自定义 128-bit UUID,Service 是 12345678-1234-5678-1234-56789abcdef0,Characteristic 是 ...f1,支持 Read 和 Notify。

服务定义(ble_ehs_service.c 第57-72行):

static struct bt_gatt_attr ehs_attrs[] = {
/* [0] 主服务 */
BT_GATT_PRIMARY_SERVICE(&ehs_service_uuid),

/* [1] 特征声明 */
BT_GATT_CHARACTERISTIC(&sensor_data_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
read_sensor_data, NULL, NULL),

/* [2] CCC */
BT_GATT_CCC(ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};

说明:标准的 3-attribute 结构。BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY 表示这个特征值可以被读,也支持推送;CCC 配成可读可写,手机端通过写 CCC 来启用/禁用 Notify。

BLE 线程的主逻辑在 ble_thread.c,以下是关键的数据发送段(第92-198行):

/* 定期发送数据 */
if (is_connected &&
(current_time - connect_time >= CONNECT_DELAY) &&
(current_time - last_send_time >= DATA_SEND_INTERVAL))
{
err = sensor_data_get(&data);

if (err == 0) {
/* 检查传感器数据是否有效(上电初期数据不稳定) */
bool data_valid = true;

if (data.pm1_0 >= 65535 || data.pm2_5 >= 65535 ||
data.pm4_0 >= 65535 || data.pm10 >= 65535) {
data_valid = false;
}
if (data.co2 >= 65535) {
data_valid = false;
}
if (data.nox_index >= 32767) {
data_valid = false;
}

if (!data_valid) {
LOG_WRN("Invalid sensor data, skipping");
last_send_time = current_time;
retry_count = 0;
continue;
}

/* 构造数据包 */
memset(&packet, 0, sizeof(packet));
packet.pm1_0 = data.pm1_0;
/* ... 其余字段赋值 ... */
packet.timestamp = data.timestamp;

err = ble_ehs_send_sensor_data(&packet);
if (err == 0) {
retry_count = 0;
} else if (err == -ENOMEM) {
retry_count++;
if (retry_count <= MAX_NOTIFY_RETRY) {
/* 不更新 last_send_time,下次循环立即重试 */
} else {
last_send_time = current_time;
retry_count = 0;
}
}
/* ... 其他错误处理 ... */
}
}

k_sleep(K_SECONDS(1));

说明:这里有几个设计点。CONNECT_DELAY = 5 秒是等手机那边配好 CCC 和 MTU;数据有效性校验过滤上电初期 SEN66 的异常值;-ENOMEM 重试是因为 ATT 层还没准备好,最多试3次,3次都失败就跳过等下一个周期。

断线重连在 ble_ehs_service.c 的第92-110行:

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("<<< Disconnected: %s (0x%02x)", addr, reason);

if (current_conn) {
bt_conn_unref(current_conn);
current_conn = NULL;
}
ccc_value = 0;

bt_le_adv_stop();
k_work_reschedule(&adv_restart_work, K_MSEC(500));
}

disconnected 回调里把 current_conn 引用释放掉、ccc_value 清零,然后停广播等 500ms 再重启。延迟重启是因为 BLE 栈内部需要时间清理旧的资源。

5.5 UI 显示线程

OLED 显示逻辑在 user_ui.c,分三页翻:

static void ui_show_page1(const struct sensor_data *data)
{
char buf[32];
snprintf(buf, sizeof(buf), "TEMP : %5.1F ^C", (double)data->temperature / 200.0);
oled_draw_string(0, 0, buf);
snprintf(buf, sizeof(buf), "HUMI : %5.1F %%", (double)data->humidity / 100.0);
oled_draw_string(0, 16, buf);
snprintf(buf, sizeof(buf), "CO2 : %5u PPM", data->co2);
oled_draw_string(0, 32, buf);
}
// page2(PM1.0/2.5/4.0)和 page3(PM10/VOC/NOx)结构一样

static void ui_update_display(const struct sensor_data *data)
{
static uint8_t page_idx = 0;
static int64_t last_page_switch = 0;
int64_t now = k_uptime_get();
char buf[16];

if (now - last_page_switch >= 5000) {
page_idx = (page_idx % 3) + 1;
last_page_switch = now;
}

oled_clear();

if (page_idx == 1) ui_show_page1(data);
else if (page_idx == 2) ui_show_page2(data);
else if (page_idx == 3) ui_show_page3(data);

snprintf(buf, sizeof(buf), "%d/3 PAGE", page_idx);
oled_draw_string(64, 48, buf);

oled_refresh();
}

说明:page_idx 从1到3循环,每5秒切一次。每页固定三个数据行(y=0,16,32),右下角显示页码。

欢迎界面的实现:

static void ui_show_welcome(void)
{
#if defined(CONFIG_BOARD_FRDM_RW612)
oled_clear();
oled_draw_centered(0, "EETREE & MOUSER");
oled_draw_centered(16, "RW612 BLE HT");
oled_draw_centered(32, "OLED && BLE");
oled_draw_centered(48, "WECHAT MINIPROGRAM");
oled_refresh();
#elif defined(CONFIG_BOARD_FRDM_MCXW71)
/* MCXW71 版显示不同的品牌 */
oled_draw_centered(0, "EEPW & ELE14");
oled_draw_centered(16, "MCXW71 BLE HT");
/* ... */
#endif
}

预编译宏区分不同开发板,换板子就显示不同的品牌信息。

5.6 微信小程序

小程序连接 BLE 设备的流程,核心代码在 realtime.js

搜索设备(精确匹配名称):

_startDiscovery() {
// 先清理旧监听器
if (this._deviceFoundHandler) {
wx.offBluetoothDeviceFound(this._deviceFoundHandler);
}

this._deviceFoundHandler = (res) => {
if (!this._isConnecting) return;
res.devices.forEach(device => {
const name = device.name || device.localName || '';
// 精确匹配设备名称 "BLE HT Meter"
if (name === TARGET_DEVICE_NAME) {
wx.stopBluetoothDevicesDiscovery();
wx.offBluetoothDeviceFound(this._deviceFoundHandler);
this._connectToDevice(device.deviceId);
}
});
};

wx.onBluetoothDeviceFound(this._deviceFoundHandler);
wx.startBluetoothDevicesDiscovery({ ... });
}

说明:TARGET_DEVICE_NAME'BLE HT Meter',用 === 全等匹配而不是 includes() 模糊匹配,这是为了避免连到周围的"BYD BLE3"、"海豹06EV"之类无关设备。

连接后的服务发现和特征值查找:

_discoverServices() {
wx.getBLEDeviceServices({
deviceId: this._deviceId,
success: (res) => {
// 优先找自定义 UUID
const targetUUID = app.globalData.serviceUUID.toUpperCase();
let targetSvc = res.services.find(
s => s.uuid.toUpperCase() === targetUUID
);
// 没找到的话,排除标准服务后取第一个
if (!targetSvc) {
const standardPrefixes = ['00001800', '00001801', '0000180a', '0000181a'];
targetSvc = res.services.find(s => {
const uuid = s.uuid.toUpperCase();
return !standardPrefixes.some(p => uuid.startsWith(p));
});
}
if (targetSvc) {
this._serviceId = targetSvc.uuid;
this._discoverCharacteristics();
} else {
this._retryDiscoverServices(); // 重试
}
},
fail: (err) => { this._retryDiscoverServices(); }
});
}

说明:先按配置的 UUID 精准查找,找不到时把标准蓝牙服务(GAP/GATT/设备信息等)排除,取第一个非标准服务作为备选。失败时最多重试3次,间隔1.5秒。

收到数据后的解析:

_handleSensorData(buffer) {
const dataView = new DataView(buffer);
const off = 0;

const pm1_0 = dataView.getUint16(off + 0, true);
const pm2_5 = dataView.getUint16(off + 2, true);
const pm4_0 = dataView.getUint16(off + 4, true);
const pm10 = dataView.getUint16(off + 6, true);
const rawHumi = dataView.getInt16(off + 8, true);
const rawTemp = dataView.getInt16(off + 10, true);
const voc_index = dataView.getInt16(off + 12, true);
const nox_index = dataView.getInt16(off + 14, true);
const co2 = dataView.getUint16(off + 16, true);
const timestamp = dataView.getUint32(off + 18, true);

const data = {
pm1_0: (pm1_0 / 10.0).toFixed(1),
pm2_5: (pm2_5 / 10.0).toFixed(1),
pm4_0: (pm4_0 / 10.0).toFixed(1),
pm10: (pm10 / 10.0).toFixed(1),
humidity: Math.max(0, Math.min(100, rawHumi / 100.0)).toFixed(1),
temperature: Math.max(-10, Math.min(80, rawTemp / 200.0)).toFixed(1),
voc_index: (voc_index / 10.0).toFixed(1),
nox_index: (nox_index / 10.0).toFixed(1),
co2: Math.min(40000, co2),
updateTime: this._formatTime(new Date())
};
app.globalData.currentData = data;
app.addHistoryRecord({ timestamp: Date.now(), ...data });
this._updateDisplayData(data);
}

说明:getUint16(off, true) 的第二个参数 true 表示小端序,跟下位机的协议对应。温湿度做了边界限制(湿度0~100%,温度-10~80°C),CO₂ 限 40000,防止 SEN66 的异常值污染显示。


六、功能展示图及说明

6.1 硬件连接

照片里能看到:

  • FRDM-RW612 开发板,USB 线供电
  • SEN66 传感器模块用杜邦线连到开发板 flexcomm2 接口
  • SSD1306 OLED 并联在同一个 SCL/SDA 上
  • 板载按钮 SW0 和 RGB LED 也可见


image.png



6.2 OLED 显示

上电先出欢迎画面,5秒后进数据轮播。


欢迎画面:

    EETREE & MOUSER
RW612 BLE HT
OLED && BLE
WECHAT MINIPROGRAM

三页轮播(每5秒):

  • Page 1:温度 (°C)、湿度 (%RH)、CO₂ (ppm)
  • Page 2:PM1.0 / PM2.5 / PM4.0 (μg/m³)
  • Page 3:PM10 (μg/m³)、VOC 指数、NOx 指数

每页右下角显示 "x/3 PAGE"。


image.png


image.png

image.png


image.png




6.3 微信小程序界面

实时数据页: 顶部显示连接状态 + 扫描按钮。中间是9张卡片,每张一个传感器数据带彩色图标。底部显示最后更新时间、数据条数、操作引导。

历史曲线页: 顶部时间筛选(1h/6h/24h/3d)。中间 Canvas 画的曲线图,可切换传感器显示。下方显示最大/最小/平均值。底部 9 宫格显示各传感器最新值,点击切换曲线。

6.4 操作步骤

  1. 开发板 USB 上电,OLED 出欢迎画面
  2. 手机微信打开小程序
  3. 点"扫描并连接",自动搜名为 "BLE HT Meter" 的设备
  4. 连上后自动显示9种数据
  5. 切"历史曲线"看趋势



6.5 微信界面演示

image.png


image.png



image.png




image.png



image.png








七、设计中遇到的难题和解决方法

7.1 BLE Notify 报 -ENOMEM

连接建立后调用 bt_gatt_notify() 频繁返回 -ENOMEM

原因:刚连上时 ATT 通道还没完全建好,TX 缓冲区没分配好就发 Notify,BLE 栈返回 -ENOMEM。

解决:

  1. 连上后等 5 秒再发(CONNECT_DELAY
  2. -ENOMEM 时最多重试 3 次
  3. -EACCES(CCC没开)和 -ENOTCONN(断连)不重试,直接跳过

7.2 断连后搜不到设备

手机断开后广播停了,或者 BLE 栈内部资源没释放干净,导致重启广播失败。

解决:disconnected 回调里一次性做完三件事:bt_conn_unref 释放引用 → bt_le_adv_stop 停旧广播 → k_work_reschedule 延迟500ms重启。丢到 workqueue 里避免在中断上下文干活。

7.3 小米/红米手机连不上

wx.openBluetoothAdapter() 返回 10001 或 10004。

原因:MIUI 的 BLE 扫描需要开位置权限,只在 Manifest 里配蓝牙权限不够。

解决:Android 端先调 wx.authorize({ scope: 'scope.userLocation' }) 要位置权限,要不到也继续走,不卡死。

7.4 SEN66 上电数据异常

刚上电时 PM 读到 65535、CO₂ 读到 65535。

原因:传感器内部的激光器、NDIR 灯丝、MOx 加热板都需要预热才能稳定。

解决:BLE 线程在发数据前做三级校验 data.pmX >= 65535data.co2 >= 65535data.nox_index >= 32767,有一个超阈值就跳过本次发送,等下一周期再试。

7.5 OLED 刷屏慢

全屏刷要传 1024 字节(128×8 pages),100kHz I²C 下要 80ms+。

解决:维护新旧两个帧 buffer,刷屏前逐 page 对比,只刷变化的 page。实测平时只变 1-2 个 page,I²C 通信量减了 75%+。

7.6 小程序连不上设备

断断续续出现 getBLEDeviceServices:fail:no service,还会连到汽车的"BYD BLE3"。

排查过程:翻控制台日志发现同时连了多个设备,服务发现失败率高,而且旧监听器没注销。

问题

根因

修法

匹配到车载蓝牙

name.includes('BLE') 太宽

改为 === 'BLE HT Meter'

同时连多个设备

onBluetoothDeviceFound 回调多次触发

_isConnecting

服务发现失败

连接完立刻查服务,栈还没稳定

失败后等1.5秒重试,最多3次

监听器泄漏

旧监听器残留在全局

断开时 offBluetoothDeviceFound 注销

断开后重连慢

closeBluetoothAdapter() 关掉整个蓝牙

改轻量清理,只清内部状态


八、心得体会

8.1 技术上的收获

Zephyr RTOS 多线程:之前写单片机都是裸机轮询,这次用 4 个独立线程加互斥锁,代码结构比裸机清晰很多。


BLE 协议栈:从配广播数据、注册 GATT 服务、处理 CCC 配置到处理 ENOMEM 重试,踩了不少坑才跑通。特别是 ATT 层时序问题,文档几乎没讲,全靠读源码试出来的。


微信小程序 BLE API:流程比想象的长——开适配器、扫设备、连、MTU协商、发现服务、发现特征值、开 Notify,七步走完才能收数据。中间任何一步失败都要处理,对异步编程的容错要求比较高。


日志驱动 debug:小程序连不上的问题花了最多时间。最后是靠逐行分析控制台日志,发现同时连了"BYD BLE3"和"BLE HT Meter"两台设备,才定位到原因。修完这个之后又修了监听器泄漏和服务发现重试,连接成功率才上去。

8.2 Zephyr 开发体验

设备树 + 代码分离做得不错,换开发板只要改 .overlay,不用改 C 代码。Kconfig 配起来也方便。

不足是文档不够全面,BLE 这块的时序细节、ATT 层状态机基本没有说明,遇到问题只能去翻 Zephyr 源码或者社区。

8.3 还能改进的地方

  • 低功耗:现在是一直采一直发,可以加休眠模式
  • 上云:数据存到云端,多设备查看
  • 告警:PM2.5/CO₂ 超限了弹通知
  • 导出:历史数据能导成 CSV
  • 多设备:一个手机盯多个传感器节点


附件下载
nxp_ble_ht_meter-dev_rw612.zip
下位机
nxp_ble_ht_miniprogram-master.zip
微信小程序
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号