平衡车PID控制结构

平均PWM:左右轮PWM的平均值,控制的是前进和后退的快慢。(简单来说就是平均PWM单独控制前后)
差速PWM:两个电机PWM的差值,很明显控制的是左右转弯的快慢。(简单来说差速PWM单独控制左右)
速度环与角度环的关系:
角度环(内环)
-
任务:保持车身直立
-
原理:车身前倾→轮子向前转;后倾→向后转
-
输入:俯仰角
-
输出:差分PWM(左右轮差速)
速度环(外环)
-
任务:控制前进/后退/静止
-
原理:检测实际速度,与目标速度比较,调整驱动力
-
输入:编码器速度
-
输出:平均PWM(左右轮同速)
两者如何配合
左轮PWM = 平均PWM + 差分PWM / 2
右轮PWM = 平均PWM - 差分PWM / 2
平均速度 = (左轮速度 + 右轮速度) / 2
差分速度 = 左轮速度 -- 右轮速度

-
角度环负责站稳
-
速度环负责跑多快
-
两者叠加输出给电机,互不干扰
一句话总结
角度环让车不倒,速度环让车听话,两者叠加驱动电机,实现直立行走。
转向环:**控制小车左转和右转。**右边输入的是用户设定的目标转弯速度,如果小车实际走的是直线,测量得到左轮速度和右轮速度绝对一致(差分速度为0)。如果不是0,就说明小车走偏了,转向环会比对目标转弯速度和实际转弯速度。如果向左走偏了,就控制小车往右走。如果向右走偏了,就控制小车往左走。也就是通过控制差分PWM来控制小车左右转弯
举例(转向环):如果没有转向环的闭环控制,那么,你让这个平衡车走直线的时候,左右轮的PWM就可能会不一样(电机差异导致),这就导致了小车可能不能走直线。
加入PID后,互补滤波由原来的1ms调整一次变成了10ms调整一次,所以需要调整α的大小(由0.001变为0.01)。

虽然这样子之后小车的角度测量效果不错,但是后期却出现了小车无论如何直立不了的问题。问题出在了MPU6050上。

这个值是0x06,对应滤波器配置最大,输出数据最平滑,会导致Delay,导致小车无论怎么调都无法保持直立。

我们这里将0x06改成0x00,不使用自带的低通滤波器。

再看采样率分频,不使用自带的滤波器后,陀螺仪时钟的频率是8kHz,那么采样率就是8kHz / (1 + 9)= 800Hz,如果这个值改成7,那么采样率就是8kHz / (1 + 7)= 1kHz(这里的采样率就是MPU6050的刷新频率)。



代码逻辑:
cpp
if (RunFlag)
{
AnglePID.Actual = Angle;
PID_Update(&AnglePID);
AvePWM = -AnglePID.Out;
LeftPWM = AvePWM + DifPWM / 2;
RightPWM = AvePWM - DifPWM / 2;
if (LeftPWM > 100) {LeftPWM = 100;} else if (LeftPWM < -100) {LeftPWM = -100;}
if (RightPWM > 100) {RightPWM = 100;} else if (RightPWM < -100) {RightPWM = -100;}
Motor_SetPWM(1, LeftPWM);
Motor_SetPWM(2, RightPWM);
}
else
{
Motor_SetPWM(1, 0);
Motor_SetPWM(2, 0);
}
之后我们开始调节P、I、D的参数:
cpp
if (BlueSerial_RxFlag == 1)
{
char *Tag = strtok(BlueSerial_RxPacket, ",");
if (strcmp(Tag, "key") == 0) //收到按键数据包
{
char *Name = strtok(NULL, ",");
char *Action = strtok(NULL, ",");
}
else if (strcmp(Tag, "slider") == 0) //滑杆数据包
{
char *Name = strtok(NULL, ",");
char *Value = strtok(NULL, ",");
if (strcmp(Name, "AngleKp") == 0)
{
AnglePID.Kp = atof(Value);
}
else if (strcmp(Name, "AngleKi") == 0)
{
AnglePID.Ki = atof(Value);
}
else if (strcmp(Name, "AngleKd") == 0)
{
AnglePID.Kd = atof(Value);
}
}
else if (strcmp(Tag, "joystick") == 0) //摇杆数据包
{
int8_t LH = atoi(strtok(NULL, ","));
int8_t LV = atoi(strtok(NULL, ","));
int8_t RH = atoi(strtok(NULL, ","));
int8_t RV = atoi(strtok(NULL, ","));
AnglePID.Target = LV / 10;
DifPWM = RH / 2;
}
BlueSerial_RxFlag = 0;
}
BlueSerial_Printf("[plot,%f,%f]", AnglePID.Target, Angle);
这里我们通过蓝牙模块进行调节:
