RTOS-F429-HAL-Systick心跳(2026/7/26)

发布时间:2026/7/26 12:16:14

RTOS-F429-HAL-Systick心跳(2026/7/26) 三个延时各走各的路改前一个 SysTick 干三件事。改后分家SysTick (10μs) TIM7 (1ms) ┌─────┴─────┐ │ │ │ │ xPortSysTickHandler SysTick_ISR_Handler HAL_TIM_PeriodElapsedCallback │ │ │ ▼ ▼ ▼ vTaskDelay Delay_us/Delay_ms HAL_Delay (FreeRTOS) (10μs精度) (1ms精度)1vTaskDelay — FreeRTOS 的心跳新增跟 HAL_Delay 一模一样的加法模式只是计数器不同。SysTick 每 1ms 中断一次 → xPortSysTickHandler() → xTickCount ← FreeRTOS 自己的全局计数器void vTaskDelay(1000) // 延 1000 个 tick 1 秒 { 记下起点: xTickCount 死等: (xTickCount - 起点) 1000 等到就走人 }FreeRTOS 的uwTick叫xTickCount算延迟的方式一模一样——增量计数器 差值比较。2Delay_us / Delay_ms — 没变还是 SysTick跟改前一模一样。还是 SysTick 每 10μs 中断一次还是同一个TimingDelay。SysTick 每 10μs 中断一次 → SysTick_ISR_Handler() → TimingDelay-- ← 没变Delay_us(50) // 50 × 10μs 500μs { TimingDelay 50; while (TimingDelay ! 0); ← 死等减到 0 }3HAL_Delay — 换了时钟源行为一模一样改前SysTick 每 100 次 10μs 中断 → HAL_IncTick() → uwTick改后TIM7 每 1ms 中断 → HAL_TIM_PeriodElapsedCallback → HAL_IncTick() → uwTickTIM7 每 1ms 中断一次 → TIM7_IRQHandler → HAL_TIM_IRQHandler → HAL_TIM_PeriodElapsedCallback → HAL_IncTick() → uwTick ← 还是它HAL_Delay(500) { 记下起点: uwTick 死等: (uwTick - 起点) 500 }uwTick还是那个uwTickHAL_Delay还是那个HAL_Delay。它不知道、也不在乎谁在喂自己——SysTick 喂还是 TIM7 喂吃到uwTick就行。4对比表改前改后uwTick谁喂SysTick每 100 次 10μsTIM7每 1msTimingDelay--谁喂SysTick每 10μsSysTick每 10μs没变xTickCount谁喂不存在SysTickFreeRTOS 内部SysTick_Handler 里干什么只调SysTick_ISR_HandlerxPortSysTickHandlerSysTick_ISR_Handler两个都调本质还是笔记里那句话——硬件到点敲门 → 中断改个变量 → 主循环等变量到位。区别只是敲门的人从一个变成两个变量从两个变成三个。5延时函数中断的跳转裸机SysTick 硬件每 10μs 触发一次 → 进 1 个 ISR → 里面顺序调 2 个函数硬件SysTick 定时器 VAL 减到 0 │ └─→ SysTick_Handler() ← 只有这一个 ISR 入口 │ ├─→ xPortSysTickHandler() ← 子函数 1FreeRTOS 调度 │ └─→ SysTick_ISR_Handler() ← 子函数 2TimingDelay--TIM7 硬件每 1ms 触发一次 → 进另一个 ISR硬件TIM7 CNT 数到 ARR │ └─→ TIM7_IRQHandler() ← 独立的 ISR 入口 │ └─→ HAL_TIM_IRQHandler(htim7) │ └─→ HAL_TIM_PeriodElapsedCallback(htim7) │ └─→ HAL_IncTick()两个硬件两个 ISR互不相干。SysTick_Handler 里调两个子函数只是把两件事塞进同一个中断服务里不是跳了三个中断6子函数 1xPortSysTickHandler() — FreeRTOS 调度// port.c void xPortSysTickHandler(void) { // 第一步屏蔽同级及以下的中断临界区 portDISABLE_INTERRUPTS(); { // 第二步核心工作让 xTickCount 加 1 if (xTaskIncrementTick() ! pdFALSE) { // 如果有更高优先级的任务到期触发 PendSV 切换 portNVIC_INT_CTRL_REG portNVIC_PEND_SYST_SET; } } portENABLE_INTERRUPTS(); }xTaskIncrementTick() 内部干了什么// tasks.c BaseType_t xTaskIncrementTick(void) { // ① 全局计数器加 1 xTickCount; // ← FreeRTOS 的 uwTick类型 TickType_t32 位 ​ // ② 如果有任务在延时检查是否到期 if (xDelayedTaskList1 里第一个任务的唤醒时间 xTickCount) { 把到期任务从延时链表挪到就绪链表 如果该任务优先级更高 → 返回 pdTRUE → 触发 PendSV 切换 } ​ // ③ 时间片轮转同优先级任务轮流跑 if (同优先级有别的任务) 把当前任务挪到链表末尾下一个上 ​ return 是否要切换; }核心变量就一个xTickCount uint32_t 每 1ms 1 FreeRTOS 全局心跳vTaskDelay(500) 做的事记录等我 xTickCount 涨 500 下再叫醒我把自己挂到延时链表然后 PendSV 切去跑别的任务。跟 HAL_Delay 逻辑完全一样只是 HAL_Delay 是死等vTaskDelay 是睡着了等。7子函数 2SysTick_ISR_Handler() — 野火 10μs 延时// BSP/SysTick/SysTick.c void SysTick_ISR_Handler(void) { if (TimingDelay ! 0x00) TimingDelay--; }毫无花活就一行。每 10μs 减一次减到 0 为止。核心变量就一个TimingDelay uint32_t 每 10μs -1 设初值后倒数归零// 使用 Delay_us(50); // TimingDelay 50然后死等 // 50 × 10μs 500μs 后 TimingDelay 变成 0退出8子函数 3HAL_IncTick() — HAL 1ms 时基// stm32f4xx_hal.c __IO uint32_t uwTick; // 全局滴答计数器 ​ __weak void HAL_IncTick(void) { uwTick uwTickFreq; // uwTickFreq 默认 1每次 1ms }比 TimingDelay 还简单就一行加法。// HAL_Delay 内部 HAL_Delay(500) { uint32_t start uwTick; // 记起点比如 start 1000 ​ while ((uwTick - start) 500) {} // 死等 uwTick 涨到 1500 // TIM7 每 1ms 调一次 HAL_IncTick // uwTick: 1000→1001→...→1500 循环退出 }核心变量就一个uwTick uint32_t 每 1ms 1 HAL 全局时基HAL_Delay 依赖9三个变量排排坐变量谁喂它频率方向谁用xTickCountxPortSysTickHandler每 1ms 1递增vTaskDelayTimingDelaySysTick_ISR_Handler每 10μs -1递减Delay_us/Delay_msuwTickHAL_IncTickTIM7 中断每 1ms 1递增HAL_Delay三个变量两套硬件SysTick TIM7三个子函数各改各的变量互不打架。10三种延时的对比HAL_DelayDelay_us/Delay_msvTaskDelay变量uwTick递增TimingDelay递减xTickCount递增谁喂变量TIM71msSysTick启动前 10μs启动后 1msSysTick1ms精度1ms启动前 10μs启动后 1ms1ms等待方式死等——while 空转CPU 废了死等——同上睡着等——CPU 去干别的阻塞期间 CPU100% 空转100% 空转0%跑别的任务能同时干别的事吗❌❌✅最小延时1ms10μs裸机/ 1msRTOS1 tick适用场景裸机单任务裸机精确时序多任务RTOS 核心死等 vs 睡着等HAL_Delay(100) vTaskDelay(100) ​ main: Task A: while (没到100ms) {} ← CPU 100% 把自己挂到延时链表 什么也干不了 主动让出 CPU ​ Task B: 嘿嘿我跑了 │ 100ms 后 SysTick 中断 Task A 到期了回来跑HAL_Delay 是你在银行排队窗口干站着什么事都不做。vTaskDelay 是你取号后坐下刷手机叫到你才起身。死等HAL_Delay / Delay_us的特点写代码简单——一行 while 搞定精确——变量到值立刻退出多等不了一个 tick浪费——CPU 从 180MHz 降到 0除了等啥也不干独占——一个 Delay 卡住整个系统心跳延时vTaskDelay的特点写代码简单——一行 API 搞定不浪费——CPU 跑别的任务精度有限——≤ 1 tick1ms。vTaskDelay(1) 可能实际等 0.01ms 到 1ms只是至少等多久——到期了不一定立刻跑可能更高优先级任务在忙vTaskDelay 适合任务 B 没到时间别烦我不适合这个脉冲必须精确 15μs。裸机延时反过来。两个用在不同层面不是谁替代谁的关系。11微妙级延时失效微妙延时Delay_us 和rtos 心跳vTaskDelay 公用SysTick 启动SysTick后rtos不是寄存器的重装载值改为1ms 进一次中断了吗那Delay_us 不是会受影响对受影响。SysTick_Handler整个中断变成 1ms 一次之后SysTick_ISR_Handler也跟着 1ms 才跑一次TimingDelay--就从 10μs 精度掉成 1ms 了。启动调度器前 SysTick 每 10μs 中断 → TimingDelay-- 每 10μs 减一次 Delay_us(1) 10μs ✅ ​ 启动调度器后 SysTick 被改成 1ms 中断 → TimingDelay-- 每 1ms 减一次 Delay_us(1) 1ms ❌ 名存实亡所以Delay_us只在裸机初始化阶段vTaskStartScheduler之前好用。调度器一跑它就退化成一毫秒级延时。补救办法也简单——拿 TIM6 做成 10μs 中断回调里专门减 TimingDelay// TIM6: PSC180-1, ARR1 → 1MHz/2500KHz2μs... // 算了直接 ARR9 → 1MHz/10100KHz10μs跟原 SysTick 一样12启动调度器是什么意思int main(void) { HAL_Init(); // ① HAL 就绪 SysTick_Init(); // ② 时钟 180MHz LED_Init(); // ③ 外设初始化 Usart1_Init(115200); ​ xTaskCreate(任务1, ...); // ④ 创建任务 xTaskCreate(任务2, ...); ​ vTaskStartScheduler(); // ⑤ 就这一句启动后不会再返回 ​ while(1) {} // 永远不会跑到这里 }vTaskStartScheduler()内部干三件事把 SysTick 重装值改成 1ms创建空闲任务最低优先级没人跑时它跑触发 PendSV → 开始跑你创建的第一个任务这句话一调main 函数的执行权交给 FreeRTOSmain 永远不会退出。之后所有事情都在任务里跑vTaskDelay、队列、信号量才开始有意义。

相关新闻