本帖最后由 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;
- }
- }
加速度(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; //更新转向角速度
- }
- }
角速度(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;
- }
|