4. 定时器输入捕获测量脉冲宽度

任务目标

使用STM32定时器的输入捕获功能测量外部信号的脉冲宽度(高电平持续时间),并通过串口将测量结果打印输出。本案例以STM32F103系列为例,使用TIM2的通道1(PA0)作为输入捕获引脚。

实现原理

输入捕获的基本思路:当检测到信号上升沿时,记录当前计数器值;当检测到下降沿时,再次记录计数器值。两次计数值之差乘以定时器时钟周期即为高电平脉冲宽度。通过配置定时器为上升沿捕获和下降沿捕获交替触发,即可完成单次脉冲宽度测量。

硬件连接

  • 信号源输出 → PA0 (TIM2_CH1)
  • STM32与PC之间通过USB转串口连接(默认PA9/PA10,即USART1)
  • 信号源建议使用PWM信号或按键产生的脉冲,频率不超过定时器计数范围

关键完整代码

/* main.c - 定时器输入捕获测量脉冲宽度 */
#include "stm32f10x.h"
#include <stdio.h>

volatile uint32_t rise_time = 0;
volatile uint32_t fall_time = 0;
volatile uint8_t capture_done = 0;
volatile uint8_t edge_flag = 0;  // 0:等待上升沿, 1:等待下降沿

void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

void TIM2_IC_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    TIM_InternalClockConfig(TIM2);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;  // 72MHz/72 = 1MHz, 计数周期1us
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 先配置上升沿捕获
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM2, &TIM_ICInitStructure);
    
    TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
    
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
        
        if (edge_flag == 0) {
            // 上升沿:记录起始时间,切换为下降沿捕获
            rise_time = TIM_GetCapture1(TIM2);
            TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling);
            edge_flag = 1;
        } else {
            // 下降沿:记录结束时间,切换回上升沿捕获
            fall_time = TIM_GetCapture1(TIM2);
            TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising);
            edge_flag = 0;
            capture_done = 1;
        }
    }
}

int fputc(int ch, FILE *f) {
    USART_SendData(USART1, (uint8_t)ch);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    return ch;
}

int main(void) {
    uint32_t pulse_width_us;
    
    USART1_Init();
    TIM2_IC_Init();
    
    printf("脉冲宽度测量开始...\r\n");
    
    while (1) {
        if (capture_done) {
            capture_done = 0;
            // 处理计数器溢出(此处简化,未处理溢出情况)
            if (fall_time >= rise_time) {
                pulse_width_us = fall_time - rise_time;
            } else {
                pulse_width_us = (0xFFFF - rise_time) + fall_time;
            }
            printf("脉冲宽度: %lu us\r\n", pulse_width_us);
        }
    }
}

编译与烧录

  1. 使用Keil MDK或STM32CubeIDE创建工程,芯片选择STM32F103C8T6
  2. 将上述代码复制到main.c中,确保包含标准库头文件路径
  3. 编译工程,通过ST-Link或串口ISP将生成的hex文件烧录到开发板

验证方法

  1. 使用信号发生器或另一块开发板输出频率1kHz、占空比50%的方波(即高电平500us)到PA0引脚
  2. 打开串口调试助手,波特率设为115200
  3. 观察串口输出,预期打印结果接近500 us(误差在±2us以内,取决于信号源精度和代码响应延迟)
  4. 改变输入信号占空比,串口打印值应随之线性变化

通过条件:串口持续输出与输入信号高电平时间一致的脉冲宽度值,误差在合理范围内(通常<5us),且测量值稳定不跳变。

注意事项

  • 定时器预分频设为71,计数频率为1MHz,计数器每计数1次代表1us,因此测量分辨率1us
  • 若输入脉冲宽度超过65535us(约65ms),需要处理计数器溢出中断,本案例未包含该处理
  • TIM_OC1PolarityConfig函数在标准外设库中用于动态切换捕获极性
  • 输入信号高电平时间应大于中断响应时间(通常几us),否则可能丢失捕获事件