STM32平衡小车电机驱动实战:TB6612驱动JGB-520直流减速电机
1. 项目缘起:从“会动”到“能站”的第一步
做平衡小车,听起来挺酷,但第一步往往就卡在“让轮子听话地转起来”这个看似简单的问题上。很多新手朋友一上来就想调PID、搞姿态解算,结果发现连最基本的电机正反转、调速都搞不定,小车在地上要么抽搐要么乱窜,根本谈不上平衡。我当年也是这么过来的,所以这个系列的第一篇,咱们不搞虚的,就扎扎实实地把最底层、最核心的动力部分——电机和驱动——给整明白。
平衡小车的动力心脏,通常就是直流减速电机。我这次选的是JGB-520,一个在小型机器人、智能车领域非常常见的型号。它体积适中,扭矩够用,自带减速箱,价格也亲民,非常适合作为学习平台。而要让这颗“心脏”听我们STM32这位“大脑”的指挥,就需要一个得力的“传令官”,也就是电机驱动模块。TB6612FNG就是这样一个角色,它比经典的L298N效率更高、发热更小,电路也更简洁。这篇文章,我就带你从零开始,完成JGB-520电机与TB6612驱动模块的硬件连接、基础驱动代码编写,并提供一个完整的测试工程,让你能亲手验证你的电机驱动系统是否健康。这是你平衡小车项目万里长征的第一步,也是最坚实的一步。
2. 核心器件选型与特性剖析:为什么是它们?
在动手接线和写代码之前,我们得先了解手里的“兵器”。知其然,更要知其所以然,这样在后续调试和升级时,你才能游刃有余。
2.1 JGB-520直流减速电机:动力源的选择考量
JGB-520不是一个特定厂商的独家型号,而是一类电机的通用规格描述。我们选择它,主要基于以下几个平衡小车初阶项目的实际需求:
工作电压:常见的JGB-520额定电压在6V到12V之间。对于由两节18650锂电池(串联约7.4V-8.4V)供电的平衡小车来说,这个电压范围非常匹配。电压直接影响电机的空载转速和最大扭矩,电压越高,理论性能上限越高,但功耗和发热也越大。7.4V是一个兼顾性能和续航的甜点电压。
减速比:这是“减速电机”的关键。电机本身转速很高(每分钟几千转),但扭矩小,直接驱动轮子会转速过快、扭矩不足,小车一动就失步。减速箱通过齿轮组将转速降低,同时扭矩成比例放大。常见的减速比有1:30, 1:48, 1:120等。对于平衡小车,我们需要电机有较快的响应速度(以对抗车身倾斜),同时也要有足够的扭矩带动车身。我个人经验是,1:48到1:120的减速比范围比较适合。比值太小(如1:30),扭矩可能略显不足;比值太大(如1:200),响应速度会变慢。我手头这个是1:120的,实测在7.4V下,带载后的转速对于平衡调试是可控的。
电机参数识别:拿到电机,你最好用万用表测一下两个电极之间的电阻。通常直流有刷电机的内阻在几欧姆到十几欧姆。这个值有两个用处:一是粗略判断电机好坏(开路无穷大,短路接近零欧姆都有问题);二是在后续计算驱动电流、选择电源时有参考价值。根据欧姆定律,堵转电流(理论上最大电流)I_max = V_supply / R_internal。例如,电源7.4V,电机内阻2欧姆,则堵转电流可达3.7A。这提醒我们驱动模块和电源必须能承受这个瞬时电流。
2.2 TB6612FNG驱动模块:高效驱动的秘密
为什么不用更经典的L298N?TB6612FNG的优势是决定性的:
效率与发热:L298N是双H桥芯片,但其压降较大(饱和压降通常在2V以上),这意味着很大一部分电池能量被浪费在驱动芯片自身的发热上。TB6612FNG采用MOSFET构建H桥,导通内阻(Rds(on))极小,典型值只有零点几欧姆,因此压降小、效率高、发热量显著降低。对于电池供电、空间紧凑的小车来说,低发热意味着更稳定、更安全。
控制逻辑更清晰:TB6612FNG每个通道(可驱动一个电机)有3个输入信号:
- AIN1/AIN2(或BIN1/BIN2):控制电机的转向和刹车。其真值表非常直观:
AIN1 AIN2 电机状态 0 0 刹车(停止) 0 1 反转(CCW) 1 0 正转(CW) 1 1 刹车(停止) - PWMA/PWMB:PWM输入引脚,用于控制电机速度。STM32的定时器通道直接连接到这里,通过改变PWM的占空比来调速。
- STBY(待机):高电平时芯片正常工作,低电平时所有输出关闭,电机自由停止。这个引脚可以用于全局使能,在紧急情况或休眠时快速切断电机动力。
集成度高与保护功能:单芯片集成双H桥,外围电路只需要几个滤波电容,比L298N节省大量板载空间。同时,它内置了过热关断和欠压锁定保护,安全性更好。
驱动能力:TB6612FNG持续输出电流可达1.2A(单通道),峰值电流可达3.2A。这完全能够满足JGB-520这类小型电机的需求(注意避开持续堵转)。
3. 硬件连接实战:从原理图到杜邦线
理论清楚了,我们开始动手连接。这一步的准确性直接决定了后续代码能否生效。我建议你先在面包板或扩展板上完成所有连接并测试,再考虑焊接集成到小车主板上。
3.1 电源系统的设计与隔离
这是最容易出错也最关键的部分。务必理解:STM32的3.3V逻辑世界和电机的7.4V动力世界,需要共地但必须电源隔离。
- 动力电源(VM):准备一个7.4V的锂电池组(两节18650串联)。电池的正极连接到TB6612模块上标有
VM或VCC的引脚,负极连接到GND。这个GND将是整个系统的“动力地”。 - 逻辑电源(VCC):TB6612模块上还有一个
VCC引脚(有时标为VIN或逻辑电源),这个引脚需要接入5V电压,用于给芯片内部的逻辑电路供电。绝对不能接7.4V!这个5V可以从你的STM32开发板的5V输出引脚获取(如果开发板有的话),或者从一个独立的5V稳压模块(如AMS1117-5.0)获取。这个VCC的GND必须与动力电源的GND连接在一起。 - STM32供电:STM32开发板通常由USB或自身的3.3V稳压器供电。其
GND必须与上述动力地、逻辑地连接在一起。共地是信号正确传递的基础! - 信号连接:将STM32的GPIO引脚连接到TB6612的控制引脚。例如:
STM32 GPIO1->TB6612 AIN1STM32 GPIO2->TB6612 AIN2STM32 TIMx_CHy (PWM)->TB6612 PWMASTM32 GPIO3->TB6612 STBYSTM32 GND->TB6612 GND(逻辑地)
连接要点与避坑:
注意:在连接电机到TB6612的
A01/A02输出端之前,务必先用万用表确认你的电源连接是否正确,特别是VM和VCC没有接反、共地已建立。接反极易烧毁芯片。经验:在面包板阶段,我习惯用不同颜色的杜邦线区分电源和信号:红色接正极(VM, VCC),黑色接地(所有GND),黄色、绿色等接控制信号。并在电源线上串联一个可恢复保险丝或至少一个几欧姆的功率电阻作为缓冲,防止初次上电因短路造成更大损失。
3.2 电机与驱动的接线确认
将JGB-520电机的两根线,任意连接到TB6612的A01和A02输出端。此时电机的正反转定义是相对的,你可以通过后续代码测试来定义哪个方向是你想要的“前进”。如果发现转向反了,只需要在代码中交换AIN1和AIN2的逻辑,或者直接交换电机两根线在驱动板上的物理连接即可。
4. STM32基础驱动代码实现
硬件准备就绪,现在让我们用代码“唤醒”电机。我们将使用STM32的HAL库,因为它移植性较好,代码直观。
4.1 GPIO与PWM定时器初始化
首先,我们需要初始化控制AIN1,AIN2,STBY的GPIO,以及产生PWM波的定时器。
// motor.h #ifndef __MOTOR_H #define __MOTOR_H #include "main.h" // 根据你的实际连接修改这些宏定义 #define MOTOR_AIN1_GPIO_PORT GPIOA #define MOTOR_AIN1_GPIO_PIN GPIO_PIN_0 #define MOTOR_AIN2_GPIO_PORT GPIOA #define MOTOR_AIN2_GPIO_PIN GPIO_PIN_1 #define MOTOR_STBY_GPIO_PORT GPIOA #define MOTOR_STBY_GPIO_PIN GPIO_PIN_2 // PWM定时器句柄和通道定义 extern TIM_HandleTypeDef htim2; // 假设使用TIM2 #define MOTOR_PWM_TIM &htim2 #define MOTOR_PWM_TIM_CHANNEL TIM_CHANNEL_1 // 假设使用通道1 // 电机控制函数声明 void Motor_Init(void); void Motor_SetSpeed(int16_t speed); // speed范围:-1000 ~ +1000 void Motor_Stop(void); void Motor_Brake(void); #endif// motor.c #include "motor.h" // GPIO初始化 void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟 // 配置AIN1, AIN2, STBY为推挽输出 GPIO_InitStruct.Pin = MOTOR_AIN1_GPIO_PIN | MOTOR_AIN2_GPIO_PIN | MOTOR_STBY_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(MOTOR_AIN1_GPIO_PORT, &GPIO_InitStruct); // 初始状态:电机刹车,驱动芯片待机 HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN1_GPIO_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN2_GPIO_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_STBY_GPIO_PORT, MOTOR_STBY_GPIO_PIN, GPIO_PIN_RESET); // STBY低电平,待机 } // PWM定时器初始化 (通常在CubeMX中配置,这里给出关键参数思路) // 使用CubeMX配置TIM2: // - 时钟源:内部时钟 // - 预分频器(Prescaler): 根据系统时钟计算,使计数器频率在1MHz量级。例如系统时钟72MHz,预分频71,则计数器频率为1MHz。 // - 计数器周期(Counter Period): 设置为1000-1。这样ARR=999,当CCR从0变化到999时,占空比从0%变化到100%。 // - PWM模式:PWM模式1或2,通常选择“PWM模式1”,比较输出极性为低或高根据电路决定,一般先选低。 // - 使能自动重装载预装载。 void Motor_Init(void) { Motor_GPIO_Init(); // 启动PWM定时器,并先关闭PWM输出 HAL_TIM_PWM_Start(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL); __HAL_TIM_SET_COMPARE(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL, 0); // 占空比设为0 // 退出待机模式,使能驱动芯片 HAL_GPIO_WritePin(MOTOR_STBY_GPIO_PORT, MOTOR_STBY_GPIO_PIN, GPIO_PIN_SET); }4.2 核心控制函数:速度与方向
Motor_SetSpeed函数是整个驱动层的核心,它将一个代表速度和方向的值(例如-1000到+1000)转换为具体的AIN1/AIN2电平组合和PWM占空比。
// motor.c 续 void Motor_SetSpeed(int16_t speed) { // 限制速度输入范围 if (speed > 1000) speed = 1000; if (speed < -1000) speed = -1000; uint16_t pwm_duty = (uint16_t)abs(speed); // 取绝对值作为PWM占空比值 if (speed > 0) { // 正转 HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN1_GPIO_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN2_GPIO_PIN, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL, pwm_duty); } else if (speed < 0) { // 反转 HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN1_GPIO_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN2_GPIO_PIN, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL, pwm_duty); } else { // 速度为零,刹车 Motor_Brake(); } } void Motor_Brake(void) { // TB6612刹车模式:AIN1和AIN2同时为高或同时为低。这里采用同时为低。 HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN1_GPIO_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_AIN1_GPIO_PORT, MOTOR_AIN2_GPIO_PIN, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL, 0); // PWM占空比也设0 } void Motor_Stop(void) { // 进入待机模式,电机自由停止 HAL_GPIO_WritePin(MOTOR_STBY_GPIO_PORT, MOTOR_STBY_GPIO_PIN, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(MOTOR_PWM_TIM, MOTOR_PWM_TIM_CHANNEL, 0); }代码逻辑解析:
Motor_SetSpeed接收一个有符号整数。正数代表一个方向(我们定义为正转),负数代表另一个方向(反转)。绝对值大小代表速度。- 根据
speed的正负,设置AIN1/AIN2的电平组合,决定电流方向,从而控制电机转向。 __HAL_TIM_SET_COMPARE函数用于设置捕获/比较寄存器(CCR)的值,这个值决定了PWM信号高电平的时间,即占空比。当CCR=0时,占空比0%;CCR=ARR(我们设为999)时,占空比接近100%。Motor_Brake与Motor_Stop的区别:刹车是电机制动,电机线圈短路,能快速停下但有轻微阻力;待机是切断驱动,电机惯性滑行。在平衡小车控制中,快速切换刹车和驱动状态有助于提高响应。
5. 系统化测试方案与问题排查
代码写好了,但直接上电电机可能不转。别急,我们需要一个系统化的测试流程来验证每一环节。
5.1 分级测试:从信号到动力
第一步:静态电压测量(不上电,不接电机)
- 用万用表检查所有电源连接点之间没有短路。
- 给系统上电(STM32通过USB, TB6612的VM接7.4V, VCC接5V)。
- 测量TB6612的
VCC引脚对GND,确认是稳定的5V左右。 - 测量
VM引脚对GND,确认是电池电压(约7.4V-8.4V)。
第二步:逻辑信号测试(不接电机)
- 在
main函数初始化后,调用Motor_Init()。 - 编写一个简单的测试函数,手动控制GPIO。例如,在循环里让
AIN1=1, AIN2=0,然后延时,再AIN1=0, AIN2=1。 - 用万用表电压档或逻辑分析仪(甚至一个LED加电阻)测量
AIN1和AIN2引脚对GND的电压。你应该能看到它们按照你的代码在高电平(3.3V)和低电平(0V)之间跳变。同时测量STBY引脚,应为高电平(3.3V)。 - 测试PWM信号:调用
Motor_SetSpeed(500)。用示波器探头测量PWMA引脚,你应该能看到一个频率为定时器时钟 / (预分频+1) / (周期+1)的方波,其高电平占整个周期的比例约为50%(因为500/1000)。如果没有示波器,可以用万用表测PWMA引脚对GND的电压,当占空比不是0%或100%时,你会测到一个介于0和3.3V之间的平均电压。例如50%占空比,平均电压约1.65V。
第三步:空载测试(接上电机)
警告:此步骤前,确保电机轴可以自由旋转,没有机械卡阻。
- 将电机接上
A01和A02。 - 在代码中,让电机以较低速度(例如
speed=200)正转几秒,停止,再反转几秒。 - 观察现象:
- 理想情况:电机按照指令平稳正反转。
- 电机不转但有嗡嗡声/发热:可能是电源功率不足(电池电量低或线径太细),或者PWM频率不合适(对于有刷电机,常用频率在1kHz到20kHz之间,太低会听到啸叫,太高可能因MOS开关损耗降低效率)。尝试更换满电电池,或调整定时器预分频器改变PWM频率。
- 电机单向转,另一向不转或无力:检查对应方向的
AIN1/AIN2电平组合是否正确,或者电机线有一根虚焊。 - 电机抖动一下然后停止:驱动芯片可能进入保护状态(过热或过流)。检查是否有短路,电机是否堵转,散热是否良好。断电冷却后再试。
第四步:带载与调速线性度测试
- 用手轻轻捏住电机轴,给予一个轻微的阻力,调用不同速度值(-1000, -500, 0, 500, 1000),感受电机扭矩和速度的变化。
- 你会发现,在很低的速度值(如50以下)时,电机可能无法启动或转动不连续。这是有刷电机和PWM驱动的典型现象——死区。由于电机静摩擦和最小启动电压的存在,占空比太小不足以克服这些阻力。在后续的平衡控制中,我们需要在软件上设置一个“死区补偿”或“最小输出阈值”。
5.2 常见问题排查清单
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 电机完全不转,无声音 | 1. 电源未接通或接反。 2. STBY引脚为低电平。 3. 电机线未接好或断开。 4. 驱动芯片损坏。 | 1. 检查所有电源电压(VM, VCC, 3.3V)。 2. 测量STBY引脚电压,应为高。 3. 万用表通断档测电机线。 4. 触摸芯片是否异常发烫,更换芯片测试。 |
| 电机不转,但有“滋滋”声 | 1. PWM频率在人耳可闻范围(几百Hz)。 2. 电源功率严重不足。 3. 电机内部短路或驱动桥臂短路。 | 1. 调整定时器配置,将PWM频率提高到15kHz以上。 2. 换用电量足的电池,检查电源线连接。 3. 断开电机,测量A01与A02之间电阻,过小则可能短路。 |
| 电机单向转动正常,另一向不转 | 1. 控制该方向的GPIO引脚配置或逻辑错误。 2. 驱动芯片该侧桥臂损坏。 3. 电机线有一根接触不良。 | 1. 用逻辑分析仪或示波器查看不转方向对应的AINx引脚电平。 2. 交换AIN1和AIN2的控制逻辑,如果故障方向随之改变,则是代码问题;否则可能是硬件问题。 3. 重新焊接电机线。 |
| 电机转速慢,无力 | 1. 电源电压过低。 2. PWM占空比设置未达到预期。 3. 电机负载过重。 4. 驱动芯片工作在限流或过热保护状态。 | 1. 测量带载时的VM电压,看是否被拉低太多。 2. 用示波器测量PWMA引脚实际波形和占空比。 3. 检查机械结构是否卡死。 4. 触摸芯片温度,确保散热良好。 |
| STM32程序运行不稳定,复位 | 1. 电机工作时产生的电流突变干扰了STM32电源。 2. 电源地线连接不良,形成电位差。 | 1. 在电机电源输入端并接一个大容量(如100uF)电解电容和一个104瓷片电容滤波。 2. 检查并确保所有“地”(电池地、驱动板地、STM32地)是星型单点连接,且导线足够粗。 |
6. 进阶话题:为平衡控制做准备
基础的驱动调通了,但这离平衡小车的要求还有距离。我们需要从“能让电机转”升级到“能让电机精密地响应控制命令”。
6.1 PWM频率与电机响应特性
PWM的频率选择是一个权衡。频率太低(如几百Hz),电机会产生可闻的噪音,且电流纹波大;频率太高(如50kHz以上),MOS管的开关损耗会增加,驱动芯片可能发热更严重。对于JGB-520这类小型有刷直流电机,我个人习惯将PWM频率设置在8kHz到16kHz之间。这个范围通常超出了人耳听觉上限,且对大多数驱动芯片来说效率较高。在CubeMX中配置定时器时,计算公式是:PWM频率 = 定时器时钟源 / ((预分频器 + 1) * (自动重装载值 + 1))例如,系统时钟72MHz,预分频器设为71,自动重装载值设为899,则PWM频率 = 72,000,000 / ((71+1)(899+1)) = 72,000,000 / (72900) =1.111kHz。这个频率就偏低了。要提高到16kHz,可以调整预分频器或重装载值,例如预分频器5,重装载值899,则频率 = 72M / (6*900) ≈13.33kHz。
6.2 速度闭环的初步思考
我们目前是开环控制:给一个PWM占空比,电机就以一个大概的速度转。但负载变化、电池电压下降都会导致实际转速变化。对于平衡小车,我们最终需要的是精确控制轮子的转速(更准确说是位置),这就需要编码器来构成速度闭环。
JGB-520通常有带编码器的版本(在电机尾部有一个小板,输出AB相脉冲)。如果你用的是带编码器的电机,那么现在就需要考虑将其连接到STM32的定时器编码器接口。STM32的定时器硬件可以直接读取编码器脉冲并计数,极大减轻CPU负担。获取到速度反馈后,我们就可以使用PID控制器:输出PWM = PID(目标速度 - 实际测量速度)。这样,无论上坡还是下坡,电机都能努力维持我们设定的转速,这是实现平衡和运动控制的基础。在下一篇文章中,我们将详细展开编码器的使用和速度环PID的初步调试。
6.3 双电机协同与驱动函数封装
一辆小车有两个轮子,需要两个独立的电机驱动通道。我们的代码需要易于扩展。一个好的做法是,将电机驱动封装成一个结构体,方便管理多个电机。
// motor.h 扩展 typedef struct { GPIO_TypeDef* AIN1_Port; uint16_t AIN1_Pin; GPIO_TypeDef* AIN2_Port; uint16_t AIN2_Pin; GPIO_TypeDef* STBY_Port; uint16_t STBY_Pin; TIM_HandleTypeDef* PWM_Tim; uint32_t PWM_Channel; int16_t current_speed; } Motor_HandleTypeDef; void Motor_Init_Handle(Motor_HandleTypeDef* motor); void Motor_SetSpeed_Handle(Motor_HandleTypeDef* motor, int16_t speed);这样,在main.c中,你可以定义两个电机句柄,分别初始化,并独立控制。这为后续实现差速转向(一个轮子正转,一个轮子反转)打下了基础。
7. 测试代码实战:一个完整的验证流程
最后,我提供一个完整的main.c测试代码框架,它实现了电机的多种运动模式测试,你可以直接用来验证你的硬件和底层驱动。
// main.c #include "main.h" #include "motor.h" #include "stdio.h" // 如果使用串口打印 int main(void) { HAL_Init(); SystemClock_Config(); // 系统时钟配置 MX_GPIO_Init(); // GPIO初始化(CubeMX生成) MX_TIM2_Init(); // PWM定时器初始化(CubeMX生成) // 如果你用串口调试,还需要初始化串口,例如 MX_USART1_UART_Init(); Motor_Init(); // 初始化电机驱动 while (1) { // 测试1:正转加速、减速 for(int i=0; i<=1000; i+=50) { Motor_SetSpeed(i); HAL_Delay(100); // 延时100ms,观察速度变化 } Motor_Brake(); HAL_Delay(1000); // 测试2:反转加速、减速 for(int i=0; i>=-1000; i-=50) { Motor_SetSpeed(i); HAL_Delay(100); } Motor_Brake(); HAL_Delay(1000); // 测试3:正反转交替 Motor_SetSpeed(800); HAL_Delay(2000); Motor_SetSpeed(-800); HAL_Delay(2000); Motor_Brake(); HAL_Delay(2000); // 测试4:低速死区测试(观察电机是否启动) Motor_SetSpeed(30); // 可能不转 HAL_Delay(2000); Motor_SetSpeed(80); // 应该能启动 HAL_Delay(2000); Motor_Brake(); HAL_Delay(3000); } }将这个程序下载到STM32,观察电机的运动是否符合预期。如果一切正常,恭喜你,你已经成功为你的平衡小车搭建了最底层、最可靠的动力控制系统。这套系统就像小车的双腿,现在它已经具备“站立”和“行走”的潜力了。下一步,我们将为它装上“眼睛”(编码器)和“小脑”(速度PID控制器),让它从能“走”进化到能“稳”。
