ARTICLE DETAIL

资讯详情

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

从裸机到RTOS:嵌入式应用快速迁移的实战指南

从裸机到RTOS:嵌入式应用快速迁移的实战指南 1. 为什么我不建议你继续“裸奔”——从裸机到RTOS的思维转变我做过不少嵌入式项目从遥控器、传感器节点到电机驱动器早期基本都是一套超级循环Super Loop打天下。说句实在话裸机开发本身没有原罪逻辑简单、确定性强、内存占用低很多场景下它依然是最优解。但是当项目开始变得复杂——比如同时要处理按键扫描、LCD刷新、传感器采集、通信协议解析、故障保护逻辑——你会发现那个while(1)大循环里的代码越来越臃肿你开始被迫用状态机、标志位、定时器切片去模拟“多任务”。这个阶段我称之为“手写RTOS的焦虑期”。我记得很清楚的是一次做多传感器数据融合的项目要轮询读取六个传感器、在OLED上绘图、响应串口指令、还要做PID输出。裸机代码里到处是if(flag_1ms)、if(flag_10ms)、if(flag_100ms)全局变量满天飞。改一个功能经常要牵动三四个模块调试起来真的头疼。后来一个朋友跟我说别硬扛了上个RTOS吧你这种情况十分钟就能移植好。我当时半信半疑但真花了两天把项目迁到RTOS上之后回头看那个裸机版本就一个感受曾经的自己是在用胶带和铁丝绑一台机器现在才算是搭了正经的架子。这篇文章要聊的“快速为嵌入式裸机应用添加RTOS”不是让你去研究内核源码、定制调度算法而是分享一条成熟的、低风险的迁移路径。我会从选型、移植步骤、任务划分到坑点排查完整过一遍。如果你手里正好有个裸机项目想改造或者你刚开始学RTOS不知道怎么落地这篇文章应该能帮你省下不少摸索时间。2. 动手之前先把选型问题想清楚2.1 市面主流RTOS的横向对比与选择逻辑今天嵌入式领域能用的RTOS非常多各有各的脾气。我先说我实际用过的、在项目里验证过的几款给你做个参考。FreeRTOS是目前市占率最高、资料最全的开源RTOS。我最早用它是在STM32F103上那时候它的内核版本才到8.0左右现在已经到V11了。它的优点不用多说文档丰富、社区活跃、各种芯片的移植示例遍地都是而且商用免费MIT协议。非常适合作为裸机转RTOS的第一站。RT-Thread是国产RTOS里做得相当出色的一个它对中文开发者特别友好文档站全是中文而且它不仅是内核还包含了设备驱动框架、组件包生态有点像嵌入式界的Linux发行版。如果你做的是物联网网关、带显示屏的人机交互产品这类资源比较丰富的项目RT-Thread能帮你省很多事。Keil RTXCMSIS-RTX是ARM官方推出的RTOS最大的优势是跟Keil MDK深度绑定在MDK里工程配置界面直接可以用图形化方式配置内核参数、调试组件不需要手动去改一堆配置头文件。如果你的工具链正好是MDK而且项目复杂度不是特别高RTX是一个非常省心的选项。所以在选型上我给的思路很明确刚入门选FreeRTOS因为它资料最全中文社区生态优先选RT-Thread工具链为MDK且想快速上手的选RTX。别在这上面太纠结内核调度思想都是通的换个平台以后适应成本不高。2.2 移植前需要做的关键评估选完型别急着往工程里拖代码先花点时间评估一下项目适不适合上RTOS很多人忽略这一步结果迁移到一半发现搞不定又回退了。第一个要评估的是实时性需求。你的系统里是否有严格的时间约束比如电机控制里的电流环周期通常要求在几十微秒级别这种高实时性的控制回路RTOS的调度开销和中断响应时间可能会成为问题。一般来说任务切换开销在几微秒到几十微秒之间如果系统里有硬实时需求且周期极短RTOS可能帮倒忙。但如果是软实时场景——比如数据采集、人机交互、通信处理——RTOS的优势就体现出来了。第二个是资源评估。看你的单片机Flash和RAM有多大。以FreeRTOS为例最小内核ROM用量在4KB到9KB左右每个任务需要独立的栈空间通常一个任务至少分配256字节到1KB实际用多少取决于任务中的局部变量和函数调用深度。如果MCU的Flash只有16KB、RAM只有2KB那上RTOS的空间就比较紧张要么精简任务数要么考虑裸机加状态机的方案。第三个是编程思想的准备。这一点可能比前两个更关键。你团队成员或者你自己是否习惯使用阻塞式编程裸机开发里大家习惯用状态机配合标志位代码是事件驱动的而RTOS里任务可以等信号量、等队列、等消息任务本身看起来像是“卡住了”但这其实是内核在帮忙做资源调度。如果团队里有人不能理解这种“主动让出CPU”的思想写出来的代码就是一堆sleep(10)轮询效率反而更差。3. 几分钟搭起RTOS运行骨架——FreeRTOS移植实操笔记3.1 拿到源码之后别急着全塞进工程我们以FreeRTOS为例先把移植的整体结构说清楚。你从官网或者GitHub拿到源码包后会发现里面有一堆目录其实真正需要关心的就两块一个是FreeRTOS/Source下的内核核心代码——list.c、queue.c、tasks.c、timers.c、event_groups.c、stream_buffer.c、croutine.c这几个C文件另一个是FreeRTOS/Source/portable下的移植层代码。很多新手最容易犯的错误就是把整个源码目录全拖进工程结果编译出来一堆没用的文件。实际上对于大部分MCU项目只需要添加tasks.c、queue.c、list.c和对应的port文件就够了。timers.c是软件定时器组件用到才加event_groups.c是事件组组件按需添加croutine.c现在已经很少用基本不碰。port层的选择也很关键。FreeRTOS对不同的编译器和芯片架构提供了不同的port实现比如GCC、IAR、Keil编译器都有对应的版本。STM32系列通常使用portable/GCC/ARM_CM4F这类路径下的port文件注意你的芯片是M0、M3、M4还是M7以及是否带FPU这些都要对应上用错了port文件编译能过但调度器一跑就死给你看。我把最小需要添加的文件列在下面这是我在一个基于STM32G0的裸机项目上移植时实际用的最小配置FreeRTOS/Source/tasks.c FreeRTOS/Source/queue.c FreeRTOS/Source/list.c FreeRTOS/Source/portable/GCC/ARM_CM0/port.c // 以CM0内核为例 FreeRTOS/Source/portable/MemMang/heap_4.c3.2 FreeRTOSConfig.h的配置逻辑逐项说人话FreeRTOSConfig.h是移植的“控制面板”里面每一项配置都直接影响系统行为。这里挑几个最关键的讲其他的可以先用默认值跑起来了再慢慢调。configUSE_PREEMPTION这个配置决定内核用的是抢占式调度还是协作式调度。给裸机项目上RTOS我强烈建议直接开抢占设为1。因为抢占式调度意味着高优先级任务就绪时可以立即打断当前运行的低优先级任务这才能体现出“实时”的意义。协作式调度需要任务主动让出CPU如果没有这个意识系统实时性会大打折扣。configTOTAL_HEAP_SIZE这是内核管理的堆内存大小。所有任务的栈、队列、信号量这些内核对象默认都从这个堆里分配。这个值怎么定我一般是先给一个估算值比如8KB跑起来后在调试器里查看xPortGetFreeHeapSize()的返回值看看堆用量最高到了多少留出20%到30%的余量就行。在资源紧张的MCU上这一步优化空间很大。configMINIMAL_STACK_SIZE这个值定义的是空闲任务的栈大小。默认值一般是128字注意是字不是字节。在ARM Cortex-M上一个字是4字节所以128字等于512字节。芯片字长不同这个值的实际字节数不一样用的时候务必搞清楚换算关系。configUSE_TIMERS这是软件定时器组件的开关。裸机项目里很多人是用定时器中断来实现定时任务的迁移到RTOS后可以把一部分周期性工作交给软件定时器来完成这时就需要把该项设为1并添加timers.c到工程里。configASSERT这个宏强烈建议在开发阶段定义。它的作用是在内核检测到参数错误或状态异常时触发断言。虽然会牺牲一些性能但在排查问题时能精确告诉你哪一行出错了。产品发布时再把configASSERT定义为空就行。我第一次用FreeRTOS时没开断言出了问题只能对着fault handler发呆后来开了断言几秒钟就定位到了问题。还有个容易踩坑的地方是中断优先级配置。在ARM Cortex-M系列上FreeRTOS要求最低优先级数值最大的中断不能调用任何FreeRTOS的API。一般在FreeRTOSConfig.h里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY对应数值为5时意思是优先级数值大于5的中断里不能调用xQueueSendFromISR这类FromISR接口。很多人移植后中断里调用API导致系统卡死或者复位排查半天发现是这里没配对。3.3 调度器启动全流程——从一个翻转LED的Hello World说起移植的第一步其实很朴素先让系统跑起来再谈业务逻辑。我用一个最简单的LED翻转任务来验证调度器是否正常工作确保移植成功。先看一下主函数的完整逻辑框架#include FreeRTOS.h #include task.h void vLEDTask(void *pvParameters) { for (;;) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); vTaskDelay(pdMS_TO_TICKS(500)); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 创建任务参数分别是任务函数、任务名、栈深度、参数、优先级、句柄 xTaskCreate(vLEDTask, LED, 128, NULL, 1, NULL); // 启动调度器正常情况下不会返回 vTaskStartScheduler(); // 如果执行到这里说明堆内存不足内核初始化失败 while (1); }这段代码的逻辑很简单创建任务、启动调度器然后在任务里翻转LED。但有几个细节值得注意。第一个细节是vTaskDelay(pdMS_TO_TICKS(500))。在裸机里你会写HAL_Delay(500)在RTOS里就要改成vTaskDelay。两者本质区别在于HAL_Delay是死等CPU被白白占住vTaskDelay是让出CPU当前任务进入阻塞状态内核调度器会去运行其他就绪的任务。如果你在RTOS任务里继续用HAL_Delay系统还能跑但你的高优先级任务可能就得不到及时响应了这是裸机思维残留。第二个细节是任务栈深度。我上面的代码给了128字也就是512字节。如果你任务内部有较大的局部数组比如char buf[256]那这个栈深度就不够了运行时会触发栈溢出。FreeRTOS提供了uxTaskGetStackHighWaterMark()这个API来检查任务历史最低剩余栈空间开发阶段可以定期调用它打印出来看看。第三个细节是vTaskStartScheduler()之后的while(1)。正常情况下调度器启动后不会返回一旦返回说明内核对象初始化失败最常见的原因就是堆内存不足。调试时可以在while(1)前打一个断电看PC指针指到的位置能帮你判断失败原因。编译下载到板子LED以1Hz频率翻转。看到这个现象说明你的RTOS内核已经跑起来了。3.4 中断回调里发消息常犯的错误裸机转RTOS中断处理是最容易出事的重灾区。裸机里你写中断服务函数时直接操作标志位、缓冲区一切都在你掌控中。但RTOS引入了优先级抢占中断和任务之间的关系必须通过内核对象来解耦。我当时踩过一个非常典型的坑在串口接收中断里调用了xQueueSend而不是xQueueSendFromISR编译时居然没有报错因为这两个函数的原型从语法角度看几乎一样。但是运行时系统偶尔会莫名其妙地卡死后来排查了很久才意识到问题。原因在于xQueueSend会可能导致任务阻塞而中断上下文里是不能阻塞的其次从ISR调用的FreeRTOS API需要带FromISR后缀因为内核需要在中断里做上下文相关的特殊处理比如延迟唤醒高优先级任务。所以移植时的铁律是中断里只能用FromISR结尾的API。我在做每一个中断相关的任务时都会在代码里用注释明确标注这个约定方便团队其他成员也遵守。来看一个正确的串口接收中断与任务配合的代码片段void USART2_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken pdFALSE; uint8_t data; if (__HAL_UART_GET_FLAG(huart2, UART_FLAG_RXNE)) { data (uint8_t)(huart2.Instance-RDR 0xFF); xQueueSendFromISR(xUartRxQueue, data, xHigherPriorityTaskWoken); } // 如果被唤醒的任务优先级高于当前运行的任务立即切换 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }注意最后一行portYIELD_FROM_ISR它至关重要。如果接收队列里原本没有数据而某个任务正阻塞在xQueueReceive上等待数据那么在中断里发送数据后该任务会进入就绪态。xHigherPriorityTaskWoken会被置为pdTRUE此时需要主动触发一次任务切换让高优先级任务立即运行。没有这一行中断退出后系统会继续执行被打断的低优先级任务高优先级任务要等到下一次调度点才能运行响应延迟就上去了。4. 把你的业务逻辑从while(1)大循环里搬出来4.1 先画任务图再写代码很多人在裸机转RTOS时犯的第二个大错就是启动调度器之后直接把裸机大循环里的代码原封不动塞到一个大任务里。这样做的结果就是名义上用了RTOS实际还是裸机逻辑只是包了一层皮。正确的做法是先画任务划分图。我一般会把任务按下面几个维度来划分按实时性划分。哪些任务对响应时间有硬性要求比如堵转保护、过流保护这一类任务必须高优先级。哪些可以容忍一定延迟比如LCD刷新50ms和100ms刷新在观感上差别不大中低优先级即可。按触发方式划分。按键扫描是事件触发型的平时不需要占用CPU可以使用队列加外部中断来唤醒按键任务。数据采集是周期性触发的适合用定时器作为时间基准。通信数据处理是数据驱动型的有数据才处理没有数据就一直阻塞在队列接收上。按资源归属划分。所有操作同一份共享资源的逻辑尽量放到同一个任务里这样你不需要在多个任务之间维护复杂的互斥逻辑。比如SD卡存储就单独建一个存储任务其他任务把数据通过队列发给它由它统一写卡。用一个实际案例来说会更清楚。我之前做的一个环境监控节点裸机版本主循环里做了五件事温湿度采集、PM2.5传感器读取、LCD显示刷新、串口协议处理、报警输出。迁移到RTOS后我划分成了四个任务任务名称触发方式优先级功能说明传感器采集任务定时器唤醒每500ms高读取温湿度和PM2.5数据处理后存入结构体串口通信任务队列接收事件触发中解析上位机指令返回设备状态显示刷新任务信号量触发数据更新时低更新OLED上的数据显示报警输出任务事件组触发条件满足时高驱动蜂鸣器和LED报警每个任务之间通过队列、信号量、事件组进行通信。传感器采集完数据后把数据发给显示刷新任务串口任务收到查询指令后把当前传感器数据打包发送报警任务监听事件组当温度超限时由采集任务置位报警事件位。4.2 裸机代码搬移的“手术技巧”——并不是简单的复制粘贴从裸机到RTOS代码不能直接复制粘贴需要在几个关键地方做“手术”。全局变量处理。裸机里全局变量满天飞这是常态。但RTOS里多个任务并发访问同一个全局变量如果不加保护很容易出现数据竞争。比如一个任务在写传感器数据另一个任务同时在读并传给串口可能出现读取到半新半旧的数据。我的处理方法分成三类只读变量不需要保护比如版本号、配置参数单写多读变量可以配合临界区或者关闭调度器真正的读写冲突变量需要用互斥量或直接改为队列消息传递。耗时函数改造。裸机里的HAL_Delay、while(flag 0)等自旋等待在RTOS里必须换成vTaskDelay或直接等待对应的内核对象。否则任务会霸占CPU其他任务全都饿死。同样道理串口接收等待一帧数据完成这种逻辑在裸机里是一个死等循环改成RTOS后可以做成“中断收字节进队列任务从队列处收完整帧并解析”的模式代码更清晰CPU利用率也更高。阻塞点处理。有些裸机代码会在一个循环里同时处理多个条件比如主循环每跑一圈就检查按键、更新显示、解析串口。在RTOS里这些逻辑分散到不同任务后每个任务都有自己的阻塞点按键任务阻塞在按键队列上显示任务阻塞在自己的刷新定时上串口任务阻塞在串口队列上。写代码时一定要想清楚每个任务“在等什么”这就是你任务的阻塞点。4.3 任务优先级分配实操中有套实用经验任务优先级的分配直接影响系统的稳定性和响应性能但很多人却把它当成一个拍脑袋的事。先说基本原则高优先级任务必须是短小精悍的不能有耗时操作。如果你把一个复杂计算任务设为最高优先级它长时间占用CPU低优先级的交互任务全部饿死系统看起来就像是“假死”了。实际项目中我一般这样考虑维持系统安全的优先级最高比如过压保护、堵转检测然后是通信类需要快速响应外部事件再次是数据处理类逻辑交互界面类最低。优先级反转是一个绕不开的问题。想象一下低优先级任务占用了一把互斥锁此时高优先级任务来了想申请同一把锁但它必须等低优先级任务释放锁于是高优先级任务被低优先级任务阻塞而中优先级任务又不让CPU低优先级任务没法运行、锁也释放不了高优先级任务干等。解决这个问题的标准做法是使用优先级继承FreeRTOS的互斥量就自带优先级继承机制。所以我的建议是如果你需要互斥访问共享资源用互斥量Mutex而不是二值信号量Binary Semaphore。二值信号量适合做同步不适合做互斥这个区别很多人分不清。再给一个参数选择的参考在ARM Cortex-M系列上FreeRTOS优先级的数值越大优先级越高。可用的优先级范围由configMAX_PRIORITIES定义我一般设置为7或者15。这个值不是越大越好优先级值越多内核调度需要遍历的链表就越长有一定的性能开销。5. 踩过的坑都值得记下来5.1 堆栈溢出最阴险的定时炸弹RTOS项目里有一种bug最折磨人系统看起来正常运行但运行几小时甚至几天后突然在不固定的时间点复位或者任务的数据被莫名修改。这种bug十有八九是栈溢出。FreeRTOS提供了两种栈溢出检测机制。一种是在任务切换时检测靠configCHECK_FOR_STACK_OVERFLOW宏控制另一种是通过uxTaskGetStackHighWaterMark()接口查询任务历史最低栈余量。我强烈建议在开发阶段打开检测机制并且在系统稳定前定期打印每个任务的剩余栈空间。有一种情况检测不到任务栈溢出后没有立即覆盖关键内存而是在一段时间后才引发问题位置飘忽不定。所以最根本的解决办法还是在写代码时控制局部变量大小。大型数组、大结构体最好用静态变量或者从堆里分配不要全部塞进任务栈里。5.2 启动阶段“跑飞”的几个原因速查表移植初期系统跑不起来是常事根据我帮朋友排错的经验90%的问题都集中在下面几个点。问题现象常见原因排查方法程序死在HardFault_Handler栈溢出/中断优先级配置错误/非法内存访问开断言、查看fault状态寄存器的值定位调度器启动后无输出configTOTAL_HEAP_SIZE过小减少堆需求或增大堆配置任务卡死其他任务不运行高优先级任务死循环不释放CPU检查是否使用了while(1)不带阻塞中断里调用API后异常在ISR里用了非FromISR接口全局搜索检查中断里的API调用使用浮点运算时报错移植层未启用FPU支持确认port文件选的是带F的版本5.3 Watchdog与RTOS的相处之道裸机程序里的喂狗通常在主循环里做反正主循环在跑就说明系统没死。但上了RTOS后主循环没有了喂狗逻辑怎么处理我见过有人新建一个最低优先级任务专门喂狗这种做法其实意义不大因为只要调度器还在切换任务最低优先级任务迟早能运行狗狗就不会复位但它无法反映业务任务是否真的在正常工作。我的做法是“任务级别Watchdog”。核心思路是创建一个高优先级的监控任务它定期检查关键业务任务是否在正常周期内运行。每一个关键业务任务每次执行完会把自己的心跳计数加一监控任务定期读取这些计数检查是否在预期时间窗口内有更新如果没有就认为系统异常执行恢复逻辑。这种方法比单纯喂硬件狗更能反映业务健康度我在这类系统里会把硬件Watchdog的超时设置得比监控周期更长双保险。6. 内存开销的精打细算——在资源受限芯片上跑RTOS的优化心得6.1 先搞清楚内核吃掉你多少资源一个常见的疑问是我的单片机资源这么少上RTOS会不会不够用先说结论大部分资源不太受限的MCU都够用关键是搞清楚内核的开销构成。以FreeRTOS在STM32G0上的实测数据为例内核本身占用的Flash大约在5KB到8KB之间具体大小取决于启用了哪些功能。RAM方面内核对象会占用一块固定内存每个任务的控制块TCB大约100字节左右每个队列大约80字节。再加上每个任务独立的任务栈比如4个任务每个分配512字节那么总共就是2KB左右。这样算下来如果你芯片Flash在32KB以上、RAM在8KB以上跑一个4到5个任务的FreeRTOS项目是完全没问题的。如果比这个配置还低那就需要精打细算。6.2 任务栈大小估算法则与实测调整任务栈的大小是RTOS项目里最需要花心思调的内存参数。太大浪费RAM太小等着栈溢出。我的估算方法是“三层递进法”。第一层粗估。主要分析任务里局部变量的大小和函数调用深度。比如任务里有一个uint8_t buf[128]那栈至少要128字节再加上函数调用需要保存的寄存器现场、返回地址等再翻一倍初步定为256字节。第二层实测。用uxTaskGetStackHighWaterMark()在任务里定时打印剩余栈空间观察一段时间内这个数值的变化趋势看最小还剩多少。第三层校准。将栈大小设定为任务实际最大使用量 30%余量。比如实测峰值使用了180字节那就分配256字节留76字节余量。实测这个方法最可靠我强烈建议不要跳过第二层。不同编译器的优化级别不同同样代码的栈使用量可能差不少纸上算的永远没有跑出来的准。6.3 堆内存碎片化问题FreeRTOS的heap_4实现是带内存合并的能有效避免碎片化算是我最推荐的内存管理方案。但即便如此如果在系统运行中频繁地创建和删除任务、队列还是有产生碎片的风险。我的经验是系统初始化阶段把所有任务、队列、信号量一次性创建好运行过程中不再动态创建内核对象。这是RTOS项目的最佳实践既避免碎片化问题也让系统的资源使用情况在启动阶段就一目了然。这样做还有一个好处你的代码逻辑更可预测内存不足的问题在启动时就会暴露而不是运行到某个深水区才炸出来。heap_3方案则是直接包装C库的malloc和free好处是能跟C标准库统一内存管理。但如果你用的大部分是RTOS自身的API分配heap_3比较合适如果涉及大量用户态的内存分配就要小心碎片化了。7. 性能还能怎么榨——调试与优化进阶技巧7.1 任务切换与调度延迟测试方法RTOS移植完成、系统稳定运行后我们需要验证实时性到底如何。裸机项目里你知道一个中断从触发到处理完成是精确的上了RTOS后系统的响应时间包含了调度器的延迟因素需要实测。我常用的方法是设置一个高优先级任务等待某个外部中断触发的信号量同时用另一个GPIO引脚在任务唤醒时翻转电平。用示波器同时测量外部中断信号和GPIO信号两者之间的时间差就是“中断唤醒任务延迟”。这个指标包含了中断响应时间、中断内的API调用开销以及任务切换时间能很直观地反映系统实时性。实测数据可以参考一个运行在STM32F103上的FreeRTOS系统主频72MHz从外部中断触发到高优先级任务运行延迟时间大约在2到5微秒之间。如果你测出来的数据远大于这个量级说明系统的中断处理里可能有耗时操作或者优先级配置不合理。7.2 用时间片轮转还是优先级抢占或者两者结合FreeRTOS支持时间片轮转调度它允许同一优先级的多个任务共享CPU时间片。这是一个很实用的功能可以在不增加优先级层次的条件下让多个近似重要性的任务轮流运行。但实际项目中我做了调整不让任务依赖于时间片轮转而是尽量用阻塞API主动让出CPU。原因是时间片轮转虽然公平但任务切换点不可预测可能在任务执行到一半时强制切走如果你对某些操作要求很强的连贯性这种情况反而有隐患。我的建议是同一优先级的任务用taskYIELD()或延迟来主动让出CPU让切换点可控。7.3 低功耗与RTOS的结合实践低功耗是嵌入式产品绕不开的需求而RTOS的调度机制天然适合低功耗设计。在FreeRTOS内核中有一个tickless idle mode机制允许系统在空闲任务运行时进入低功耗模式并根据休眠时间修正系统节拍计数。这个机制的原理可以概括为空闲任务运行时说明当前没有任务处于就绪态此时系统不需要精确的系统节拍可以进入睡眠状态。睡眠前记录当前时间到下一个任务该被唤醒的时间点用硬件定时器设置唤醒醒了之后补偿系统节拍计数。通过这种方式系统可以在一个10ms的任务调度周期里把大部分时间都用来睡眠而不是空转。使能这个功能需要在FreeRTOSConfig.h里配置#define configUSE_TICKLESS_IDLE 1 #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2第二个宏的含义是如果预期空闲时间小于2个节拍周期就不进入低功耗模式因为进出的功耗开销可能大于省下的。这个阈值需要根据芯片的数据手册合理设置。实测下来在STM32L4系列上开启动态低功耗模式后系统待机电流能从毫安级降到微安级效果非常显著。8. 从裸机到RTOS最后再给你三句过来人的建议第一句不要一步到位。第一次迁移先保留裸机版本的全部代码新建一个分支来折腾RTOS版本这样做的好处是出了问题随时可以回退对比。我见过不少人一上来就把裸机代码删了重写结果RTOS版本出了bug还找不到对照折腾两周后连夜回退到裸机版本继续发货。第二句优先把通信接口和交互接口迁到RTOS上计算密集型的算法逻辑可以先不迁移。因为通信和交互任务最能体现RTOS的异步处理优势——一边接收数据一边处理另一边UI体验完全不同。而算法逻辑迁移到RTOS后收益相对有限工作量和风险却一点不少。第三句调试手段不要一刀切。裸机时代你可能习惯了打断点、单步执行RTOS项目里多任务交错运行打断点的方式很容易把系统调死。我的习惯是善用RTOS自身的调试组件比如FreeRTOS的trace功能可以记录任务切换历史、各个任务的运行时间和切换次数。还有就是在关键路径上加日志输出用串口或者EasyLogger这类库把系统状态打出来。这些手段在排查一些“跑着跑着就不对劲”的软问题时比断点调试高效得多。最后再分享一个小技巧。我在迁移过程中习惯在代码里用条件编译把RTOS相关代码和业务逻辑代码隔开这样可以让代码既能在裸机模式下编译运行也能在RTOS模式下编译运行。虽然前期会多花一点时间做适配层但好处是后续在项目间切换、在仿真和实测之间切换时自由度就大很多。这个习惯帮我省下的时间远超一开始投入的成本。
返回列表