为什么要自己写电机驱动
市面上有很多现成的电机驱动模块,接好线就能用。但一旦你要做平衡车、机械臂、云台、AGV,就会发现:现成模块要么精度不够、要么不开放接口、要么和你的主控通信协议对不上。这时候就得自己写一套电机控制:PWM 调速、编码器反馈、PID 闭环。
电机控制的本质是"用输出去修正误差"。你让它转到某个位置,它会偏,PID 就是把偏差一点点拉回来的算法。
这篇从直流有刷电机讲起,一路写到无刷 FOC 入门,把这套东西真正调顺。
PWM 调速与 H 桥
直流电机的转速和两端电压近似成正比。你不能直接调电压(MCU 引脚只有 3.3V),所以用 PWM:快速开关 MOS 管,平均电压靠占空比控制。H 桥决定转向:对角两个 MOS 同时通,电流从左到右;另一对同时通,电流反向。
// STM32 TIM 输出 20kHz PWM
// 占空比 0~1000 对应 0~100%
void motor_set_duty(int duty, int dir) {
if (dir > 0) {
HAL_GPIO_WritePin(AIN1_GPIO, AIN1_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(AIN2_GPIO, AIN2_PIN, GPIO_PIN_RESET);
} else {
HAL_GPIO_WritePin(AIN1_GPIO, AIN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(AIN2_GPIO, AIN2_PIN, GPIO_PIN_SET);
}
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty);
}
PWM 频率选 20kHz 以上,人耳听不到啸叫;频率太低电机会抖。
编码器:知道电机转了多少
开环调速只是"我给多少占空比就跑多快",负载一变速度就飘。要闭环必须知道电机实际转了多少,这就是编码器的活。常见 AB 相增量编码器:A、B 两路方波,相位差 90°,根据谁领先判断正反转,根据脉冲数算位置。
// STM32 TIM 编码器模式
// CNT 寄存器自动加减,不需要中断
void encoder_init(void) {
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
HAL_TIM_Encoder_Init(&htim2, &htim2.Init);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
}
// 每 10ms 读一次位移和速度
int32_t pos = (int16_t)__HAL_TIM_GET_COUNTER(&htim2);
编码器线数 × 减速比 = 每圈脉冲数。先把这个数算对,不然 PID 怎么调都不对。
PID 闭环怎么写
PID 的公式:输出 = Kp×误差 + Ki×误差积分 + Kd×误差微分。三个项各有分工:P 拉速度、I 消静差、D 抑超调。
typedef struct {
float kp, ki, kd;
float integral, prev_err;
float out_max;
} pid_t;
float pid_update(pid_t *p, float target, float actual, float dt) {
float err = target - actual;
p->integral += err * dt;
// 积分限幅,防饱和
if (p->integral > p->out_max) p->integral = p->out_max;
if (p->integral < -p->out_max) p->integral = -p->out_max;
float deriv = (err - p->prev_err) / dt;
p->prev_err = err;
return p->kp*err + p->ki*p->integral + p->kd*deriv;
}
每个控制周期(建议 1ms~10ms)跑一次 PID,输出直接当 PWM 占空比。
无刷电机 FOC 入门
直流有刷结构简单但碳刷磨损。无刷电机(BLDC/PMSM)寿命长、效率高,但驱动要做矢量控制(FOC)。核心思想:把三相电流解耦成"力矩分量"和"励磁分量",像控制直流电机一样控制无刷。
- Clarke 变换三相电流 → 两相静止坐标。
- Park 变换两相静止 → 两相旋转坐标。
- SVPWM反变换后输出三相 PWM。
自己写 FOC 工程量很大,入门推荐用 SimpleFOC 库或者 TI/Microchip 的参考设计。
PID 参数整定实操
整定顺序:先 P 后 I 再 D。
- Ki=Kd=0,逐渐加大 Kp 直到输出开始振荡,再退回 70%。
- 加 Ki,消除稳态误差,到刚好不超调为止。
- 加 Kd,抑制超调和振荡。
- 实际系统有摩擦力、有死区,参数要实测,不要套公式。
一个最小可跑骨架
// main loop 1kHz
while (1) {
uint16_t cnt = __HAL_TIM_GET_COUNTER(&htim2);
float speed = (cnt - last_cnt) / dt; // 算实际速度
float target = set_speed;
float out = pid_update(&pid, target, speed, dt);
motor_set_duty((int)out, out > 0 ? 1 : -1);
last_cnt = cnt;
delay_ms(1);
}
PID 不难调,难的是先把编码器读准、PWM 频率选对。这两步错了,怎么调参数都白搭。
跑通这个骨架,下一步就是做多环串级(位置环套速度环),那是机械臂级别的事了。