一、TB6612 简介
TB6612FNG 是东芝的双路 H 桥电机驱动芯片,比 L298N 更高效(MOSFET 而非 BJT)。
| 特性 |
TB6612 |
L298N |
| 驱动类型 |
MOSFET |
BJT |
| 压降 |
~0.5V |
~2V |
| 单路连续电流 |
1.2A |
2A |
| 峰值电流 |
3.2A |
3A |
| PWM 频率 |
最高 100KHz |
最高 25KHz |
| 控制方式 |
IN1/IN2 + PWM |
IN1/IN2 + ENA |
| 体积 |
小 |
大(带散热片) |
二、引脚功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ┌─────────────────┐ GND ────┤1 24├──── VM (电机电源 2.5~13.8V) VCC ────┤2 23├──── PWMB AIN1 ────┤3 22├──── BIN2 AIN2 ────┤4 21├──── BIN1 PWMA ────┤5 TB6612FNG 20├──── GND AOUT1───┤6 19├──── GND GND ────┤7 18├──── BOUT1 GND ────┤8 17├──── BOUT2 AOUT2───┤9 16├──── VM VM ────┤10 15├──── GND VM ────┤11 14├──── VM GND ────┤12 13├──── GND └─────────────────┘
|
| 引脚组 |
引脚 |
功能 |
| 电源 |
VM(10/11/14/16/24) |
电机电源 2.5~13.8V |
| 电源 |
VCC(2) |
逻辑电源 2.7~5.5V |
| 电源 |
GND(1/7/8/12/13/15/19/20) |
全部接地 |
| A路控制 |
AIN1(3), AIN2(4) |
A路方向控制 |
| A路控制 |
PWMA(5) |
A路速度 PWM |
| A路输出 |
AOUT1(6), AOUT2(9) |
接电机 A |
| B路控制 |
BIN1(21), BIN2(22) |
B路方向控制 |
| B路控制 |
PWMB(23) |
B路速度 PWM |
| B路输出 |
BOUT1(18), BOUT2(17) |
接电机 B |
| 待机 |
STBY(未标注) |
高电平=工作,低=待机 |
三、控制逻辑
| IN1 |
IN2 |
PWM |
电机状态 |
| 0 |
0 |
X |
刹车(短路制动) |
| 0 |
1 |
PWM |
反转,PWM 调速 |
| 1 |
0 |
PWM |
正转,PWM 调速 |
| 1 |
1 |
X |
刹车(短路制动) |
注意:刹车和待机不同。刹车时 H 桥下管导通,电机惯性旋转产生反向电动势被短路→快速停止。待机时所有 MOS 管关断→自由滑行。
四、硬件连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| STM32F103C8T6 TB6612 PA0 ──── PWM ──── PWMA (A路速度) PA1 ──── IN1 ──── AIN1 (A路方向) PA2 ──── IN2 ──── AIN2 (A路方向) PA3 ──── PWM ──── PWMB (B路速度) PA4 ──── IN1 ──── BIN1 (B路方向) PA5 ──── IN2 ──── BIN2 (B路方向) PB0 ──── STBY ──── STBY (待机,高有效)
3.3V ──── VCC (逻辑电源) 12V ──── VM (电机电源,按电机额定电压) GND ──── GND (共地!)
┌── AOUT1 ── 电机A(+) TB6612 ──────────┤ └── AOUT2 ── 电机A(-)
|
五、CubeMX 配置
| 外设 |
引脚 |
配置 |
| TIM2_CH1 |
PA0 |
PWM Generation, 20KHz |
| TIM2_CH2 |
PA1 |
PWM Generation, 20KHz |
| GPIO |
PA2, PA3, PA4, PA5, PB0 |
Output, Push-Pull |
PWM 周期:72MHz / 72 / 1000 = 1KHz,实际常用 10K~20KHz。
六、代码实现
电机驱动封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #ifndef __MOTOR_H #define __MOTOR_H
#include "stm32f1xx_hal.h"
typedef enum { MOTOR_STOP = 0, MOTOR_FORWARD, MOTOR_BACKWARD, } MotorDir_t;
typedef struct { GPIO_TypeDef *IN1_Port; uint16_t IN1_Pin; GPIO_TypeDef *IN2_Port; uint16_t IN2_Pin; TIM_HandleTypeDef *htim; uint32_t PWM_Channel; GPIO_TypeDef *STBY_Port; uint16_t STBY_Pin; } Motor_t;
void Motor_Init(Motor_t *m); void Motor_Run(Motor_t *m, MotorDir_t dir, uint16_t speed); void Motor_Stop(Motor_t *m); void Motor_Coast(Motor_t *m);
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include "motor.h"
static Motor_t *motor_A = NULL, *motor_B = NULL; static uint8_t standby = 0;
void Motor_Init(Motor_t *m) { HAL_TIM_PWM_Start(m->htim, m->PWM_Channel); HAL_GPIO_WritePin(m->IN1_Port, m->IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(m->IN2_Port, m->IN2_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(m->STBY_Port, m->STBY_Pin, GPIO_PIN_SET); }
void Motor_Run(Motor_t *m, MotorDir_t dir, uint16_t speed) { if (speed > 999) speed = 999;
switch (dir) { case MOTOR_FORWARD: HAL_GPIO_WritePin(m->IN1_Port, m->IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(m->IN2_Port, m->IN2_Pin, GPIO_PIN_RESET); break; case MOTOR_BACKWARD: HAL_GPIO_WritePin(m->IN1_Port, m->IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(m->IN2_Port, m->IN2_Pin, GPIO_PIN_SET); break; default: speed = 0; HAL_GPIO_WritePin(m->IN1_Port, m->IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(m->IN2_Port, m->IN2_Pin, GPIO_PIN_RESET); break; }
__HAL_TIM_SET_COMPARE(m->htim, m->PWM_Channel, speed); }
void Motor_Stop(Motor_t *m) { Motor_Run(m, MOTOR_STOP, 0); }
void Motor_Coast(Motor_t *m) { __HAL_TIM_SET_COMPARE(m->htim, m->PWM_Channel, 0); HAL_GPIO_WritePin(m->STBY_Port, m->STBY_Pin, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(m->STBY_Port, m->STBY_Pin, GPIO_PIN_SET); }
|
主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int main(void) { HAL_Init(); SystemClock_Config(); MX_TIM2_Init(); MX_GPIO_Init();
Motor_t motorA = { .IN1_Port = GPIOA, .IN1_Pin = GPIO_PIN_2, .IN2_Port = GPIOA, .IN2_Pin = GPIO_PIN_3, .htim = &htim2, .PWM_Channel = TIM_CHANNEL_1, .STBY_Port = GPIOB, .STBY_Pin = GPIO_PIN_0, }; Motor_Init(&motorA);
while (1) { for (int s = 0; s <= 800; s += 10) { Motor_Run(&motorA, MOTOR_FORWARD, s); HAL_Delay(20); } HAL_Delay(1000);
Motor_Stop(&motorA); HAL_Delay(500);
Motor_Run(&motorA, MOTOR_BACKWARD, 500); HAL_Delay(2000);
Motor_Coast(&motorA); HAL_Delay(2000); } }
|
七、双路控制(小车差速)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Motor_t motorL = { }; Motor_t motorR = { };
Motor_Run(&motorL, MOTOR_FORWARD, 600); Motor_Run(&motorR, MOTOR_FORWARD, 600);
Motor_Run(&motorL, MOTOR_FORWARD, 200); Motor_Run(&motorR, MOTOR_FORWARD, 800);
Motor_Run(&motorL, MOTOR_FORWARD, 500); Motor_Run(&motorR, MOTOR_BACKWARD, 500);
Motor_Stop(&motorL); Motor_Stop(&motorR);
|
八、麦克纳姆轮解算
麦轮(Mecanum Wheel)辊子与轮毂成 45°,通过四轮差速组合实现全向移动。
1 2 3 4 5 6 7 8 9 10 11
| ┌─── 前方 ───┐ │ │ ┌──┴──┐ ┌──┴──┐ │ ① │ │ ② │ 轮①: 左前 (↗) └──┬──┘ └──┬──┘ 轮②: 右前 (↖) │ │ │ 车身 │ 轮③: 左后 (↖) │ │ 轮④: 右后 (↗) ┌──┴──┐ ┌──┴──┐ │ ③ │ │ ④ │ 辊子方向: ④↗ ②↖ └─────┘ └─────┘ ①↖ ③↗
|
运动学模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 轮速度 = 运动学矩阵 × 期望速度
┌──────┐ ┌─────────────────────┐ ┌────┐ │ ω₁ │ │ 1 -1 -(Lx+Ly) │ │ Vx │ │ ω₂ │ │ 1 1 (Lx+Ly) │ │ Vy │ │ ω₃ │ = │ 1 1 -(Lx+Ly) │ │ ω │ │ ω₄ │ │ 1 -1 (Lx+Ly) │ └────┘ └──────┘ └─────────────────────┘
Vx: 横向速度 (右为正) Vy: 纵向速度 (前为正) ω : 旋转角速度 (逆时针为正) Lx: 轮距的一半 (左右轮间距/2) Ly: 轴距的一半 (前后轮间距/2)
|
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <math.h>
#define WHEEL_BASE_X 0.15f #define WHEEL_BASE_Y 0.12f #define WHEEL_RADIUS 0.032f #define MAX_PWM 999
typedef struct { float vx; float vy; float w; } RobotVel_t;
void Mecanum_Kinematics(RobotVel_t *vel, int16_t pwm[4]) { float L_sum = WHEEL_BASE_X + WHEEL_BASE_Y;
float w1 = vel->vx - vel->vy - vel->w * L_sum; float w2 = vel->vx + vel->vy + vel->w * L_sum; float w3 = vel->vx + vel->vy - vel->w * L_sum; float w4 = vel->vx - vel->vy + vel->w * L_sum;
float speed[4] = {w1, w2, w3, w4};
float max_speed = 0; for (int i = 0; i < 4; i++) { if (fabsf(speed[i]) > max_speed) max_speed = fabsf(speed[i]); } if (max_speed > MAX_PWM) { float scale = MAX_PWM / max_speed; for (int i = 0; i < 4; i++) speed[i] *= scale; }
for (int i = 0; i < 4; i++) pwm[i] = (int16_t)speed[i]; }
void Mecanum_Drive(Motor_t *m[4], RobotVel_t *vel) { int16_t pwm[4]; Mecanum_Kinematics(vel, pwm);
for (int i = 0; i < 4; i++) { MotorDir_t dir = (pwm[i] >= 0) ? MOTOR_FORWARD : MOTOR_BACKWARD; uint16_t speed = (uint16_t)(pwm[i] >= 0 ? pwm[i] : -pwm[i]); if (speed > MAX_PWM) speed = MAX_PWM; Motor_Run(m[i], dir, speed); } }
|
典型运动指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| RobotVel_t cmd;
cmd = (RobotVel_t){0, 300, 0}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){0, -300, 0}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){ 300, 0, 0}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){-300, 0, 0}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){ 300, 300, 0}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){0, 0, 200}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){ 200, 0, 100}; Mecanum_Drive(motors, &cmd);
cmd = (RobotVel_t){0, 0, 0}; Mecanum_Drive(motors, &cmd);
|
公式速查(轮转速 → 机器人速度)
1 2 3
| Vx = (ω₁ + ω₂ + ω₃ + ω₄) / 4 Vy = (-ω₁ + ω₂ + ω₃ - ω₄) / 4 ω = (-ω₁ + ω₂ - ω₃ + ω₄) / (4 × (Lx + Ly))
|
九、PWM 频率选择
| 频率 |
效果 |
适用 |
| < 100Hz |
电机抖动,尖锐啸叫 |
❌ 不用 |
| 1KHz |
可听见轻微啸叫 |
勉强可用 |
| 10K~20KHz |
安静平滑,推荐 |
✅ 通用 |
| > 20KHz |
超出人耳范围,完全无声 |
✅ 精密控制 |
TB6612 PWM 最高 100KHz。超过 20KHz 会增大 MOSFET 开关损耗,通常选 10K~20KHz。
十、常见问题
| 现象 |
原因 |
解决 |
| 电机不转 |
STBY 未拉高 |
HAL_GPIO_WritePin(STBY, SET) |
| 电机只朝一个方向转 |
IN1/IN2 接反 |
交换 IN1/IN2 或交换电机线 |
| 低速抖动 |
PWM 频率太低 |
提高到 10KHz 以上 |
| 芯片发烫 |
PWM 频率太高 / 电流过大 |
降到 20KHz,检查电机堵转 |
| 调速不线性 |
死区太大 |
低于 10% 占空比可能带不动,加死区补偿 |
| VM 和 VCC 短接 |
接错线 |
VM=电机电压(3~13.8V),VCC=逻辑电压(3.3V/5V) |
十一、刹车 vs 惰行
1 2 3 4 5 6 7 8 9 10
| 刹车(STOP): H 桥下管导通,电机两端短路 → 电机旋转产生的反电动势被短接→形成制动扭矩 → 快速停车,适合精确控制
惰行(COAST): STBY=0,所有 MOS 管截止 → 电机两端开路,无电流回路 → 靠摩擦慢慢停下,适合省电场景
|