STM32 HAL库输入捕获配置

1.定时器输入捕获简介

  定时器输入捕获是嵌入式微控制器(MCU)中定时器模块的一项核心硬件功能,主要用于精确测量外部输入信号的时间特性,例如方波的周期、频率、高电平脉宽、占空比,或者记录外部事件发生的准确时刻。

  其工作原理基于定时器内部自由运行的计数器(CNT)。当外部引脚上出现预设的边沿跳变(如上升沿、下降沿或双边沿)时,硬件会立即将当前计数器的值"捕获"并锁存到对应的捕获比较寄存器(CCR)中。整个过程由纯硬件完成,无需中央处理器(CPU)干预,因此延迟极低,通常仅为定时器时钟的1到2个周期,能够实现纳秒级到微秒级的时间分辨率。

  通过先后两次捕获得到的计数值之差,再结合定时器的计数时钟频率,就能精确换算出两次边沿之间的时间间隔。例如:

  • 第一次捕获到上升沿时计数器值为1000
  • 第二次捕获到下一个上升沿时计数器值为5000
  • 计数时钟为1MHz(每微秒计数一次)
  • 差值4000代表4毫秒,这就是输入信号的周期,其倒数即为频率

  如果捕获边沿依次设置为上升沿(高电平开始)和下降沿(高电平结束),则差值代表高电平的脉宽。若同时使用两个捕获通道分别捕获上升沿和下降沿,配合定时器的"复位模式"或"PWM输入模式",便可一次性测出周期和占空比。

1.1 典型应用场景

  输入捕获广泛应用于各种测频场景:

  • 测量旋转编码器或霍尔传感器输出的脉冲频率以计算电机转速
  • 测量超声波测距模块返回的高电平脉宽以推算距离
  • 解析遥控接收头的PPM(脉冲位置调制)信号以获得通道数据
  • 测量工频交流信号的频率和相位
  • 多路外部事件的高精度时间戳记录,用于时间同步分析

1.2 标准软件流程

  实现输入捕获功能的标准软件流程通常包括:

  1. GPIO配置:配置相关GPIO引脚为定时器输入捕获复用功能
  2. 定时器设置:设置定时器的预分频器(PSC)和自动重装载寄存器(ARR),确定计数时钟频率和最大计数范围(例如16位定时器最大计数值65535)
  3. 捕获参数配置:选择捕获通道的边沿极性、输入滤波参数(可滤除短于设定宽度的毛刺噪声)
  4. 数据处理方式 :决定是否使能捕获中断或直接存储器访问(DMA)
    • 中断方式:适合低频信号,每次捕获由CPU读取数据并处理
    • DMA方式:自动将连续多次捕获值存入内存数组,适合高频信号或无需实时处理的数据批量分析

1.3 关键注意事项

  在设计可靠的输入捕获程序时,有几个关键陷阱必须留意:

  • 定时器溢出问题

  如果两次捕获之间定时器从ARR溢出回到0(例如从65535跳变到0),若不进行溢出计数,计算出的差值将远小于实际间隔。

解决方案

  • 在定时器的更新中断(溢出中断)中累加一个溢出计数器
  • 在捕获中断里通过判断当前捕获值与上次值的大小关系来补全溢出
2. 测量范围与分辨率的矛盾
  • 提高分辨率:需要更高的计数时钟(例如72MHz得到13.9ns分辨率)
  • 限制:相同定时器位数下更高的时钟会更快导致溢出,从而限制最大可测周期

解决方案

  • 根据待测信号的最大周期合理选择预分频值
  • 必要时可以采用两个定时器级联来扩展为32位计数器
3. 信号边沿抖动或毛刺问题

机械开关、慢速编码器等可能产生多次误触发。

解决方案

  • 开启定时器自带的数字输入滤波器,设置采样频率与滤波长度,滤除窄脉冲
  • 在软件中加入去抖动逻辑
4. 中断响应延迟

虽然捕获本身由硬件完成,但如果信号频率很高(如几百kHz以上),每次捕获都触发中断可能导致CPU来不及处理,从而丢失后续捕获。

解决方案

  • 优先使用DMA搬运
  • 采用定时器的从模式(如门控模式)降低中断频率

技术优势对比

与其他测量方式相比,输入捕获具有明显优势:

对比项 输入捕获 纯软件方案(外部中断+软件计时) 专用频率计芯片
精度 1-2个时钟周期误差 数微秒至数百微秒误差 高精度
成本 低(复用MCU资源)
灵活性 高(可与其他外设协同) 中等
CPU负载 不占用CPU

