第一次尝试做平衡小车的时候,我盯着桌上那堆零件发呆了半小时——MPU6050的引脚该怎么接?电机为什么总是一边转一边不转?PID参数调了一整天,小车还是像醉汉一样东倒西歪。如果你也经历过这种绝望,这篇文章就是为你准备的。我们将用最通俗的语言,把平衡小车拆解成可执行的步骤,每个环节都标注了新手最容易翻车的地方。
提示:购买电机时务必确认是否带编码器,无编码器的小车后期速度控制会非常困难
这是我在实验室见过最多的错误接线方式:
| 模块 | 错误接法 | 正确接法 | 后果 |
|---|---|---|---|
| MPU6050 | 直接接5V电源 | 接3.3V并加104电容滤波 | 数据漂移/芯片烧毁 |
| 电机驱动 | 与单片机共用地线 | 采用星型接地或磁珠隔离 | 单片机频繁复位 |
| 编码器 | 未接上拉电阻 | SCK/SDA接4.7K上拉 | 脉冲计数丢失 |
c复制// 正确的MPU6050初始化代码(I2C版本)
void MPU6050_Init(void) {
I2C_WriteByte(MPU6050_ADDR, PWR_MGMT_1, 0x00); // 解除休眠
I2C_WriteByte(MPU6050_ADDR, SMPLRT_DIV, 0x07); // 采样率1kHz
I2C_WriteByte(MPU6050_ADDR, CONFIG, 0x06); // 低通滤波
I2C_WriteByte(MPU6050_ADDR, GYRO_CONFIG, 0x18); // ±2000°/s量程
I2C_WriteByte(MPU6050_ADDR, ACCEL_CONFIG, 0x18);// ±16g量程
}
上电后必须先执行校准流程:
python复制# 简易校准算法示例(实际需在STM32实现)
def calibrate_gyro():
offsets = [0, 0, 0]
for _ in range(200):
data = read_gyro_raw()
offsets = [offsets[i] + data[i] for i in range(3)]
return [x/200 for x in offsets]
经典互补滤波公式看似简单,但参数选择有讲究:
code复制当前角度 = 0.98×(上一角度 + 陀螺仪读数×dt) + 0.02×加速度计角度
注意:不要盲目追求卡尔曼滤波,新手往往调不好状态方程反而更糟
必须严格按照以下顺序调试,否则会陷入无限死循环:
角度环单独调试:先让小车能勉强站立
加入速度环:抑制角度环的长期漂移
位置环(可选):实现定点停车
这是我总结的"土方法"调试流程:
c复制// 增量式PID实现(抗积分饱和)
typedef struct {
float Kp, Ki, Kd;
float last_error, prev_error;
} PID;
float PID_Update(PID* pid, float target, float feedback) {
float error = target - feedback;
float delta = pid->Kp*(error - pid->last_error)
+ pid->Ki*error
+ pid->Kd*(error - 2*pid->last_error + pid->prev_error);
pid->prev_error = pid->last_error;
pid->last_error = error;
return delta;
}
当小车出现以下症状时,可以这样诊断:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 上电瞬间翻倒 | 电机极性接反 | 交换电机线序 |
| 周期性前后摇摆 | Kp过大或Kd过小 | 降低Kp并增大Kd |
| 向一侧缓慢倾斜 | 机械结构不对称 | 调整重心或增加配重 |
| 电机发热严重 | PWM频率太低(<1kHz) | 调整定时器频率到5-10kHz |
滑动平均滤波:适用于编码器速度计算
c复制#define FILTER_SIZE 5
float speed_filter[FILTER_SIZE];
float moving_average(float new_val) {
static int index = 0;
speed_filter[index] = new_val;
index = (index + 1) % FILTER_SIZE;
float sum = 0;
for(int i=0; i<FILTER_SIZE; i++) {
sum += speed_filter[i];
}
return sum / FILTER_SIZE;
}
低通滤波:消除高频噪声
c复制float low_pass(float new_val, float old_val, float alpha) {
return alpha * new_val + (1 - alpha) * old_val;
}
中值滤波:应对突发干扰
推荐使用USART+HMI工具的方案:
c复制typedef struct {
float angle;
float speed;
PID_params pid;
} DebugData;
最后分享一个血泪教训:永远在电机电源线上加磁环,我的第一块STM32就是因为电机反电动势烧毁了串口芯片。现在我的小车已经能顶着书本稳定站立了——这可能是最有成就感的毕业设计。