一、问题背景:低频信号测量为何容易溢出?

使用STM32定时器输入捕获测量频率时,常规做法是:捕获上升沿,读取CCR值,两次捕获之差即为信号周期对应的计数值。但低频信号(如1Hz~100Hz)周期长,而定时器计数频率高(如72MHz),单个周期内计数值远超ARR(自动重装载值),必然发生多次溢出。

例如:72MHz计数,ARR=65535,则溢出周期约0.91ms。测量1Hz信号时,一个信号周期内定时器溢出约1098次。若只读CCR,得到的差值只是“余数”,完全错误。

解决思路:必须记录溢出次数,将“溢出次数×(ARR+1)+ 当前CCR - 上次CCR”作为真实周期。

二、方案一:中断计数法

原理

开启定时器更新中断(溢出中断),每次溢出在ISR中递增一个全局变量overflow_cnt。捕获中断中读取overflow_cnt和CCR,计算总计数。

配置步骤

  1. 定时器配置为输入捕获模式,开启捕获中断和更新中断。
  2. 在更新中断中overflow_cnt++。
  3. 在捕获中断中计算周期。

完整代码示例

volatile uint32_t overflow_cnt = 0;
volatile uint32_t last_ccr = 0;
volatile uint32_t period = 0;
volatile uint8_t  capture_flag = 0;

// 定时器更新中断(溢出)
void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;
        overflow_cnt++;
    }
    // 捕获中断
    if (TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR &= ~TIM_SR_CC1IF;
        uint32_t ccr = TIM2->CCR1;
        if (capture_flag == 0) {
            // 第一次捕获
            last_ccr = ccr;
            overflow_cnt = 0;
            capture_flag = 1;
        } else {
            // 第二次捕获
            period = overflow_cnt * (TIM2->ARR + 1) + ccr - last_ccr;
            capture_flag = 0;
        }
    }
}

优缺点

  • 优点:逻辑简单,不依赖DMA,适合所有STM32系列。
  • 缺点:中断频繁,低频时CPU开销大;若溢出中断被更高优先级打断,可能丢失计数。

三、方案二:DMA传输法

原理

利用DMA将定时器的CNT值或捕获值自动搬运到内存数组,配合定时器溢出事件触发DMA请求,实现无CPU干预的连续记录。测量时通过分析数组计算周期。

配置步骤

  1. 定时器配置为输入捕获,开启捕获DMA请求。
  2. 配置DMA通道,源地址为&TIMx->CCR1,目标地址为数组,循环模式。
  3. 同时开启更新DMA请求,将溢出次数记录到另一数组。
  4. 测量完成后分析数据。

完整代码示例(以STM32F4为例)

#define BUF_SIZE 256
uint32_t ccr_buf[BUF_SIZE];
uint32_t ovf_buf[BUF_SIZE];

void TIM2_DMA_Init(void)
{
    // 捕获DMA
    DMA_InitTypeDef dma;
    DMA_StructInit(&dma);
    dma.DMA_Channel = DMA_Channel_3;
    dma.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
    dma.DMA_Memory0BaseAddr = (uint32_t)ccr_buf;
    dma.DMA_DIR = DMA_DIR_PeripheralToMemory;
    dma.DMA_BufferSize = BUF_SIZE;
    dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
    dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    dma.DMA_Mode = DMA_Mode_Circular;
    dma.DMA_Priority = DMA_Priority_High;
    DMA_Init(DMA1_Stream5, &dma);
    DMA_Cmd(DMA1_Stream5, ENABLE);

    // 溢出DMA(类似配置,源为TIM2->CNT或使用更新事件)
    // 实际常用更新事件触发DMA写内存,此处省略重复代码

    TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);
    TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
}

优缺点

  • 优点:几乎不占用CPU,适合高频或长时间采集。
  • 缺点:配置复杂,需要额外内存;分析数据有延迟,不适合实时性要求高的场景。

四、方案三:从模式复位法

原理

将定时器配置为“从模式复位”(Slave Mode Reset),触发源选择输入捕获引脚。每当捕获边沿到来时,硬件自动将CNT清零。这样两次捕获之间CNT不会溢出(只要信号周期小于溢出周期),直接读取CCR即为周期。

配置步骤

  1. 定时器时基配置,ARR设为最大。
  2. 输入捕获通道配置,触发源选择TI1FP1。
  3. 从模式选择TIM_SlaveMode_Reset。
  4. 捕获中断中直接读取CCR。

完整代码示例

void TIM2_IC_Init(void)
{
    TIM_TimeBaseInitTypeDef tb;
    TIM_ICInitTypeDef ic;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    tb.TIM_Prescaler = 72 - 1;      // 1MHz计数
    tb.TIM_CounterMode = TIM_CounterMode_Up;
    tb.TIM_Period = 0xFFFF;
    tb.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &tb);

    ic.TIM_Channel = TIM_Channel_1;
    ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
    ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
    ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    ic.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM2, &ic);

    // 从模式复位:TI1FP1触发CNT复位
    TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);

    TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR &= ~TIM_SR_CC1IF;
        uint32_t period = TIM2->CCR1;  // 直接就是周期计数值
        // 频率 = 1MHz / period
    }
}

优缺点

  • 优点:硬件自动处理,无溢出问题,代码极简,实时性好。
  • 缺点:要求信号周期小于定时器溢出周期(即频率不能太低),否则仍会溢出;只能测量单通道,无法同时捕获多路。

五、三种方案对比与选型建议

| 方案 | CPU占用 | 实时性 | 最低可测频率 | 复杂度 | 适用场景 | |------|---------|--------|--------------|--------|----------| | 中断计数法 | 高 | 好 | 极低 | 低 | 低频、单路、资源紧张 | | DMA传输法 | 极低 | 差 | 极低 | 高 | 多路、长时间采集 | | 从模式复位法 | 低 | 好 | 受ARR限制 | 中 | 中高频、单路、实时 |

选型建议:

  • 若信号频率高于1kHz且单路,优先选从模式复位法。
  • 若信号极低(<10Hz)且CPU空闲,可用中断计数法。
  • 若需同时测量多路或连续记录,选DMA传输法。

六、注意事项

  • 中断计数法中,更新中断和捕获中断可能同时发生,需先读CCR再清标志,避免竞争。
  • 从模式复位法需确保信号周期小于定时器溢出周期,否则需配合预分频器降低计数频率。
  • DMA传输法注意缓冲区大小和循环模式,防止数据覆盖。
  • 所有方案都应考虑输入滤波(ICFilter)和预分频(ICPSC),抑制噪声。
  • 测量前最好先测量一个已知频率信号校准。

掌握这三种方案,你就能根据项目需求灵活应对STM32输入捕获中的溢出问题,实现稳定可靠的低频测量。