发新帖本帖赏金 10.00元(功能说明)我要提问
12下一页
返回列表
打印
[STM32F4]

基于NUCLEOf411的两轮平衡车制作

[复制链接]
3583|28
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lianqiang|  楼主 | 2016-2-18 15:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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板给烧了
(好伤心的说),这更加坚定了我使用双电源的决心。
       以上就是主要的硬件部分,在制作过程主要注意以下几点,
.12V5V电源分离并固定,做好防反接。
二.做好引线的标记,防止接错。
三.将各个模块固定结实,防止运动的时候脱落。

以上都是惨痛的经验希望大家不要放同样的错误。


三:软件主体
以下介绍一下编写平衡车的主要过程
一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)
而如果将MPU6050INT脚引出利用中断读取就不会出现尖峰。但是读取的角度的波形不如采用定时器读取时平滑,这我一直没搞懂,因为采用外部中断读取的频率大概也在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进行处理。
  功能
  
  
8个字节的有效数据
  
  CRC校验
  
共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;
}





打赏榜单

21ic小喇叭 打赏了 10.00 元 2016-02-24

评分
参与人数 1威望 +3 收起 理由
justtest111 + 3 等级不够不能发链接的
沙发
lianqiang|  楼主 | 2016-2-18 15:19 | 只看该作者
为什么我的优酷HTML链接一发表就没了,上不了视频
怎么发视频

使用特权

评论回复
板凳
wahahaheihei| | 2016-2-18 16:30 | 只看该作者
你放个文本里上传上来,我去欣赏欣赏

使用特权

评论回复
地板
wtyt| | 2016-2-18 16:42 | 只看该作者
牛X

使用特权

评论回复
5
lianqiang|  楼主 | 2016-2-18 17:20 | 只看该作者
//v.youku.com/v_show/id_XMTQ3NDk5OTQ0MA==.html为什么没有权限???


使用特权

评论回复
6
lianqiang|  楼主 | 2016-2-18 17:22 | 只看该作者
wahahaheihei 发表于 2016-2-18 16:30
你放个文本里上传上来,我去欣赏欣赏

//v.youku.com/v_show/id_XMTQ3NDk5OTQ0MA==.html

使用特权

评论回复
7
史迪威将军| | 2016-2-18 20:04 | 只看该作者
楼主做的真好,麻烦问一下,平衡的传感器用的是哪款?

使用特权

评论回复
8
justtest111| | 2016-2-18 20:06 | 只看该作者

我帮你发一下

使用特权

评论回复
9
lianqiang|  楼主 | 2016-2-18 20:06 | 只看该作者
史迪威将军 发表于 2016-2-18 20:04
楼主做的真好,麻烦问一下,平衡的传感器用的是哪款?

MPU6050

使用特权

评论回复
10
绚紫飞鸥| | 2016-2-19 08:14 | 只看该作者
支持下楼主,最近也打算做一个玩玩:P

使用特权

评论回复
11
bkn1860| | 2016-2-19 09:05 | 只看该作者
这个不错

使用特权

评论回复
12
klffnj| | 2016-2-19 09:56 | 只看该作者
做的很好,请教一下 这个函数 Kalman_Filter(Accel_Y,-Gyro_Y); 怎么做的

使用特权

评论回复
13
大秦正声| | 2016-2-19 09:59 | 只看该作者
支持

使用特权

评论回复
14
Leeone| | 2016-2-19 10:04 | 只看该作者
MPU6050这块程序能否发我一份 Leeone1990@163.com

使用特权

评论回复
15
zxmxx| | 2016-2-19 10:44 | 只看该作者
学习了

使用特权

评论回复
16
ecoren| | 2016-2-19 14:08 | 只看该作者
不孬,

使用特权

评论回复
17
ecoren| | 2016-2-19 14:11 | 只看该作者
减速电机多少大洋?

使用特权

评论回复
18
michael_llh| | 2016-2-19 14:37 | 只看该作者
很棒,支持一下!!!

使用特权

评论回复
19
woosoo521| | 2016-2-19 15:04 | 只看该作者
楼主很厉害

使用特权

评论回复
20
lianqiang|  楼主 | 2016-2-19 15:21 | 只看该作者
ecoren 发表于 2016-2-19 14:11
减速电机多少大洋?

加轮子好像60多,不过不建议买转速太低,跑不快,120r\min

使用特权

评论回复
发新帖 本帖赏金 10.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

24

主题

105

帖子

3

粉丝