RT-Thread实战:基于星火一号的温湿度监测与报警系统

RT-Thread实战:基于星火一号的温湿度监测与报警系统 简介本资源是一套基于RT-Thread实时操作系统的嵌入式温湿度监测与报警系统完整工程面向物联网开发初学者、嵌入式工程师及高校实践教学人员解决工业环境、仓储物流、医药冷链等场景中对温湿度实时感知、阈值预警与本地/远程响应的核心需求。压缩包共44个文件涵盖6个C源文件含传感器驱动、LVGL图形界面、OneNet云对接等核心逻辑、6个头文件定义硬件抽象层与配置参数、2个Keil工程文件uvprojx/uvoptx、1个LVGL界面截图png及SConscript构建脚本、rtconfig.py配置工具等全面支撑从底层驱动到上层应用的全栈开发包体大小为1.95MB。已有1011人学习下载提供可直接编译运行的星火一号开发板工程包含DHT系列传感器采集、LCD/LED本地显示、蜂鸣器报警触发、Wi-Fi联网上传等完整功能链路目录结构按board、applications、drivers分层组织便于理解RTT组件化设计思想与多任务协同机制。 做温湿度监测与报警放到几年前我大概率会拿一块51单片机接个DHT11然后写个死循环轮询。典型的能跑就行方案但代码越写越乱加阈值判断、加显示、加报警逻辑最后全堆在一个while(1)里改一个功能就要翻半天代码。这次我把方案换成了RT-Thread官方的星火一号开发板配合RT-Thread操作系统来做整套温湿度监测与报警系统整个思路清晰了很多。这篇文章把我的完整实现过程、踩过的坑、调试技巧都记录下来给准备用RT-Thread做类似项目的朋友一个参考。星火一号这块板子的核心价值在于传感器、显示、报警输出这些外设全部板载不用自己在面包板上飞线。你拿到手就是一个完整的硬件平台系统级的开发和调试体验比传统单片机开发好太多。无论你是刚接触RT-Thread的新手还是已经用裸机开发过几个小项目、想往RTOS方向转的开发者这篇文章都值得看一看。1. 星火一号的硬件底子为什么这个板子适合做温湿度报警1.1 板载资源一次理清做项目之前先把手里的硬件资源摸清楚。星火一号的主控是一颗Cortex-M7内核的高性能MCU主频跑到480MHz片上RAM和Flash的容量都很充裕。M7内核带双精度硬件浮点单元处理温湿度数据的换算、滤波算法基本没什么压力。这和以前在51单片机上掐着手指头算资源是完全不同的体验。更关键的是板载外设。整个温湿度检测报警系统需要的核心外设这板子几乎都给了功能模块板载资源说明温湿度采集AHT21传感器I2C接口精度高响应快数据显示4.3寸RGB LCD屏480x272分辨率支持LVGL报警输出有源蜂鸣器GPIO控制高电平触发状态指示RGB三色LED可分别控制红、绿、蓝通道交互输入板载按键可做阈值设置、报警消除无线扩展WiFi模组后期可做云端上报这里有一个很关键的判断为什么不用DHT11/DHT22DHT11虽然便宜、资料多但它的时序是单总线协议需要GPIO做严格的延时控制而且采样精度低温度±2℃湿度±5%。在RT-Thread这种多线程环境下用GPIO模拟时序很容易被线程调度打断导致读取失败。AHT21走标准I2C协议由硬件控制器管理时序配合RT-Thread的I2C设备驱动框架读取稳定得多。温度精度±0.3℃湿度精度±2%做环境监测完全够用。1.2 报警链路的硬件映射报警系统涉及三个动作检测到超限、发出声光报警、用户消除报警。在星火一号上这三个动作的硬件链路非常清楚传感器数据异常 - 触发蜂鸣器鸣叫 RGB LED闪烁LCD屏幕显示当前状态的文字和图标用户按下按键 - 清除报警状态或延时重新检测蜂鸣器用的是有源蜂鸣器也就是说给它一个高电平就会发出固定频率的声音不需要PWM去驱动。RGB LED同理GPIO拉低点亮。这类简单外设在RT-Thread里注册为pin设备操作接口统一为rt_pin_write。从项目架构的角度看这个系统的复杂度不在于单个外设的驱动而在于多任务协作。数据采集是一个周期性任务报警判断是一个实时响应任务LCD刷新又是一个独立任务。如果全部堆在裸机循环里采集、判断、刷新必须排队执行时序上会有明显的耦合。这正是我选择RTOS的根本原因。2. 工程搭建从RT-Thread Studio到驱动配置2.1 SDK与BSP的关系RT-Thread Studio是官方IDE内置了工程向导。用Studio创建星火一号工程时最核心的一步是选对SDK和BSP。Studio的SDK Manager里可以下载星火一号对应的开发板支持包这里我强烈建议直接用官方仓库最新版本别用旧版。我一开始图省事用了某个旧版本BSP结果LCD驱动和当前版本的LVGL库不兼容后面踩了不少坑。创建工程后目录结构是这样的project ├── applications │ └── main.c ├── board ├── rt-thread ├── packages └── rtconfig.hboard目录下是板级支持包包含所有外设的初始化代码。applications/main.c是用户入口。RT-Thread的工程构建体系会自动把内核、组件、软件包链接到一起你不用自己管Makefile这比传统Keil工程省心得多。2.2 外设驱动的在线配置RT-Thread用rtconfig.h和RT-Thread Settings来管理组件开关。在Studio里打开RT-Thread Settings界面勾选需要的组件I2C设备驱动必须启用AHT21挂在I2C总线上Sensor框架RTT的传感器抽象层可以自动挂载AHT21Pin设备驱动控制GPIO输出蜂鸣器、LED、按键输入LVGL图形库用于LCD界面显示FinSH控制台调试利器后面细说这些配置本质上是修改Kconfig和rtconfig.h里的一堆宏定义但通过图形界面操作直观得多。配置完成后Studio会自动重新生成工程并编译。有一点要注意Sensor框架和直接读写I2C是两条不同的技术路线。用Sensor框架的好处是统一接口上层调find和read就行系统帮你管理采集时机。但缺点是框架层会多一些抽象调试时不够直观。我这次是直接操作I2C设备因为项目逻辑简单而且我想把AHT21的读取时序理解透彻。3. 采集链路AHT21的读取逻辑与I2C细节3.1 AHT21寄存器与指令协议AHT21的数据手册要点不多但每个都关键。它的I2C设备地址是0x387位地址支持两个测量指令0xAC触发测量0xE1读取校准状态。每次测量前需要发送0xAC指令附带两个参数0x33和0x00然后等待约80ms再读取6个字节的原始数据。这6个字节的分布是第1字节是状态字第2-4字节是湿度原始值20位第5-7字节只用前20位是温度原始值。换算公式很固定湿度(%RH) (原始湿度值 / 2^20) * 100 温度(℃) (原始温度值 / 2^20) * 200 - 50代码实现如下#include rtthread.h #include rtdevice.h #define AHT21_ADDR 0x38 #define AHT21_MEAS_CMD 0xAC #define AHT21_MEAS_PARAM 0x33 #define AHT21_NOP_CMD 0x00 static struct rt_i2c_bus_device *aht21_bus RT_NULL; static rt_err_t aht21_write_reg(rt_uint8_t reg, rt_uint8_t data) { struct rt_i2c_msg msgs[1]; rt_uint8_t buf[2]; buf[0] reg; buf[1] data; msgs[0].addr AHT21_ADDR; msgs[0].flags RT_I2C_WR; msgs[0].buf buf; msgs[0].len 2; return rt_i2c_transfer(aht21_bus, msgs, 1); } static rt_err_t aht21_read_raw(rt_uint8_t raw[6]) { struct rt_i2c_msg msgs[1]; rt_uint8_t buf[6]; msgs[0].addr AHT21_ADDR; msgs[0].flags RT_I2C_RD; msgs[0].buf buf; msgs[0].len 6; rt_err_t ret rt_i2c_transfer(aht21_bus, msgs, 1); if (ret 1) { for (int i 0; i 6; i) { raw[i] buf[i]; } } return ret; }这里的rt_i2c_transfer是RT-Thread I2C设备驱动框架的核心接口支持一次传输多个消息可以组合成先写指令再读数据的完整交互流程。但要注意的是AHT21的读取流程拆成了写触发指令和延时后读数据两个步骤所以代码要分成独立的函数调用中间不能像某些I2C传感器那样在一个transfer里完成写入和读取。3.2 完整读取与数据换算触发测量和读取数据是两个独立的I2C事务中间必须等待足够的时间这是AHT21最容易出错的地方。实测下来测量时间至少要等80ms数据手册上写的是典型80ms但在低温和高湿环境下实际可能需要更长时间。我设置的阈值是100ms宁可多等一些也不要在未就绪时去读返回的数据会是全0或者有明显跳变。rt_err_t aht21_read_temp_humi(float *temp, float *humi) { rt_uint8_t raw[6] {0}; rt_uint32_t raw_humi 0; rt_uint32_t raw_temp 0; // 1. 发送测量触发指令 aht21_write_reg(AHT21_MEAS_CMD, AHT21_MEAS_PARAM); aht21_write_reg(AHT21_NOP_CMD, AHT21_NOP_CMD); // 2. 等待测量完成 rt_thread_mdelay(100); // 3. 读取6字节原始数据 if (aht21_read_raw(raw) ! RT_EOK) { return -RT_ERROR; } // 4. 检查状态字是否正常 if ((raw[0] 0x80) ! 0) { rt_kprintf(AHT21: device busy!\n); return -RT_ERROR; } // 5. 拼接湿度原始值raw[1]的高4位 raw[2] raw[3] raw_humi ((rt_uint32_t)raw[1] 12) | ((rt_uint32_t)raw[2] 4) | ((rt_uint32_t)raw[3] 4); // 6. 拼接温度原始值raw[3]的低4位 raw[4] raw[5] raw_temp ((rt_uint32_t)(raw[3] 0x0F) 16) | ((rt_uint32_t)raw[4] 8) | ((rt_uint32_t)raw[5]); // 7. 换算成实际物理量 *humi (float)raw_humi / 1048576.0f * 100.0f; *temp (float)raw_temp / 1048576.0f * 200.0f - 50.0f; return RT_EOK; }这里有个大坑原始数据的位拼接必须严格按数据手册来。湿度是20位数据分布在raw[1]的全部、raw[2]的全部、raw[3]的高4位。温度是20位数据分布在raw[3]的低4位、raw[4]的全部、raw[5]的全部。如果你拼接错了位读出来的数值会非常离谱比如温度变成-30℃或者湿度变成120%。我一开始把raw[3]的高低4位弄反了排查了很久才发现是这里错了。所以建议任何I2C传感器第一次读取先打印原始字节对照数据手册验证拼接逻辑再往上层走。3.3 I2C总线地址和引脚的确认在写代码之前还有一个确认步骤容易被忽略确认AHT21在板子上挂在哪条I2C总线上。星火一号的板载传感器挂在哪条总线、设备名是什么直接决定了rt_device_find的入参。我是通过原理图和BSP里的驱动初始化代码确认的。在RT-Thread里I2C总线的设备名通常是i2c1、i2c2这样的编号。找总线的方式很直接aht21_bus (struct rt_i2c_bus_device *)rt_device_find(i2c1); if (aht21_bus RT_NULL) { rt_kprintf(find i2c1 failed\n); return -RT_ERROR; }如果你用了Sensor框架这些细节框架层都封装好了但自己实现驱动时就要对总线名格外留心。可以在初始化时遍历一下所有注册的设备用list_device命令在FinSH控制台里查看当前系统注册了哪些设备这是一个很有用的调试习惯。4. 报警逻辑用事件集编排传感器与报警线程4.1 单靠轮询为什么不行温湿度报警系统的核心需求是实时监测、超限即报。用裸机轮询当然也能做但你会发现逻辑层越来越别扭。假设你设定温度超过30℃报警湿度超过70%报警两个阈值还要能通过按键调整。如果300ms轮询一次传感器每次轮询里都要做阈值比较、判断去抖、蜂鸣器控制、LED闪烁状态切换、LCD刷新。这些逻辑全塞在一个循环里按键扫描还要穿插进去代码可读性急剧下降。在RT-Thread里我更推荐把系统拆成几个独立线程采集线程周期性读取AHT21换算数据发布到消息或事件报警线程阻塞等待事件收到超限事件后驱动蜂鸣器和LED显示线程周期性从共享数据区取数据刷新LCD界面按键扫描线程检测按键输入调整报警阈值或消除报警这样的架构下每个线程只关心自己的事情线程之间通过RTOS的同步机制通信逻辑清爽很多。4.2 事件集怎么发信号RT-Thread提供了多种线程间通信方式信号量、互斥量、消息队列、事件集。这里用事件集是最合适的。为什么不用信号量因为报警条件有多个——温度超限、湿度超限、温度恢复、湿度恢复这四类事件要组合判断。信号量只适合发生了一次这种计数型通知而事件集天然支持多标志位的与/或逻辑判断。事件集的用法很直接#define EVENT_TEMP_HIGH (1 0) #define EVENT_HUMI_HIGH (1 1) #define EVENT_TEMP_NORMAL (1 2) #define EVENT_HUMI_NORMAL (1 3) static struct rt_event alarm_event; void alarm_thread_entry(void *param) { rt_uint32_t e; while (1) { // 等待任意一个报警/恢复事件 rt_event_recv(alarm_event, EVENT_TEMP_HIGH | EVENT_HUMI_HIGH | EVENT_TEMP_NORMAL | EVENT_HUMI_NORMAL, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, e); if (e (EVENT_TEMP_HIGH | EVENT_HUMI_HIGH)) { // 触发报警 rt_pin_write(PIN_BEEP, PIN_HIGH); rt_pin_write(PIN_LED_RED, PIN_LOW); } else { // 恢复正常的处理 rt_pin_write(PIN_BEEP, PIN_LOW); rt_pin_write(PIN_LED_RED, PIN_HIGH); } } }这里用RT_EVENT_FLAG_OR表示多个事件中任意一个满足就唤醒线程。但一个细节要注意RT_EVENT_FLAG_CLEAR标志。事件集接收后会清掉对应的标志位这样下次报警才能再次触发。如果不加这个标志事件标志位会一直保持置位状态导致报警线程反复进入处理逻辑。采集线程里的发送端逻辑float temp, humi; if (aht21_read_temp_humi(temp, humi) RT_EOK) { if (temp TEMP_THRESHOLD) { rt_event_send(alarm_event, EVENT_TEMP_HIGH); } else { rt_event_send(alarm_event, EVENT_TEMP_NORMAL); } if (humi HUMI_THRESHOLD) { rt_event_send(alarm_event, EVENT_HUMI_HIGH); } else { rt_event_send(alarm_event, EVENT_HUMI_NORMAL); } }表面上看这样的写法是每次采集都会发送一个事件这没有问题。事件集的好处在于如果报警线程处理不过来新的事件标志不会丢失会继续累积。但报警这个场景不需要累积只关心当前状态所以接收端用OR模式配合RT_EVENT_FLAG_CLEAR就行了。4.3 蜂鸣器与LED的驱动细节蜂鸣器和LED在RT-Thread里都归为pin设备。初始化代码#define PIN_BEEP GET_PIN(G, 12) // 以实际原理图为准 #define PIN_LED_RED GET_PIN(B, 2) #define PIN_LED_GREEN GET_PIN(B, 3) void alarm_hw_init(void) { rt_pin_mode(PIN_BEEP, PIN_MODE_OUTPUT); rt_pin_mode(PIN_LED_RED, PIN_MODE_OUTPUT); rt_pin_mode(PIN_LED_GREEN, PIN_MODE_OUTPUT); rt_pin_write(PIN_BEEP, PIN_LOW); rt_pin_write(PIN_LED_RED, PIN_HIGH); rt_pin_write(PIN_LED_GREEN, PIN_HIGH); }注意有源蜂鸣器只需要高电平就能响不响应PWM频率所以控制逻辑很简单。但连续鸣叫特别吵。我建议报警线程里不要一直让蜂鸣器响而是做间歇性鸣叫每秒响200ms、停800ms。这样既达到了提醒效果又不会让人烦躁。实现方式是在报警线程里用rt_thread_mdelay做延时而不是真的去开定时器。还有一个细节报警消除逻辑。当温度回落到阈值以下报警线程收到恢复事件后自动停止报警。但实际场景里用户可能希望报警后人工确认。这时候可以加一个按键按下后设置一个手动消除标志蜂鸣器停止但LED继续保持闪烁直到温湿度恢复正常。这个逻辑看具体项目需求没有绝对标准。5. LCD显示让数据看得见5.1 LVGL的初始化与页面构建星火一号板载的LCD屏使用RGB接口分辨率480x272。RT-Thread的LVGL软件包已经把显示驱动封装好了你只需要在lv_conf.h里配置好颜色深度和缓冲区大小然后在主线程里调用lv_init和lv_disp_drv_register。显示逻辑的架构是LVGL有自己的心跳节拍通常由硬件定时器驱动UI刷新和事件处理都在LVGL的任务循环里。所以LVGL相关的代码不应该放在采集线程里频繁调用而是把数据存到共享结构体在LVGL的刷新回调里读取并更新控件。这样UI刷新和传感器采集互不干扰。我的界面布局比较简单顶部系统标题和当前日期/时间中部一个大号温度数值一个大号湿度数值下部报警状态提示文字正常显示环境正常报警显示温度超限 / 湿度超限底部报警阈值显示比如温度阈值30.0℃ 湿度阈值70%LVGL里创建标签控件很简单lv_obj_t *temp_label lv_label_create(lv_scr_act(), NULL); lv_obj_set_style(temp_label, style_temp_num); lv_label_set_text(temp_label, 25.5); lv_obj_align(temp_label, NULL, LV_ALIGN_IN_TOP_MID, 0, 40);关键在更新数据时。我建议定义一个全局结构体typedef struct { float temperature; float humidity; rt_uint8_t alarm_flag; } env_data_t; env_data_t g_env;采集线程更新g_env数据显示线程周期性调用lv_label_set_text更新UI。要注意的是g_env是跨线程共享的虽然两个float和一个uint8_t在32位MCU上基本不会出现撕裂读取但严谨的做法是加一个互斥锁保护或者在LVGL刷新时直接重新读取传感器值。考虑到系统简单我用了互斥锁避免以后加功能时踩并发问题。5.2 刷新策略与界面状态切换一开始我用的实时刷新策略每100ms刷新一次温度、湿度数值。实际显示效果是数字快速跳动视觉上很不稳定因为传感器本身的微小波动会被放大。后来我改成1秒刷新一次并且在代码里做了一级低通滤波g_env.temperature g_env.temperature * 0.7f temp * 0.3f; g_env.humidity g_env.humidity * 0.7f humi * 0.3f;数值平滑很多。这种指数加权平均滤波虽然简单但在温湿度这种慢变场景里效果非常好。报警状态切换时除了蜂鸣器和LED动作LCD上也要同步变化。这里有个小技巧报警状态用颜色区分正常时数值显示白色或绿色报警时显示红色。LVGL可以通过lv_obj_set_style_local_text_color来设置文字颜色。这样用户扫一眼屏幕就能知道当前状态不需要凑近了看文字。接口上还要考虑一点LCD刷新本身比较耗时间。在LVGL的缓冲区配置里默认用的是部分缓冲区比如一行一行刷新对整个显示区域刷新可能需要几十毫秒。如果采集线程和显示线程同时运行要注意优先级设置。我的推荐是采集线程优先级高于显示线程因为数据采集的实时性比UI刷新更高。显示线程哪怕稍微卡顿也不影响报警功能。6. 实测与踩坑记录这些坑你可能也会遇到6.1 I2C总线的假死与地址冲突系统跑了一段时间后我发现一个诡异的现象AHT21偶尔会读取失败而且失败后连续多次都失败像是传感器彻底不响应了。用FinSH调试时手动调用读取函数仍然失败。用逻辑分析仪抓波形发现I2C总线上SCL一直有波形但SDA始终没有ACK。排查思路是I2C设备在异常时序下可能进入错误状态。AHT21作为从机如果主机在读取过程中发送了错误的起始/停止条件或是在从机未就绪时强行读取它可能会进入内部异常状态需要复位才能恢复。解决办法有两种第一种在I2C控制器层面发送一个空的停止条件来复位总线static void i2c_bus_reset(void) { struct rt_i2c_msg msg; rt_uint8_t dummy 0; msg.addr AHT21_ADDR; msg.flags RT_I2C_WR; msg.buf dummy; msg.len 0; rt_i2c_transfer(aht21_bus, msg, 1); }第二种更简单粗暴在检测到读取失败时把AHT21断电再上电。但星火一号板载传感器没有单独的电源控制引脚不方便做硬件复位。所以我采用了第一种方案同时在主循环里加了一个连续失败计数超过3次就重新初始化传感器复位I2C总线。真实原因是AHT21在长时间连续运行后会因为内部校准状态未更新而不再响应测量指令。查询资料后发现AHT21有一个校准标志位上电后需要检查这个位如果未校准需要发送初始化指令0xBE 0x08 0x00。加上之后这个问题基本消失了。6.2 FinSH控制台调试效率翻倍的关键用RT-Thread开发不利用FinSH简直是浪费。FinSH是RT-Thread内置的命令行解释器通过串口接入。我最常用的几个调试操作list_device查看所有注册的设备、信号量、互斥量、事件集list_thread查看所有线程状态、优先级、栈使用率ps等价于list_thread看线程栈顶和剩余栈空间自定义命令把自己写的函数注册成命令手动调用自定义命令的注册方式非常有用static void read_aht21(void) { float temp, humi; if (aht21_read_temp_humi(temp, humi) RT_EOK) { rt_kprintf(temp: %.1f, humi: %.1f\n, temp, humi); } else { rt_kprintf(read failed\n); } } MSH_CMD_EXPORT(read_aht21, read temp and humi);直接在串口终端输入read_aht21就能立即触发读取不用重新编译下载。这个调试方式在阈值测试和传感器故障定位时帮了我大忙。比如怀疑报警逻辑不对时我直接命令行触发一个温度超限事件看报警线程是否正常响应完全不用改代码。6.3 线程栈大小与内存优化RT-Thread中每个线程都有独立的栈空间。栈太小会溢出导致系统崩溃栈太大浪费内存。我之前犯过一个错误LVGL线程栈设小了运行几分钟后系统就重启查了很久才在list_thread里看到那个线程的栈使用率已经到了99%。线程栈大小的预估经验值线程栈大小备注采集线程1024字节调用I2C驱动和简单计算报警线程1024字节事件集逻辑简单显示线程8192字节LVGL绘图较吃栈主线程2048字节初始化后基本空闲想要准确的栈使用数据可以在FinSH里执行list_thread查看每根线程的最大栈使用量然后调整配置。注意Cortex-M7内核的硬件浮点单元在上下文切换时会把FPU寄存器压栈所以用了float运算的线程栈要留足余量。6.4 环境数据的抖动与去抖温湿度数据天然有波动尤其湿度开窗、有人走过、空调出风口数值都会跳一下。如果不做处理报警逻辑会频繁触发和恢复非常烦人。我在代码里加了连续N次超限才触发报警的机制#define ALARM_CONFIRM_CNT 3 static rt_uint8_t temp_high_cnt 0; static rt_uint8_t humi_high_cnt 0; // 在采集线程中 if (temp TEMP_THRESHOLD) { if (temp_high_cnt ALARM_CONFIRM_CNT) { temp_high_cnt; } if (temp_high_cnt ALARM_CONFIRM_CNT) { rt_event_send(alarm_event, EVENT_TEMP_HIGH); } } else { temp_high_cnt 0; rt_event_send(alarm_event, EVENT_TEMP_NORMAL); }这样设定的逻辑是确认3次才报警消除了一定的瞬时干扰。同样的逻辑也用在报警恢复上避免了报警状态在临界值附近反复横跳。实际测试下来当温度在阈值附近±0.2℃波动时系统不会出现频繁报警的情况。7. 后续扩展别让项目止步于此温湿度监测报警系统做完后其实还有很多扩展方向我当时做的只是第一版。有几点值得继续深入第一数据记录与云端上报。星火一号板载WiFi模块可以接入MQTT协议把温湿度数据上报到云平台。接入RT-Thread的kawaii-mqtt或者wlan框架后原本的本地报警系统就能升级成远程监测系统。哪怕人不在现场也能通过手机看到环境数据。第二多传感器融合。目前采集的只是温湿度两个量如果接入空气质量传感器、光照传感器就能构建更完整的环境质量监测中心。星火一号的I2C总线还支持挂载多台设备通过地址区分扩展起来不复杂。第三屏幕交互升级。现在的界面只能看不能改阈值。可以加一个简单的触摸屏或者用板载按键配合菜单逻辑让用户直接修改报警阈值。这样项目的完整度会再上一层。第四低功耗设计。如果项目要做成便携式设备就可以思考加入休眠机制。RT-Thread的电源管理组件可以对MCU进行低功耗调度采集周期从1秒改为10秒一次平时让LCD休眠需要时再唤醒可以大幅降低功耗。想清楚这些扩展点再回头看你会发现当前的实现虽然简单但骨架是清晰、模块化、易扩展的。这也是选RTOS而不是裸机的最大意义——系统的复杂度不会成为迭代的阻碍每个功能都是独立的模块你随时可以往系统里加东西而不必担心把原来的代码弄乱。最后分享一个个人体会用RT-Thread做这类项目一开始最大的门槛不是写代码而是转换思维方式。裸机编程你需要考虑所有资源的串行调度而RTOS编程你需要考虑的是任务拆分、线程通信和同步。一旦跨过这个门槛你会发现同样的功能用RTOS实现反而比裸机更简单、更清晰。这个温湿度报警系统对你来说可能不算很酷的东西但把它完全吃透RTOS的开发思想也就打通了一半。本文还有配套的精品资源点击获取