STM32输入捕获:精准捕捉信号的“硬核神器”

STM32输入捕获:精准捕捉信号的“硬核神器”

在嵌入式开发中,我们经常需要测量外部信号的周期、频率或占空比——比如解析PWM波控制的电机速度、读取红外遥控的编码,或是检测传感器的脉冲信号。这时候,STM32的输入捕获功能就成了“刚需”:它能像高速摄像机一样,精准捕捉信号跳变的瞬间,用硬件级的速度和精度完成测量,远比软件延时可靠得多。

一、什么是输入捕获?硬件级的“瞬间抓拍”

简单来说,输入捕获就是让STM32定时器“盯住”某个GPIO引脚,一旦检测到电平跳变(上升沿/下降沿),就立刻把当前定时器计数器(CNT)的值“快照”到捕获/比较寄存器(CCR)里。整个过程由硬件自动完成,不受中断延迟、CPU负载的影响,哪怕是高频信号也能稳稳捕捉。

举个直观的例子:
如果定时器时钟配置为1MHz(每1μs计数一次),当第一个上升沿到来时,CNT值是5000;第二个上升沿到来时,CNT变成15000。那么信号周期就是(15000-5000)×1μs=10ms,对应频率就是100Hz。这种精度,是软件延时(比如delay_ms())根本达不到的。

它的“硬核优势”在哪?

  1. 速度快:硬件触发,无需CPU干预,哪怕是MHz级的高频信号也能响应;
  2. 抗干扰强:支持输入滤波——可设置“连续N次采样电平一致才判定为有效跳变”,轻松滤除工业现场的电磁毛刺;
  3. 灵活配置:可选择检测上升沿/下降沿、设置分频系数(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);
    }
}

三、代码说明

  1. 时钟配置:示例中定时器时钟为1MHz(72MHz主频分频72),若需更高精度可减小分频系数(如分频0,时钟72MHz)。
  2. 滤波设置TIM_ICFilter = 0x0F表示16次采样一致才判定有效跳变,可根据实际场景调整(0~0xF)。
  3. 溢出处理:单通道捕获需考虑CNT溢出(超过0xFFFF),需在计算时补加65536;PWM输入模式因CNT自动复位,无需处理溢出。
  4. 引脚映射:TIM2_CH1对应PA0,TIM3_CH1对应PA6,若需更换通道需修改GPIO和定时器通道配置。

四、使用建议

  • 优先选择PWM输入模式:逻辑简单、精度高,适合中高频PWM测量;
  • 单通道方案仅在定时器通道资源紧张时使用,需注意极性切换的时序问题;
  • 实际项目中可增加“数据防抖”(多次采样取平均值),进一步提升稳定性。

将上述代码整合到你的工程中,配合串口打印(如printf("频率:%.2fHz,占空比:%.2f%%\r\n", Frequency, DutyCycle)),就能直观看到测量结果啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值