基于STM32的平衡小车学习笔记
这个还是个记录帖子,纯当做学习笔记来写,没有任何技术含量,喵~
直立环:
直立环是保证小车平衡的第一步,采用的是PD算法(为什么要采用PD算法我还是不太明白,看大家都是这样做的)买的编码器电机,商家附赠的PID开发指南也说这里是增量PID(更不明白了)
首先要找出其机械中值,先根据小车直立与倒下,观察发生变化的欧拉角(我的是roll角),先让小车濒临倒下,观察倒下瞬间的roll角,另一个倒下的方向按照同样的方法再记录一次,两者相加再÷2得出的数值就是机械中值,同时机械中值也是作为直立环中的“期望角度”,真实的角度就是实际根据MPU6050测出的实时的roll角
至于PD算法中的D,是微分的意思,意思就是对roll角进行微分,roll角的微分,就是角速度,正好可以由MPU6050读出,而在这里,roll对应的微分就是x轴的角速度,所以这里直接用Kd * GyroX,当然看到其他的是Kd * 两次误差的值,也可以
根据PID算法中的PD的概念,可以简单得出直立环的函数如下:
int ZhiLiHuan_Control(float MiddleAngle, float RealAngle, float gyro_X)
{
int OutPut;
OutPut = ZhiLiHuan_Kp * (RealAngle - MiddleAngle) + ZhiLiHuan_Kd * gyro_X;
return OutPut;
}
速度环:
借用roll角来维持平衡这个很符合大家的常理,但是为什么还需要速度环?当初自己非常不理解,看到有个帖子说,只靠直立环,小车是无法达到平衡的,是因为直立环只可以保证当平衡的角度出现偏差的时候,小车需要通过直立环保持平衡,但是忽略了该以一个什么样的速度去这样做
想象一下平衡小车就像一个站在滑板上的人。如果只靠角度控制(就像这个人只看自己是不是快要摔倒来调整身体),当滑板开始滑动时,他可能会手忙脚乱。
但是如果有速度环,就相当于这个人不仅看自己是不是要摔倒,还会关注滑板滑动的速度。当滑板滑动太快,他就会主动控制速度让它慢下来。例如,平衡小车在倾斜后会因为重力作用开始加速运动,速度环能在这个时候 “拉住” 小车,不让它因为速度越来越快而失控。
从另一个角度看,没有速度环,小车可能会像一个钟摆一样,在平衡位置附近来回大幅度摆动。而速度环可以像一个阻尼器一样,减少这种摆动,使小车更快地稳定在平衡位置。
平衡小车的速度环的目的是为了抵消直立环产生的速度,让小车保持平衡时的同时转速为0(理论值)。速度的控制关键点在于消除静差,因此需要引入积分运算,而不需要微分运算
速度环采用的是PI控制器,既然有P,就说明参数需要有速度,由于当小车平衡的时候(理想状态),小车的轮子速度是0,所以速度环中,我们的期望值就是0,实际速度是将左右两个编码器的数值相加再除以2
这里比较特殊的就是低通滤波,为什么需要用低通滤波,查了一下,感觉有个非常好的说法:
因为编码器只能用离散值采样连续值,会产生量化误差,导致采集的速度发生突变,不得不来回校准。由于噪声和干扰的存在,速度信号可能会出现波动,导致电机的实际速度与设定速度之间存在偏差。低通滤波代码也很简单,就一行代码的事情
积分限幅:了解过一点PID的都知道,由于单片机使用的都是些离散的数据,所以这里都是用累加的方法代替真实意义上的积分概念,由于PID控制在我的代码里面是每隔10ms执行一次,所以积分如果放着不管,1s内,哪怕是极小的小误差,最终都会让这个“累加”的结果的数值变得超级大,除非系统完全没有误差(想想都不可能),所以可以看做积分是一直作用的
下面就是我的代码:
int SpeedHuan_Control(int Target_Speed,int encoder_L,int encoder_R)
{
static int Error_DiTong_Last; //前次低通滤波输出值
static float DiTong_Rate = 0.7; //低通滤波a值,取经验值为0.7
int Error_Now, Err_LowOut, OutPut; //本次速度误差值,本次低通滤波后的误差值,速度环输出值
SpeedHuan_Ki = SpeedHuan_Kp / 200; //ki取经验值 为 kp/200
//1、计算偏差值
Error_Now = (encoder_L + encoder_R) - Target_Speed;
//2、对本次误差值进行低通滤波
Err_LowOut = (1 - DiTong_Rate) * Error_Now + DiTong_Rate * Error_DiTong_Last;
Error_DiTong_Last = Err_LowOut; //将本次低通滤波输出值记录下来
//3、积分
Encoder_Sum += Err_LowOut; //编码器积分值等于其自身累加
//4、积分限幅(-20000~20000)
if(Encoder_Sum > 20000) Encoder_Sum = 20000;
if(Encoder_Sum < -20000) Encoder_Sum = -20000;
//5、速度环计算
OutPut = SpeedHuan_Kp * Err_LowOut + SpeedHuan_Ki * Encoder_Sum;//Kp * 速度偏差滤波值 + Ki * 真实速度积分
return OutPut;
}
转向环:
转向环顾名思义,就是用来控制转向的,虽然说有了前面的直立环 + 速度环的作用,只要可以耐心调好PID参数,小车是可以平衡的,但是你会发现小车会趁你不注意一点点的偏离原来的方向,航向突然斜了,如果你不在意,是可以不需要这个转向环的,不过看大家都弄了,所以我也加入了转向环
转向环我这里只用了一个P算法,目标转向当然是0,我不想让他乱跑别的方向,实际的方向可以说就是偏航角Yaw的数值嘛,这个没啥可说的,感觉是最简单的
int TurnningHuan_Control(float Yaw, int TargetSpeed_turn)
{
int OutPut;
OutPut = TurnningHuan_Kp * (Yaw - TargetSpeed_turn); //将偏航角减去目标转向值得到误差,再与Kp相乘
return OutPut;
}
串级PID:
这里附上商家附赠的PID开发指南里面的一张图片:
根据这张图片,我们可以得知平衡小车采用的并非普通的PID算法,而是一种“串级PID”
串级PID不理解没关系,但是根据图片,我们可以得知对于直立环,其输入不止来源于其设定的理想值(这里是机械中值),还包括速度环的输出
所以我们的核心控制函数中,我们应该先算出速度环,将速度环的输出值,再加上直立环的理想输入值(机械中值),作为直立环的输入,由此将速度环与直立环组合,构成串级PID,我的代码如下:
SpeedHuan_Output = SpeedHuan_Control(Target_Speed, Encoder_Left, Encoder_Right); //PI速度环控制器输出角度值
ZhiLiHuan_Output = ZhiLiHuan_Control(SpeedHuan_Output + Middle_Angle, Roll, gyrox);//PD直立环输出PWM Med_Angle 即机械中值,加上平衡时角度偏移量用以修正
TurnningHuan_Output = TurnningHuan_Control(Yaw, Target_turn);
如果不考虑转向环,直立环的输出结果就可以直接作为PID控制后的PWM输入值,直接作用给编码器电机即可
如果说加入转向环,首先我们知道,小车转向的原理就是两边的轮子分别向相反的方向转动,小车即可实现转向,代码如下:
Motor1 = PID_out - TurnningHuan_Output; //左右电机的PWM 一边减去一边加上 便形成了差速转向
Motor2 = PID_out + TurnningHuan_Output;
至于哪边是加哪边是减(Motor1应该是PID-转向环还是PID+转向环),可以根据实际情况修改嘛,加入开启后小车偏的越来越厉害,就反过来加减即可
最后再加上速度限幅与抬起检测
抬起检测:
相信大家都遇到过,就是当我们用手拿起平衡小车的时候,小车会猛烈加速,直到达到我们设定的速度最大值,为什么会这样?原因如下:由于小车被拿起,直立环因为没有地面支撑,而无法正常发挥修正角度的作用,直立环没有办法像在地面上那样通过调整电机扭矩来修正角度(没有地面摩擦,转来转去都无法直立)
紧接着,就是速度环的误差累计(积分作用),速度环由于误差一直存在而不断累积,这种累计误差作用给电机就会使电机达到最大转速,造就了用手拿起平衡小车的时候,小车会猛烈加速的现象
可以通过检测Z轴加速度是否发生变化来判断是否被拿起,毕竟正常情况下小车都是在地面工作的Z轴加速度一般不会变化的(我懒得写了,所以不放代码,这里只记录一种思路)
摔倒检测:
如果小车一次性受到极大的外力,小车就算PID调的很好,也会倒下,我这里使用了判断Roll角的变化来判断极端情况,假设小车摔倒了,就让点击速度设置为0,同时消除积分累加(这个非常重要!!!!,不然等人为扶正后小车由于没消除积分累加,会突然加速又倒下了)
if(Roll > 25 || Roll < -25)
{
Motor_SetSpeed(0, 0);
Encoder_Sum = 0;
}
总结:
自己石粒很菜,是个飞舞,就算自己跟着教程来,一步一步构建代码,硬件也是自己买的(甚至有几次都买错型号的了),PCB也是自己一步步搭建的(也因为自己技术不成熟,打板了4次才成功,前几次PCB是自己设计的,但是发现接上电源后不知道为什么供电很差,供电效果都不太好)自己焊接的(最开始焊接技术不怎么样,焊坏了好几个板子),做出来也感觉非常不容易
不过我自己也从这次项目中收获到了很多,也明白了自己还有很多需要学习,未来再好好努力吧!争取学习完FreeRTOS也能做一个小项目(暂定是基于STM32的智能手表),然后就可以准备找实习了,整个大学四年也快要结束了......加油加油!!