为什么要自己写电机驱动

市面上有很多现成的电机驱动模块,接好线就能用。但一旦你要做平衡车、机械臂、云台、AGV,就会发现:现成模块要么精度不够、要么不开放接口、要么和你的主控通信协议对不上。这时候就得自己写一套电机控制:PWM 调速、编码器反馈、PID 闭环。

电机控制的本质是"用输出去修正误差"。你让它转到某个位置,它会偏,PID 就是把偏差一点点拉回来的算法。
电机配图:直流电机与驱动板环绕 PWM 波形与编码器圆盘
PWM + 编码器 + 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 占空比。