1. 直流电机控制基础与硬件选型
直流电机作为最常用的动力装置之一,在智能小车、机械臂等嵌入式项目中扮演着核心角色。记得我第一次用STM32驱动电机时,直接被电机的"暴躁"反应吓了一跳——没有调速控制的直流电机就像脱缰的野马,一通电就全速狂奔。这让我深刻认识到PWM调速的重要性。
TB6612这款驱动芯片是我的首选方案,相比传统的L298N,它的优势非常明显:
- 体积只有指甲盖大小,适合嵌入式项目
- 效率高达95%(L298N仅约70%)
- 最大1.2A持续电流输出(峰值3.2A)
- 内置过热保护和低电压检测
实际项目中我对比过几种常见驱动方案:
| 型号 | 工作电压 | 持续电流 | 效率 | 体积 |
|---|---|---|---|---|
| TB6612 | 2.5-10V | 1.2A | 95% | 极小 |
| L298N | 5-35V | 2A | 70% | 较大 |
| DRV8833 | 2.7-10V | 1.5A | 90% | 小 |
选择TB6612的另一个重要原因是它的双H桥设计。一个芯片可以同时控制两个直流电机,或者驱动一个步进电机。H桥电路相当于一个"智能开关矩阵",通过四个MOS管的组合切换,能灵活控制电流方向。我画个简单示意图:
正转模式: AIN1=1 AIN2=0 → 电流: VM→AO1→电机→AO2→GND 反转模式: AIN1=0 AIN2=1 → 电流: VM→AO2→电机→AO1→GND2. 硬件电路设计与连接要点
第一次连接TB6612时,我犯了个低级错误——把电机电源和逻辑电源混接了。结果电机一启动,整个STM32就重启。后来才明白必须分开供电的重要性:
- VM引脚:接4.5-10V电机专用电源(建议用18650锂电池)
- VCC引脚:接3.3V/5V单片机电源
- GND共地:两个电源的GND必须连接在一起
具体到STM32F103C8T6与TB6612的连接,我的推荐方案是:
// 电机1控制线 PA0 → PWMA // PWM调速信号 PA4 → AIN2 // 方向控制 PA5 → AIN1 // 方向控制 // 电机2控制线 PA1 → PWMB PA6 → BIN1 PA7 → BIN2 // 公共线 3.3V → VCC BAT+ → VM GND → GND 3.3V → STBY // 禁用待机模式特别注意:电机电源电压不要超过10V!我有次用了12V电源,虽然TB6612没烧毁,但发热明显加剧。建议在VM端加个100μF的电解电容,能有效抑制电机启停时的电压波动。
3. PWM调速原理与参数优化
PWM调速的本质是通过快速开关来控制平均电压。比如50%占空比的5V PWM,等效输出电压就是2.5V。但这里有几个关键参数需要特别注意:
PWM频率选择:
- 太低(<1kHz):电机会发出刺耳的啸叫声
- 太高(>20kHz):驱动芯片开关损耗增大
- 推荐值:16-20kHz(超出人耳听觉范围)
占空比分辨率: 如果ARR=100,那么:
- 最小速度调节幅度:1/100=1%
- 适合普通小车应用 如果需要更精细控制,可以设ARR=1000,但会降低PWM频率
我常用的定时器配置公式:
// 假设72MHz主频 PWM频率 = 72000000 / (PSC + 1) / (ARR + 1) 占空比 = CCR / (ARR + 1) // 示例:20kHz PWM,100阶分辨率 PSC = 36-1 // 2MHz计数频率 ARR = 100-1 // 20kHz实测发现,很多直流电机在占空比<10%时会出现"死区"——电机不转但有电流声。解决方法是在代码中加入最小启动阈值:
void Motor_SetSpeed(int8_t speed) { if(abs(speed) < 10) { // 死区处理 PWM_SetCompare3(0); return; } // ...正常调速逻辑 }4. 软件架构设计与代码实现
好的电机驱动代码应该像乐高积木——模块化、可复用。我的项目通常采用三层结构:
- 硬件抽象层(PWM.c/H) 负责最底层的定时器配置:
void PWM_Init(void) { // 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // GPIO配置为复用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 时基单元配置 TIM_TimeBaseInitStructure.TIM_Period = 100-1; // ARR TIM_TimeBaseInitStructure.TIM_Prescaler = 36-1;// PSC // 输出比较配置 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OC3Init(TIM2, &TIM_OCInitStructure); }- 驱动层(Motor.c/H) 封装方向控制和速度映射:
void Motor_SetSpeed(int8_t speed) { if(speed >= 0) { GPIO_SetBits(GPIOA, GPIO_Pin_4); // 正转 GPIO_ResetBits(GPIOA, GPIO_Pin_5); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 反转 GPIO_SetBits(GPIOA, GPIO_Pin_5); } PWM_SetCompare3(abs(speed)); // 速度绝对值 }- 应用层(main.c) 实现业务逻辑,比如按键调速:
while(1) { if(Key_Pressed()) { speed = (speed + 20) % 120; // 步进20,循环0-100 Motor_SetSpeed(speed); } }实用技巧:在Motor.h中使用枚举定义运动状态,代码可读性更好:
typedef enum { MOTOR_STOP, MOTOR_FORWARD, MOTOR_BACKWARD } MotorState;5. 调试技巧与常见问题排查
调试电机系统时,我的必备工具清单:
- 万用表(检测电源电压)
- 逻辑分析仪(观察PWM波形)
- 红外测温枪(监控芯片温度)
典型故障排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机不转 | STBY引脚未使能 | 接高电平或3.3V |
| 只有一个方向能转 | 方向控制线接触不良 | 检查AIN1/AIN2连接 |
| PWM调速无效 | 定时器通道未正确映射 | 检查GPIO_AF配置 |
| 电机抖动严重 | 电源功率不足 | 增加电容或换电池 |
有个坑我踩过两次:当电机从正转突然切换到反转时,电流冲击会导致单片机复位。后来我加了软件保护:
void Motor_SafeStop(void) { PWM_SetCompare3(0); // 先停止PWM Delay_ms(100); // 等待能量释放 // 再切换方向 }6. 性能优化与进阶技巧
要让电机运行更安静平稳,可以尝试这些方法:
PWM频率渐变: 启动时逐步提高频率,避免电流突变
for(int i=5000; i<=20000; i+=1000) { Set_PWM_Frequency(i); Delay_ms(10); }速度平滑滤波: 对速度指令进行低通滤波
float current_speed = 0; void Update_Speed(float target) { current_speed += 0.2 * (target - current_speed); Motor_SetSpeed(current_speed); }电流检测保护: 通过采样电阻+运放检测电流
if(Read_Current() > MAX_CURRENT) { Motor_Stop(); LED_Alert(); }
对于需要精确位置控制的场景,可以结合编码器实现闭环控制。我常用的增量式编码器接线方式:
编码器A相 → TIMx_CH1 编码器B相 → TIMx_CH2 Z相 → 外部中断最后分享一个实测数据:使用优化后的PWM参数,电机噪音从65dB降到了42dB,而功耗降低了约15%。这告诉我们,合适的参数调校不仅能提升用户体验,还能延长电池续航。