用STM32的TIM2和TIM3搞定JGB37-520电机:PWM调速与编码器测速保姆级代码解析
STM32双定时器驱动JGB37-520电机全流程实战:从PWM配置到转速闭环的工程实现
在智能小车、机械臂等嵌入式运动控制项目中,直流电机驱动是最基础却最容易踩坑的环节。很多开发者虽然能快速搭建起PWM调速系统,却在编码器测速、定时器资源分配等细节上反复调试。本文将基于STM32F1的TIM2和TIM3定时器,完整呈现JGB37-520电机的驱动方案,重点解析那些开发文档不会告诉你的实战经验。
1. 硬件架构设计与定时器选型
JGB37-520作为一款带霍尔编码器的减速电机,其控制需要同时解决两个核心问题:PWM驱动信号生成和转速反馈采集。在STM32的资源规划中,我们选择:
- TIM2:高级定时器,用于生成PWM驱动信号
- TIM3:通用定时器,配置为编码器接口模式
这种分配方案的优势在于:
- 高级定时器的互补输出特性为后续H桥驱动留出扩展空间
- 编码器接口需要定时器支持正交解码模式
- 两个定时器分属不同APB总线,可降低总线负载压力
实际项目中遇到过因定时器选择不当导致的PWM频率受限问题:若使用TIM4等没有重映射功能的定时器,当需要更高频率时可能受限于引脚复用冲突。
2. PWM驱动模块深度配置
电机控制的第一个关键点是PWM信号的精确生成。以下是TIM2的初始化代码核心片段:
// TIM2 PWM初始化关键参数 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 100 - 1; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 36 - 1; // 预分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // PWM通道配置 TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC3Init(TIM2, &TIM_OCInitStructure);参数选择背后的工程考量:
| 参数 | 计算依据 | 典型值 |
|---|---|---|
| TIM_Prescaler | 72MHz/(36*100) = 20kHz | 36-1 |
| TIM_Period | 决定PWM分辨率(100步进) | 100-1 |
| TIM_Pulse | 占空比= Pulse/(Period+1) | 0~100 |
调试时发现,电机在低频PWM下会出现啸叫现象。通过逻辑分析仪捕获波形后,将频率提升到20kHz以上即可消除。但要注意:
- 频率过高会导致MOS管开关损耗增加
- 需平衡电机的电气特性和驱动电路性能
3. 编码器接口的陷阱与优化
JGB37-520电机内置的霍尔编码器每转输出11个脉冲,通过TIM3的编码器接口模式可实现四倍频:
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);转速计算的关键代码:
int16_t Get_Speed(void) { static uint32_t last_count = 0; uint32_t current_count = TIM_GetCounter(TIM3); int32_t diff = current_count - last_count; last_count = current_count; // 每转44个计数(11PPR*4), 采样周期1s return diff * 60 / 44; // RPM }常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 转速读数跳变 | 信号抖动 | 调整滤波器参数(TIM_ICFilter) |
| 反向旋转计数错误 | 相位接反 | 交换A/B相接线 |
| 高速时计数丢失 | 定时器溢出 | 改用32位定时器或缩短采样周期 |
曾遇到过一个隐蔽的BUG:当电机快速正反转切换时,计数器会出现累计误差。最终发现需要在每次读取后立即清零计数器:
int16_t Encoder_Get(void) { int16_t temp = TIM_GetCounter(TIM3); TIM_SetCounter(TIM3, 0); // 关键步骤! return temp; }4. 系统整合与实时性保障
当PWM生成和编码器采集同时进行时,需要特别注意定时器中断的优先级管理。建议配置:
NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // PWM定时器 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // 编码器定时器 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_Init(&NVIC_InitStructure);在电机控制循环中,推荐采用状态机模式避免阻塞:
typedef enum { MOTOR_IDLE, MOTOR_ACCEL, MOTOR_STEADY, MOTOR_BRAKE } MotorState; void Motor_Control_Loop(void) { static MotorState state = MOTOR_IDLE; int16_t actual_speed = Get_Speed(); switch(state) { case MOTOR_IDLE: if(target_speed > 0) state = MOTOR_ACCEL; break; case MOTOR_ACCEL: PWM_Set(IncreaseDuty()); if(abs(actual_speed - target_speed) < 5) state = MOTOR_STEADY; break; // 其他状态处理... } }5. 调试技巧与性能优化
使用示波器调试时的几个关键检测点:
- PWM输出引脚:观察占空比变化是否平滑
- 编码器A/B相:确认正交信号质量
- 电机两端电压:检查是否有异常毛刺
对于追求更高性能的场景,可以考虑:
- 使用DMA传输编码器计数值
- 采用PID算法闭环控制
- 启用定时器的刹车功能实现紧急停止
在最近的一个AGV项目中,通过将PWM频率提升到50kHz并优化采样算法,使转速控制精度达到了±2RPM。关键点在于:
- 精确校准编码器每转脉冲数
- 使用滑动窗口滤波处理转速数据
- 动态调整PWM更新速率
