用STM32F1的定时器玩点花的:PWM呼吸灯、编码器测速、输入捕获测频一站式搞定
STM32F1定时器高阶应用实战PWM呼吸灯、编码器测速与输入捕获测频1. 定时器技术概览与项目设计思路STM32F1系列微控制器搭载了功能强大的定时器外设其灵活性和可配置性为嵌入式开发者提供了丰富的设计可能性。本系列实验将聚焦通用定时器TIMx的三种典型应用场景通过实际项目案例深入掌握定时器的高级功能。定时器核心功能模块解析时基单元由16位预分频器(PSC)和16位自动重装载寄存器(ARR)构成决定计数频率和周期输入捕获精确测量脉冲宽度或信号频率输出比较生成PWM波形或驱动数字输出编码器接口直接处理正交编码器信号// 定时器基本配置结构体示例 typedef struct { uint32_t Prescaler; // 预分频值 uint32_t CounterMode; // 计数模式(向上/向下/中央对齐) uint32_t Period; // 自动重载值 uint32_t ClockDivision; // 时钟分频 uint32_t RepetitionCounter; // 重复计数器(高级定时器) } TIM_TimeBaseInitTypeDef;提示STM32F103C8T6包含3个通用定时器(TIM2-TIM4)每个定时器具有4个独立通道可配置为输入捕获或输出比较模式。2. PWM呼吸灯实现与调光技巧2.1 PWM原理与硬件连接脉宽调制(PWM)通过调节占空比实现对平均电压的控制是LED调光的理想方案。呼吸灯效果本质上是占空比按特定规律周期性变化的结果。硬件连接方案LED阳极接VCC阴极通过限流电阻接TIMx_CHy或LED阴极接地阳极通过限流电阻接TIMx_CHy需配置输出极性典型限流电阻值220Ω-1kΩ根据LED参数调整2.2 定时器PWM模式配置void PWM_Init(TIM_TypeDef* TIMx, uint32_t Channel, uint16_t arr, uint16_t psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; // 时基单元配置 TIM_TimeBaseStruct.TIM_Prescaler psc; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_Period arr; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIMx, TIM_TimeBaseStruct); // 输出比较配置 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStruct.TIM_Pulse 0; // 初始占空比0% switch(Channel) { case TIM_Channel_1: TIM_OC1Init(TIMx, TIM_OCInitStruct); TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable); break; // 其他通道配置类似... } TIM_Cmd(TIMx, ENABLE); }2.3 呼吸效果算法实现指数渐变算法提供更符合人眼感知的亮度变化void Breath_LED_Update(TIM_TypeDef* TIMx, uint32_t Channel) { static uint8_t dir 0; static uint16_t val 0; static float factor 1.05; // 渐变系数 if(!dir) { val (uint16_t)(val * factor) 1; if(val TIMx-ARR) { val TIMx-ARR; dir 1; } } else { val (uint16_t)(val / factor); if(val 1) { val 0; dir 0; } } switch(Channel) { case TIM_Channel_1: TIMx-CCR1 val; break; // 其他通道... } }注意在main循环中定期调用Breath_LED_Update()调用间隔影响呼吸节奏。可使用SysTick或另一个定时器实现精确时间控制。3. 编码器接口与电机测速3.1 编码器工作原理与接口配置正交编码器输出两路相位差90°的方波信号通过检测边沿和相位关系判断旋转方向和计数。STM32编码器模式特点支持X2/X4计数模式每个边沿计数自动识别方向TI1/TI2相位关系16位向上/向下计数器void Encoder_Config(TIM_TypeDef* TIMx) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_ICInitTypeDef TIM_ICInitStruct; // 时基配置ARR设为最大值 TIM_TimeBaseStruct.TIM_Prescaler 0; TIM_TimeBaseStruct.TIM_Period 0xFFFF; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIMx, TIM_TimeBaseStruct); // 编码器接口配置 TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); // 输入捕获配置滤波参数根据实际信号质量调整 TIM_ICInitStruct.TIM_Channel TIM_Channel_1; TIM_ICInitStruct.TIM_ICFilter 0x6; TIM_ICInit(TIMx, TIM_ICInitStruct); TIM_ICInitStruct.TIM_Channel TIM_Channel_2; TIM_ICInitStruct.TIM_ICFilter 0x6; TIM_ICInit(TIMx, TIM_ICInitStruct); TIM_Cmd(TIMx, ENABLE); }3.2 速度计算与溢出处理测速算法公式转速(RPM) (ΔCount × 60) / (PPR × 采样周期(秒))其中PPR为编码器每转脉冲数int32_t Get_Speed(TIM_TypeDef* TIMx, uint16_t PPR, float Sample_Period) { static int16_t last_count 0; int16_t current_count TIMx-CNT; int32_t delta (int32_t)(current_count - last_count); // 处理计数器溢出0xFFFF ↔ 0 if(delta 0x7FFF) delta - 0x10000; else if(delta -0x7FFF) delta 0x10000; last_count current_count; return (delta * 60) / (PPR * Sample_Period); }优化技巧使用32位变量存储计数值差定期重置计数器避免长期累积误差添加软件滤波移动平均或卡尔曼滤波4. 输入捕获测频与占空比测量4.1 输入捕获模式配置PWMI模式可同时测量频率和占空比需要配置两个输入捕获通道void InputCapture_Config(TIM_TypeDef* TIMx) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_ICInitTypeDef TIM_ICInitStruct; // 时基配置使用内部时钟 TIM_TimeBaseStruct.TIM_Prescaler 72 - 1; // 1MHz计数频率 TIM_TimeBaseStruct.TIM_Period 0xFFFF; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIMx, TIM_TimeBaseStruct); // 通道1配置上升沿捕获用于周期测量 TIM_ICInitStruct.TIM_Channel TIM_Channel_1; TIM_ICInitStruct.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStruct.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStruct.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStruct.TIM_ICFilter 0x4; TIM_ICInit(TIMx, TIM_ICInitStruct); // 通道2配置下降沿捕获用于占空比测量 TIM_ICInitStruct.TIM_Channel TIM_Channel_2; TIM_ICInitStruct.TIM_ICPolarity TIM_ICPolarity_Falling; TIM_ICInitStruct.TIM_ICSelection TIM_ICSelection_IndirectTI; TIM_ICInit(TIMx, TIM_ICInitStruct); // 从模式配置复位模式 TIM_SelectSlaveMode(TIMx, TIM_SlaveMode_Reset); TIM_SelectInputTrigger(TIMx, TIM_TS_TI1FP1); TIM_Cmd(TIMx, ENABLE); }4.2 频率与占空比计算测频法 vs 测周法对比方法适用场景精度影响因素计算公式测频法高频信号(1kHz)闸门时间精度f N / T测周法低频信号(1kHz)参考时钟精度f f_clk / Nvoid Get_Freq_Duty(TIM_TypeDef* TIMx, float* freq, float* duty) { uint16_t ic1 TIM_GetCapture1(TIMx); // 周期值 uint16_t ic2 TIM_GetCapture2(TIMx); // 高电平时间 // 计算频率假设时钟1MHz *freq 1000000.0f / ic1; // 计算占空比 *duty (float)ic2 / ic1 * 100; }误差优化策略对于高频信号采用输入捕获定时器溢出计数添加数字滤波中值滤波或递推平均自动切换测频/测周法中界频率自适应5. 工程整合与性能优化5.1 资源冲突解决当多个功能共用同一定时器时需注意时基共享所有通道共用同一PSC和ARR中断管理合理设置中断优先级DMA应用高速数据传输时考虑使用DMA推荐资源配置方案功能推荐定时器可用替代方案PWM呼吸灯TIM3_CH1TIM2_CH1/TIM4_CH1编码器接口TIM2TIM3/TIM4输入捕获测频TIM4_CH1TIM2_CH2/TIM3_CH25.2 实时性优化技巧使用定时器硬件自动重装载ARR预装载关键代码使用寄存器直接操作如TIMx-CCR1中断服务函数精简仅做标志位处理主循环采用状态机设计// 高效PWM更新示例直接寄存器操作 #define PWM_SET_DUTY(TIMx, ch, duty) \ do { \ uint32_t ccr (TIMx-ARR * (duty)) / 100; \ switch(ch) { \ case 1: TIMx-CCR1 ccr; break; \ case 2: TIMx-CCR2 ccr; break; \ /* 其他通道... */ \ } \ } while(0)5.3 调试与验证方法调试技巧使用逻辑分析仪捕获PWM波形通过串口实时输出测量数据利用断点检查寄存器状态常见问题排查表现象可能原因解决方案PWM无输出GPIO未重映射/配置错误检查AFIO配置和GPIO模式编码器计数方向相反TI1/TI2相位接反交换A/B相信号或修改极性配置频率测量值跳动信号噪声过大增加输入捕获滤波器参数占空比测量不准边沿检测抖动优化硬件电路添加施密特触发器通过这三个项目的实践开发者可以全面掌握STM32定时器的高级应用技巧。在实际项目中往往需要根据具体需求灵活组合这些功能例如同时使用PWM驱动电机和编码器测速实现闭环控制或者利用输入捕获监测传感器信号并同步生成控制PWM。