PID(Proportional-Integral-Derivative,比例-积分-微分)是工业控制中最经典、应用最广泛的闭环控制算法。它的核心思想是:根据“目标值”和“当前测量值”之间的误差,通过一套数学公式计算出控制量,让系统快速、稳定地逼近目标值。
PID 的名字来源于它的三个控制环节:P(比例)、I(积分)、D(微分)
. P(比例,Proportional):“看现在差多少”
逻辑:误差越大,输出控制量越大。
公式:
输出 = 误差 × Kp作用:它是PID的主心骨,负责“推”系统往目标值靠。
缺点:如果Kp太大,系统会“刹不住车”,在目标值附近反复震荡;如果Kp太小,反应又太慢。
2. I(积分,Integral):“记住过去积累的差”
逻辑:把过去每次的误差都累加起来,用累积值去消除“最后的倔强”。
公式:
输出 = 历史误差累加值 × Ki作用:专门解决稳态误差。比如系统差最后2度就到目标了,P已经推不动了(输出太小),这时候I就会像“老黄牛”一样把误差慢慢累积,强行把最后那2度推平。
缺点:如果Ki太大,会导致“积分饱和”,系统反应过度,产生严重超调。
3. D(微分,Derivative):“预测未来会差多少”
逻辑:观察误差变化的速度(这次误差比上次大还是小),提前进行“反向刹车”。
公式:
输出 = (当前误差 - 上次误差) × Kd作用:抑制震荡。当系统快速冲向目标时,D能提前感受到“冲过头”的风险,给一个反向的力踩刹车。
缺点:对传感器噪声极度敏感,会放大毛刺导致系统抖动
一个变量单独控制前后,一个变量单独控制左右
平均pwm控制前后,差分pwm控制左右
差分pwm给正值往右转,给负值往左转
(内环)角度环通过对比速度环提供的目标俯仰角和姿态检测给的俯仰角,计算得到一个输出控制量,作用于平均pwm
目标俯仰角为0,小车直立;大于0前进,小于0后退
直接操作俯仰角前进的话,小车会做加速运动,保持平衡比较困难
(外环)速度环会动态改变倾斜角,使得实际速度稳定在目标速度,一开始小车角度过大,加速运动时,电机编码器计算出平均速度后告诉速度环,使速度环输出俯仰角变小,直到小车做匀速运动,达到目标速度
即使左右轮都输入相同的pwm,也有可能走不了直线,因为两个轮子的差异。所以需要转向环的闭环控制
用户给转向环速度是0,就是直走。即使两个轮子转速有差异,转向环也会动态计算,通过输出差分pwm来弥补这个差异
/*角度环PID参数*/ PID_t AnglePID = { .Kp = 3, .Ki = 0.1, .Kd = 3, .OutMax = 100, .OutMin = -100, .OutOffset = 3, };outmax与outmin的值和电机pwm一样
一开始发现小车不能直立
config寄存器
最低三位可以配置MPU6050自带的一个低通滤波器,可以让输出数据更平滑,但缺点是会导致延迟
如果MPU6050自带的低通滤波设置得太强,导致数据延迟过高,平衡车会“反应迟钝”,最终导致剧烈摇晃甚至直接失控摔倒。
内环(角度环)反应滞后:车身已经倾斜了,传感器还没把真实角度传过来,等角度环反应过来时,惯性已经让车冲过头了,小车会像磕了药一样前后疯狂震荡。
内外环打架:用滞后且过时的加速度计数据去修正陀螺仪的漂移,会导致姿态角在真实值附近飘忽不定,小车呈现“软绵绵”的摇晃感,永远找不到平衡点。
速度环过冲:速度反馈慢半拍,小车实际速度已经达标了,速度环还在输出前倾角,导致小车超速后又猛刹车,表现出一顿一顿的狂奔
传感器采样要迅速准确,执行器输出要迅速且强劲
角度环pid控制
if (RunCount0 >= ANGLE_T) //每隔ANGLE_T定义的时间执行一次 { RunCount0 = 0; /*角度环PID计算*/ AnglePID.Actual = Angle; PID_Update(&AnglePID); AvePWM = -AnglePID.Out; //角度环输出作用于平均PWM /*平均PWM和差分PWM,合成左轮PWM和右轮PWM*/ PWML = AvePWM + DifPWM; PWMR = AvePWM - DifPWM; /*PWM限幅*/ if (PWML > 100) {PWML = 100;} else if (PWML < - 100) {PWML = -100;} if (PWMR > 100) {PWMR = 100;} else if (PWMR < - 100) {PWMR = -100;} /*PWM输出至电机*/ Motor_SetPWM(1, PWML); Motor_SetPWM(2, PWMR); }kp过大会超调抖动
姿态传感器由于安装误差等原因,目标中心角度有偏差
校准
/*中心角度校准*/ AngleAcc_Cali = AngleAcc + AngleAcc_Offset;为了防止小车第二次启动的时候发疯,把pid清0
void PID_Init(PID_t *p) { p->Target = 0; p->Actual = 0; p->Actual1 = 0; p->Out = 0; p->Error0 = 0; p->Error1 = 0; p->POut = 0; p->IOut = 0; p->DOut = 0; }速度环代码编写
定义全局变量
/*速度环PID参数*/ PID_t SpeedPID = { .Kp = 2, .Ki = 0.05, .Kd = 0, .OutMax = 10, .OutMin = -10, };角度环直接输出给电机pwm,限幅大
速度环输出给内环(角度环)的“目标倾角,值不可过大
内环是10ms,外环调控周期必须大于内环,给50ms
读取编码器,50ms
if (SensorCount1 >= SPEED_T) //每隔SPEED_T定义的时间执行一次 { SensorCount1 = 0; /*通过编码器获取电机旋转速度*/ SpeedLeft = Encoder_Get(1) / 408.0 / (SPEED_T / 1000.0); SpeedRight = Encoder_Get(2) / 408.0 / (SPEED_T / 1000.0); /*获取均速和差速*/ AveSpeed = (SpeedLeft + SpeedRight) / 2.0; DifSpeed = SpeedLeft - SpeedRight; }速度环
/*速度环PID计算*/ SpeedPID.Actual = AveSpeed; PID_Update(&SpeedPID); AnglePID.Target = SpeedPID.Out; //速度环输出作用于角度环输入速度环kp给到4会出现明显震荡
转向环
/*转向环PID参数*/ PID_t TurnPID = { .Kp = 2, .Ki = 1, .Kd = 0, .OutMax = 30, .OutMin = -30, };主要ki的影响比较大
/*转向环PID计算*/ TurnPID.Actual = DifSpeed; PID_Update(&TurnPID); DifPWM = TurnPID.Out; //转向环输出作用于差分PWM