总结

  定时器输入捕获是嵌入式开发中解决时间测量问题的利器,它在实现高精度、低延迟的前提下保持了对CPU的低负载,适用于从电机转速检测、无线信号解码到精密时序分析等各种实时场景。

  掌握输入捕获的基本原理------硬件锁存、差值计算、溢出处理和滤波配置------是每一个嵌入式工程师从轮询思维迈入事件驱动型系统设计的关键一步,也是实现高性能测量与控制的基础能力。

2.STM32定时器捕获

复制代码
  STM32控制器定时器有4路捕获通道,每路通道支持独立配置,设置捕获极性、滤波器和边沿检测器。
  在输入捕获模式下,当检测到ICx信号上相应的边沿后,计数器的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中。当捕获事件发生时,相应的CCxIF标志(TIMx_SR寄存器)被置'1',如果使能了中断或者DMA操作,则将产生中断或者DMA操作。如果捕获事件发生时CCxIF标志已经为高,那么重复捕获标志CCxOF(TIMx_SR寄存器)被置'1'。写CCxIF=0可清除CCxIF,或读取存储在TIMx_CCRx寄存器中的捕获数据也可清除CCxIF。写CCxOF=0可清除CCxOF。

3.输入捕获配置

复制代码
  本次通过定时器输入捕获通道,实现方波信号采集,完成波形绘制。采用TIM3_CH1通道作为输入捕获。配置如下:

设置TIM3_CH1通道参数信息。相关参数包括定时器分频系数、工作周期、捕获边沿以及中断。

生成代码输入捕获代码如下:

  启动TIM3_CH1通道:

c 复制代码
  HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//中断方式启动CH1

4.编写中断服务函数

  中断代码处理,实现方波信号捕获,并保存高低电平时间、周期时间、以及边沿触发标志。

c 复制代码
void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
    static uint8_t flag=0;
  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */
    htim3.Instance->CNT=0;
    uint16_t time=htim3.Instance->CCR1;
    //__HAL_TIM_SetCounter() //设置计数器值
    //HAL_TIM_ReadCapturedValue() //读取通道数据
    if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_6)){
        htim3.Instance->CCER|=1<<1;//下一次捕获下降沿
        adc_info.flag=1;//上升沿
        flag++;
        adc_info.time_l=time;
        if(flag==2){
            flag=0;
            adc_info.per=adc_info.time_h+adc_info.time_l;
        }
        adc_info.time_h=0;
    }
    else{
        htim3.Instance->CCER&=~(1<<1);//捕获上升沿
        adc_info.time_h=time;//高电平时间
        adc_info.flag=2;//下降沿
        flag++;
        printf("高电平:%d\r\n",adc_info.time_h);
        if(flag==2){
            flag=0;
            adc_info.per=adc_info.time_h+adc_info.time_l;
        }
        adc_info.time_l=0;
    }
    
  /* USER CODE END TIM3_IRQn 1 */
}

5.在LCD屏幕上实现波形绘制

  通过定时器输入捕获中断实时采集方波信号,并在屏幕上实时绘制波形。计算频率、周期时间、高低电平。代码实现如下:

