2. STM32硬件基础与实时性约束

实时任务设计的第一步不是写调度器,而是弄清“硬件到底给了我们多少确定性”。STM32 的实时性由四层硬件共同决定:Cortex-M 内核的指令与异常行为、NVIC 的中断响应机制、时钟树决定的时序基准、以及存储器结构决定的取指与数据访问延迟。这四层中任何一层出现不确定,任务的最坏执行时间(WCET)就无法收敛。

Cortex-M 内核:实时性的起点

以常见的 Cortex-M4 为例,它采用 3 级流水线(取指、译码、执行),并配有独立的指令总线(I-Code)与数据总线(D-Code),因此取指与数据访问可以并行。内核提供两种运行模式(Thread / Handler)和两种特权级(特权 / 非特权),实时任务通常运行在 Thread 模式,而中断服务程序(ISR)强制进入 Handler 模式。这种模式切换由硬件自动完成,不需要软件保存通用寄存器,是低中断延迟的基础。

真正影响实时性的是几个硬件特性:

  • 尾链(Tail-chaining):当一个中断返回时若已有挂起中断,硬件跳过出栈与入栈,仅用约 6 个周期完成切换,而不是完整的 12+12 周期。
  • 迟到(Late-arriving):高优先级中断在低优先级中断入栈阶段到来时,先执行高优先级 ISR,再返回低优先级,避免额外压栈。
  • 咬尾与压栈:异常进入自动压入 xPSR、PC、LR、R12、R3~R0 共 8 个寄存器,若启用 FPU 且使用浮点,还会额外压入 S0~S15 与 FPSCR,压栈时间显著增加。

因此,在硬实时 ISR 中应避免使用浮点运算,或至少明确 FPU 上下文带来的额外延迟。此外,Cortex-M 的 SysTick 是一个 24 位递减计数器,常被用作 RTOS 的时基,其溢出周期决定了最小时间片粒度。

NVIC:中断优先级与响应确定性

NVIC 支持可编程优先级,STM32 通常实现 4 位优先级(16 级),并通过优先级分组寄存器(AIRCR.PRIGROUP)划分抢占优先级与子优先级。抢占优先级决定能否打断正在执行的 ISR,子优先级只在多个中断同时挂起时决定谁先执行,不影响抢占。

实时设计的关键约束是:抢占优先级必须与任务的实时性要求严格对应。例如,电机 PWM 故障保护应设为最高抢占优先级,通信接收中断次之,而按键扫描等低速事件放在最低。若两个中断抢占优先级相同,它们之间不会互相打断,高频率中断可能阻塞低频率中断,造成响应抖动。

另一个常被忽略的点是中断嵌套深度。每嵌套一层,栈空间就多消耗一份上下文。若栈空间不足,会触发 HardFault,实时性直接崩溃。因此,NVIC 配置必须与栈大小估算同步进行。

STM32 实时性四层约束与任务影响 Cortex-M 内核 3级流水线 I-Code / D-Code 双总线 尾链 / 迟到机制 自动压栈 8 寄存器 FPU 额外压栈 S0~S15 → 影响 ISR 入口延迟 NVIC 4位优先级 / 16级 抢占优先级 + 子优先级 嵌套深度 = 栈消耗 尾链减少切换周期 挂起 / 活跃状态管理 → 影响响应抖动 时钟树 HSI / HSE / PLL AHB / APB1 / APB2 分频 SysTick 时基 外设时钟使能 低功耗模式切换 → 影响时间片精度 存储器结构 Flash 取指等待周期 SRAM 数据访问 CCM RAM 零等待 Cache / 预取 DMA 与 CPU 争用 → 影响 WCET 波动 实时任务可预测性 = 内核延迟 + NVIC 抖动 + 时钟精度 + 存储访问波动 硬实时任务 PWM 故障保护 电流环控制 要求:确定性响应 软实时任务 通信协议解析 人机界面刷新 允许:有限抖动 非实时任务 日志记录 参数存储 可:后台执行

时钟树:时间基准的源头

