STM32输入捕获:精准捕捉信号的“硬核神器”
在嵌入式开发中,我们经常需要测量外部信号的周期、频率或占空比——比如解析PWM波控制的电机速度、读取红外遥控的编码,或是检测传感器的脉冲信号。这时候,STM32的输入捕获功能就成了“刚需”:它能像高速摄像机一样,精准捕捉信号跳变的瞬间,用硬件级的速度和精度完成测量,远比软件延时可靠得多。
一、什么是输入捕获?硬件级的“瞬间抓拍”
简单来说,输入捕获就是让STM32定时器“盯住”某个GPIO引脚,一旦检测到电平跳变(上升沿/下降沿),就立刻把当前定时器计数器(CNT)的值“快照”到捕获/比较寄存器(CCR)里。整个过程由硬件自动完成,不受中断延迟、CPU负载的影响,哪怕是高频信号也能稳稳捕捉。
举个直观的例子:
如果定时器时钟配置为1MHz(每1μs计数一次),当第一个上升沿到来时,CNT值是5000;第二个上升沿到来时,CNT变成15000。那么信号周期就是(15000-5000)×1μs=10ms,对应频率就是100Hz。这种精度,是软件延时(比如delay_ms())根本达不到的。
它的“硬核优势”在哪?
- 速度快:硬件触发,无需CPU干预,哪怕是MHz级的高频信号也能响应;
- 抗干扰强:支持输入滤波——可设置“连续N次采样电平一致才判定为有效跳变”,轻松滤除工业现场的电磁毛刺;
- 灵活配置:可选择检测上升沿/下降沿、设置分频系数(N个跳变才触发一次捕获),适配不同场景需求。
二、进阶玩法:PWM输入模式,同时测频率+占空比
如果只测周期,单次输入捕获就够了。但实际开发中,我们往往还需要知道占空比(高电平占整个周期的比例)——比如电机调速需要PWM的占空比,红外解码需要高低电平的时间比。这时候,PWM输入模式就能一站式解决问题。
PWM输入模式的核心逻辑:交叉连接+自动复位
PWM输入模式利用定时器内部的“交叉连接”机制,把同一个输入信号拆成两路处理,用两个通道分工协作:
- 通道1(CH1):检测上升沿,并配置为“从模式-复位”——每次上升沿到来时,自动把CNT计数器清零;
- 通道2(CH2):监听通道1的下降沿信号(通过内部TI1FP2链路),只记录高电平的计数值,不清零。
最终:
- CCR1寄存器:存的是整个周期的计数值(因为CNT从0重新计数);
- CCR2寄存器:存的是高电平持续的计数值(上升沿到下降沿的差值)。
直接套用公式就能算出结果:
频率 = 定时器时钟频率 / CCR1
占空比 = (CCR2 / CCR1) × 100%
这种设计既省引脚(只用一个物理引脚),又省逻辑——不用手动切换捕获极性,硬件自动完成分工,精度直接拉满。
三、单通道vs双通道:怎么选更合适?
如果不用PWM输入模式,也能手动实现“周期+占空比”测量,这就涉及到单通道和双通道两种方案的取舍:
1. 单通道方案:省资源,但考验逻辑
原理:复用同一个通道,先捕获上升沿(周期起点),再切换为下降沿(高电平终点),下一次上升沿(周期终点)。
✅ 优势:只占用1个定时器通道,适合通道资源紧张的场景;
❌ 劣势:需要在中断中动态切换捕获极性,高频信号下可能因切换延迟导致精度偏差,调试难度稍高;
🔧 适用:低频PWM(≤10kHz)、对精度要求不高的场景。
2. 双通道方案:精度高,逻辑更简洁
原理:两个通道共用一个物理引脚(通过直接/间接映射),通道1固定捕获上升沿,通道2固定捕获下降沿,直接读取两个CCR值计算。
✅ 优势:无需切换极性,捕获时序无延迟,精度更高(适配≥100kHz的高频PWM),中断处理简单;
❌ 劣势:占用2个定时器通道(但仅用1个引脚,另一个通道的引脚可正常复用);
🔧 适用:中高频PWM、对精度要求高的场景(主流优选)。
四、为什么输入捕获这么“强”?
总结下来,输入捕获的核心竞争力在于:
- 硬件级响应:避开软件层面的延迟,精准捕捉纳秒级的信号跳变;
- 抗干扰设计:输入滤波机制能应对工业环境的电磁噪声,测量更稳定;
- 灵活扩展:从单次捕获测周期,到PWM输入模式测占空比,能覆盖绝大多数信号测量需求。
无论是电机控制、传感器数据采集,还是通信协议解析,输入捕获都是STM32开发者手中的“硬核工具”——毕竟,精准捕捉信号的瞬间,才能让嵌入式系统真正“读懂”外部世界。
如果你也在做信号测量相关的项目,不妨试试输入捕获功能,体验一下硬件级精准度带来的丝滑感~
当然可以!下面我会结合单通道捕获和**PWM输入模式(双通道)**两种常用场景,给出STM32标准库的代码示例(以STM32F103为例),包含初始化配置、中断处理和数据计算逻辑,方便你直接参考使用。
一、PWM输入模式(双通道,推荐)
1. 定时器初始化(以TIM2_CH1/CH2为例)
#include "stm32f10x.h"
// 全局变量:存储捕获值
uint16_t CCR1_Value = 0; // 周期计数值
uint16_t CCR2_Value = 0; // 高电平计数值
float Frequency = 0; // 频率
float DutyCycle = 0; // 占空比
// TIM2_CH1初始化(PA0)+ PWM输入模式配置
void TIM2_PWMInput_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO(PA0为TIM2_CH1,浮空输入)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入(关键)
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置定时器时基
TIM_TimeBaseStruct.TIM_Period = 0xFFFF; // 最大ARR值,防止溢出
TIM_TimeBaseStruct.TIM_Prescaler = 71; // 分频72,时钟=72MHz/(71+1)=1MHz
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// 4. 配置PWM输入模式(CH1捕获上升沿,CH2捕获下降沿)
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // CH1捕获上升沿
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;// 直接映射到CH1
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频
TIM_ICInitStruct.TIM_ICFilter = 0x0F; // 16次采样滤波,抗干扰
TIM_PWMIConfig(TIM2, &TIM_ICInitStruct); // 启用PWM输入模式
// 5. 配置从模式:CH1上升沿触发CNT复位
TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); // 触发源为CH1滤波后的信号
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); // 复位模式:上升沿清零CNT
TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable);
// 6. 使能捕获中断(CH1和CH2)
TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
// 7. 配置NVIC中断
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 8. 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
2. 中断服务函数
void TIM2_IRQHandler(void)
{
// 捕获CH1(上升沿,周期)
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
CCR1_Value = TIM_GetCapture1(TIM2);
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
// 捕获CH2(下降沿,高电平)
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
CCR2_Value = TIM_GetCapture2(TIM2);
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
}
// 计算频率和占空比(需确保CCR1_Value不为0)
if (CCR1_Value != 0)
{
Frequency = 1000000.0 / CCR1_Value; // 定时器时钟1MHz,频率=1MHz/周期计数值
DutyCycle = (float)CCR2_Value / CCR1_Value * 100;
}
}
二、单通道捕获(动态切换极性)
1. 定时器初始化(以TIM3_CH1为例)
#include "stm32f10x.h"
// 全局变量
uint16_t RisingValue = 0; // 上升沿捕获值
uint16_t FallingValue = 0; // 下降沿捕获值
uint16_t PeriodValue = 0; // 周期计数值
float Freq = 0;
float Duty = 0;
u8 CaptureFlag = 0; // 捕获状态标志:0=等待上升沿,1=等待下降沿,2=等待下一个上升沿
// TIM3_CH1初始化(PA6)
void TIM3_SingleIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO(PA6为TIM3_CH1,浮空输入)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 定时器时基配置(同PWM输入模式)
TIM_TimeBaseStruct.TIM_Period = 0xFFFF;
TIM_TimeBaseStruct.TIM_Prescaler = 71; // 1MHz时钟
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
// 4. 配置输入捕获(初始捕获上升沿)
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // 先捕获上升沿
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICFilter = 0x0F;
TIM_ICInit(TIM3, &TIM_ICInitStruct);
// 5. 使能捕获中断
TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
// 6. NVIC配置
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 7. 启动定时器
TIM_Cmd(TIM3, ENABLE);
}
2. 中断服务函数(动态切换极性)
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
switch(CaptureFlag)
{
case 0: // 第一次捕获:上升沿(周期起点)
RisingValue = TIM_GetCapture1(TIM3);
// 切换为捕获下降沿
TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling);
CaptureFlag = 1;
break;
case 1: // 第二次捕获:下降沿(高电平终点)
FallingValue = TIM_GetCapture1(TIM3);
// 切换为捕获上升沿(周期终点)
TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising);
CaptureFlag = 2;
break;
case 2: // 第三次捕获:下一个上升沿(周期终点)
PeriodValue = TIM_GetCapture1(TIM3);
// 处理溢出情况(如果CNT溢出,需加上65536)
if (PeriodValue < RisingValue) PeriodValue += 0xFFFF;
PeriodValue -= RisingValue;
// 计算高电平时间(同理处理溢出)
uint16_t HighTime = FallingValue - RisingValue;
if (FallingValue < RisingValue) HighTime += 0xFFFF;
// 计算频率和占空比
if (PeriodValue != 0)
{
Freq = 1000000.0 / PeriodValue;
Duty = (float)HighTime / PeriodValue * 100;
}
// 重置状态,准备下一次捕获
CaptureFlag = 0;
break;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
}
}
三、代码说明
- 时钟配置:示例中定时器时钟为1MHz(72MHz主频分频72),若需更高精度可减小分频系数(如分频0,时钟72MHz)。
- 滤波设置:
TIM_ICFilter = 0x0F表示16次采样一致才判定有效跳变,可根据实际场景调整(0~0xF)。 - 溢出处理:单通道捕获需考虑CNT溢出(超过0xFFFF),需在计算时补加65536;PWM输入模式因CNT自动复位,无需处理溢出。
- 引脚映射:TIM2_CH1对应PA0,TIM3_CH1对应PA6,若需更换通道需修改GPIO和定时器通道配置。
四、使用建议
- 优先选择PWM输入模式:逻辑简单、精度高,适合中高频PWM测量;
- 单通道方案仅在定时器通道资源紧张时使用,需注意极性切换的时序问题;
- 实际项目中可增加“数据防抖”(多次采样取平均值),进一步提升稳定性。
将上述代码整合到你的工程中,配合串口打印(如printf("频率:%.2fHz,占空比:%.2f%%\r\n", Frequency, DutyCycle)),就能直观看到测量结果啦!


4562

被折叠的 条评论
为什么被折叠?



