从零搭建差动底盘:Arduino与TB6612的电机同步实战指南
当你第一次尝试搭建差动底盘机器人时,是否遇到过这样的场景:明明发送了相同的速度指令,机器人却像喝醉酒一样原地转圈或跑偏?这不是你的代码写错了,而是差动底盘最常见的电机同步问题在作祟。本文将用最直白的语言,带你从硬件接线到代码调试,彻底解决这个让无数初学者抓狂的难题。
1. 差动底盘同步问题的根源剖析
差动底盘由两个驱动轮和两个万向轮组成,理论上左右轮速度相同时应直线行驶。但现实中,三大因素会导致运动偏差:
- 电机特性差异:即使是同型号电机,KV值和内阻也存在±10%的差异
- 供电不均衡:TB6612双路驱动可能存在电压差
- 机械摩擦不均:轮子与地面的接触摩擦力不同
实测数据表明,在3.7V供电下,两个标称相同的TT电机空载转速可能相差15-20RPM。这就是为什么直接给相同PWM值会导致明显偏航。
提示:用手机慢动作视频拍摄旋转的轮子,可以直观比较两轮实际转速差异
2. 硬件配置与关键参数测量
2.1 必备组件清单
- Arduino Uno开发板
- TB6612FNG电机驱动模块
- 带编码器的直流减速电机(建议6V/200RPM)
- 18650电池组(两节串联,带保护板)
- 万用表(测量实际电压)
2.2 关键参数实测方法
在编写控制代码前,需要先获取这些基础参数:
| 参数名称 | 测量方法 | 典型值示例 |
|---|---|---|
| 轮子直径 | 直尺测量轮胎外缘 | 65mm |
| 轴距(WheelSpa) | 两驱动轮中心点距离 | 120mm |
| 电机空载RPM | 满占空比PWM驱动,用编码器计数 | 左:185RPM 右:198RPM |
| 减速比 | 查看电机规格书或旋转输出轴计数 | 1:48 |
// 编码器测速示例代码 volatile long encoderCount = 0; void setup() { attachInterrupt(digitalPinToInterrupt(2), countPulse, RISING); } void countPulse() { encoderCount++; }3. 基础速度控制实现
3.1 TB6612标准接线方案
TB6612的每路驱动需要三个控制信号:
左电机: PWMA -> Arduino D5 AIN1 -> D6 AIN2 -> D7 右电机: PWMB -> D10 BIN1 -> D8 BIN2 -> D9注意:VM(电机电源)和VCC(逻辑电源)必须分开供电,共用电源会导致PWM不稳定
3.2 差速运动基本公式
根据差动底盘运动学模型:
// 运动学转换公式 #define WHEEL_RADIUS 32.5 // 轮半径(mm) #define WHEEL_SPACING 120 // 轮间距(mm) void setSpeed(float linear, float angular) { // linear: 线速度 mm/s // angular: 角速度 rad/s float vr = linear + angular * WHEEL_SPACING / 2; float vl = linear - angular * WHEEL_SPACING / 2; int pwmR = vr * 60 / (2 * PI * WHEEL_RADIUS) * PWM_PER_RPM; int pwmL = vl * 60 / (2 * PI * WHEEL_RADIUS) * PWM_PER_RPM; analogWrite(PWMA, constrain(pwmL, 0, 255)); analogWrite(PWMB, constrain(pwmR, 0, 255)); }4. 同步校准与PID调参实战
4.1 开环测试发现问题
上传基础代码后,发送setSpeed(100, 0)指令,机器人可能出现三种异常:
- 明显右偏:右轮实际转速 > 左轮
- 原地顺时针旋转:左轮几乎不转
- 蛇形前进:两轮速度周期性波动
4.2 闭环PID控制实现
添加编码器反馈构成闭环控制:
// PID参数结构体 typedef struct { float Kp = 0.8; float Ki = 0.05; float Kd = 0; float integral = 0; float prevError = 0; } PID; PID leftPID, rightPID; float computePID(float target, float current, PID* pid) { float error = target - current; pid->integral += error; float derivative = error - pid->prevError; pid->prevError = error; return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; } void loop() { float leftRPM = getLeftRPM(); // 编码器计算实际转速 float rightRPM = getRightRPM(); int adjustL = computePID(targetL, leftRPM, &leftPID); int adjustR = computePID(targetR, rightRPM, &rightPID); analogWrite(PWMA, basePwmL + adjustL); analogWrite(PWMB, basePwmR + adjustR); }4.3 调参经验分享
按照这个顺序调整PID参数:
- 先设Ki=0, Kd=0,逐渐增大Kp直到出现轻微振荡
- 取振荡时Kp值的50%作为基准
- 小幅增加Ki改善稳态误差
- 最后加Kd抑制超调
典型参数范围:
- Kp: 0.5-2.0
- Ki: 0.01-0.1
- Kd: 0-0.3
5. 常见问题排查指南
5.1 供电不足症状
- 电机启动时Arduino重启
- 高速运行时突然减速
- TB6612芯片发烫严重
解决方案:
// 在setup()中添加电源监控 void checkVoltage() { float voltage = analogRead(A0) * (5.0 / 1023.0) * 2; // 分压电路 if(voltage < 6.4) { // 单节锂电<3.2V stopMotors(); alertLowBattery(); } }5.2 编码器干扰处理
- 电机运行时编码器计数异常
- 出现"幽灵脉冲"
硬件改进方案:
- 为编码器信号线加磁珠滤波
- 电机电源线与信号线分开走线
- 在编码器输出端添加10kΩ上拉电阻
软件容错措施:
// 脉冲间隔时间校验 unsigned long lastPulseTime = 0; void countPulse() { if(micros() - lastPulseTime > 100) { // 最小间隔100μs encoderCount++; lastPulseTime = micros(); } }6. 进阶优化技巧
6.1 运动平滑处理
突然的速度变化会导致轮子打滑,添加加速度限制:
float constrainAccel(float current, float target, float maxAccel) { float delta = target - current; if(delta > maxAccel) return current + maxAccel; if(delta < -maxAccel) return current - maxAccel; return target; } void updateSpeed() { currentLeft = constrainAccel(currentLeft, targetLeft, 10.0); // 最大加速度10mm/s² currentRight = constrainAccel(currentRight, targetRight, 10.0); // ...更新PWM输出... }6.2 电池电压补偿
锂电池放电时电压下降会导致转速降低:
float getCompensationFactor() { float voltage = analogRead(A0) * (5.0 / 1023.0) * 2; return 7.4 / voltage; // 标称电压/当前电压 } void setMotorPWM(int pwm, bool isLeft) { int compensated = pwm * getCompensationFactor(); analogWrite(isLeft ? PWMA : PWMB, constrain(compensated, 0, 255)); }在完成所有调试后,建议用不同表面(木板、地砖、地毯)测试底盘运动性能。我在实验室地砖上调好的参数,搬到铺有地毯的展厅时出现了15%的速度偏差,这时需要重新微调PID的I参数来适应新的摩擦条件。