目录
- 0. 关于本项目
- 1. 硬件
-
- ??1.1 器件购买
- ??1.2 PCB
- ??1.3 车模
- 2. 算法
-
- ??2.1 PID算法
-
- ??2.1.1 理论分析
- ??2.1.2 调参经验
- ??2.2 Mahony算法
-
- ??2.2.1 IMU(以MPU6050举例)
- ??2.2.2 算法实现
- 3. 程序逻辑
- 4. 补充
0. 关于本项目
本项目使用STM32F103C8T6
作为主控,Keil5
开发,Mahony
用算法解决姿态的平衡车。项目中给出了MPU6050
、ICM20602
、ICM42605
三种主流IMU驱动。目前直立平衡只实现,设计设计增加了BlueTooth模块、手机遥控部分仍在开发中(新文件夹) (°ー°〃)
第一个前提知识是:
- Keil5的使用
- STM32有一定了解
- 使用电机驱动的原理
1. 硬件
??1.1 器件购买
要做一辆平衡车,需要使用的硬件材料有:轮子、带编码器的直流电机、电机排线、面包板、杜邦线、STM32.稳压模块、蓝牙模块、电机驱动模块、陀螺仪模块、超声波模块、电池、底盘、螺栓、铜柱、固定轧带、开关等。以下是材料表:
类型 | 链接 | 数量 |
---|---|---|
MG513P20_12V 电机 |
直流减速电机带光电霍尔编码器码盘测速 | 1 |
电池 | 2S锂电池和充电器 | 1 |
5V DCDC 稳压 |
DCDC降压模块 12V转5V3A | 1 |
LCD ST7789主控 240×240 |
1.3寸ips TFT显示屏ips液晶屏 | 1 |
ID | 名称 | 型号 | 数量 |
---|---|---|---|
1 | STM32最小系统板 | STM32F103C8T6 | 1 |
2 | 电机驱动 | TB6612FNG | 1 |
3 | 陀螺仪 | MPU6050或ICM42605、20602 | 1 |
4 | 蓝牙 | HC-07 | 1 |
5 | 10uF钽电容 | 6.3V,3528,3.52.82.1mm | 1 |
6 | 20uF钽电容 | 6.3V,3528,3.52.82.1mm | 1 |
7 | 电机插座,布线 | 6P-2.54mm 10cm排线 | 2 |
8 | T型头 | 航模电池T型头 | 1 |
9 | AMS1117-3.3V | AMS1117-3.3V贴片 | 1 |
10 | 开关 | 摇臂开关 | 2 |
11 | 铜柱 | 六角3mm | 若干 |
12 | 扎带 | 3.6mm | 若干 |
??1.2 PCB
目前是第一版,画的很好,用的都是插件,线也很乱 (:3」∠)。以后会更新PCB(计划用贴片代替,体积会更小)
??1.3 车模
SolidWorks简单的底板尺寸如图所示(mm),用3D打印机制造,源文件在学校电脑上,回学校后上传。
2. 算法
??2.1 PID算法
??2.1.1 理论分析
本项目使汽车平衡最重要的两个环是直立环和速度环,通过叠加获得电机的最终输出,这也是绝大多数在线汽车平衡控制方法。
- 我们首先使用传统的速度负反馈算法来想象它。首先,我们给出了一个目标速度值,因为汽车在直立控制的作用下,汽车向前倾斜以获得加速度,车轮需要向后移动,这样汽车的速度就会下降。因为这是负反馈,在速度下降后,速度控制的偏差增加,汽车向前倾斜的角度增加,所以重复,汽车就会倒下。
- 为了确保直立控制的优先级,我们将速度控制放在直立控制的前面,即速度控制调整的结果只是改变直立控制的目标值。因为根据经验,汽车的运行速度与汽车的倾角有关。例如,为了提高汽车的前进速度,有必要增加汽车的前倾角度。倾斜角度增加后,车轮需要在直立控制的作用下向前移动。因此,直立环
Kp
后乘值不是当前角度-机械中值
,这是我们对速度的期望。
a 1 = K p ? ( θ ? a 2 ) K d ? θ ′ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ① 直 立 环 a_1=Kp*(\theta-a_2) Kd*\theta' \qquad········································①直立环
a 2 = K p 1 ∗ E r r s p e e d + K i 1 ∗ ∑ E r r s p e e d ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ② 速 度 环 a_2=Kp_1*Err_{speed}+Ki_1*\sum Err_{speed}\space \qquad·····························②速度环 a2=Kp1∗Errspeed+Ki1∗∑Errspeed ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅②速度环
a = K p ∗ ( θ ) + K d ∗ θ ′ − K p [ k p 1 ∗ E r r s p e e d + k i 1 ∗ ∑ E r r s p e e d ] ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ③ 最 终 式 a=Kp*(\theta)+Kd*\theta'-Kp[kp_1*Err_{speed}+ki_1*\sum Err_{speed}] \qquad········③最终式 a=Kp∗(θ)+Kd∗θ′−Kp[kp1∗Errspeed+ki1∗∑Errspeed]⋅⋅⋅⋅⋅⋅⋅⋅③最终式
因此在代码实现上我们就可以实现两个环的直接相加或相减,在TIM3
定时器中10ms
一个周期进行控制。
Balance_PID_Result = Position_PID_Cal(&Balance_PID, imu.Roll + 0.3f);
if (Time_GAP_20ms) Velocity_PID_Result = Position_PID_Cal(&Velocity_PID, Velocity);
PID_Result = Balance_PID_Result + Velocity_PID_Result;
Set_Motor_Speed(PID_Result, PID_Result);
2.1.2 调参经验
根据上边的分析我们只需要分别调整直立环的Kp,kd
和速度环的Kp,Ki
- 对于直立环的
Kp
,是调整最方便观察现象的,太小时小车没有足够的恢复力,太大时小车会在中值附近大幅震荡,调整到一个略微震荡的值即可 - 直立环的
Kd
作用是减小低频振荡,但Kd
太大小车又会造成高频振动,从小到大增大Kd
,直到小车出现小幅高频振荡 - 直立环调整结束后小车可以平衡,但受到扰动便会往一个方向疯跑,现在引入直立环
- 网上查到的资料对于速度环
Ki = Kp/200
,但在我的实际调整中最终确定了Ki = Kp/100
,这个看自己小车了,可以先按照Ki = Kp/200
去调 - 先将直立环
Kp,Kd
同时×0.8。调整速度环Kp
,速度环Kp
越大,小车便越不会出现向一个方向狂奔的情况(因为速度被速度环控住了),但会减弱直立环的控制效果,因此调整到一个车受到干扰会摇摇晃晃停下的一个状态。 - 摇摇晃晃的原因就是因为速度环
Kp
太大,回调速度环Kp
,并且增大直立环Kd
(想想Kd
的作用是什么呢?)
2.2 Mahony算法
2.2.1 IMU(以MPU6050举例)
MPU6050
是一个集成了陀螺仪和加速度计的传感器,它能输出在直角坐标系下的x,y,z
轴的角速度和加速度数据。
陀螺仪输出的格式为:绕x
轴的旋转角速度,绕y
轴的角速度,绕z
轴的角速度(分别称为roll
角速度,pitch角速度和yaw
角速度)。
加速度计输出的格式为:x
轴的加速度,y
轴的加速度,z
轴的加速度。
另外还需要关注传感器的其他参数如:
- 陀螺仪的量程:
eg.±2000dps
- 加速度计的量程:
eg.±2g
ADC
转换精度为16bit
- 传感器采样率
4-1000hz:eg.1000hz
我们从IMU那就得到了陀螺仪数据gx,gy,gz
,加速度数据az,ay,az
螺仪转换精度
采样率就是数据的更新率,也就是我们每次读取数据的频率。
首先将陀螺仪的数据转换成角度,这里封装成一个函数
2.2.2 算法实现
static void Get_IMU_Values(float *values)
{
int16_t gyro[3],acc[3];
IMU_readGyro_Acc(&gyro[0],&acc[0]);
for(int i=0;i<3;i++)
{
//gyro range +-2000; adc accuracy 2^16=65536; 65536/4000=16.4;
values[i]=((float) gyro[i])/16.4f;
values[3+i]=(float) acc[i];
}
}
void IMU_Update(void)
{
static float q[4];
float Values[6];
Get_IMU_Values(Values);
//change angle to radian,the calculate the imu with Mahony
MahonyAHRSupdateIMU(Values[0] * PI/180, Values[1] * PI/180, Values[2] * PI/180,
Values[3], Values[4], Values[5]);
//save Quaternion
q[0] = q0;
q[1] = q1;
q[2] = q2;
q[3] = q3;
imu.ax = Values[3];
imu.ay = Values[4];
imu.az = Values[5];
imu.Pitch_v = Values[0];
imu.Roll_v = Values[1];
imu.Yaw_v = Values[2];
//calculate the imu angle with quaternion
imu.Roll = (atan2(2.0f*(q[0]*q[1] + q[2]*q[3]),1 - 2.0f*(q[1]*q[1] + q[2]*q[2])))* 180/PI;
imu.Pitch = -safe_asin(2.0f*(q[0]*q[2] - q[1]*q[3]))* 180/PI;
imu.Yaw = -atan2(2 * q1 * q2 + 2 * q0 * q3, -2 * q2*q2 - 2 * q3 * q3 + 1)* 180/PI;
}
逻辑上,首先用加速度计校准陀螺仪,方式是通过计算当前四元数姿态下的重力分量,与加速度计的重力分量作叉积,得到误差。 对误差作P(比例)和I(积分)运算后加到陀螺仪角速度上。最终由角速度计算新的四元数。
void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) {
float recipNorm;
float halfvx, halfvy, halfvz; //1/2 重力分量
float halfex, halfey, halfez; //1/2 重力误差
float qa, qb, qc;
//对加速度数据归一化
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;
// 由四元数计算重力分量
halfvx = q1 * q3 - q0 * q2;
halfvy = q0 * q1 + q2 * q3;
halfvz = q0 * q0 - 0.5f + q3 * q3;
// 将四元数重力分量 与 加速度计重力分量 作叉积 得到误差
halfex = (ay * halfvz - az * halfvy);
halfey = (az * halfvx - ax * halfvz);
halfez = (ax * halfvy - ay * halfvx);
//对误差作积分
integralFBx += twoKi * halfex * (1.0f / sampleFreq);
integralFBy += twoKi * halfey * (1.0f / sampleFreq);
integralFBz += twoKi * halfez * (1.0f / sampleFreq);
//反馈到角速度
gx += integralFBx; gy += integralFBy; gz += integralFBz;
// 对误差作比例运算并反馈
gx += twoKp * halfex; gy += twoKp * halfey; gz += twoKp * halfez;
// 计算1/2 dt
gx *= (0.5f * (1.0f / sampleFreq));
gy *= (0.5f * (1.0f / sampleFreq));
gz *= (0.5f * (1.0f / sampleFreq));
qa = q0; qb = q1; qc = q2;
// 更新四元数
q0 += (-qb * gx - qc * gy - q3 * gz);
q1 += (qa * gx + qc * gz - q3 * gy);
q2 += (qa * gy - qb * gz + q3 * gx);
q3 += (qa * gz + qb * gy - qc * gx);
// 四元数归一化
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm; q1 *= recipNorm; q2 *= recipNorm; q3 *= recipNorm;
}
在Mahony
中提供了包含磁场数据的融合函数:
> void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az,
> float mx, float my, float mz);
3. 程序逻辑
首先看main函数:
int main(void)
{
All_HardWare_init();
while (1)
{
Protect_Check();
LED_show_working();
LCD_show_Brief_info();
}
}
代码都封装在了函数里,因此主控的main函数非常简单。All_HardWare_init();
包含了所有硬件、片内资源的初始化。
TIM3_Int_Init(99, 7199); // 72M ÷7200 ÷100 = 10 ms
几秒过后再初始化PID控制器
PID_init(); //直立环,速度环PID控制器初始化
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);