STM32定时器实战用CubeMX和HAL库实现PWM测量与LCD显示附完整代码在嵌入式开发中定时器是最常用也最强大的外设之一。无论是简单的延时功能还是复杂的PWM信号生成与捕获定时器都能胜任。对于准备参加蓝桥杯嵌入式组比赛的开发者来说掌握定时器的各种应用场景尤为重要。本文将带你完成一个完整的实战项目利用STM32的定时器输入捕获功能测量外部PWM信号的频率和占空比并通过LCD实时显示测量结果。1. 项目概述与硬件准备这个项目的主要目标是构建一个PWM信号分析仪能够实时测量并显示输入PWM信号的频率和占空比。我们将使用STM32的定时器输入捕获功能来实现这一目标并通过LCD模块直观地展示测量结果。所需硬件组件STM32开发板本文以STM32G431为例LCD显示屏支持SPI或I2C接口PWM信号源可使用另一个定时器生成测试信号杜邦线若干软件工具STM32CubeMX版本6.5.0或更高Keil MDK-ARM或STM32CubeIDESTM32 HAL库在开始之前确保你已经熟悉STM32CubeMX的基本操作和HAL库的基本函数调用方式。如果你是完全新手建议先完成几个简单的GPIO和定时器实验。2. CubeMX工程配置2.1 时钟树配置首先打开CubeMX并创建新工程选择你的STM32型号。进入时钟配置界面根据你的开发板设置系统时钟。对于STM32G431我们可以配置如下SYSCLK: 80MHz APB1 Timer clocks: 80MHz APB2 Timer clocks: 80MHz时钟配置直接影响定时器的精度务必确保正确设置。保存时钟配置后CubeMX会自动生成相应的初始化代码。2.2 定时器配置我们需要配置两个定时器一个用于生成PWM信号作为测试信号源另一个用于捕获输入的PWM信号。PWM生成定时器配置TIM15选择TIM15设置为PWM Generation CH1和CH2Prescaler: 79 (80分频得到1MHz计数频率)Counter Mode: UpPeriod: 199 (200个计数周期对应5kHz PWM)Pulse: 120 (占空比60%)CH2 Pulse: 50 (占空比25%)输入捕获定时器配置TIM2选择TIM2设置为Input Capture direct mode和indirect modePrescaler: 79 (80分频1MHz计数频率)Counter Mode: UpPeriod: 65535 (最大计数值)Slave Mode: Reset ModeTrigger Source: TI1FP1Channel 1: Rising Edge, Direct TIChannel 2: Falling Edge, Indirect TI这种配置实现了PWM输入模式可以同时测量周期和占空比。TIM2会在上升沿复位计数器在下降沿捕获通道2的值高电平时间在下一个上升沿捕获通道1的值整个周期。2.3 LCD接口配置根据你的LCD模块类型配置相应的接口。常见的有SPI接口配置SPI外设和CS、DC、RESET等控制引脚I2C接口配置I2C外设并行接口配置FSMC或直接使用GPIO确保在CubeMX中正确配置了所有必要的引脚并生成初始化代码。3. 代码实现3.1 定时器初始化和启动CubeMX生成的代码已经包含了定时器的基本配置我们只需要添加启动代码void PWM_Input_Init(void) { // 启动PWM生成定时器 HAL_TIM_PWM_Start(htim15, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim15, TIM_CHANNEL_2); // 启动输入捕获定时器 HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_2); // 如果需要可以启动基本定时器用于计时 HAL_TIM_Base_Start_IT(htim1); }3.2 输入捕获中断处理输入捕获的核心逻辑在中断回调函数中实现。我们需要处理两个通道的捕获事件// 全局变量存储测量结果 uint32_t pwm_period_count 0; uint32_t pwm_duty_count 0; float pwm_duty 0.0; uint16_t pwm_freq 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { if(htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { // 通道1捕获上升沿获取周期 pwm_period_count HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) 1; pwm_freq 1000000 / pwm_period_count; // 1MHz计数频率 } else if(htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { // 通道2捕获下降沿获取高电平时间 pwm_duty_count HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) 1; pwm_duty ((float)pwm_duty_count / pwm_period_count) * 100.0; } } }3.3 LCD显示实现LCD显示部分需要根据你使用的具体LCD模块来实现。这里给出一个通用的显示函数框架void Update_LCD_Display(void) { char display_str[32]; // 清屏或局部刷新 LCD_Clear(WHITE); // 显示频率 sprintf(display_str, Freq: %d Hz, pwm_freq); LCD_DisplayString(10, 50, (uint8_t *)display_str, BLACK, WHITE); // 显示占空比 sprintf(display_str, Duty: %.1f %%, pwm_duty); LCD_DisplayString(10, 70, (uint8_t *)display_str, BLACK, WHITE); // 可以添加其他信息如信号质量指示等 }4. 系统集成与调试4.1 主循环实现在主循环中我们需要定期更新LCD显示并可以添加一些调试信息int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); MX_TIM15_Init(); MX_LCD_Init(); PWM_Input_Init(); LCD_Init(); while (1) { Update_LCD_Display(); HAL_Delay(200); // 每200ms更新一次显示 } }4.2 常见问题与调试技巧在实际开发中你可能会遇到以下问题捕获不到信号检查GPIO配置是否正确特别是复用功能确认信号电压电平匹配STM32通常是3.3V使用逻辑分析仪或示波器验证信号是否到达引脚测量结果不准确检查定时器时钟配置是否正确确保预分频器和周期值设置合理对于高频信号考虑使用更高性能的定时器LCD显示异常验证LCD初始化序列是否正确检查数据传输时序是否符合LCD规格要求确保电源稳定背光正常工作调试建议使用STM32的调试功能设置断点观察捕获值在关键位置添加LED指示灯或串口打印调试信息逐步验证每个功能模块先确保PWM生成正确再测试捕获功能5. 进阶优化与扩展5.1 多通道测量如果需要同时测量多个PWM信号可以配置多个定时器或使用一个定时器的多个通道// 配置TIM3为第二个输入捕获通道 HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_2);在回调函数中区分不同的定时器实例进行处理。5.2 数字滤波对于噪声较大的信号可以启用定时器的输入捕获滤波器// 在CubeMX中配置Input Filter参数 sConfigIC.ICFilter 6; // 设置适当的滤波值或者在软件中实现移动平均滤波#define FILTER_SIZE 5 uint32_t freq_buffer[FILTER_SIZE] {0}; uint8_t buffer_index 0; // 在捕获回调中 freq_buffer[buffer_index] pwm_freq; buffer_index (buffer_index 1) % FILTER_SIZE; // 计算平均值 uint32_t avg_freq 0; for(int i0; iFILTER_SIZE; i) { avg_freq freq_buffer[i]; } avg_freq / FILTER_SIZE;5.3 自动量程切换对于宽范围的频率测量可以实现自动量程切换功能void Auto_Range_Adjust(void) { if(pwm_freq 1000) { // 低频模式增加预分频 htim2.Init.Prescaler 799; // 100kHz计数 HAL_TIM_Base_Init(htim2); } else { // 高频模式减少预分频 htim2.Init.Prescaler 79; // 1MHz计数 HAL_TIM_Base_Init(htim2); } HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_2); }6. 完整工程代码结构一个完整的STM32工程通常包含以下关键文件Project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── tim.h │ │ └── lcd.h │ └── Src/ │ ├── main.c │ ├── tim.c │ └── lcd.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32G4xx_HAL_Driver/ └── STM32CubeMX/ └── Project.ioc关键代码文件说明main.c: 包含主循环和系统初始化tim.c: 定时器配置和中断处理lcd.c: LCD驱动和显示函数Project.ioc: CubeMX工程配置文件在蓝桥杯嵌入式比赛中通常会提供基本的工程框架你需要在此基础上添加功能模块。建议将不同功能封装成独立的.c/.h文件保持代码结构清晰。