網站首頁 趣味美術 生活小製作 兒童製作 親子互動 陶藝製作 傳統手工 民間手藝 歲月綻放 創意與設計

DIY一輛自己的 “賽格威”平衡車!手工電子DIY教程

欄目: 電子DIY / 發佈於: / 人氣:2.05W

DIY一輛自己的 “賽格威”平衡車!

首先向大家表示歉意,去年發帖展示初代平衡車時就已經承諾編輯出一份製作說明,但一直未能完成,很是抱歉!

原因有四:
1、    太過粗糙,發出來確實有礙觀瞻;
2、    上次做車沒留下幾張照片,無圖無真相;
3、    一直工作比較繁忙,實在沒能抽出時間;
4、    (接原因2)本意儘快再做一個,留下照片。但女皇遲遲不給批款,所以才延誤至今!

此次爲了完成自己的承諾,可是耗費了2月的零花錢。並且有視頻有真相,希望您能喜歡。

原理簡介“賽格威”平衡車

圖片來自 

“賽格威”(英語: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,電壓直接由電阻分壓而來。

其餘部分可自由發揮。

視頻演示

無視頻無真相,怕熊上門所以拍了一小段視頻。

客廳實在太小,還放了些雜物,能夠行走的地方就只有中間一小塊了,跑不開。

友情提示:此車有一定危險性,不排除摔倒、失控等問題,在空地上玩玩就好,打算用來代步上班的,請給自己買好保險!

附件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++;        }     }