一、问题背景:低频信号测量为何容易溢出?
使用STM32定时器输入捕获测量频率时,常规做法是:捕获上升沿,读取CCR值,两次捕获之差即为信号周期对应的计数值。但低频信号(如1Hz~100Hz)周期长,而定时器计数频率高(如72MHz),单个周期内计数值远超ARR(自动重装载值),必然发生多次溢出。
例如:72MHz计数,ARR=65535,则溢出周期约0.91ms。测量1Hz信号时,一个信号周期内定时器溢出约1098次。若只读CCR,得到的差值只是“余数”,完全错误。
解决思路:必须记录溢出次数,将“溢出次数×(ARR+1)+ 当前CCR - 上次CCR”作为真实周期。
二、方案一:中断计数法
原理
开启定时器更新中断(溢出中断),每次溢出在ISR中递增一个全局变量overflow_cnt。捕获中断中读取overflow_cnt和CCR,计算总计数。
配置步骤
- 定时器配置为输入捕获模式,开启捕获中断和更新中断。
- 在更新中断中
overflow_cnt++。 - 在捕获中断中计算周期。
完整代码示例
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干预的连续记录。测量时通过分析数组计算周期。
配置步骤
- 定时器配置为输入捕获,开启捕获DMA请求。
- 配置DMA通道,源地址为
&TIMx->CCR1,目标地址为数组,循环模式。 - 同时开启更新DMA请求,将溢出次数记录到另一数组。
- 测量完成后分析数据。
完整代码示例(以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即为周期。
配置步骤
- 定时器时基配置,ARR设为最大。
- 输入捕获通道配置,触发源选择TI1FP1。
- 从模式选择
TIM_SlaveMode_Reset。 - 捕获中断中直接读取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输入捕获中的溢出问题,实现稳定可靠的低频测量。