STM32输入捕获测量高低电平持续时间

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. 编译和下载

  1. 使用Keil MDK或STM32CubeIDE创建工程
  2. 将上述文件添加到工程中
  3. 编译并下载到STM32F103开发板

2. 测试方法

  1. 使用信号发生器产生PWM信号连接到PA0
  2. 打开串口调试助手,设置波特率115200
  3. 观察串口输出的测量结果

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

五、注意事项

  1. 定时器溢出处理:当测量的脉冲宽度超过定时器周期时,需要处理溢出情况
  2. 信号幅度:确保输入信号幅度在STM32的IO电压范围内(0-3.3V)
  3. 信号稳定性:对于不稳定的信号,建议添加硬件滤波(RC滤波电路)
  4. 中断优先级:输入捕获中断应该具有较高优先级,避免丢失边沿
  5. 测量范围:当前配置下,最大可测量约65ms的脉冲宽度(16位定时器)

专注于matlab/simulink,电子电路,编程