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);
}
}
}
编译与烧录
- 使用Keil MDK或STM32CubeIDE创建工程,芯片选择STM32F103C8T6
- 将上述代码复制到main.c中,确保包含标准库头文件路径
- 编译工程,通过ST-Link或串口ISP将生成的hex文件烧录到开发板
验证方法
- 使用信号发生器或另一块开发板输出频率1kHz、占空比50%的方波(即高电平500us)到PA0引脚
- 打开串口调试助手,波特率设为115200
- 观察串口输出,预期打印结果接近500 us(误差在±2us以内,取决于信号源精度和代码响应延迟)
- 改变输入信号占空比,串口打印值应随之线性变化
通过条件:串口持续输出与输入信号高电平时间一致的脉冲宽度值,误差在合理范围内(通常<5us),且测量值稳定不跳变。
注意事项
- 定时器预分频设为71,计数频率为1MHz,计数器每计数1次代表1us,因此测量分辨率1us
- 若输入脉冲宽度超过65535us(约65ms),需要处理计数器溢出中断,本案例未包含该处理
- TIM_OC1PolarityConfig函数在标准外设库中用于动态切换捕获极性
- 输入信号高电平时间应大于中断响应时间(通常几us),否则可能丢失捕获事件