4、周期性触发配置:周期时间设置、抖动控制、偏移量配置、实际项目中的周期选择策略

周期性触发,说白了就是让系统按照你设定的心跳节奏去干活。我做了十几年RTE,见过太多因为周期没配好导致系统崩掉的案例。今天咱们就把这块掰开揉碎了讲清楚。

4.1 周期时间设置——别想当然

周期时间设置,第一反应就是「我要跑1ms就设1ms呗」。嗯,这里要注意,事情没那么简单。

我个人的习惯是:先看硬件定时器精度,再定周期。举个例子,你用了个16位的定时器,时钟源是100MHz,那最小分辨率是10ns。但你能设1ms吗?可以。但实际跑出来可能不是1ms整。

核心原则:周期时间必须是定时器时钟周期的整数倍。否则你会看到「丢脉冲」或者「周期漂移」。

我在项目中遇到过一件事:某次做电机控制,设了500μs的周期。结果电机低速时抖得厉害。查了半天,发现定时器分频没算对,实际周期是498.7μs。你想想看,1.3μs的误差,在高速控制里就是灾难。

所以,设置周期时间时,我建议你按这个步骤来:

  1. 确认硬件定时器的时钟源频率
  2. 计算最小分辨率(1/时钟频率)
  3. 确定目标周期是否在分辨率整数倍上
  4. 如果不行,调整分频系数或换定时器
/* 示例:基于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。声音出来全是杂音。后来怎么解决的?

我的经验:抖动控制的核心是「减少不确定性」。把周期触发放在最高优先级中断里,中断服务函数只做「置标志位」一件事,具体处理放到任务级去。

具体做法:

  1. 周期中断优先级设为最高(0或1)
  2. 中断里只写一个全局变量或发一个信号量
  3. 主循环或任务里轮询这个标志
  4. 如果抖动仍然超标,考虑用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. 从需求反推:控制周期 = 1 / (控制带宽 × 5)。比如电机电流环带宽1kHz,那周期至少200μs。
  2. 考虑最坏情况:周期不能小于所有中断服务函数执行时间之和。我曾经见过有人设100μs周期,但中断里跑了80μs,剩下20μs啥也干不了。
  3. 留有余量:实际周期 = 理论周期 × 0.7。比如理论需要1ms,我设700μs。为什么?因为系统负载会波动,留30%余量是底线。
  4. 分层设计:高速控制用硬件定时器直接触发,中速任务用软件定时器,低速任务用调度器轮询。

避坑指南:我曾经在一个项目中,把所有任务都设成了1ms周期。结果系统负载到了95%,稍微有点干扰就死机。后来改成「1ms控制 + 5ms采集 + 20ms通信 + 100ms日志」,负载直接降到40%。记住:不是越快越好,够用就行。

最后,给你一张图,把周期性触发配置的整个逻辑串起来:

周期性触发配置知识体系 周期性触发配置 周期时间设置 抖动控制 偏移量配置 周期选择策略 硬件定时器精度 整数倍原则 中断优先级 最小化中断处理 DMA硬件触发 错峰触发 偏移量计算 需求反推法 最坏情况分析 30%余量原则 核心:精度 + 低抖动 + 错峰 + 留余量

好了,周期性触发配置这块,核心就这些。记住:周期不是拍脑袋定的,是算出来的;抖动不是天生的,是能控住的;偏移量不是摆设,是调出来的。下次做项目,按这个思路走,基本不会出大问题。


可可精品资料库