本帖最后由 lianqiang 于 2016-2-18 15:17 编辑
先上视频:
">
一:设计目标 一直想做一台平衡车玩,但是怕自己能力不够一直不敢动手去做,中间不断的收集各种资料,吸取别人的经验,了解PID算法等等。正好大三结课比较早,感觉自己已经了解大概了就开始动手做。
先期的目标是使平衡车站立平稳,再就是使用2.4G遥控控制,最后添加其他的功能——自动巡航等等。
功能实现框图
平衡车成品图
二硬件设计首先确定平衡车主控,因为是第一次制作平衡车,选一款熟悉的开发平台十分重要,本人对于STM32软件开发也比较熟悉,并且以前申请得到一个NUCLEOF411板,主频高达100MHz,集成SPI接口等,完全能够胜任平衡车的要求。 接下来是加速度陀螺仪传感器的选择,从价格开源方面来说MPU6050无疑是初次接触平衡车必选的模块,一块大概12元,经过测试,配合卡尔曼滤波得到的角度曲线还是比较好的。 因为贪便宜选择日本纳米奇(namiki)直流减速电机后来收到货才发现电机转速太慢,建议电机转速大一点的,不然到后来电机平衡常数不好调,而且小车的机动性能比较好。
再来就是电机驱动板,看过不少平衡车的制作方案,有的用L298N,也有用TB6612的,但是作为TI的粉丝,个人感觉TI的电机驱动芯片做的相当好,作为个人DIY感谢TI的样片申请制度,为学生党省了不少钱。以前用过TI的集成无刷驱动芯片DRV11873做硬盘时钟,也没用过TI的有刷芯片,去他的官网果然有一款芯片非常适合我的设计方案——DRV8881,电路简单,驱动方式也十分方便。 因为DRV8881的背面有散热PAD,但是市面上买的TTSOP转接板基本没有散热块。所以我采用反面焊接的方式,你也可以加焊一根粗一点的铜线加大散热,不过TI的芯片一般发热极少。
再来就是无线传输模块,因为在调试PID参数时,参数的整定是最耗时间的,基本都在几十次左右,如果通过重编译下载程序对于芯片的寿命也是一种影响,如果通过串口更改,线对于车的运动也会产生干扰。所以最好是通过无线传输进行PID整定。而且对于后续的遥控控制也必须采用无线传输。在以前的一个项目中使用过NRF24L01这个模块,对于它的传输机制也比较了解,故采用NRF24L01模块。 再来就是电源的选择,看过其他方案的平衡车,一般采用12V3S航模电池,但是如果采用航模电池就得用特定的充电器,这样总的成本比较大,所以我采用DC-DC升压模块加移动电源,方便充电也容易维护。 本方案采用双电源设计,原来使用一个12v电源,加7805降压给主控板,但是12v升压板的电源输出对光电编码器的影响比较大,所以采用双电源。而且在一次测试的过程中插错线把NUCLEO板给烧了 (好伤心的说),这更加坚定了我使用双电源的决心。 以上就是主要的硬件部分,在制作过程主要注意以下几点, 一.12V和5V电源分离并固定,做好防反接。 二.做好引线的标记,防止接错。 三.将各个模块固定结实,防止运动的时候脱落。
以上都是惨痛的经验希望大家不要放同样的错误。
三:软件主体 以下介绍一下编写平衡车的主要过程 一MPU6050的DMP移植我采用的是平衡小车之家的DMP文件进行的移植。
将红色框中的文件移到工程文件中,因为他的工程是基于103创建的,而我是f411所以要修改其中的头文件,延时函数,IIC接口时钟等等。
对于在文件下的.h文件的包涵如果出现错误可以如下操作
#include "filter/filter.h"
#include "MiniBalance/MiniBalance.h"
#include "ioi2c.h"
移植完成后发现的问题:
如果按照原来文件中的读法利用void Read_DMP(void)这个函数在main函数中读取角度,加速度会存在尖峰。
void Read_DMP(void)
{
unsigned long sensor_timestamp;
unsigned char more;
long quat[4];
dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
if (sensors & INV_WXYZ_QUAT )
{
q0=quat[0] / q30;
q1=quat[1] / q30;
q2=quat[2] / q30;
q3=quat[3] / q30;
Pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;
}
}
[size=19.09090805053711px]
加速度(visualscope) 角度(visualscope)
而如果将MPU6050的INT脚引出利用中断读取就不会出现尖峰。但是读取的角度的波形不如采用定时器读取时平滑,这我一直没搞懂,因为采用外部中断读取的频率大概也在200Hz左右但是读取的角度在PD直立环时存在很大的延迟,反应非常慢。车的抖动非常大。
所以我采用他给出的第二个方案——void Get_Angle(u8 way)这个函数进行读取加速度和角度。取得了不错的效果。
void Get_Angle(u8 way)
{
float Accel_Y,Accel_X,Accel_Z,Gyro_Y,Gyro_Z;
if(way==1) //DMP没有涉及到严格的时序问题,在主函数读取
{
Read_DMP();
Angle_Balance=Pitch; //===更新平衡倾角
GyroLimit(gyro[1]);
Gyro_Balance=LastGyro; //===更新平衡角速度
Gyro_Turn=gyro[2]; //===更新转向角速度
}
else
{
Gyro_Y=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_L); //读取Y轴陀螺仪
Gyro_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_L); //读取Z轴陀螺仪
Accel_X=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_L); //读取X轴加速度记
Accel_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_L); //读取Z轴加速度记
if(Gyro_Y>32768) Gyro_Y-=65536; //数据类型转换
if(Gyro_Z>32768) Gyro_Z-=65536; //数据类型转换
if(Accel_X>32768) Accel_X-=65536; //数据类型转换
if(Accel_Z>32768) Accel_Z-=65536; //数据类型转换
Gyro_Balance=-Gyro_Y; //更新平衡角速度
Accel_Y=atan2(Accel_X,Accel_Z)*180/PI; //计算与地面的夹角
Gyro_Y=Gyro_Y/16.4; //陀螺仪量程转换
if(Way_Angle==2) Kalman_Filter(Accel_Y,-Gyro_Y);//卡尔曼滤波
else if(Way_Angle==3) Yijielvbo(Accel_Y,-Gyro_Y); //互补滤波
Angle_Balance=angle; //更新平衡倾角
Gyro_Turn=Gyro_Z; //更新转向角速度
}
}
[size=19.09090805053711px]
角速度(visualscope) 角度(visualscope)
二2.4G无线传输
无线传输发送模块采用51单片机,电脑将PID参数发送到51单片机,51单片机通过一定的协议发送给STM32进行处理。
共11个字节 三电机编码器器
因为STM32集成编码器电路,通过简单的设置就可以使用。
void EncoderConfig(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// NVIC_InitTypeDef NVIC_InitStructure;
//TIM3 clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC , ENABLE);
//TIM4 clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB , ENABLE);
//TIM3 GPIO
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM3);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//TIM4 GPIO
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Timer3 configuration in Encoder mode */
TIM_DeInit(TIM3);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler =0;
TIM_TimeBaseStructure.TIM_Period = 8000;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter =0;
// TIM_ICInitStructure.TIM_Channel=TIM_Channel_1|TIM_Channel_2;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// Clear all pending interrupts
// TIM_ClearFlag(TIM3, TIM_FLAG_Update);
// TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
//Reset counter
TIM_SetCounter(TIM3,5000);
TIM_Cmd(TIM3, ENABLE);
/* Timer4 configuration in Encoder mode */
TIM_DeInit(TIM4);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler =0;
TIM_TimeBaseStructure.TIM_Period = 8000;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter =0;
// TIM_ICInitStructure.TIM_Channel=TIM_Channel_1|TIM_Channel_2;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
// Clear all pending interrupts
// TIM_ClearFlag(TIM4, TIM_FLAG_Update);
// TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);
//Reset counter
TIM_SetCounter(TIM4,5000);
TIM_Cmd(TIM4, ENABLE);
}
四PID算法编写
一直立环PD
int16_t balance(float Angle,float Gyro)
{
float Bias;
int16_t balance;
Bias=Angle+0; //===求出平衡的角度中值 和机械相关 +0意味着身重中心在0度附近 如果身重中心在5度附近 那就应该减去5
balance=140*Bias+Gyro*0.9;//===计算平衡控制的电机PWM PD控制 215 0.75
return balance;
}
二速度环PI环
int16_t velocity(void)
{
int16_t Encoder_Least,Velocity;
int8_t Movement;
//=============遥控前进后退部分=======================//
if(Qian_Flag==1) Movement=50; //===如果前进标志位置1 位移为
else if(Hou_Flag==1) Movement=-50; //===如果后退标志位置1 位移
else Movement=0;
//=============速度PI控制器======================//
Encoder_Least =Encoder_Left+Encoder_Right; //===获取最新速度偏差
// printf("Encoder=%d\n",Encoder);
// Encoder *= 0.8; //===一阶低通滤波器
// Encoder += Encoder_Least; //===一阶低通滤波器
Encoder_Integral +=Encoder_Least; //===积分出位移 积分时间:10ms
Encoder_Integral+=Movement;
//防止站立抖动
if(Encoder_Least<3&&Encoder_Least>-3&&Movement==0)
{
Encoder_Least=0;
}
if(Encoder_Integral>1700) Encoder_Integral=1700; //===积分限幅
if(Encoder_Integral<-1700) Encoder_Integral=-1700; //===积分限幅
Velocity=Encoder_Least*120+Encoder_Integral*0.9; //===速度PI控制器
return Velocity;
}
三转向PID环
int16_t TurnBalance(void)
{
int16_t Encoder_Diff,Turn_PWM;
int8_t Turn_Move;
if(Zuo_Flag==1) Turn_Move=20; //===如果前进标志位置1 位移为负
else if(You_Flag==1) Turn_Move=-20; //===如果后退标志位置1 位移为正
else Turn_Move=0;
Encoder_Diff=Encoder_Left-Encoder_Right;
Encoder_Diff_Integral+=Encoder_Diff;
Encoder_Diff_Integral+=Turn_Move;
// t++;
// if(t>10)
// {
// printf("Encoder_Diff_Integral=%d,Encoder_Diff=%d\n",Encoder_Diff_Integral,Encoder_Diff);
// t=0;
// }
if(Encoder_Diff_Integral>2500) Encoder_Diff_Integral=2500; //===积分限幅
if(Encoder_Diff_Integral<-2500) Encoder_Diff_Integral=-2500; //===积分限幅
Turn_PWM=Encoder_Diff*100+Encoder_Diff_Integral*0.9-Gyro_Turn*0.75;
return Turn_PWM;
}
|