
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驱动模块高效驱动的秘密为什么不用更经典的L298NTB6612FNG的优势是决定性的效率与发热L298N是双H桥芯片但其压降较大饱和压降通常在2V以上这意味着很大一部分电池能量被浪费在驱动芯片自身的发热上。TB6612FNG采用MOSFET构建H桥导通内阻Rds(on)极小典型值只有零点几欧姆因此压降小、效率高、发热量显著降低。对于电池供电、空间紧凑的小车来说低发热意味着更稳定、更安全。控制逻辑更清晰TB6612FNG每个通道可驱动一个电机有3个输入信号AIN1/AIN2或BIN1/BIN2控制电机的转向和刹车。其真值表非常直观AIN1AIN2电机状态00刹车停止01反转CCW10正转CW11刹车停止PWMA/PWMBPWM输入引脚用于控制电机速度。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将是整个系统的“动力地”。逻辑电源VCCTB6612模块上还有一个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。这样ARR999当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信号高电平的时间即占空比。当CCR0时占空比0%CCRARR我们设为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。例如在循环里让AIN11, AIN20然后延时再AIN10, AIN21。用万用表电压档或逻辑分析仪甚至一个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。在代码中让电机以较低速度例如speed200正转几秒停止再反转几秒。观察现象理想情况电机按照指令平稳正反转。电机不转但有嗡嗡声/发热可能是电源功率不足电池电量低或线径太细或者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 / ((711)(8991)) 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 i0; i1000; i50) { Motor_SetSpeed(i); HAL_Delay(100); // 延时100ms观察速度变化 } Motor_Brake(); HAL_Delay(1000); // 测试2反转加速、减速 for(int i0; 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控制器让它从能“走”进化到能“稳”。