STM32输入捕获测量高低电平持续时间
STM32F103输入捕获程序,用于测量定时器产生的高低电平持续时间。这个程序使用TIM2的通道1(PA0)作为输入捕获引脚,测量外部信号的脉冲宽度。
一、硬件连接
外部信号源 → PA0 (TIM2_CH1)
二、代码实现
1. 头文件 input_capture.h
#ifndef __INPUT_CAPTURE_H
#define __INPUT_CAPTURE_H
#include "stm32f10x.h"
#include <stdio.h>
// 输入捕获配置
#define IC_TIM_PERIOD 0xFFFF // 定时器周期
#define IC_TIM_PRESCALER 72 // 预分频器(72MHz/72 = 1MHz,1us计数)
#define IC_CHANNEL TIM_Channel_1
#define IC_GPIO_PORT GPIOA
#define IC_GPIO_PIN GPIO_Pin_0
// 测量状态
typedef enum {
IC_IDLE = 0, // 空闲状态
IC_WAIT_RISING, // 等待上升沿
IC_WAIT_FALLING, // 等待下降沿
IC_MEASURED // 测量完成
} IC_State;
// 测量结果结构体
typedef struct {
uint32_t high_time_us; // 高电平时间(微秒)
uint32_t low_time_us; // 低电平时间(微秒)
uint32_t period_us; // 周期(微秒)
uint32_t frequency_hz; // 频率(Hz)
uint32_t pulse_count; // 脉冲计数
IC_State state; // 当前状态
uint8_t overflow_count; // 定时器溢出次数
} IC_Measurement;
// 全局变量声明
extern volatile IC_Measurement ic_measure;
extern volatile uint8_t ic_new_data_available;
// 函数声明
void IC_Init(void);
void IC_Start(void);
void IC_Stop(void);
void IC_Reset(void);
void IC_IRQHandler(void);
void IC_CalculateResults(void);
void IC_PrintResults(void);
#endif /* __INPUT_CAPTURE_H */
2. 源文件 input_capture.c
#include "input_capture.h"
// 全局变量
volatile IC_Measurement ic_measure = {0};
volatile uint8_t ic_new_data_available = 0;
// 私有变量
static uint32_t rising_edge_value = 0;
static uint32_t falling_edge_value = 0;
static uint32_t last_rising_edge = 0;
static uint8_t measurement_started = 0;
/**
* @brief 初始化输入捕获
*/
void IC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 配置GPIO
GPIO_InitStructure.GPIO_Pin = IC_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IC_GPIO_PORT, &GPIO_InitStructure);
// 3. 配置定时器基础设置
TIM_TimeBaseStructure.TIM_Period = IC_TIM_PERIOD;
TIM_TimeBaseStructure.TIM_Prescaler = IC_TIM_PRESCALER - 1; // 72分频,1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置输入捕获
TIM_ICInitStructure.TIM_Channel = IC_CHANNEL;
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);
// 5. 配置中断
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 6. 使能更新中断和捕获中断
TIM_ITConfig(TIM2, TIM_IT_Update | TIM_IT_CC1, ENABLE);
// 7. 初始化测量结构体
ic_measure.state = IC_IDLE;
ic_measure.high_time_us = 0;
ic_measure.low_time_us = 0;
ic_measure.period_us = 0;
ic_measure.frequency_hz = 0;
ic_measure.pulse_count = 0;
ic_measure.overflow_count = 0;
// 8. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
/**
* @brief 启动输入捕获
*/
void IC_Start(void)
{
ic_measure.state = IC_WAIT_RISING;
measurement_started = 0;
ic_new_data_available = 0;
}
/**
* @brief 停止输入捕获
*/
void IC_Stop(void)
{
ic_measure.state = IC_IDLE;
TIM_Cmd(TIM2, DISABLE);
}
/**
* @brief 重置测量
*/
void IC_Reset(void)
{
ic_measure.high_time_us = 0;
ic_measure.low_time_us = 0;
ic_measure.period_us = 0;
ic_measure.frequency_hz = 0;
ic_measure.pulse_count = 0;
ic_measure.overflow_count = 0;
rising_edge_value = 0;
falling_edge_value = 0;
last_rising_edge = 0;
measurement_started = 0;
ic_new_data_available = 0;
}
/**
* @brief 输入捕获中断处理函数
*/
void IC_IRQHandler(void)
{
// 检查是否是捕获中断
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
uint16_t capture_value = TIM_GetCapture1(TIM2);
switch (ic_measure.state)
{
case IC_WAIT_RISING:
// 捕获到上升沿
rising_edge_value = capture_value;
last_rising_edge = rising_edge_value;
ic_measure.state = IC_WAIT_FALLING;
measurement_started = 1;
// 切换为下降沿捕获
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling);
break;
case IC_WAIT_FALLING:
// 捕获到下降沿
falling_edge_value = capture_value;
// 计算高电平时间
if (falling_edge_value >= rising_edge_value)
{
ic_measure.high_time_us = falling_edge_value - rising_edge_value;
}
else
{
// 发生了定时器溢出
ic_measure.high_time_us = (IC_TIM_PERIOD - rising_edge_value) + falling_edge_value;
}
ic_measure.state = IC_WAIT_RISING;
ic_measure.pulse_count++;
// 切换回上升沿捕获
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising);
// 如果有新的上升沿,计算周期和低电平时间
if (measurement_started && last_rising_edge != 0)
{
uint32_t current_rising = capture_value;
uint32_t period;
if (current_rising >= last_rising_edge)
{
period = current_rising - last_rising_edge;
}
else
{
period = (IC_TIM_PERIOD - last_rising_edge) + current_rising;
}
ic_measure.period_us = period;
ic_measure.low_time_us = period - ic_measure.high_time_us;
// 计算频率
if (period > 0)
{
ic_measure.frequency_hz = 1000000 / period; // 1MHz时钟
}
ic_new_data_available = 1;
}
last_rising_edge = capture_value;
break;
default:
break;
}
// 清除捕获中断标志
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
// 检查是否是更新中断(定时器溢出)
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
ic_measure.overflow_count++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
/**
* @brief 计算测量结果
*/
void IC_CalculateResults(void)
{
// 这里可以添加额外的计算逻辑
// 例如:平均值、最大值、最小值等
}
/**
* @brief 打印测量结果
*/
void IC_PrintResults(void)
{
if (ic_new_data_available)
{
printf("===== 输入捕获测量结果 =====\n");
printf("高电平时间: %lu us\n", ic_measure.high_time_us);
printf("低电平时间: %lu us\n", ic_measure.low_time_us);
printf("周期: %lu us\n", ic_measure.period_us);
printf("频率: %lu Hz\n", ic_measure.frequency_hz);
printf("脉冲计数: %lu\n", ic_measure.pulse_count);
printf("定时器溢出次数: %u\n", ic_measure.overflow_count);
printf("===========================\n\n");
ic_new_data_available = 0;
}
}
3. 主程序 main.c
#include "stm32f10x.h"
#include "input_capture.h"
#include "usart.h"
#include "delay.h"
#include <stdio.h>
// 重定向printf到串口
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
/**
* @brief 系统时钟初始化
*/
void SystemClock_Init(void)
{
ErrorStatus HSEStartUpStatus;
// 使能HSE
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS)
{
// 设置PLL倍频系数为9倍,8MHz * 9 = 72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 等待PLL就绪
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 设置系统时钟源为PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟源切换到PLL
while (RCC_GetSYSCLKSource() != 0x08);
}
}
/**
* @brief 主函数
*/
int main(void)
{
// 系统初始化
SystemClock_Init();
Delay_Init();
USART1_Init(115200);
printf("STM32输入捕获测量高低电平时间\r\n");
printf("系统时钟: 72MHz\r\n");
printf("定时器时钟: 1MHz (1us计数)\r\n");
printf("输入引脚: PA0 (TIM2_CH1)\r\n\n");
// 初始化输入捕获
IC_Init();
IC_Start();
printf("开始测量...\r\n");
printf("请连接信号到PA0引脚\r\n\n");
while (1)
{
// 打印测量结果
IC_PrintResults();
// 每500ms打印一次
Delay_ms(500);
// 每10秒重置一次计数器
static uint32_t reset_counter = 0;
if (++reset_counter >= 20) // 20 * 500ms = 10s
{
reset_counter = 0;
printf("重置计数器...\r\n");
IC_Reset();
}
}
}
/**
* @brief TIM2中断处理函数
*/
void TIM2_IRQHandler(void)
{
IC_IRQHandler();
}
4. 串口初始化 usart.c
#include "usart.h"
#include "stm32f10x.h"
/**
* @brief 初始化USART1
* @param baudrate 波特率
*/
void USART1_Init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置TX引脚 (PA9)
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);
// 配置RX引脚 (PA10)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART参数
USART_InitStructure.USART_BaudRate = baudrate;
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_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
5. 延时函数 delay.c
#include "delay.h"
#include "stm32f10x.h"
static uint32_t sys_tick_count = 0;
/**
* @brief 初始化延时函数
*/
void Delay_Init(void)
{
// 配置SysTick定时器,1ms中断一次
if (SysTick_Config(SystemCoreClock / 1000))
{
while (1); // 配置失败,死循环
}
}
/**
* @brief 毫秒级延时
* @param ms 延时毫秒数
*/
void Delay_ms(uint32_t ms)
{
uint32_t target = sys_tick_count + ms;
while (sys_tick_count < target);
}
/**
* @brief 微秒级延时
* @param us 延时微秒数
*/
void Delay_us(uint32_t us)
{
uint32_t ticks = us * (SystemCoreClock / 1000000);
uint32_t start = SysTick->VAL;
uint32_t end = start;
while (1)
{
end = SysTick->VAL;
if (end < start)
{
if ((start - end) >= ticks)
break;
}
else
{
if (((SysTick->LOAD - end) + start) >= ticks)
break;
}
}
}
/**
* @brief SysTick中断处理函数
*/
void SysTick_Handler(void)
{
sys_tick_count++;
}
三、使用说明
1. 编译和下载
- 使用Keil MDK或STM32CubeIDE创建工程
- 将上述文件添加到工程中
- 编译并下载到STM32F103开发板
2. 测试方法
- 使用信号发生器产生PWM信号连接到PA0
- 打开串口调试助手,设置波特率115200
- 观察串口输出的测量结果
3. 示例输出
STM32输入捕获测量高低电平时间
系统时钟: 72MHz
定时器时钟: 1MHz (1us计数)
输入引脚: PA0 (TIM2_CH1)
开始测量...
请连接信号到PA0引脚
===== 输入捕获测量结果 =====
高电平时间: 500 us
低电平时间: 1500 us
周期: 2000 us
频率: 500 Hz
脉冲计数: 25
定时器溢出次数: 0
===========================
===== 输入捕获测量结果 =====
高电平时间: 750 us
低电平时间: 1250 us
周期: 2000 us
频率: 500 Hz
脉冲计数: 26
定时器溢出次数: 0
===========================
四、高级功能扩展
1. 多通道输入捕获
// 支持多个输入捕获通道
#define IC_CHANNEL_1 TIM_Channel_1
#define IC_CHANNEL_2 TIM_Channel_2
#define IC_CHANNEL_3 TIM_Channel_3
#define IC_CHANNEL_4 TIM_Channel_4
// 为每个通道创建独立的结构体
typedef struct {
IC_Measurement ch1;
IC_Measurement ch2;
IC_Measurement ch3;
IC_Measurement ch4;
} MultiChannelMeasurement;
2. 滤波和去抖动
// 添加软件滤波
#define FILTER_WINDOW_SIZE 5
static uint32_t filter_buffer[FILTER_WINDOW_SIZE] = {0};
static uint8_t filter_index = 0;
uint32_t ApplyMedianFilter(uint32_t new_value)
{
filter_buffer[filter_index] = new_value;
filter_index = (filter_index + 1) % FILTER_WINDOW_SIZE;
// 冒泡排序取中值
uint32_t sorted[FILTER_WINDOW_SIZE];
memcpy(sorted, filter_buffer, sizeof(sorted));
for (int i = 0; i < FILTER_WINDOW_SIZE - 1; i++)
{
for (int j = 0; j < FILTER_WINDOW_SIZE - i - 1; j++)
{
if (sorted[j] > sorted[j + 1])
{
uint32_t temp = sorted[j];
sorted[j] = sorted[j + 1];
sorted[j + 1] = temp;
}
}
}
return sorted[FILTER_WINDOW_SIZE / 2];
}
3. DMA传输
// 使用DMA自动传输捕获数据
void IC_DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel5); // TIM2_CH1对应DMA1_Channel5
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)capture_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = CAPTURE_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);
TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);
}
4. 频率和占空比计算
// 更精确的频率和占空比计算
void CalculateFrequencyAndDutyCycle(IC_Measurement *meas)
{
if (meas->period_us > 0)
{
meas->frequency_hz = 1000000 / meas->period_us; // 1MHz时钟
if (meas->period_us > 0)
{
meas->duty_cycle_percent = (meas->high_time_us * 100) / meas->period_us;
}
}
}
参考代码 利用STM32的输入捕获通道,捕获定时器产生的高低电平持续的时间 www.youwenfan.com/contentcnv/72001.html
五、注意事项
- 定时器溢出处理:当测量的脉冲宽度超过定时器周期时,需要处理溢出情况
- 信号幅度:确保输入信号幅度在STM32的IO电压范围内(0-3.3V)
- 信号稳定性:对于不稳定的信号,建议添加硬件滤波(RC滤波电路)
- 中断优先级:输入捕获中断应该具有较高优先级,避免丢失边沿
- 测量范围:当前配置下,最大可测量约65ms的脉冲宽度(16位定时器)