Post Go back to editing

教你DIY一辆自己的 “赛格威”平衡车!(ZT)

网上看到一位强大的技术宅自己打造的平衡车以及完整的图文分享(很详细,有结构设计、电路设计和部分软件代码。。。),看到用了ADIMEMS传感器,转过来分享下,也希望ADI的传感器专家能讲解下里面传感器的关键作用,给咱们这种MEMS门外汉扫个盲啥滴~~~~~~~~另外,想知道赛格威的那个平衡车也是用的ADIADXL335么?

(原帖见http://www.guokr.com/article/437624/

原理简介

“赛格威”平衡车

“赛格威”(英语:Segway)是一种电力驱动、具有自我平衡能力的个人用运输载具,是都市用交通工具的一种。由美国发明家狄恩·卡门与他的DEKA研发公司(DEKA Research and Development Corp.)团队发明设计,并创立思维车责任有限公司(Segway LLC.),自2001年12月起将思维车商业化量产销售。(资料来源:维基百科中文)

“赛格威”是一种让人留下深刻印象的代步工具,它占地不足一平方米,乘车人像使用滑板一样站立其上,双手解放,但却可以仅通过身体移动改变重心位置,就进行前进后退,转弯刹车等操作。传统的交通工具都无法做到随心而动,必须把大部分精力放在控制方向和速度上,而“赛格威”并不需要专门的操控装置,一切由车身自主完成,也由此获得了“平衡车”的别名。

“赛格威”平衡车看来神奇,但你有没有发现它的原理其实很简单呢?拜最新科技所赐,关键零件都可以在淘宝上直接买到,而控制程序也可以查阅原理自行编写。拥有自己的平衡车,其实非常简单。

倒立摆和机器人

“赛格威”的平衡问题,实际上是一个多级倒立摆问题。当一个人用手托住一根竹竿的底部使它在空中竖直不倒下,这就是一个一级倒立摆系统的模型。如果第一根竹竿上面用铰链连着其他竹竿,或者竹竿本身具有一定的弹性(可比拟“赛格威”上的有骨骼和关节的大活人),就成了多级倒立摆。



  • DIY自己的“赛格威”

    和人类行走一样,“赛格威”的控制也需要传感器和致动器。它依靠MEMS技术制造的精密固态陀螺仪和加速度计感应车体的旋转,速度和倾斜,高速微处理器计算传感器数据,并驱动轮毂电机完成前进/后退/差速转弯的动作。而在电路之外,为了让它从实验室中的倒立摆变成实用的代步车,还需要准备一些必需的结构零件和附件。

    机械部分

    此次设计的机械机构包括一个简单的独立悬挂。缓冲部分直接采用自行车的避震器(需要更换弹簧),机体做得不很紧凑,主要为了能够拆卸折叠,便于收放和运输。(需要说明的是,结构已提交专利申请,请勿用于商业用途。

    整机材料很简单,两个独立驱动的轮子+电机驱动板+车身角度传感器+转弯传感器+电池+一个装下这些东西的盒子  。两个轮子、电机、避震器都是来自淘宝的成品。钣金和机加件为单独加工。
    这里贴一些制作图片,详细的零件工程图列在最后。

    整机外形

    结构细节

      电机安装部分

     

    电机为优耐特电机,250W,24v/质量不好,不作推荐。

    电机法兰部分剖视

    转向机部分:

    整机背面

  • 装配过程

    锂电池仓

    原设计为铅酸电池,后一朋友为我无偿提供了锂电池,在此再次表示感谢。

    车铣加工

    电机法兰安装

    整体安装

  • 电路部分

    主控采用AVR的ATMEGA_32,电机驱动为H桥驱动方式,元件选用的IR2184和IRF1405。传感器选用IDG300和ADXL335,电流传感器为ACS755。另外还有一些外围的小功能,可有可无,不详述了。

    控制驱动PCB图

    传感器PCB图

    PCB空板

    焊接需要注意的就是——别太马虎就行。先焊低矮的元器件,再焊大个的!

    焊接基本完成

    连接电机测试

    散热器:

    遥控和语音模块

  • 控制程序部分

    果壳网友们的素质都很高,这里就提一些关键部分。一些个人认为有用的代码附在最后。

    流程图

    车身角度获取

    选用的传感器为模拟量输出,因此只需要用单片机的AD采集数据后计算出角度值即可,需要注意的是,采集后的数据直接使用效果会很糟糕。需要再次进行滤波计算,得到一个准确、及时、抗扰动的真实角度数据。调速过程中可以用串口将数据输出,辅助调试。

    计算车轮速度

    这里就是简单的PID控制车轮转速,如果不记得就百度看看。调试参数会花点时间,刚开始参数别调过大,否则抖动起来有危险!另外需要设置角度过大停机的功能。

    获取转向数据

    转向数据为采集转向电位器而来,采集后的数据进行滤波处理后再用。转向中间设置一个无效的死区,也是防止误动作。

    遥控

    (图片来自网络)

    遥控为最普通的4键遥控器,淘宝成品。

    语音

    语音选用成品语音模块,厂家提供完整说明文档。

    温度

    硬件原先选用18b20,很是遗憾这部分程序没调通,可能原因1:系统必须有多处中断,并且中断服务程序比较多,因而打乱了18b20的时序,加上没有示波器,因而没调通。可能原因2:智商问题。

    尝试调试了近2小时无果后改用模拟量温度芯片LM35D,电压直接由电阻分压而来。

    其余部分可自由发挥。

  • 视频演示

    http://v.youku.com/v_show/id_XNjM2MDM2NDU2.html

    无视频无真相,怕熊上门所以拍了一小段视频。

    客厅实在太小,还放了些杂物,能够行走的地方就只有中间一小块了,跑不开。

    友情提示:此车有一定危险性,不排除摔倒、失控等问题,在空地上玩玩就好,打算用来代步上班的,请给自己买好保险!

  • 附件1:零件工程图

    点击下载完整工程图(文件大小:6.15M)(本设计已提交专利申请,请勿用于商业用途。)

    附件2:重点代码

    2.1车身角度滤波代码

    /************滤波************/

    float P[2][2] = {{ 1, 0 },{ 0, 1 }};

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

    const char C_0 = 1;

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

    float Q_angle=0.001, Q_gyro=0.003, R_angle=0.5, dt=0.01;

    void Kalman_Filter(float angle_m,float gyro_m) 

    {

        angle+=(gyro_m-q_bias) * dt;              

        Pdot[0]=Q_angle - P[0][1] - P[1][0];     

        Pdot[1]=- P[1][1];

        Pdot[2]=- P[1][1];

        Pdot[3]=Q_gyro;

        P[0][0] += Pdot[0] * dt;            

        P[0][1] += Pdot[1] * dt;

        P[1][0] += Pdot[2] * dt;

        P[1][1] += Pdot[3] * dt;

        angle_err = angle_m - angle;         

        PCt_0 = C_0 * P[0][0];

        PCt_1 = C_0 * P[1][0];

        E = R_angle + C_0 * PCt_0;

        K_0 = PCt_0 / E;

        K_1 = PCt_1 / E;

        t_0 = PCt_0;

        t_1 = C_0 * P[0][1];

        P[0][0] -= K_0 * t_0;             

        P[0][1] -= K_0 * t_1;

        P[1][0] -= K_1 * t_0;

        P[1][1] -= K_1 * t_1;

        angle   += K_0 * angle_err;       

        q_bias  += K_1 * angle_err;      

        angle_dot = gyro_m-q_bias;       

    }

    //**************滤波*****************//

    static float C_angle,C_angle_dot;

    static float bias_cf;

    void Complement_filter(float angle_m_cf,float gyro_m_cf)

    {

        bias_cf=0.998*bias_cf+0.002*gyro_m_cf;

        C_angle_dot=gyro_m_cf-bias_cf;

        C_angle=0.98*(C_angle+C_angle_dot*0.02)+0.02*angle_m_cf;

    }

    //***************************** 滤波结束*********************************/

    2.2 转向数据处理代码

    /************转向************/

    void Steering_handle(void)

    {

                 Buf=  0.9 *Buf + 0.1 * AD_Turn;                 

                 Turning= Buf -Turn_Zero;    //                

                 if(Turning <- Turn_Dead)                       //死区

                         Turning+=Turn_Dead;   

                 else if(Turning> Turn_Dead)

                         Turning-=Turn_Dead;   

                 else    Turning= 0;   

                if (mode==0)  

                {

                    Drive_A=0;

                    Drive_B=0;

                    if (!(angle>0.1||angle<-0.1))

                    {

                        mode=1;

                    }

                }

                else

                {

                    if(lab==0)

                        {

                            Turning=0;

                        }

                    else if (Turning>55||Turning<-55)//

                        {

                            Turning=0;

                            lab=3;// turn error

                        }

                    else           //按车速整定转向数据

                        {

                        //buf2=Drivespeed;

                        //if (buf2<0)buf2*=-1;

                        //buf2/=3;

                        //Turning/=buf2;

                        Turning/=1;

                        }

                     Drive_A=Drivespeed-Turning;

                     Drive_B=Drivespeed+Turning;

                }       

    }

    //***************************** 转向结束*********************************/

    2.3遥控部分状态机

    /***********按键********/

    #define BOOL int

    #define FALSE 0

    #define TRUE  1

    #define INT8U unsigned int

    /**********硬件接口***********/

        #define     KEYPIN1               (PINC&(1<<3))

        #define     KEYPIN2               (~PINB&(1<<0))

        #define     KEYPIN3               (~PINB&(1<<1))

        #define     KEYPIN4               (~PINB&(1<<3))

        #define     KEYPIN5               (~PINB&(1<<4))   

    /**********按恪键属性**********/

        #define KEY_JT 0x0e

        #define KEY_A  0x0d

        #define KEY_B  0x0b

        #define KEY_C  0x07

        #define KEY_D  0x08

        #define KEY_NULL    0x0f

    //

    #define KEY_LONG_PERIOD      250

    #define KEY_CONTINUE_PERIOD  25

    //

    #define KEY_DOWN       0x80

    #define KEY_LONG       0x40

    #define KEY_CONTINUE   0x20

    #define KEY_UP         0x10

    //

    #define KEY_STATE_INIT     0

    #define KEY_STATE_WOBBLE   1

    #define KEY_STATE_PRESS    2

    #define KEY_STATE_LONG     3

    #define KEY_STATE_CONTINUE 4

    #define KEY_STATE_RELEASE  5

    uchar KeyScan(void) 

    {

        if(KEYPIN2==0) return KEY_A;

        if(KEYPIN3==0) return KEY_B;

        if(KEYPIN4==0) return KEY_C;

        if(KEYPIN5==0) return KEY_D;

        if(KEYPIN1==0) return KEY_JT;

        return KEY_NULL;

    }

    void GetKey(uchar *pKeyValue)

    {

         static char KeyState = KEY_STATE_INIT;

         static char KeyTimeCount = 0;

         static char LastKey = KEY_NULL;

         char KeyTemp = KEY_NULL;

         KeyTemp = KeyScan();  

         switch(KeyState)

         {

             case KEY_STATE_INIT:

                  {

                      if(KEY_NULL!=(KeyTemp))

                      {

                         KeyState = KEY_STATE_WOBBLE;

                      }

                  }

              break;

              case KEY_STATE_WOBBLE:   

                   {

                         KeyState = KEY_STATE_PRESS;

                    }   

              break;

              case KEY_STATE_PRESS:

                   {

                        if(KEY_NULL!=(KeyTemp)) 

                        {

                            LastKey = KeyTemp;   

                            KeyTemp|=KEY_DOWN;   

                            KeyState = KEY_STATE_LONG ;

                        }

                        else

                        {

                            KeyState = KEY_STATE_INIT;

                        }

                    }

              break;

              case KEY_STATE_LONG:

                   {

                     if(KEY_NULL !=(KeyTemp))

                     {

                      if(++KeyTimeCount > KEY_LONG_PERIOD)

                      {

                           KeyTimeCount = 0;

                           KeyTemp|=KEY_LONG;   

                           KeyState = KEY_STATE_CONTINUE;

                       }

                      }

                      else

                      {

                           KeyState = KEY_STATE_RELEASE;

                      }

                    }

            break;

            case KEY_STATE_CONTINUE:

                   {

                      if(KEY_NULL !=(KeyTemp))

                      {

                          if(++KeyTimeCount > KEY_CONTINUE_PERIOD)

                           {

                               KeyTimeCount = 0;

                               KeyTemp |= KEY_CONTINUE;

                            }

                      }

                      else

                      {

                           KeyState = KEY_STATE_RELEASE;

                       }

                   }

                   break;

                   case KEY_STATE_RELEASE:

                   {

                        LastKey |=KEY_UP;

                        KeyTemp = LastKey;

                        KeyState = KEY_STATE_INIT;

                   }

                   break;

                   default:break;

            }

            *pKeyValue = KeyTemp; 

    }

    2.4电池电压

    void Get_Batt_Volt(void)

    {

         int buf3=0,b=0;

         buf3=0.9*buf3+0.1*AD_Batt;

         if (b>10)

             {

                Voltage=buf3*3000.0/1024/65;

                b=10;

             }

        else

            {

                b++;

            }    

    }

  • 谢谢楼主的分享!动手能力超级强大的网友!!!

    说明下,ADXL335是咱们推出的一款小尺寸、低功耗、三轴±3G 加速度计。主要特性:小尺寸、薄型、低功耗、完整的三轴加速度计,提供经过信号调理的电压输出,该产品的满量程加速度测量范围为±3 g(最小值),可以测量倾斜检测应用中的静态重力加速度,以及运动、冲击或振动导致的动态加速度……其目标应用就包括运动与保健器材。产品详情,可以看这个产品页面哦http://www.analog.com/zh/mems-sensors/mems-inertial-sensors/adxl335/products/product.html