查看: 4156|回复: 3

基于AVR单片机: 两轮自平衡智能车

[复制链接]
  • TA的每日心情
    开心
    2014-1-17 23:05
  • 签到天数: 9 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2014-3-7 23:39:26 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 东隅 于 2014-4-1 13:33 编辑

        寒假无所事事,决定做一个两轮平衡车来玩玩.个人觉得两轮自平衡车是集合所有算法的精髓.它就像蛋炒饭,最简单也是最困难.他可以用简单的算法实现,当然也可以用最复杂的控制理论来实现. 本质上来说它就是个倒立摆.

    个人原创,转载请注明原文出处:
       http://www.embbnux.com/2014/02/21/avr_mcu_balance_car/

       
    所需原件:

    AVR单片机 : 采用之前的ATmega128开发板
    传感器: ENC03陀螺仪  mma8451加速度计
    电机驱动: L298n
    总体思路:
    使用陀螺仪测角速度,使用加速度计测角度,使用测得的这两个值进行传感器融合,获取准确的角度.由得到的角度来控制车子两个轮子的转向,转速等参数.进而实现两轮车的平衡. 传感器融合使用卡尔曼滤波算法,不得不大赞一下卡尔曼同学,给了个这么perfect的算法,传说当年挑战者号就是用的这个算法.
    角度的测量:
    enc03陀螺仪: 采用的这个陀螺仪是模拟量的,输出电压需要用ATmega128进行adc转换成数字量,测得电压,再转换成角速度:
    1. ISR(ADC_vect){

    2.   if(adc_done_flag==0){
    3.      adc_read(adc_channel);
    4.       if(adc_channel==1) {
    5.        adc_channel=0;
    6.       }
    7.       else{
    8.        adc_channel++;
    9.        adc_mid_tmp0[cou]=adc_value[0];
    10. //中值滤波
    11.       cou++;
    12.       if(cou==3){
    13.           cou=0;
    14.           if(adc_mid_tmp0[0]>adc_mid_tmp0[1]){
    15.              mid_tmp =  adc_mid_tmp0[0];
    16.              adc_mid_tmp0[0] =adc_mid_tmp0[1];
    17.              adc_mid_tmp0[1] =mid_tmp;
    18.           }
    19.           if(adc_mid_tmp0[2]>adc_mid_tmp0[1])
    20.              adc_mid[0]=adc_mid_tmp0[1] ;
    21.           else if (adc_mid_tmp0[2]>adc_mid_tmp0[0])
    22.              adc_mid[0]=adc_mid_tmp0[2] ;
    23.           else
    24.              adc_mid[0]=adc_mid_tmp0[0] ;
    25.       }
    26.    }
    27.    adc_set_channel(adc_channel);
    28.    adc_done_flag =1;
    29. }
    30. else adc_done_flag = 0;
    31. //ADCSRA|=(1<<ADSC);

    32. }

    复制代码
    1.     float gyro_enc03_w(unsigned int adc_value,float adc_bias){  
    2.        float w_value;  
    3.        //把adc采集的值转化为电压  
    4.        w_value = (float)(adc_value)*2540/1024;  //mV  
    5.        w_value = w_value-adc_bias;  
    6.        //把电压转化为角速度  
    7.        w_value = -w_value;  
    8.        w_value = w_value/0.67;  
    9.        return w_value;  
    10.     }  
    复制代码
    mma8451加速度计: mma8451使用的I2C接口,不过没事,128上面就有一个了,这里我使用硬件I2C来实现,代码较多这里给出关键代码:
    1. unsigned char i2c_mma8451_Write(unsigned char Address,unsigned char Wdata)
    2. {
    3.    unsigned char i;
    4.    Start();//I2C启动
    5.    Wait();
    6.    if(TestAck()!=START) return 1;//ACK

    7.    Write8Bit(MMA845x_IIC_ADDRESS);//写I2C 8451从器件地址和写方式
    8.    Wait();
    9.    if(TestAck()!=MT_SLA_ACK) return 1;//ACK

    10.    Write8Bit(Address);//写x地址
    11.    Wait();
    12.    if(TestAck()!=MT_DATA_ACK) return 1;//ACK

    13.    Write8Bit(Wdata);//写入设备ID及读信
    14.    Wait();
    15.    if(TestAck()!=MT_DATA_ACK) return 1;//ACK

    16.     Stop();//I2C停止
    17.    for(i=0;i<250;i++);
    18.    return 0;
    19. }
    20. //mma8451初始化
    21. //初始化为指定模式
    22. void mma845x_init()
    23. {   i2c_init();
    24.     i2c_mma8451_Write(CTRL_REG1,ASLP_RATE_20MS+DATA_RATE_5MS);
    25.     i2c_mma8451_Write(XYZ_DATA_CFG_REG, FULL_SCALE_2G); //2G
    26.     i2c_mma8451_Write(CTRL_REG1, (ACTIVE_MASK+ASLP_RATE_20MS+DATA_RATE_5MS)&(~FREAD_MASK)); //激活状态   14bit
    27. }
    28. //
    29. unsigned char i2c_mma8451_Read(unsigned char Address)
    30. {
    31.     unsigned char temp;
    32.     Start();//I2C启动
    33.     Wait();
    34.     if (TestAck()!=START) return 0;//ACK

    35.     Write8Bit(MMA845x_IIC_ADDRESS);//写入设备ID及写信号
    36.     Wait();
    37.     if (TestAck()!=MT_SLA_ACK) return 0;//ACK //
    38. //
    39.    Write8Bit(Address);//写X地址
    40.    Wait();
    41.    if (TestAck()!=MT_DATA_ACK) return 0; //?
    42. //data1=TestAck();
    43.    Start();//I2C重新启动
    44.    Wait();
    45.    if (TestAck()!=RE_START)  return 0;

    46.    Write8Bit(MMA845x_IIC_ADDRESS+1);//写I2C从器件地址和读方式
    47.    Wait();
    48.    if(TestAck()!=MR_SLA_ACK)  return 0;//ACK

    49.    Twi();//启动主I2C读方式
    50.    Wait();
    51.    if(TestAck()!=MR_DATA_NOACK) return 0;//ACK
    52.    temp=TWDR;//读取I2C接收数据
    53.    Stop();//I2C停止
    54.    return temp;
    55. }
    56. //求出加速度原始对应数值
    57. unsigned int get_mma8451_data(unsigned char Address){
    58.     unsigned char x;
    59.     unsigned int wx;
    60.     unsigned char address_LSB;
    61.     x = i2c_mma8451_Read(Address);
    62.    if(Address==OUT_Z_MSB_REG)
    63.    address_LSB= OUT_Z_LSB_REG;
    64.    else if(Address==OUT_Y_MSB_REG)
    65.      address_LSB= OUT_Y_LSB_REG;
    66.    else
    67.      address_LSB= OUT_X_LSB_REG;
    68.      wx = ((i2c_mma8451_Read(address_LSB))|x<<8);
    69.    return wx;
    70. }
    复制代码
    使用卡尔曼滤波融合两个测量量:
    1. float angle, angle_dot;         //外部需要引用的变量
    2. //-------------------------------------------------------
    3. float Q_angle=0.001, Q_gyro=0.003, R_angle=5.0, dt=0.01;
    4. //注意:dt的取值为kalman滤波器采样时间;
    5. float P[2][2] = {
    6.               { 1, 0 },
    7.               { 0, 1 }
    8. };

    9. float Pdot[4] ={0,0,0,0};

    10. const char C_0 = 1;

    11. float q_bias, angle_err, PCt_0, PCt_1, E, K_0, K_1, t_0, t_1;

    12. void Kalman_Filter(float angle_m,float gyro_m)            //gyro_m:gyro_measure
    13. {
    14.    angle+=(gyro_m-q_bias) * dt;//先验估计

    15.    Pdot[0]=Q_angle - P[0][1] - P[1][0];// Pk-' 先验估计误差协方差的微分
    16.    Pdot[1]=- P[1][1];
    17.    Pdot[2]=- P[1][1];
    18.    Pdot[3]=Q_gyro;

    19.    P[0][0] += Pdot[0] * dt;// Pk- 先验估计误差协方差微分的积分 = 先验估计误差协方差
    20.    P[0][1] += Pdot[1] * dt;
    21.    P[1][0] += Pdot[2] * dt;
    22.    P[1][1] += Pdot[3] * dt;

    23.   angle_err = angle_m - angle;//zk-先验估计

    24.   PCt_0 = C_0 * P[0][0];
    25.   PCt_1 = C_0 * P[1][0];

    26.   E = R_angle + C_0 * PCt_0;

    27.   K_0 = PCt_0 / E;//Kk
    28.   K_1 = PCt_1 / E;

    29.   t_0 = PCt_0;
    30.   t_1 = C_0 * P[0][1];

    31.   P[0][0] -= K_0 * t_0;//后验估计误差协方差
    32.   P[0][1] -= K_0 * t_1;
    33.   P[1][0] -= K_1 * t_0;
    34.   P[1][1] -= K_1 * t_1;

    35.   angle    += K_0 * angle_err;//后验估计
    36.   q_bias    += K_1 * angle_err;//后验估计
    37.   angle_dot = gyro_m-q_bias;//输出值(后验估计)的微分 = 角速度
    38. }
    复制代码
    卡尔曼算法是要在定时器里面不断的更新,才能发挥作用.dt就是定时器的时间,这里我采用10ms.
    主要算法在定时器中断里面实现:
    定时器里面完成对角速度,角度的测量,卡尔曼更新,以及对电机的控制.
    1. //定时器0 初始化
    2. void timer0_init(void){
    3.    //设置计数开始的初始值
    4.    TCNT0 = 176 ; // 208>>6ms 176>>10ms
    5.    //设置分频
    6.    TCCR0 = (1<<CS02)|(1<<CS00)|(1<<CS01); //1024分频  47次6ms 62.5次8ms
    7.    //设置中断屏蔽寄存器
    8.    TIMSK = (1<<TOIE0);   //设置溢出使能中断
    9. }
    10. //定时器0溢出中断
    11. ISR(TIMER0_OVF_vect){

    12.     //设置计数开始的初始值
    13.     TCNT0 = 176 ;  //10ms

    14.     mma8451_data = get_mma8451_data(OUT_Z_MSB_REG);
    15.     acc = mma8451_i2c_to_angel(mma8451_data);
    16.     acc_tmp=acc;
    17.     gyro = gyro_enc03_w(adc_mid[0],adc_bias);
    18.     gyro_tmp = gyro;
    19.     //卡尔曼
    20.     Kalman_Filter(acc,gyro);

    21. //控制车速
    22. if (angle>0) {
    23.    car_back();
    24.    if(angle>15)
    25.    car_speed(0x2f,0x2f);
    26. else
    27.    car_speed(0x4f,0x4f);
    28. }
    29. else if(angle<0){
    30.     car_forward();
    31.     if(angle<-15)
    32.     car_speed(0x2f,0x2f);
    33. else
    34.    car_speed(0x4f,0x4f);
    35. }

    36. }
    复制代码
    对于车速的控制,这里只采用简单的开环控制,之后会采用更复制的DIP控制或者模糊控制等算法,后续持续更新,敬请关注本博客。






    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2015-2-11 16:15
  • 签到天数: 463 天

    连续签到: 1 天

    [LV.9]以坛为家II

    发表于 2014-3-8 20:04:11 | 显示全部楼层
    赞一个,就是楼主的开发板有点太大了,还是用最小系统版好
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2016-8-15 09:28
  • 签到天数: 222 天

    连续签到: 1 天

    [LV.7]常住居民III

    发表于 2014-3-17 10:50:56 | 显示全部楼层
    赞赞赞,跟随大牛学习!
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2014-3-30 03:37:08 | 显示全部楼层
    MARK
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /5 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-12-21 01:07 , Processed in 0.144473 second(s), 23 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.