从TB6612到编码器:构建精准电机控制系统的实践指南

张开发
2026/4/11 10:46:32 15 分钟阅读

分享文章

从TB6612到编码器:构建精准电机控制系统的实践指南
1. 为什么需要精准电机控制系统当你给智能小车装上电机接上电源就能转这看起来很简单对吧但实际做项目时会发现这种开环控制根本不够用。比如想让小车以固定速度爬坡或者精确移动到某个位置仅靠通电-转动这种粗暴方式完全无法实现。这就是我们需要构建闭环控制系统的原因。我做过一个自动送货机器人的项目最初用开环控制结果每次走到同一位置能偏差十几厘米。后来加上编码器反馈精度直接提升到±2mm以内。精准控制的核心在于实时感知动态调整通过编码器获取电机实际状态再用算法计算调整量最后通过驱动芯片输出修正信号。整个过程就像开车时不断微调方向盘只不过这里调整的是PWM占空比。TB6612和编码器的组合相当于给电机装上了眼睛和手脚。编码器持续汇报现在转到哪了TB6612则根据指令快速响应。这种组合在机器人、3D打印机、CNC机床等需要位置控制的场景中非常常见。接下来我会用最接地气的方式带你从器件级到系统级完整走通这个流程。2. TB6612电机驱动实战2.1 硬件选型与电路设计第一次用TB6612时我被它的小体积惊到了——只有指甲盖大小却能驱动两个直流电机。相比老旧的L298N它的效率提升不是一点半点。实测驱动12V电机连续工作半小时芯片温度才40℃左右而L298N早就烫得能煎鸡蛋了。接线其实很简单记住这几个关键点VM引脚接电机电源2.5-13.5V我常用18650锂电池供电VCC引脚接3.3V或5V给逻辑电路供电STBY引脚直接接高电平使能芯片PWM引脚接单片机定时器输出建议用10kHz频率这里有个坑要注意PWM频率不能太低否则电机会有可闻噪音。但也不能太高超过100kHz会导致MOS管开关损耗增大。我的经验值是8-15kHz这个甜区。2.2 软件控制逻辑控制电机无非就是方向速度对应到代码里// 初始化GPIO和定时器 void Motor_Init(void) { GPIO_Init(AIN1_PIN, OUTPUT); GPIO_Init(AIN2_PIN, OUTPUT); PWM_Init(PWMA_PIN, 10000); // 10kHz PWM } // 设置电机转向和速度 void Motor_Set(int speed) { if(speed 0) { GPIO_Write(AIN1_PIN, HIGH); GPIO_Write(AIN2_PIN, LOW); } else { GPIO_Write(AIN1_PIN, LOW); GPIO_Write(AIN2_PIN, HIGH); speed -speed; } PWM_SetDuty(PWMA_PIN, speed); }实测发现当PWM占空比低于5%时电机可能无法启动。解决方法是在代码里加个死区补偿if(speed 5 speed ! 0) speed 5;3. 编码器信号采集与处理3.1 编码器工作原理揭秘拆开一个光电编码器你会看到码盘和光电传感器。电机转动时码盘上的栅格会交替阻挡光线产生AB两相脉冲。关键点在于这两路信号的相位差——正转时A相比B相超前90°反转时则滞后。霍尔编码器原理类似只是用磁铁替代了光栅。虽然精度稍低通常每圈几百个脉冲但胜在价格便宜且抗干扰强。我的智能小车在户外跑时就用的霍尔编码器从没出现过信号丢失。3.2 STM32的编码器模式STM32的定时器有个超实用功能——硬件编码器接口。配置起来非常简单void Encoder_Init(TIM_HandleTypeDef *htim) { TIM_Encoder_InitTypeDef sConfig {0}; sConfig.EncoderMode TIM_ENCODERMODE_TI12; sConfig.IC1Polarity TIM_ICPOLARITY_RISING; sConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler TIM_ICPSC_DIV1; sConfig.IC1Filter 6; // 适当滤波防抖动 HAL_TIM_Encoder_Init(htim, sConfig); HAL_TIM_Encoder_Start(htim, TIM_CHANNEL_ALL); }这个配置实现了四倍频计数意味着每收到一个完整的AB相周期计数器会加减4。直接读取CNT寄存器就能得到当前位置int32_t Get_Position(TIM_HandleTypeDef *htim) { return (int32_t)__HAL_TIM_GET_COUNTER(htim); }4. 闭环控制实现4.1 速度计算技巧要计算实时速度最简单的办法是定时采样位置值int32_t last_pos 0; uint32_t last_time 0; float Get_Speed(TIM_HandleTypeDef *htim) { int32_t current_pos Get_Position(htim); uint32_t current_time HAL_GetTick(); float speed (current_pos - last_pos) * 1000.0f / (current_time - last_time) / ENCODER_PPR; // 每转脉冲数 last_pos current_pos; last_time current_time; return speed; }注意这里有个细节速度计算间隔不宜过短否则会受编码器抖动影响。我一般用50ms的采样周期平衡了响应速度和稳定性。4.2 PID控制实战当我把PID算法加到电机控制上时效果立竿见影。分享一个经过实战检验的PID实现typedef struct { float Kp, Ki, Kd; float integral; float last_error; } PID_Controller; float PID_Update(PID_Controller *pid, float error, float dt) { float proportional pid-Kp * error; pid-integral error * dt; float integral pid-Ki * pid-integral; float derivative pid-Kd * (error - pid-last_error) / dt; pid-last_error error; return proportional integral derivative; }调参时记住这个口诀先调P再调I最后调D。具体步骤把Ki和Kd设为零逐渐增大Kp直到系统开始振荡取振荡时Kp值的50%作为初始值慢慢增加Ki消除静差但别加太多否则会超调最后加少量Kd抑制振荡我的小车电机参数最终定为Kp0.8, Ki0.2, Kd0.05供大家参考。5. 系统集成与调试把TB6612和编码器组装到小车上后我遇到了典型的地线干扰问题——电机一转编码器就乱跳。解决方法很简单电机电源和逻辑电源用0Ω电阻隔离所有信号线加磁环编码器线用双绞线用示波器看编码器信号时发现波形边缘有振铃。在AB相线上各加个100Ω电阻串联问题迎刃而解。这些经验都是踩坑踩出来的现在你的项目可以直接避开这些雷区。最后测试闭环效果时可以先用阶跃响应观察系统行为给目标速度一个突变看实际速度是否能快速平稳跟随。我的实测数据显示加入PID后速度调节时间从原来的1.2秒缩短到了0.3秒且超调量小于5%。

更多文章