STM32 的时钟树从 HSI(内部 RC,约 8/16 MHz)、HSE(外部晶振)出发,经 PLL 倍频后得到 SYSCLK,再通过 AHB 预分频器、APB1/APB2 预分频器分配给内核与各外设。实时任务必须清楚三件事:

  1. SysTick 的时钟源:通常选择 HCLK 或 HCLK/8。若 HCLK=72 MHz,SysTick 重装载值设为 72000-1,则中断周期为 1 ms。这个 1 ms 就是 RTOS 的最小时间片,也是任务切换的基准。
  2. 外设时钟与中断频率的关系:定时器中断频率由 APB 总线时钟和预分频器共同决定。若 APB1 为 36 MHz,定时器时钟可能被倍频到 72 MHz,计算时不能直接用 APB 频率。
  3. 低功耗模式下的时钟切换:进入 Stop 模式后 HSE/PLL 关闭,唤醒后需要重新稳定,这段时间内中断响应会被拉长,硬实时任务必须评估唤醒延迟。

一个常见的实时性陷阱是:在中断中调用 HAL_Delay()。该函数依赖 SysTick 中断,而 SysTick 优先级通常低于外设中断,导致在 ISR 中调用时死等。正确做法是使用硬件定时器或 DWT 周期计数器做短延时。

存储器结构:WCET 波动的隐藏来源

STM32 的存储器分为 Flash、SRAM、CCM RAM(部分型号)以及外设寄存器区。对实时任务影响最大的是 Flash 取指等待周期。当 HCLK 高于 Flash 访问速度时,需要插入等待周期(Latency),例如 72 MHz 下通常需要 2 个等待周期。若启用预取缓冲(Prefetch)和指令缓存(I-Cache),顺序执行时取指可以接近零等待,但遇到分支跳转时预取失效,取指延迟突然增加。

这意味着:同一段代码在不同执行路径下的执行时间可能不同。对于硬实时任务,应尽量将关键 ISR 和调度器代码放入 CCM RAM 或 SRAM 中执行,避免 Flash 等待周期和预取失效带来的抖动。此外,DMA 与 CPU 同时访问 SRAM 时会产生总线仲裁延迟,高频 DMA 传输会间接拉长 CPU 的数据访问时间。

实时性检查要点:在确定任务周期和优先级之前,先确认以下参数——HCLK 频率、Flash 等待周期、SysTick 中断周期、NVIC 优先级分组、关键 ISR 是否使用浮点、关键代码是否位于零等待存储器。这些参数共同决定了任务的最坏响应时间,而不是仅由 RTOS 调度算法决定。

把硬件约束落到任务设计上

综合以上四层,可以形成一条可操作的实时任务设计路径:

硬件层 关键参数示例 对任务设计的影响
Cortex-M 内核 FPU 关闭,ISR 无浮点 ISR 入口压栈约 12 周期,延迟可控
NVIC 抢占优先级 0~4,分组 4 高优先级任务响应抖动小于 1 个中断周期
时钟树 HCLK=72 MHz,SysTick=1 ms 时间片最小 1 ms,任务周期取整数倍
存储器 关键代码放 CCM RAM WCET 波动从 ±30% 降到 ±5% 以内

例如,一个电机控制任务要求 100 μs 周期执行电流环。若 HCLK=72 MHz,100 μs 对应 7200 个周期。扣除 ISR 入口、ADC 读取、PWM 更新和退出开销,实际可用于控制算法的周期可能只有 5000 左右。若算法代码位于 Flash 且存在分支跳转,取指等待可能额外消耗数百周期,导致周期抖动。将电流环函数复制到 CCM RAM 执行,并关闭该 ISR 的浮点使用,可以把抖动压缩到可接受范围。

实时性不是靠“提高优先级”解决的,而是靠对硬件行为的精确掌握。理解内核的异常机制、NVIC 的优先级模型、时钟树的频率传递和存储器的访问延迟,才能在任务划分、周期设定和优先级分配时做出有依据的决策,而不是凭经验猜测。