ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于FreeRTOS的STM32多传感器房间监测系统设计与实现

基于FreeRTOS的STM32多传感器房间监测系统设计与实现 最近刚把一个房间环境监测项目从裸机循环改造成基于FreeRTOS的多任务版本就是标题里的这套 FreeRTOS-Based STM32 Multisensor Room Monitoring System。折腾了差不多两周踩了一堆坑趁思路还热乎把整个设计过程、任务划分、通信机制和最终调通的细节一起梳理出来。如果你正准备在 STM32 上跑 FreeRTOS或者想把 DHT11、BH1750、MQ-2 这些传感器数据统一管理起来再配一个 OLED 显示和网络报警那这篇应该能帮你少走不少弯路。这个系统本身不复杂STM32F103C8T6 作为主控外接温湿度、光照、烟雾、人体红外、可选超声波测距模块所有传感器数据由 FreeRTOS 的多任务协同采集经过滤波和阈值判断后一方面刷新到本地 OLED 屏幕另一方面通过 ESP8266 上报到巴法云同时驱动蜂鸣器做超限报警。听起来很常规但真正把多个传感器和显示、网络放在同一个 RTOS 环境里跑稳定牵扯到的引脚分配、任务优先级、队列/事件组设计、中断处理、堆栈和内存调优每一项都够你折腾一壶的。1. 从裸机到FreeRTOS为什么房间监测这块板子非要上RTOS1.1 裸机轮询到底哪里不行我很长一段时间做 STM32 项目都是裸机 while(1) 大循环单传感器的时候还好一旦传感器数量上来了问题立刻变得很现实。拿 DHT11 举例主机要先拉低 18ms 发出起始信号然后切换输入模式等待响应再按照时序读 40bit 数据每一位都要精确延时一次完整读取少说也要 25ms 左右。如果任务循环里还接着读 BH1750、MQ-2、人体红外再刷一下 OLED一轮下来轻松超过一两百毫秒。这个时间在用户交互上特别难受。按键按下去如果刚好卡在 DHT11 的 18ms 低电平里面按键响应要等到整个循环跑完才有反应体感就是“卡”。OLED 刷新也跟着闪烁因为刷屏的过程被其他传感器的延时打断画了一半个屏幕又去做别的事了。最怕的还不是这个而是某个传感器读取超时比如 DHT11 响应脚没正常拉低裸机的 delay 会一直等下去整个系统看起来就是死机状态你只能复位。1.2 FreeRTOS 的核心思路把忙等变成让出CPURTOS 解决这个问题的思路并不神秘。在裸机里 delay 是忙等CPU 在循环里空转在 FreeRTOS 里任务调用 vTaskDelay 或者等待某个队列/信号量时当前任务会被挂起调度器寻找其他就绪状态的任务来执行。于是当采集任务在等 DHT11 的数据位时CPU 可以去刷新 OLED、检测按键、处理串口接收互不耽误。这个项目最典型的场景就是传感器采集任务、数据处理任务、显示任务、上报任务、报警任务同时存在。如果全塞在一个循环里做哪怕只加一个蜂鸣器报警逻辑都容易把时序搅乱。而用 FreeRTOS你只要把每一项工作定义成一个独立任务划分好优先级和栈空间剩下的调度就交给内核去处理。1.3 系统需要哪些任务优先级怎么排先列一下这个项目最终跑通后的任务清单任务名周期/触发优先级主要工作vTaskSensor100ms 定时触发2读取 DHT11、BH1750、MQ-2 ADC、人感等vTaskProcess队列通知/500ms3数据处理、滤波、判断阈值、设置事件位vTaskDisplay500ms 周期1刷新 OLED 或后续 LVGL 界面vTaskReport5s 周期/事件触发2通过串口/ESP8266上报到云平台vTaskAlarm事件组触发4蜂鸣器报警超时自动关闭vTaskKey10ms 轮询1按键消抖、设置阈值优先级数字越大越优先。这里故意把报警任务设到最高因为烟雾或温度超限属于安全事件必须第一时间响应。传感器采集任务次之保证数据不丢数据处理又比采集高一级因为采集任务只负责拿到原始数据处理任务要把数据转成温度、湿度、光照值并判断是否越界越界就要立刻置事件位。显示任务和按键任务最低慢几十毫秒用户完全无感。网络上报也只在传感器数据周期性地发送没必要给高优先级。2. 传感器选型与外设分配引脚、供电、I2C地址这些坑要提前排掉2.1 引脚映射一顿操作猛如虎结果PB3不能做按键我最初把按键放在 PB3、PB4 上代码写完死活没反应后来才意识到这两个引脚默认是 JTAG 的 TMS/TCK不是普通 GPIO。如果你不想用完整 JTAG只想保留 SWD 下载一定要在初始化时调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)这样才能把 PB3、PB4 释放出来做普通输入。这也是很多新手容易踩的地方。完整引脚分配如下表方便你直接抄作业功能传感器/外设接口类型STM32引脚备注温湿度DHT11单总线PA63.3V供电数据线上拉4.7k光照BH1750I2C1PB6/SCLPB7/SDA地址0x23烟雾MQ-2ADC1_IN0PA0模拟输出需分压到3.3V人体HC-SR501GPIO输入PA1可选提升/下拉超声波(可选)HC-SR04GPIO定时器输入捕获PA2/PA3TRIG/ECHOOLEDSSD1306I2C1PB6/PB7地址0x3C蜂鸣器有源蜂鸣器GPIO输出PB0加NPN三极管驱动按键轻触按键GPIO输入PB3/PB4需禁用JTAG网络ESP8266USART2PA2/PA3但注意和超声波复用二选一注意 PA2、PA3 在这里有冲突如果超声波模块占用 PA2/PA3那么 ESP8266 的串口就要换到 USART3 的 PB10/PB11。我最终为了保留超威波把 ESP8266 接到了 USART3。你可以根据实际模块取舍。2.2 I2C 总线上的地址冲突和上拉电阻BH1750 和 SSD1306 OLED 可以共用一条 I2C1因为他们的器件地址不同一个是 0x23一个是 0x3C总线不冲突。但这里有一个隐藏坑很多现成模块出厂时都自带 4.7k 或 10k 上拉电阻两个模块并联到总线上上拉电阻相当于减半SDA/SCL 的低电平可能拉不下去I2C 通信就会出现间歇性卡死。我遇到过一次用逻辑分析仪看波形SDA 低电平只有 0.8V明显异常。解决办法是剪掉其中一个模块的上拉电阻或者在总线上统一只用一组上拉。如果你手头没有逻辑分析仪排查这种问题会很痛苦所以我建议从上电开始就给每个 I2C 设备独立测试确认模块本身的地址和工作电压。2.3 ADC 通道量程MQ-2 的模拟输出直接怼到 PA0 会爆表MQ-2 模块最常见的两种输出数字输出 DO 直接接 GPIO模拟输出 AO 需要接 ADC。很多模块厂家标注 AO 输出电压范围 0-5V模块以 5V 供电但 STM32 的 ADC 参考电压只有 3.3V直接把 5V 模拟量怼进 PA0轻则读取满量程重则损伤引脚。正确做法是让 MQ-2 模块用 5V 供电AO 输出经过一个电阻分压网络再进 PA0比如两个 10k 电阻分压把 5V 降到 2.5V 量程。如果你不想改动硬件也可以用 ADC 的输入阻抗和软件放大系数做校准但分压是最省心的。分压后在代码里注意换算公式实际电压 ADC值 * 3.3 / 4095 * 分压比。ADC 我建议用 ADC1 DMA 循环扫描这样采集任务直接读 DMA 缓冲区的平均值不需要每次阻塞在 ADC 转换上。对应网上常说的“stm32 adc中断”其实 DMA 比中断更适合这种周期采集场景。2.4 供电和地线是稳定性的基石这个系统里最大的两个耗电模块是 ESP8266 和 MQ-2。ESP8266 在 WiFi 发包瞬间电流可能超过 300mA很多 STM32 最小系统板上的 3.3V LDO 根本带不动电压一跌MCU 直接复位你以为代码被写坏了其实只是供电不够。MQ-2 的加热丝更是耗电大户冷启动电流接近 150mA。我的做法是分成两路供电STM32 最小板用 USB 5V 供电板载 LDO 转 3.3V 供 MCU、OLED、DHT11、BH1750ESP8266 单独用一个 5V 转 3.3V 的稳压模块供电MQ-2 直接 5V 供电模拟输出再分压。所有模块的地线必须共地否则 I2C、串口、ADC 全都会出现莫名其妙的数据错乱。3. 任务划分与数据交换队列、事件组和互斥量如何撑起整个系统3.1 任务栈大小怎么定别凭感觉刚开始创建任务时栈大小基本都是拍脑袋。我用量 256 words 创建网络上报任务结果跑几秒就 HardFault查了半天才意识到是浮点格式化 printf 把栈爆了。FreeRTOS 的栈大小单位是 word不是字节STM32 一个 word 是 4 字节。简单任务比如按键消抖128 words 足够涉及 sprintf 浮点转换的任务建议至少给 512 words显示任务如果用了复杂菜单别低于 1024 words。后续我会单独讲怎么用uxTaskGetStackHighWaterMark实测每个任务的真实剩余栈而不是继续猜。这里先记住一个原则栈宁大勿小RAM 不够就砍任务数量不要省栈。3.2 用队列传传感器数据越简单越有效多任务之间最常用的数据交换方式是队列。这个项目里传感器采集任务拿到了温湿度、光照、烟雾等原始数据需要交给处理任务做滤波和判断。队列就是那条“单行道”。我推荐直接用长度为 1 的队列加xQueueOverwrite因为传感器数据只需要最新值不需要缓存历史覆盖旧数据可以保证对列里永远是最近一次采集的结果。代码类似typedef struct { float temp; float humi; uint16_t lux; uint16_t smoke; uint8_t motion; } SensorData_t; QueueHandle_t sensorQueue; sensorQueue xQueueCreate(1, sizeof(SensorData_t)); // 采集任务 SensorData_t data readAllSensors(); xQueueOverwrite(sensorQueue, data); // 处理任务 SensorData_t cur; if (xQueueReceive(sensorQueue, cur, portMAX_DELAY) pdTRUE) { processSensorData(cur); }注意xQueueOverwrite只适用于队列长度为 1 的队列如果你队列深度设为 8想更新最新值应该用xQueueSend并处理满队列的情况或者手动读取并更新队首。对于周期性传感器数据长度 1 的覆盖队列是最适合的simplicity wins。3.3 事件组报警机制的正确触发姿势报警需求是温度大于 28℃、烟雾浓度超阈值、或者检测到人体入侵任何一个条件满足都要报警但报警持续一段时间后自动停止如果条件还成立则再次触发。用事件组非常合适。定义三个事件位#define EVT_TEMP_HIGH (1 0) #define EVT_SMOKE_HIGH (1 1) #define EVT_MOTION (1 2) EventGroupHandle_t eventGroup;处理任务判断完数据后如果温度越界就xEventGroupSetBits(eventGroup, EVT_TEMP_HIGH)烟雾越界就xEventGroupSetBits(eventGroup, EVT_SMOKE_HIGH)。报警任务死等事件组EventBits_t bits xEventGroupWaitBits(eventGroup, EVT_TEMP_HIGH | EVT_SMOKE_HIGH | EVT_MOTION, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000)); if (bits (EVT_TEMP_HIGH | EVT_SMOKE_HIGH)) { BEEP_On(); vTaskDelay(pdMS_TO_TICKS(1000)); BEEP_Off(); }这里pdTRUE表示等待到事件位后自动清除pdFALSE表示任意一个位触发即可不需要全部满足。报警任务只在有事件时短暂鸣响随后继续阻塞等待。如果你希望报警持续到手动确认可以把清除时机改成拿到事件后置一个标志按键确认后再清除这个可以按需扩展。3.4 互斥量OLED 这种共享外设不能谁想写就写OLED 通过 I2C 挂在总线上显示任务和网络上报任务如果同时去写 OLED串口打印还好I2C 字节流就会互相打乱屏幕上直接出现乱码和花屏。所以对 OLED 这种共享外设必须用互斥量保护。xSemaphoreTake(xI2CMutex, pdMS_TO_TICKS(100)); OLED_Clear(); OLED_ShowString(0, 0, Temp: 26.5C); xSemaphoreGive(xI2CMutex);用互斥量而不是二值信号量的原因后续我会专门讲优先级反转问题。这里只要记住互斥量带优先级继承机制能有效避免高优先级任务被低优先级任务长时间阻塞。4. FreeRTOS下的中断与定时器超声波、按键和ADC的配合4.1 中断服务函数的三条纪律很多从裸机转 FreeRTOS 的人最容易在中断处理上翻车。三条纪律提前说清楚第一ISR 里不能调用任何会阻塞的函数比如 vTaskDelay、队列接收但带超时的那种。第二如果要在 ISR 里给任务发通知必须用 API 的 FromISR 版本例如xSemaphoreGiveFromISR、xQueueSendFromISR并且记得检查pxHigherPriorityTaskWoken是否需要切换。第三ISR 里不要做浮点运算、不要去读大片传感器时序耗时操作全部丢给任务去干。如果你原来习惯在裸机里用 HAL_Delay 做延时任务里千万不要继续用因为 HAL_Delay 依赖 SysTick 的全局中断和 FreeRTOS 的调度器配合不好轻则延迟不准重则调度卡死。统一用vTaskDelay或者等待信号量超时。4.2 超声波测距输入捕获 信号量才是王道超声波 HC-SR04 的测距原理很简单TRIG 拉高 10us 触发发射 ECHO 高电平的持续时间就是声波往返时间。但你如果在任务里用循环等 ECHO 变高再测量高电平持续时间极容易在等待期间被更高优先级任务抢占导致测距不准。我用的方案是定时器输入捕获。ECHO 脚接到 TIM2 的通道 1 复用功能配置上升沿和下降沿都触发捕获中断。中断里记录捕获时间上升沿记录 start下降沿记录 end然后计算duration_us (end - start) / 72在 72MHz 主频下最后通过二值信号量通知测距任务去读取结果void TIM2_IRQHandler(void) { uint32_t now; uint8_t needYIELD pdFALSE; if (TIM_GetITStatus(TIM2, TIM_IT_CC1)) { now TIM_GetCapture1(TIM2); if (isRisingEdge) { startTick now; isRisingEdge 0; } else { endTick now; isRisingEdge 1; distance_cm (endTick - startTick) / 58.0; xSemaphoreGiveFromISR(measureSem, needYIELD); } } TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); if (needYIELD) portYIELD_FROM_ISR(); }测距任务只需xSemaphoreTake(measureSem, pdMS_TO_TICKS(100))获得信号量后就认为回波测量完成。这样测距任务不会傻等CPU 还能做其他事。4.3 按键消抖别再用延时大法裸机按键消抖常用 delay 十几毫秒再判断一次但在 RTOS 任务里延时没问题问题是你在消抖期间如果有个更高优先级任务长时间运行按键任务会被饿死。更稳妥的写法是做一个 10ms 轮询状态机每次只读取一次 GPIO记录状态连续读到两次稳定电平才确认按下。简单状态机思路定义 IDLE、DEBOUNCE_PRESS、PRESSED、DEBOUNCE_RELEASE 四个状态按键任务每 10ms 跑一次状态之间不依赖长延时只靠周期轮询推进。这样按键任务本身栈很小也不会被其他任务影响消抖效果反而更好。4.4 ADCDMA循环采样别用中断打扰CPUMQ-2 的烟雾浓度不是瞬间值需要连续采样求平均。推荐 ADC1 配 DMA配置为循环模式每次转换结果自动存入一个数组。采集任务每 100ms 读一下 DMA 缓冲区的平均值再做滤波。这个方案比在 ADC 中断里读数据舒服得多也不会频繁打断调度器。网上常问的“stm32 adc中断”用在这里其实不是最优解DMA 就是为这个场景设计的。5. 显示与上报OLED、LVGL和巴法云这条链路怎么串起来5.1 OLED显示任务最简单的也要注意临界区OLED 显示在这个系统里优先级最低但别小看它。我用的是 0.96 寸 SSD1306128x64I2C 接口。显示任务每 500ms 从处理结果结构体里读一次最新数据然后格式化成字符串通过 I2C 发送。I2C 总线是共享的因此写入前必须拿互斥量否则与 ESP8266 初始化时碰一下就会花屏。如果显示内容多了比如要同时显示温度、湿度、光照、烟雾、距鴨一屏写不完可以分页或者每 2 秒翻页一次。我给 OLED 加了简单的菜单逻辑短按按键切换页面长按进入设置阈值页面。这属于业务逻辑不建议放在高优先级的传感器任务里而是单独开了一个按键任务通过事件组告诉显示任务切页。5.2 升级到LVGL不是不行但要配好心跳和互斥如果你后续想把这个系统做成更界面化的控制面板比如触摸彩屏、图标、曲线图那就该上 LVGL 了。网上搜“freertos移植lvgl”的教程很多但容易踩几个坑。首先要保证 LVGL 的心跳 tick 来源一致。建议在 FreeRTOS 里配置LV_TICK_CUSTOM 1让 LVGL 直接使用 FreeRTOS 的 tick 函数避免用定时器中断驱动 LVGL 时和系统 tick 打架。其次LVGL 不是线程安全的所有lv_开头的 API 必须在同一个任务里调用如果你有多个任务想操作 UI必须给 LVGL 加一个互斥量保护。最后是内存LVGL 的显示缓冲至少要一块 1KB 的局部缓冲复杂的控件和字体都会吃 HeapF103C8T6 只有 20KB RAM跑复杂界面会比较紧张。如果你用 ILI9341 这种 320x240 彩屏初始化第一步是读 ID。网上很多人读出来 0xA1A1这代表命令没有正确完成大概率是 RESET 脚没处理好或 SPI 模式不对而不是屏幕坏了。务必先把 ID 读成 0x9341 再进行后续初始化不然显示出来的画面全是花的。5.3 ESP8266上报巴法云从AT指令到MQTT网络上报是这个系统里最能折腾的部分。ESP8266 模块价格便宜接入巴法云也简单关键是串口缓冲和任务阻塞要处理好。我用的是 ESP8266 AT 固件USART3 连接波特率 115200然后通过 AT 指令配置 WiFi 和 MQTT。大致指令序列ATCWMODE1 ATCWJAPyour_ssid,your_password ATMQTTUSERCFG0,1,device_id,user,pass,0,0, ATMQTTCONN0,bemfa.com,9501,1 ATMQTTPUB0,topic,25.6,68,0,1,0如果你用的是 MQTT.fx 或者巴法云控制台需要在平台创建对应主题然后把设备 ID 填到 clientID 里。上报任务每 5 秒把温度、湿度、光照打包成一个字符串发布一次。注意 ESP8266 的串口回显会混入你的调试串口输出所以调试信息最好用一个单独的日志串口或者通过ATE0关闭回显。5.4 自定义串口帧格式为后续多设备扩展打底本地调试时如果直接用 printf 打印传感器数据任务一多打印会乱而且没办法做上位机解析。我建议在项目初期就定一个简单帧格式帧头 0xAA 0x55 | 类型 1字节 | 长度 1字节 | 数据区 | 校验和比如类型 0x01 表示温湿度0x02 表示光照0x03 表示烟雾。这样以后接 CAN 总线设备、接其他 MCU 协作都能用同一套协议解析也容易。很多“stm32串口调试pid”的场景需要把实时曲线发到上位机同样需要这样的结构化帧。6. 稳定性调试与长期运行堆栈溢出、内存和“莫名掉线”的排查6.1 堆栈溢出检测RTOS程序的安全带FreeRTOS 最容易忽略的问题就是栈溢出。你永远不知道某个任务在某次执行时因为一个嵌套函数调用就把栈踩穿了。好在 FreeRTOS 提供了两个开关在FreeRTOSConfig.h里设置configCHECK_FOR_STACK_OVERFLOW 2然后实现vApplicationStackOverflowHookvoid vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { /* 进入这里说明栈爆了打印任务名后停在这里方便调试 */ printf(Stack Overflow: %s\r\n, pcTaskName); while (1); }值设为 2 比 1 检查更严格但会多消耗一点 CPU。平时调试开着发布产品时如果性能不够可以关掉。更静态的检查方法是调用uxTaskGetStackHighWaterMark它会返回任务从创建以来剩余的最小可用栈空间字节数。每个任务跑完一轮后在主循环里周期性打印一次你就能知道哪个任务栈快到警戒线了。我实测后发现显示任务因为用 sprintf 格式化浮点数栈余量经常不到 100 字节果断从 512 提到 1024 words立刻稳定了。6.2 内存池 heap_4 和任务创建失败任务创建失败一般返回pdFAIL最常见原因是configTOTAL_HEAP_SIZE太小。FreeRTOS 默认用 heap_4 实现它会将碎片化的空闲内存合并但如果你一次性创建完所有任务后发现还有任务创建失败可以适当调大堆内存上限。F103C8T6 有 20KB RAM实际可用的 FreeRTOS 堆大小在 CubeMX 里默认给到 8KB 左右你如果任务多可以调到 14KB但要留一部分给栈和全局变量。在任务内部尽量少用malloc和free容易产生碎片而且不同钩子可能被覆盖。建议所有内存分配发生在启动阶段运行中保持稳定。6.3 优先级反转互斥量才是正确的锁如果共享资源用了二值信号量是有风险的。想象一个场景低优先级显示任务持有 OLED 的锁但被一个中等优先级网络任务抢占此时高优先级处理任务想写 OLED就得等显示任务释放锁而显示任务自己又在等网络任务让出 CPU结果高优先级任务被中等优先级任务间接阻塞这就是优先级反转。FreeRTOS 的互斥量自带优先级继承持有锁的低优先级任务会被临时提升到等待者同等的优先级从而减少这种反转。所以只要涉及共享资源的锁强烈建议用互斥量而不是二值信号量。6.4 排查“通信突然连不上”的通用套路网上搜“stm32 can通信突然连不上”的人很多我虽然这次没用 CAN但排查思路跟串口/WiFi 掉线完全一样。先说结论物理层的问题比代码多。先看波形CAN_H 和 CAN_L 之间的差分电压是否正确是否共地终端电阻 120 欧有没有接对保证总线空闲时 CAN_H/CAN_L 都在 2.5V 附近。如果物理层正常再看波特率是否配置一致协议解析有没有被别的任务抢占了 CPU 导致长时间不处理。最后再看代码里是不是在 ISR 里做了耗时操作导致 FIFO 溢出丢帧。WiFi 掉线也一样先看 ESP8266 是否还在线供电电压是否充足能不能 ping 通不要一上来就怀疑 FreeRTOS 队列配置错了。6.5 长期运行看门狗和滤波房间监测系统经常是 7x24 小时跑的万一哪个任务卡死整个系统就废了所以必须开独立看门狗 IWDG。在 FreeRTOS 里我一般让处理任务每 500ms 喂狗一次因为它周期固定而且如果它真的挂了说明系统已经不健康。传感器数据的噪声要在处理任务里做比如滑动平均保留最近 5 次温度值每次求平均这样 MQ-2 的烟雾值不会因为瞬间波动触发误报警。6.6 开发环境与调试链路我习惯用 STM32CubeMX 生成基础工程勾选 FreeRTOS然后代码里再手动创建任务和队列。编译工具 Keil MDK 和 VSCode CMake J-Link 都行。如果你用 VSCode 调试记得在launch.json里配置好 J-Link 的接口和芯片型号不然很容易出现连接不上调试器的问题。Keil 装的时候注意同时支持 C51 和 STM32 的版本需要分别安装对应的 Pack否则会报缺设备文件。日常调试如果能买一个逻辑分析仪很多这种多传感器项目的坑一眼就能看出来。7. 跑完这个项目后的几点个人经验7.1 先画数据流图再写代码这次项目最大的教训不是某个具体的 API 写法而是整个设计流程。如果一开始你就把“传感器→队列→处理→事件组→显示/上报/报警”这条数据流画出来很多优先级分配问题根本不会出现。多传感器系统最怕的就是每个传感器都是一个独立世界互相不知道对方的数据在哪最后代码堆成一锅粥。7.2 调试顺序从底向上传感器一定要先在裸机环境下逐个验证能稳定读数再上 FreeRTOS。不然你以为 RTOS 出了问题其实只是某个传感器时序不对导致数据错乱。我用 100ms 周期并行读 4 个传感器单独跑一点问题没有上了 FreeRTOS 后偶尔中断卡死查了很久才发现是某个模块初始化时在中断里调用延时函数不是任务划分的问题。把硬件层和系统层分开排查效率高很多。7.3 栈大小用数据说话不要再拍脑袋给任务分配栈了。跑通基础功能后用uxTaskGetStackHighWaterMark打一遍所有任务的栈余量把低于 20% 余量的任务栈调大把长期余量 500 字节以上的任务栈适当缩小。这样不会浪费 RAM也不会埋雷。跑完这个项目你可以继续往里面加语音播报、数据曲线、触控屏、甚至电池供电低功耗模式。换了 FreeRTOS 之后加一个新功能基本都是新增一个任务的事不用再改原有多任务的逻辑。不过真的到了低功耗那一步又要重新平衡唤醒周期和传感器供电了那就是另一个大坑了。
返回列表