1. 硬件原理图分析
拿到开发板第一件事就是看懂原理图。这次我们要测量的是XL555芯片生成的两路PWM信号,分别连接到STM32的PA15和PB4引脚。这两个引脚可不是随便选的,它们都支持定时器的输入捕获功能。
PA15对应的是TIM2_CH1,PB4对应的是TIM3_CH1。这里有个小细节要注意:PA15默认是JTAG的JTDI功能,使用前需要先禁用JTAG。我在第一次调试时就栽在这个坑里,死活捕获不到信号,后来才发现是复用功能没配置对。
555芯片的电路设计也有讲究。典型应用中,频率由RC电路决定,计算公式是f=1.44/((R1+2R2)*C)。比赛时建议准备几个不同阻值的电阻,方便快速调整输出频率范围。实测发现,当频率超过10kHz时,软件计算可能会遇到溢出问题,这点后面写代码时要特别注意。
2. CubeMX工程配置
2.1 基础工程设置
打开CubeMX新建工程,选择对应型号的STM32芯片。我习惯先配置时钟树,把HSE时钟源勾选上,系统时钟设为72MHz。调试接口选择SWD模式,这样既节省引脚又方便调试。
定时器时钟要特别注意,APB1总线上的定时器时钟默认是36MHz,如果直接使用这个频率,输入捕获的测量精度会受限。建议在时钟树配置里把APB1的预分频设为2,这样定时器时钟就是72MHz,测量分辨率直接翻倍。
2.2 定时器参数配置
TIM3的配置最为关键:
- 时钟源选择内部时钟
- Channel1设为输入捕获直接模式
- Slave Mode选择Reset Mode
- Trigger Source选TI1FP1
- 预分频设为71,这样计数器时钟就是1MHz(72MHz/(71+1))
- 自动重装载值设为65535
- Channel2设为输入捕获间接模式,边沿选择下降沿
- 别忘了勾选定时器中断
TIM2的配置与TIM3类似,只是引脚对应PA15。两个定时器的中断优先级建议都设为3,确保不会互相抢占导致数据错乱。
3. 核心代码实现
3.1 变量定义与初始化
在main.c的USER CODE BEGIN PV区域定义测量变量:
uint16_t PWM1_T_Count, PWM2_T_Count; // 周期计数值 uint16_t PWM1_D_Count, PWM2_D_Count; // 高电平计数值 float PWM1_Duty, PWM2_Duty; // 占空比初始化部分放在USER CODE BEGIN 2区域:
HAL_TIM_Base_Start(&htim2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_Base_Start(&htim3); HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);3.2 中断回调函数
这是整个项目的核心算法所在。在stm32f1xx_it.c中找到中断回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance==TIM2) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { PWM2_T_Count = __HAL_TIM_GET_COMPARE(htim,TIM_CHANNEL_1)+1; PWM2_Duty = (float)PWM2_D_Count/PWM2_T_Count; } else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { PWM2_D_Count = __HAL_TIM_GET_COMPARE(htim,TIM_CHANNEL_2)+1; } } if(htim->Instance==TIM3) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { PWM1_T_Count = __HAL_TIM_GET_COMPARE(htim,TIM_CHANNEL_1)+1; PWM1_Duty = (float)PWM1_D_Count/PWM1_T_Count; } else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { PWM1_D_Count = __HAL_TIM_GET_COMPARE(htim,TIM_CHANNEL_2)+1; } } }这里有个优化技巧:原始代码直接使用HAL_TIM_ReadCapturedValue,实测发现改用__HAL_TIM_GET_COMPARE宏能减少约20%的中断处理时间。在测量高频信号时,这个优化能显著提高稳定性。
4. LCD显示实现
4.1 数据显示格式
在main函数的while循环中添加显示代码:
sprintf((char *)Lcd_Disp_String, "PWM1:%05dHz %4.1f%%", (unsigned int)(1000000/PWM1_T_Count), PWM1_Duty*100); LCD_DisplayStringLine(Line8, Lcd_Disp_String); sprintf((char *)Lcd_Disp_String, "PWM2:%05dHz %4.1f%%", (unsigned int)(1000000/PWM2_T_Count), PWM2_Duty*100); LCD_DisplayStringLine(Line9, Lcd_Disp_String);显示格式做了三点优化:
- 频率显示固定5位数字,避免数值跳动
- 占空比保留1位小数
- 添加了单位标识,提高可读性
4.2 防抖处理
实测发现当PWM频率较低时,LCD刷新会导致显示闪烁。解决方法是在sprintf前添加判断:
if(abs(1000000/PWM1_T_Count - last_freq1) > 5) { last_freq1 = 1000000/PWM1_T_Count; // 更新显示代码 }这个阈值5可以根据实际测量需求调整,既能过滤抖动又不会影响实时性。
5. 调试技巧与常见问题
5.1 信号捕获失败排查
如果始终捕获不到信号,建议按以下步骤排查:
- 先用示波器确认555芯片确实输出了PWM
- 检查CubeMX中引脚复用配置是否正确
- 确认定时器时钟使能且分频配置合理
- 测量信号电压是否在STM32识别范围内(最好3.3V)
- 检查中断优先级是否冲突
5.2 精度优化方法
要提高测量精度,可以尝试:
- 提高定时器时钟频率(最大72MHz)
- 使用定时器的输入滤波功能(适合噪声较大的环境)
- 对连续10次测量结果取平均值
- 在中断回调中添加时间戳校验,避免丢失边沿
我在实际测试中发现,当PWM频率超过50kHz时,建议将定时器预分频设为0(即72MHz时钟),这样可以获得1us的时间分辨率。不过要注意自动重装载值不能太小,否则会频繁溢出。