1. 从频率测量到占空比分析:定时器捕获的进阶玩法
刚接触蓝桥杯嵌入式竞赛时,很多同学对定时器捕获功能的理解停留在基础频率测量阶段。记得我第一次用STM32G4的定时器测量555定时器信号时,看着屏幕上跳动的频率数值还挺有成就感,直到发现评委评分表里还有"占空比测量精度"这一项——原来真正的挑战才刚刚开始。
定时器捕获功能就像电子工程师的"听诊器",不仅能听出心跳次数(频率),还要能分析心跳强弱(占空比)。在G4系列开发板上,两个555定时器通过旋钮可调节输出信号,这为我们提供了绝佳的实战环境。实际开发中会遇到不少坑,比如我最初用整型变量计算占空比,结果永远显示0%,调试半天才发现是类型转换问题。这种细节往往决定比赛成败,接下来我们就拆解这个"频率与占空比双测量"的完整实现方案。
2. 硬件连接与CubeMX配置要点
2.1 开发板信号链路解析
蓝桥杯嵌入式开发板的精妙之处在于其信号通路设计。板载两个NE555定时器,分别通过R39和R40旋钮调节输出特性。具体来看:
- 第一个555输出经跳线帽连接PA15,对应TIM2_CH1通道
- 第二个555输出连接PB4,对应TIM16_CH1通道
这里有个实用技巧:用杜邦线将PA7(PWM输出)与PB4相连,PA6与PA15相连,就能用同一套代码测量自己生成的PWM信号,方便验证算法准确性。我在省赛前夜发现这个技巧,连夜改进了测试方案。
2.2 CubeMX参数配置实战
打开CubeMX后,关键配置分三步走:
定时器基础配置:
- 时钟源选择内部时钟
- 分频系数设为79(80MHz主频分频后得1MHz时基)
- 重装载值保持默认0xFFFF即可
- 切记开启捕获通道中断
双通道协同配置(以TIM2为例):
// 直接模式通道(测量周期) htim2.Instance->CCMR1 |= TIM_CCMR1_CC1S_0; // 输入映射到TI1 // 间接模式通道(测量高电平时间) htim2.Instance->CCMR1 |= TIM_CCMR1_CC2S_1; // 输入映射到TI1 htim2.Instance->CCER |= TIM_CCER_CC2P; // 下降沿触发中断优先级设置: 建议将定时器中断优先级设为2-3级,避免与其他外设冲突。我曾遇到USB中断抢占导致捕获值丢失的情况,调整优先级后问题立解。
3. 双通道捕获的代码实现细节
3.1 回调函数编写技巧
在stm32g4xx_hal_tim.h中定位到2550行附近的HAL_TIM_IC_CaptureCallback,我们需要实现一个增强版回调:
volatile float ccrl_val1a, ccrl_val1b; // 必须用float! volatile uint32_t frq1, frq2; volatile float duty1, duty2; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { ccrl_val1a = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 周期值 ccrl_val1b = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 高电平时间 __HAL_TIM_SetCounter(htim, 0); // 频率计算:1MHz时钟源/计数值 frq1 = 1000000 / ccrl_val1a; // 占空比计算关键点:必须先做浮点除法! duty1 = (ccrl_val1b / ccrl_val1a) * 100; HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); } } // TIM16处理逻辑(略) }3.2 变量类型的血泪教训
初版代码我用了unsigned int存储捕获值,结果占空比永远为0。原因在于:
- 当
ccrl_val1b < ccrl_val1a时,整数除法结果为0 - 解决方案有两种:
- 强制类型转换:
(float)ccrl_val1b / ccrl_val1a - 直接声明为float类型(推荐)
- 强制类型转换:
3.3 主函数启动逻辑
在main.c中需要正确启动捕获功能:
// 启动TIM2双通道 HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); // TIM16单通道(略)4. 调试技巧与性能优化
4.1 信号质量诊断方案
遇到测量异常时,建议按以下步骤排查:
- 用示波器观察原始信号波形
- 检查GPIO复用配置是否正确
- 在中断入口处设置断点,观察原始捕获值
- 添加防抖处理(适合低频信号):
// 简单的软件防抖 if(abs(ccrl_val1a - last_val) > 100) { ccrl_val1a = last_val; // 过滤突变值 }4.2 实时性优化策略
在高频信号测量时(>10kHz),建议:
- 关闭不必要的调试输出
- 将计算任务移出中断,仅保存原始值
- 使用DMA传输捕获值(G4系列支持)
// DMA配置示例(以TIM2为例) HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t*)&ccr_buffer, 2);4.3 精度提升技巧
要获得更精确的测量结果:
- 使用定时器从模式(Reset Mode)
- 开启输入滤波(TIM_CCMRx寄存器中的ICxF位)
- 多次测量取平均值:
#define SAMPLE_NUM 5 uint32_t sum = 0; for(int i=0; i<SAMPLE_NUM; i++) { sum += ccrl_val1a; while(!new_data_flag); // 等待新数据 new_data_flag = 0; } frq1 = 1000000 / (sum / SAMPLE_NUM);5. 竞赛实战经验分享
去年指导的学生在国赛遇到一个刁钻问题:需要同时测量两路PWM的频率和占空比,还要实时显示波形趋势。我们的解决方案是:
- 用TIM2处理主信号(双通道模式)
- TIM16配合DMA做第二路测量
- 在LCD绘制界面时,采用"原始值缓存+后台计算"的方式
关键代码如下:
// 双缓冲设计 typedef struct { float freq[2]; float duty[2]; uint8_t updated; } MeasureData; MeasureData buf[2]; uint8_t current_buf = 0; // 在回调中仅更新数据 void HAL_TIM_IC_CaptureCallback(...) { buf[current_buf].freq[0] = 1000000 / ccrl_val1a; buf[current_buf].duty[0] = (ccrl_val1b / ccrl_val1a) * 100; buf[current_buf].updated = 1; } // 主循环中处理显示 void MainTask() { if(buf[!current_buf].updated) { DrawWaveform(buf[!current_buf]); current_buf ^= 1; // 切换缓冲区 } }这种设计将耗时运算与界面刷新分离,保证了系统实时性,最终帮助团队获得了该赛题的最高分。