4、周期性触发配置:周期时间设置、抖动控制、偏移量配置、实际项目中的周期选择策略
周期性触发,说白了就是让系统按照你设定的心跳节奏去干活。我做了十几年RTE,见过太多因为周期没配好导致系统崩掉的案例。今天咱们就把这块掰开揉碎了讲清楚。
4.1 周期时间设置——别想当然
周期时间设置,第一反应就是「我要跑1ms就设1ms呗」。嗯,这里要注意,事情没那么简单。
我个人的习惯是:先看硬件定时器精度,再定周期。举个例子,你用了个16位的定时器,时钟源是100MHz,那最小分辨率是10ns。但你能设1ms吗?可以。但实际跑出来可能不是1ms整。
核心原则:周期时间必须是定时器时钟周期的整数倍。否则你会看到「丢脉冲」或者「周期漂移」。
我在项目中遇到过一件事:某次做电机控制,设了500μs的周期。结果电机低速时抖得厉害。查了半天,发现定时器分频没算对,实际周期是498.7μs。你想想看,1.3μs的误差,在高速控制里就是灾难。
所以,设置周期时间时,我建议你按这个步骤来:
- 确认硬件定时器的时钟源频率
- 计算最小分辨率(1/时钟频率)
- 确定目标周期是否在分辨率整数倍上
- 如果不行,调整分频系数或换定时器
/* 示例:基于STM32的周期设置 */
/* 假设时钟源 72MHz,预分频 71,则定时器时钟 1MHz */
/* 周期 1000 对应 1ms */
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 71; /* 72MHz / (71+1) = 1MHz */
TIM_InitStruct.TIM_Period = 999; /* 1MHz / (999+1) = 1kHz = 1ms */
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
4.2 抖动控制——别让周期「忽快忽慢」
抖动,就是实际触发时刻和理想时刻之间的偏差。说白了,你的周期不是完美的正弦波,而是带毛刺的方波。
为什么会这样?原因很多:
- 中断优先级抢占
- 总线仲裁延迟
- 代码执行时间不稳定
- 缓存未命中
我记得有一次做音频采集,要求采样周期严格控制在1ms±5μs。结果一测,抖动到了±50μs。声音出来全是杂音。后来怎么解决的?
我的经验:抖动控制的核心是「减少不确定性」。把周期触发放在最高优先级中断里,中断服务函数只做「置标志位」一件事,具体处理放到任务级去。
具体做法:
- 周期中断优先级设为最高(0或1)
- 中断里只写一个全局变量或发一个信号量
- 主循环或任务里轮询这个标志
- 如果抖动仍然超标,考虑用DMA或硬件定时器直接触发外设
/* 低抖动中断处理示例 */
volatile uint32_t g_uiTickFlag = 0;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
g_uiTickFlag = 1; /* 只做一件事,越快越好 */
}
}
void main_loop(void)
{
while (1)
{
if (g_uiTickFlag)
{
g_uiTickFlag = 0;
/* 这里做周期任务处理 */
process_periodic_task();
}
}
}
4.3 偏移量配置——让多个周期「错峰出行」
偏移量,就是让同一个定时器产生的多个事件,在时间上错开。为什么要这么做?
你想想看,如果系统里有10个周期任务,都在同一个时刻触发。那CPU瞬间负载飙升到100%,然后空闲90%的时间。这叫「群聚效应」,对实时性非常不友好。
我建议的做法是:
- 给每个周期任务分配一个初始偏移量
- 偏移量 = 周期 / 任务数 × 任务序号
- 确保高优先级任务的偏移量靠前
| 任务 | 周期 | 偏移量 | 触发时刻 |
|---|---|---|---|
| 控制任务 | 1ms | 0μs | 0, 1000, 2000... |
| 采集任务 | 1ms | 300μs | 300, 1300, 2300... |
| 通信任务 | 1ms | 600μs | 600, 1600, 2600... |
| 日志任务 | 2ms | 900μs | 900, 2900, 4900... |
注意:偏移量不能大于周期。如果偏移量 ≥ 周期,那这个任务实际上就变成了「周期+偏移量」的新周期,完全乱套了。
4.4 实际项目中的周期选择策略
这部分是我最想跟你聊的。很多工程师喜欢「拍脑袋」定周期——感觉1ms够快就设1ms,感觉10ms差不多就设10ms。这种做法,说白了就是给自己挖坑。
我总结了一套策略,你可以参考:
- 从需求反推:控制周期 = 1 / (控制带宽 × 5)。比如电机电流环带宽1kHz,那周期至少200μs。
- 考虑最坏情况:周期不能小于所有中断服务函数执行时间之和。我曾经见过有人设100μs周期,但中断里跑了80μs,剩下20μs啥也干不了。
- 留有余量:实际周期 = 理论周期 × 0.7。比如理论需要1ms,我设700μs。为什么?因为系统负载会波动,留30%余量是底线。
- 分层设计:高速控制用硬件定时器直接触发,中速任务用软件定时器,低速任务用调度器轮询。
避坑指南:我曾经在一个项目中,把所有任务都设成了1ms周期。结果系统负载到了95%,稍微有点干扰就死机。后来改成「1ms控制 + 5ms采集 + 20ms通信 + 100ms日志」,负载直接降到40%。记住:不是越快越好,够用就行。
最后,给你一张图,把周期性触发配置的整个逻辑串起来:
好了,周期性触发配置这块,核心就这些。记住:周期不是拍脑袋定的,是算出来的;抖动不是天生的,是能控住的;偏移量不是摆设,是调出来的。下次做项目,按这个思路走,基本不会出大问题。
可可精品资料库