首先向大家表示歉意,去年發帖展示初代平衡車時就已經承諾編輯出一份製作説明,但一直未能完成,很是抱歉!
原因有四:
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++; } }