STM32F4单通道PWM捕获实战:从配置到中断处理的完整指南(附代码)

张开发
2026/4/16 11:54:14 15 分钟阅读

分享文章

STM32F4单通道PWM捕获实战:从配置到中断处理的完整指南(附代码)
1. STM32F4单通道PWM捕获基础概念第一次接触PWM捕获功能时我完全被那些专业术语搞晕了。后来在实际项目中踩过几次坑才明白PWM捕获本质上就是个信号侦探——它能准确捕捉外部信号的边沿变化时刻。想象一下你在用秒表记录短跑运动员冲过起跑线和终点线的时刻PWM捕获干的就是类似的活儿。STM32F4的定时器模块非常强大每个通用定时器都有4个独立通道。我常用的TIM5是32位定时器特别适合需要长时间测量的场景。PWM捕获有两种典型应用场景一种是测量脉冲宽度比如舵机控制信号另一种是测量信号频率比如转速传感器。去年做无人机项目时我就用TIM5的通道2成功捕获了转速传感器输出的高频PWM信号。与普通IO口检测电平不同PWM捕获有三大优势高精度计时基于定时器时钟分辨率可达微秒级硬件自动记录边沿触发时自动保存计数器值不占用CPU资源灵活触发方式可配置上升沿、下降沿或双边沿触发2. 硬件配置与初始化记得第一次配置PWM捕获时我漏掉了GPIO复用功能设置结果调试了一整天。后来发现STM32的引脚复用是个关键点必须把GPIO映射到对应的定时器通道上。以TIM5通道2PA1引脚为例完整配置流程如下2.1 时钟使能配置RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);这里有个坑TIM5挂在APB1总线上而GPIOA挂在AHB1总线上。如果只开启定时器时钟而忘记开GPIO时钟配置就会失效。2.2 GPIO模式设置GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; // 必须设为复用模式 GPIO_InitStructure.GPIO_Speed GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_DOWN; // 下拉防止悬空干扰 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM5); // 关键映射到TIM52.3 定时器基础配置TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler 84 - 1; // 84分频1MHz计数频率 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 0xFFFFFFFF; // 32位最大值 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM5, TIM_TimeBaseStructure);这里TIM_Period设置为最大值是为了防止在测量长脉冲时溢出。实际项目中可以根据信号特点调整这个值。3. 输入捕获通道配置输入捕获的核心配置都在TIM_ICInitTypeDef结构体中。最近做的一个项目中我需要测量高电平持续时间配置如下3.1 捕获参数设置TIM_ICInitTypeDef TIM5_ICInitStructure; TIM5_ICInitStructure.TIM_Channel TIM_Channel_2; TIM5_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 初始设为上升沿 TIM5_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM5_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; // 不分频 TIM5_ICInitStructure.TIM_ICFilter 0x00; // 不滤波 TIM_ICInit(TIM5, TIM5_ICInitStructure);3.2 中断使能TIM_ITConfig(TIM5, TIM_IT_Update | TIM_IT_CC2, ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel TIM5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM5, ENABLE);提示滤波参数TIM_ICFilter在信号有噪声时特别有用。我曾遇到因电源干扰导致误触发的情况设置适当的滤波值后问题解决。具体值需要根据信号特性调整。4. 中断服务程序实现中断处理是整个PWM捕获最核心的部分。下面这个代码框架我用了不下十个项目稳定可靠4.1 全局变量定义volatile uint32_t TIM5CH2_CAPTURE_STA 0; // 状态寄存器 volatile uint32_t TIM5CH2_CAPTURE_VAL; // 捕获值 #define CAPTURE_FLAG (0x80) // 捕获完成标志 #define RISING_EDGE (0x40) // 上升沿标志4.2 中断服务函数void TIM5_IRQHandler(void) { if((TIM5CH2_CAPTURE_STA CAPTURE_FLAG) 0) { if(TIM_GetITStatus(TIM5, TIM_IT_Update) ! RESET) { if(TIM5CH2_CAPTURE_STA RISING_EDGE) { if((TIM5CH2_CAPTURE_STA 0x3F) 0x3F) // 溢出保护 { TIM5CH2_CAPTURE_STA | CAPTURE_FLAG; TIM5CH2_CAPTURE_VAL 0xFFFFFFFF; } else { TIM5CH2_CAPTURE_STA; } } } if(TIM_GetITStatus(TIM5, TIM_IT_CC2) ! RESET) { if(TIM5CH2_CAPTURE_STA RISING_EDGE) // 下降沿捕获 { TIM5CH2_CAPTURE_STA | CAPTURE_FLAG; TIM5CH2_CAPTURE_VAL TIM_GetCapture2(TIM5); TIM_OC2PolarityConfig(TIM5, TIM_ICPolarity_Rising); // 恢复初始设置 } else // 首次上升沿捕获 { TIM5CH2_CAPTURE_STA 0; TIM5CH2_CAPTURE_VAL 0; TIM5CH2_CAPTURE_STA | RISING_EDGE; TIM_SetCounter(TIM5, 0); TIM_OC2PolarityConfig(TIM5, TIM_ICPolarity_Falling); // 切换为下降沿 } } } TIM_ClearITPendingBit(TIM5, TIM_IT_CC2 | TIM_IT_Update); }这个中断逻辑实现了完整的脉冲宽度测量首次上升沿触发时清零计数器并切换为下降沿捕获下降沿触发时记录计数值并标记完成期间处理定时器溢出情况确保长脉冲也能准确测量5. 测量结果处理与应用在主程序中我们需要解析中断服务程序设置的标志位和捕获值。上周调试电机控制器时我用下面这段代码成功测量了PWM占空比5.1 主循环处理while(1) { if(TIM5CH2_CAPTURE_STA CAPTURE_FLAG) { uint32_t pulse_width (TIM5CH2_CAPTURE_STA 0x3F) * 0xFFFFFFFF; pulse_width TIM5CH2_CAPTURE_VAL; // 单位微秒 // 计算占空比假设周期已知 float duty_cycle (float)pulse_width / pwm_period * 100; printf(Pulse Width: %lu us, Duty Cycle: %.1f%%\r\n, pulse_width, duty_cycle); TIM5CH2_CAPTURE_STA 0; // 准备下一次测量 } // 其他任务... }5.2 常见问题处理在实际项目中我遇到过几个典型问题信号抖动添加适当的滤波参数TIM_ICFilter长脉冲测量使用32位定时器并正确处理溢出多通道同步如果需要同时测量多个信号可以考虑使用定时器的从模式6. 进阶技巧与优化建议经过多个项目的积累我总结出一些实用技巧6.1 双边沿捕获优化如果需要同时测量高电平和低电平时间可以优化中断逻辑// 在捕获中断中增加以下逻辑 if(当前是上升沿捕获) { high_level_start TIM_GetCounter(TIMx); 切换为下降沿捕获; } else { high_level_width TIM_GetCounter(TIMx) - high_level_start; 切换为上升沿捕获; }6.2 DMA传输捕获值对于高频信号可以使用DMA自动传输捕获值到内存DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)TIM5-CCR2; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)CaptureBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize BUFFER_SIZE; DMA_Init(DMA1_Stream0, DMA_InitStructure); TIM_DMACmd(TIM5, TIM_DMA_CC2, ENABLE);6.3 低功耗优化在电池供电设备中可以这样优化// 只在预期有信号时使能定时器 TIM_Cmd(TIM5, DISABLE); // 检测到信号边沿EXTI中断后再开启 TIM_SetCounter(TIM5, 0); TIM_Cmd(TIM5, ENABLE);7. 调试技巧与实战经验调试PWM捕获功能时我常用的方法有逻辑分析仪验证同时抓取输入信号和调试输出分段测试先用固定频率信号测试再测实际信号边界测试特别测试最小/最大脉冲宽度情况最近遇到一个棘手问题在电机启动时捕获值异常。后来发现是电源噪声导致解决方法是在信号线上加100Ω电阻和100nF电容组成低通滤波。另一个实用技巧是使用定时器的溢出中断来检测信号丢失。如果超过预期时间没有捕获到边沿可以判定信号异常if(TIM_GetITStatus(TIM5, TIM_IT_Update)) { timeout_counter; if(timeout_counter MAX_TIMEOUT) { // 信号丢失处理 } }

更多文章