c 复制代码
  LCD_Init();//初始化屏幕
  USART1_SendStr((uint8_t *)"USART1初始化完成!\r\n");
  uint16_t id=W25Q64_ReadID();
  printf("id=%#x\r\n",id);
  Touch_Init();//触摸屏初始化
  uint8_t key;
  uint16_t ccr=10000;
  uint16_t arr=20000;
    float hz=0;//10KZ
    char buffer[100];
    uint16_t H,L;
    snprintf(buffer,sizeof(buffer),"输出频率:--- Hz ");
    LCD_ShowStr(10,200,24,(uint8_t*)buffer,WHITE);
    snprintf(buffer,sizeof(buffer),"周期时间:--- ms ");
    LCD_ShowStr(10,225,24,(uint8_t*)buffer,WHITE);
    snprintf(buffer,sizeof(buffer),"高电平(H):--- ms " );
    LCD_ShowStr(10,250,24,(uint8_t*)buffer,WHITE);
    snprintf(buffer,sizeof(buffer),"低电平(L):--- ms ");
    LCD_ShowStr(10,275,24,(uint8_t*)buffer,WHITE);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    key=Key_GetVal();
    if(key==2){//调节占空比
        BEEP(1);
        Delay_Ms(50);
        BEEP(0);
        ccr+=1000;
        if(ccr>=arr)ccr=arr;
        htim4.Instance->CCR1=ccr;
    }
    else if(key==3)
    {
        BEEP(1);
        Delay_Ms(50);
        BEEP(0);
        if(ccr>1000)
            ccr-=1000;
        htim4.Instance->CCR1=ccr;
    }
    else if(key==4)//调节周期
    {
        BEEP(1);
        Delay_Ms(50);
        BEEP(0); 
        arr+=1000;
        if(arr>20000)arr=1000;
        htim4.Instance->ARR=arr;
        htim4.Instance->CCR1=arr/2;
        
    }
    if(usart1_flag)
    {
        //printf("ret=%d,len=%d\r\n",ret,len);
        usart1_flag=0;
        usart1_cnt=0;
    }
        //printf("y=%d,index=%d,count=%d\r\n",adc_info.y_value[adc_info.index],adc_info.index,adc_info.count);
     if(adc_info.time_h){
            adc_info.y_value[adc_info.index]=160;//高电平
            H=adc_info.time_h/10;
        }
        else {
            adc_info.y_value[adc_info.index]=10;
            L=adc_info.time_l/10;
        }
         adc_info.index=(adc_info.index+1)%LCD_WIDTH;
           
        
        //波形往前移动
          for(int i=0;i<adc_info.count;i++)
          {
              if(adc_info.count>=300){
                //清除上一个位置
                OLED_DrawLine(i, adc_info.y_value[(adc_info.index+i+LCD_WIDTH-1)%LCD_WIDTH], i+1, adc_info.y_value[(adc_info.index+i)%LCD_WIDTH],DARKBLUE);
                  
                OLED_DrawLine(i, adc_info.y_value[(adc_info.index+i)%LCD_WIDTH], i+1, adc_info.y_value[(adc_info.index+i+1)%LCD_WIDTH],WHITE);
              }
              else{
                OLED_DrawLine(i, adc_info.y_value[i], i+1, adc_info.y_value[i+1],WHITE);
              } 
          } 
         if(adc_info.count<300) adc_info.count++;  
        LCD_Draw_Kedu();
        if(adc_info.per){
            uint32_t time=adc_info.per;
            //10000 *0.1=1000ms
            hz=(1000*1.0)/(time*0.1);
            snprintf(buffer,sizeof(buffer),"%.1f Hz  ",hz);
            LCD_ShowStr(118,200,24,(uint8_t*)buffer,WHITE);
            snprintf(buffer,sizeof(buffer),"%d ms  ",time/10);
            LCD_ShowStr(118,225,24,(uint8_t*)buffer,WHITE);
            snprintf(buffer,sizeof(buffer),"%d ms  ",H);
            LCD_ShowStr(130,250,24,(uint8_t*)buffer,WHITE);
            snprintf(buffer,sizeof(buffer),"%d ms  ",L);
            LCD_ShowStr(130,275,24,(uint8_t*)buffer,WHITE);
        }
  }

案例链接:输入捕获波形绘制

相关推荐
nuoxin1141 小时前
WILX1200HC-5TG144I替代 LCMXO2-1200HC-5TG144I(富利威)
人工智能·嵌入式硬件·fpga开发·电脑·硬件工程·dsp开发
zlinear数据采集卡2 小时前
555触摸延时开关深度解析:从电路原理到智能楼道灯应用
单片机·嵌入式硬件
国科安芯5 小时前
国科安芯推出商业航天级抗辐照全双工 RS485/422 收发器 ASC491S2Y
网络·分布式·单片机·架构·安全性测试
czhaii5 小时前
LCD320240间接接口 RA8835控制器 温度MAX6675显示
单片机·嵌入式硬件·硬件工程
破晓单片机5 小时前
030、STM32项目分享:计时充电桩系统
stm32·单片机·嵌入式硬件
森利威尔电子-6 小时前
森利威尔SL3150H |PIN TO PIN 替换 MRDC88-1 10~150V 输入 0.6A 降压电源芯片
单片机·嵌入式硬件·物联网·集成电路·芯片
kebidaixu7 小时前
FreeRTOS 移植到 STM32F407VETX 记录
stm32·单片机
qq_411262427 小时前
硬件是ESP32-P4连接LAN8720A,正常初始化之后,设备DHCP失败
stm32·单片机·fpga开发
SUNNYSPY0018 小时前
BSS138-ASEMI中低压通用MOS管BSS138
单片机