如何在FreeRTOS任务中周期性执行函数?以及在1ms调度任务中无阻塞每10ms执行指定函数的实现方法
在FreeRTOS 1ms调度任务中实现非阻塞式10ms周期函数的方案
嘿,这个需求在嵌入式FreeRTOS开发里太常见了!我给你分享几个完全满足要求的方案,都不会阻塞你的1ms调度任务,咱们一个个说:
方案一:任务内部计数器跟踪(最简单直接)
这个方案不需要额外的FreeRTOS内核对象,纯靠任务内部的静态计数器来实现,特别适合逻辑简单、不需要动态调整周期的场景。
原理很简单:你的任务每1ms被调度一次,每次进入任务就给计数器加1,当计数器累加到10的时候,执行目标函数,然后重置计数器就行。
代码示例:
void Your1msPeriodicTask(void *pvParameters) { // 静态变量,任务重启也会保留值,适合计数 static uint8_t uxCycleCounter = 0; const uint8_t uxTargetInterval = 10; // 对应10ms间隔 for(;;) // FreeRTOS任务标准死循环 { // 先执行你这个1ms任务本身要做的常规工作 execute_1ms_required_work(); // 计数判断逻辑 uxCycleCounter++; if(uxCycleCounter >= uxTargetInterval) { // 执行每10ms需要跑的函数 execute_10ms_periodic_work(); uxCycleCounter = 0; // 重置计数器 } // 让出CPU,确保任务每1ms被调度一次 vTaskDelay(pdMS_TO_TICKS(1)); } }
优点:
- 零额外资源占用,实现成本极低
- 完全不阻塞任务,只是简单的计数判断,执行完就继续任务流程
- 逻辑直观,容易调试
注意点:
- 计数器尽量用无符号类型(比如
uint8_t/uint16_t),避免溢出问题 - 如果你的1ms任务偶尔被高优先级任务抢占,周期可能会有微小延迟,但绝大多数嵌入式场景下完全可以接受
方案二:FreeRTOS软件定时器(更精准灵活)
如果你需要更精准的周期控制,或者未来可能要动态调整周期、随时启停这个10ms函数,那FreeRTOS的软件定时器是更好的选择。
软件定时器的回调函数是在FreeRTOS的定时器服务任务中执行的,完全独立于你的1ms任务,所以绝对不会阻塞你的任务流程。
代码示例:
// 定义定时器句柄 TimerHandle_t x10msPeriodicTimer = NULL; // 定时器回调函数:这里就是你要每10ms执行的逻辑 void v10msTimerCallback(TimerHandle_t xTimer) { execute_10ms_periodic_work(); } // 初始化函数(在任务启动前调用) void App_Init(void) { // 创建软件定时器 x10msPeriodicTimer = xTimerCreate( "10msTimer", // 定时器名称(仅调试用) pdMS_TO_TICKS(10), // 周期:10ms pdTRUE, // pdTRUE表示自动重载(周期性执行) (void *)0, // 定时器ID(这里用不到,传0即可) v10msTimerCallback // 回调函数指针 ); // 启动定时器(如果创建成功的话) if(x10msPeriodicTimer != NULL) { // 第二个参数是等待启动的超时时间,0表示立即尝试 xTimerStart(x10msPeriodicTimer, 0); } } // 你的1ms任务该干嘛干嘛,完全不受定时器影响 void Your1msPeriodicTask(void *pvParameters) { for(;;) { execute_1ms_required_work(); vTaskDelay(pdMS_TO_TICKS(1)); } }
优点:
- 周期精准,不受你的1ms任务的执行时长、被抢占情况影响
- 支持动态调整周期(
xTimerChangePeriod)、随时启停(xTimerStop/xTimerStart) - 逻辑解耦,1ms任务和10ms周期函数完全独立,代码更清晰
注意点:
- 需要确保FreeRTOS配置中开启了软件定时器(
configUSE_TIMERS设为1) - 定时器回调函数里不能调用会阻塞的API(比如
vTaskDelay、队列阻塞式读取),因为定时器服务任务优先级固定,阻塞会影响其他定时器
方案三:利用vTaskDelayUntil实现任务内精准延迟(适合特殊场景)
如果你的1ms任务其实大部分时间是等待,只是需要穿插10ms的逻辑,也可以用vTaskDelayUntil来实现,但这个方案更适合单独的周期任务,不太适配你当前“1ms调度一次任务”的场景,不过也可以作为补充了解:
void YourMixedTask(void *pvParameters) { TickType_t xLastWakeTime = xTaskGetTickCount(); const TickType_t x1msInterval = pdMS_TO_TICKS(1); static uint8_t uxCounter = 0; for(;;) { execute_1ms_required_work(); uxCounter++; if(uxCounter >= 10) { execute_10ms_periodic_work(); uxCounter = 0; } // 精准延迟到下一个1ms时间点 vTaskDelayUntil(&xLastWakeTime, x1msInterval); } }
这个方案和方案一类似,但用vTaskDelayUntil比vTaskDelay的精度更高,因为它是基于绝对时间点的延迟,而不是相对时间。
总结选择建议:
- 如果逻辑简单、不需要动态调整,优先选方案一,最省心
- 如果需要精准周期、动态控制,选方案二,扩展性更强
内容的提问来源于stack exchange,提问作者QBKM
相关产品推荐
相关产品推荐

