1. STM32G474定时器基础回顾
STM32G474系列微控制器内置了丰富多样的定时器资源,包括高级控制定时器、通用定时器、基本定时器以及高精度定时器(HRTIM)。其中通用定时器因其灵活性和适中的资源占用,成为大多数嵌入式项目的首选。我刚开始接触STM32定时器时,最困惑的就是如何理解那些专业术语——时基单元、预分频器、自动重载寄存器,后来发现用生活中的水龙头比喻就很好理解:预分频器就像调节水流大小的阀门,计数器就像水表读数,自动重载值就是水表转满一圈的刻度值。
通用定时器的核心功能模块可以概括为四个部分:
- 时基单元:包含16位/32位计数器、预分频器和自动重载寄存器
- 时钟源:支持内部时钟、外部触发和内部触发三种模式
- 输入捕获:用于测量外部信号的时间参数
- 输出比较:用于生成特定波形(如PWM)
在实际项目中,我特别喜欢使用STM32CubeMX来配置定时器,它能直观地展示各个参数之间的关系。比如配置1ms定时中断时,只需要输入期望的周期值,工具会自动计算ARR和PSC的最佳组合,避免了手动计算的繁琐和错误。
2. PWM波形生成实战
2.1 PWM基础原理
PWM(脉冲宽度调制)是控制电机速度、LED亮度的核心技术。它的本质是通过调节高电平在一个周期内的占比(占空比)来控制平均功率输出。记得我第一次用PWM调LED亮度时,发现占空比从0%增加到100%,LED并不是线性变亮,这是因为人眼对光强的感知本身就不是线性的。
STM32G474的通用定时器支持多达12路独立PWM输出,每路都可以单独配置频率和占空比。关键寄存器包括:
- TIMx_ARR:决定PWM频率(周期=ARR+1)
- TIMx_CCRx:决定通道的占空比
- TIMx_CCMRx:配置输出模式
2.2 CubeMX配置步骤
- 在Pinout界面启用TIM3,选择Channel1为PWM Generation CH1
- 在Configuration选项卡设置:
- Prescaler = 169(170分频)
- Counter Mode = Up
- Period = 999(ARR值)
- Pulse = 500(初始占空比50%)
- 生成代码后,添加以下关键语句:
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 启动PWM输出 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 300); // 动态修改占空比我在调试四轴飞行器电机时发现,PWM频率选择很关键。太低会导致电机啸叫(典型值应>20kHz),太高又会增加MOS管开关损耗。经过实测,24kHz是个不错的折中点。
2.3 高级PWM技巧
互补PWM在电机驱动中尤为重要,STM32G474的高级定时器支持带死区时间的互补输出。虽然通用定时器不能直接生成互补PWM,但可以通过以下方式模拟:
- 配置两个通用定时器为主从模式
- 从定时器设置为单脉冲模式
- 主定时器更新事件触发从定时器
// 主从定时器配置示例 TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig); TIM_SlaveConfigTypeDef sSlaveConfig = {0}; sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; sSlaveConfig.InputTrigger = TIM_TS_ITR0; HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig);3. 输入捕获技术详解
3.1 输入捕获工作原理
输入捕获功能就像高速拍照的快门,当检测到指定边沿(上升沿/下降沿)时,立即"冻结"当前计数器的值到CCR寄存器。通过记录两个边沿的捕获值,就能计算出脉冲宽度或频率。我在开发超声波测距模块时,就是利用这个原理测量回波时间。
STM32G474的输入捕获有三大亮点:
- 支持4个独立捕获通道
- 最高捕获分辨率可达58.8ns(170MHz时钟)
- 内置滤波器可消除信号抖动
3.2 编码器接口配置
对于电机测速应用,正交编码器接口是最佳选择。CubeMX配置步骤如下:
- 选择TIM2->Combined Channels->Encoder Mode
- 设置Encoder Mode为TI1 and TI2
- 配置IC1和IC2为上升沿触发
- 生成代码后启用编码器接口:
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);实际使用中我遇到过编码器计数方向反了的问题,解决方法很简单:
- 交换TI1和TI2的接线
- 或者修改TIMx_CCER寄存器的CC1P/CC2P极性位
3.3 频率测量方案对比
测量信号频率有三种常用方法,各有优缺点:
| 方法 | 精度 | 适用频率范围 | 资源占用 |
|---|---|---|---|
| 输入捕获 | 高 | 中低频 | 中等 |
| 外部时钟+定时器 | 最高 | 低频 | 高 |
| 中断计数法 | 低 | 高频 | 低 |
在智能车竞赛中,我采用"输入捕获+定时器溢出计数"的混合方案:用输入捕获测量脉冲宽度,同时开启定时器溢出中断统计周期数,这样既能保证高频信号的测量精度,又不会丢失低频信号。
4. 闭环控制系统实现
4.1 系统架构设计
结合PWM生成和输入捕获,可以构建完整的电机闭环控制系统:
- PWM驱动电机转动
- 编码器反馈转速信号
- 输入捕获测量实际转速
- PID算法调节PWM占空比
关键代码结构:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM3) { static uint32_t last = 0; uint32_t current = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t period = current - last; // 计算脉冲周期 last = current; // 转速换算:假设编码器1000线,period单位为us float rpm = 60000000.0f / (1000 * period); pid_update(rpm); // PID算法更新 } }4.2 PID参数整定技巧
调试PID时我总结出几个实用经验:
- 先调P(比例)让系统有基本响应
- 再加D(微分)抑制超调
- 最后加I(积分)消除静差
- 采样周期应为控制周期的1/5~1/10
一个经过验证的PID实现:
typedef struct { float Kp, Ki, Kd; float integral; float prev_error; } PID_Controller; float pid_compute(PID_Controller *pid, float setpoint, float input) { float error = setpoint - input; pid->integral += error; float derivative = error - pid->prev_error; pid->prev_error = error; // 抗积分饱和处理 if(pid->integral > 1000) pid->integral = 1000; else if(pid->integral < -1000) pid->integral = -1000; return pid->Kp*error + pid->Ki*pid->integral + pid->Kd*derivative; }4.3 抗干扰措施
工业现场常见的干扰问题可以通过以下方法解决:
- 信号线使用双绞线并加磁环
- 在GPIO口添加RC滤波(如100Ω+100nF)
- 软件上采用中值滤波算法
#define FILTER_SIZE 5 float median_filter(float new_val) { static float buffer[FILTER_SIZE] = {0}; static uint8_t index = 0; buffer[index++] = new_val; if(index >= FILTER_SIZE) index = 0; // 排序取中值 float temp[FILTER_SIZE]; memcpy(temp, buffer, sizeof(temp)); bubble_sort(temp, FILTER_SIZE); // 实现略 return temp[FILTER_SIZE/2]; }5. 性能优化技巧
5.1 定时器级联技术
当需要超长定时(如1小时)时,可以级联多个定时器。我曾用TIM2作主定时器,TIM3作从定时器实现24小时计时:
- 配置TIM2每1秒产生更新事件
- TIM3设置为从模式,时钟源为ITR1(TIM2)
- TIM3的ARR设为3600-1(1小时)
// 主定时器配置 TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); // 从定时器配置 TIM_SlaveConfigTypeDef sSlaveConfig = {0}; sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; sSlaveConfig.InputTrigger = TIM_TS_ITR1; HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);5.2 DMA加速技巧
频繁的定时器数据搬运会消耗CPU资源,使用DMA可以大幅提升效率。例如用DMA自动更新PWM占空比:
// 配置DMA从内存到TIMx_CCR1 hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE; hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH; HAL_DMA_Init(&hdma_tim1_ch1); // 启动DMA传输 uint16_t pwm_values[100] = {...}; // 预计算的PWM波形 HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)pwm_values, 100);5.3 低功耗优化
在电池供电设备中,定时器的低功耗配置很关键:
- 选择LPTIM低功耗定时器
- 降低时钟频率(HSI16代替HSE)
- 使用停止模式+定时器唤醒
// 进入停止模式前配置唤醒定时器 HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0xFFFF, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 唤醒后重新初始化定时器 HAL_TIM_Base_Start_IT(&htim1);通过以上实战技巧的组合应用,我在多个工业控制项目中实现了精确的电机控制和速度测量。特别是在自动化生产线改造项目中,基于STM32G474的定时器方案将电机控制精度提升到了±0.5RPM,远超客户要求的±2RPM指标。