资讯动态

STM32 FreeRTOS 非阻塞延时实战:用 APP_Time_IsElapsed 让多任务和平共处

发布时间:2026/8/22 17:13:50 来源:尧图企业网站定制
一、我们遇到了什么问题在 FreeRTOS 里写一个任务函数经常会是这样voidAPPTask(voidconst*argument){for(;;){AppCore_ProcessKeypadQueue(pdMS_TO_TICKS(10));AppCore_ProcessHall();osDelay(1);}}你会希望按键扫描 ProcessKeypadQueue 每隔 10ms 执行一次。霍尔传感器读取 ProcessHall 内部的 SVC_Hall_Read() 每隔 100ms 执行一次。但如果在各自的函数里面直接写一个 HAL_Delay(100) 或者 osDelay(100)整个任务都会被卡住按键扫描就完全没法工作了。哪怕使用 vTaskDelayUntil也只能让整个任务有一个固定周期没法让内部不同函数各自拥有不同的节奏。我们需要一种轻量级的非阻塞定时器它只负责判断“时间到了没有”而不实际让任务休眠。这样任务循环可以快速运转各个子功能按照各自的节拍各干各的。二、核心工具APP_Time_IsElapsed设计思想借用系统滴答计数器 HAL_GetTick()给每一个需要定时的功能配上一个“私有闹钟”——一个静态变量 last_tick记录上次执行的时间。每次循环都去问一下这个闹钟“距离上一次执行已经过去 100 毫秒了吗”如果没到就立刻跳过如果到了就执行业务代码并把闹钟拨到当前时间。代码实现修正优化版#ifndef __APP_TIME_H #define __APP_TIME_H #includestm32f4xx_hal.h#includestdint.h/** * brief 非阻塞时间检查 * param last_tick : 保存上一次触发时刻的变量指针必须是持久的推荐 static 局部变量 * param period_ms : 期望的周期间隔毫秒 * retval 1: 时间已到可以执行0: 时间未到 */uint8_tAPP_Time_IsElapsed(uint32_t*last_tick,uint32_t period_ms);#endifapp_time.c#includeapp_time.h/** * brief 判断自上次触发后是否经过了 period_ms * note 首次调用时 last_tick 为 0默认会立即触发一次并初始化 last_tick。 * 利用 HAL_GetTick() 的 uint32_t 溢出回绕特性只要 period_ms 不超过 24 天即安全。 */uint8_tAPP_Time_IsElapsed(uint32_t*last_tick,uint32_t period_ms){uint32_t now;if(last_tick0){return0;}nowHAL_GetTick();if((uint32_t)(now-*last_tick)period_ms){return0;}*last_ticknow;return1;}三、如何在任务里使用以你实际的需求为例ProcessHall 里的霍尔读取每 100ms 执行一次。按键队列处理也有自己的计时这里不展开方式完全一样。封装霍尔读取函数我们可以单独写一个函数来承载定时逻辑保持代码清晰voidSVC_Hall_Task(void){// 这个变量必须为 static保证在多次调用间保持值staticuint32_t last_tick0;// 时间没到函数直接返回不阻塞任何其他代码if(!APP_Time_IsElapsed(last_tick,100)){return;}// ---- 下面就是 100ms 才执行一次的代码 ----SVC_Hall_Read();}然后在主任务循环中调用它voidAPPTask(voidconst*argument){for(;;){AppCore_ProcessKeypadQueue(pdMS_TO_TICKS(10));// 内部可能也有时间判断SVC_Hall_Task();// 每 100ms 读取一次平时直接跳过osDelay(1);// 释放 CPU给其他任务运行的机会}}现在发生了什么任务的循环体每 约 1ms 跑一圈。SVC_Hall_Task 在 100 次循环中有 99 次都是瞄一眼时间就立刻 return耗时几乎忽略不计。第 100 次时才会真正执行 SVC_Hall_Read()。osDelay(1) 让出 CPU保证了整个系统的实时性完全不影响其他任务。四、这背后的原理与效果为什么 osDelay(1) 不会拖慢其他函数osDelay(1) 只是将当前任务挂起 1 个系统 tick通常 1ms这 1ms 完全可以被其他任务利用。对于我们的定时函数来说最坏情况是多等了 1ms 才被下次调用对于 100ms 周期的任务而言误差 ±1% 完全可以接受。多个定时任务如何共存你可以为每一个不同周期的功能都声明一个 static uint32_t last_tick互不干扰voidProcess_10ms_Task(void){staticuint32_t last0;if(!APP_Time_IsElapsed(last,10))return;// 处理 10ms 逻辑}voidProcess_50ms_Task(void){staticuint32_t last0;if(!APP_Time_IsElapsed(last,50))return;// 处理 50ms 逻辑}voidProcess_1s_Task(void){staticuint32_t last0;if(!APP_Time_IsElapsed(last,1000))return;// 处理 1 秒逻辑}最后在 APPTask 中依次调用它们再加一个 osDelay(1) 作为节拍所有功能便井井有条。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价