【學渣的自我修養】麥克納姆輪淺談
在競賽機器人和特殊工業機器人中,全向移動經常是一個必須的功能。「全向移動」意味著可以在平面上做出任意方向平移並同時自轉的動作。為了實現全向移動,一般機器人會使用「全向輪」(Omni Wheel) 或 「麥克納姆輪」 (Mecanum Wheel) 這兩種特殊輪子。 全向輪與麥克納姆輪的共同點在於它們都由兩大部分組成:輪轂和輥子 ( roller )。輪轂是整個輪子的主體支架,輥子則是安裝在輪轂上的鼓狀物。全向輪的輪轂軸與輥子轉軸相互垂直,而麥克納姆輪的輪轂軸與輥子轉軸呈 45° 角。理論上,這個夾角可以是任意值,根據不同的夾角可以製作出不同的輪子,但最常用的還是這兩種。 全向輪與麥克納姆輪 ( 以下簡稱「麥輪」) 在結構、力學特性、運動學特性上都有差異,其本質原因是輪轂軸與輥子轉軸的角度不同。經過分析,二者的運動學和力學特性區別可以通過以下表格來表現。 計算過程如下,供參考,可驗算: 近年來,麥輪的應用逐漸增多,特別是在 Robocon、FRC 等機器人賽事上。這是因為麥克納姆輪可以向傳統輪子一樣,安裝在相互平行的軸上。而若想使用全向輪完成類似的功能,幾個輪轂軸之間的角度就必須是 60°,90° 或 120° 等角度,這樣的角度產生和製造起來比較麻煩。所以許多工業全向移動平台都是使用麥克納姆輪而不是全向輪,比如這個大陸國產的叉車: 全向移動平台 麥克納姆輪叉車 美科斯叉車 另外一個原因,可能是麥輪的造型比全向輪要酷炫的多,看起來有一種不明厲害的感覺...... 的確,第一次看到麥輪運轉起來,不少人都會驚嘆。以下視頻直接說明了麥輪底盤在平移和旋轉時的輪子旋轉方向。
麥輪一般是四個一組使用,兩個左旋輪,兩個右旋輪。左旋輪和右旋輪呈手性對稱,區別如下圖。 安装方式有多种,主要分为:X-正方形(X-square)、X-长方形(X-rectangle)、O-正方形(O-square)、O-长方形(O-rectangle)。其中 X 和 O 表示的是与四个轮子地面接触的辊子所形成的图形;正方形与长方形指的是四个轮子与地面接触点所围成的形状。 安裝方式有多種,主要分為:X-正方形(X-square)、X-長方形(X-rectangle)、O-正方形(O-square)、O-長方形(O-rectangle)。
以O-長方形的安裝方式為例,四個輪子地著地點形成一個矩形。正運動學模型(forward kinematic model)將 得到一系列公式,讓我們可以通過四個輪子的速度,計算出底盤的運動狀態;而逆運動學模型(inverse kinematic model)得到的公式則是可以根據底盤的運動狀態解算出四個輪子的速度。需要注意的是,底盤的運動可以用三個獨立變數來描述:X軸平動、Y軸平動、yaw軸自轉;而四個麥輪的速度也是由四個獨立的馬達提供的。所以四個麥輪的合理速度是存在某種約束關係的,逆運動學可以得到唯一解釋,而正運動學中部符合這個約束關係的方程式將無解。 先試圖建構逆運動學模型,由於麥輪底盤的數字模型比較複雜,我們在此分四部運行: ①將底盤的運動分解為三個獨立變數來描述; ②根據第一步的結果,計算出每個輪子軸心位置的速度; ③根據第二步的結果,計算出每個輪子與地面接觸的的輥子的速度; ④根據第三步的結果,計算出輪子的真實轉速。 一、底盤運動的分解 我們知道,結構在平面上的運動可以分解為三個獨立值:X軸平動、Y軸平動、yaw 軸自轉。如下圖所示,底盤的運動也可以分解為三個值: 以上三個值一般都視為四個輪子的幾何中心 ( 矩形的對角線交點 ) 的速度。 二、計算出輪子軸心位置的速度 定義: 那麼可以計算出: 分別計算 X、Y 軸的值為: 同理可以算出其他三個輪子軸心的速度。 三、計算輥子的速度 四、計算輪子的速度 從輥子速度到輪子轉速的計算比較簡單: 根據上圖所示的 a 和 b 的定義,有 結合以上四個步驟,可以根據底盤運動狀態解算出四個輪子的轉速: 以上方程組就是O-長方形麥輪底盤的逆運動學模型,而正運動學模型可以直接根據逆運動學模型中的三個方程式解出來,此處就不再贅述。 另一種計算方式「傳統」的推倒過程雖然嚴謹,但還是比較繁瑣的。這裡介紹一種簡單的逆運動學計算方式。 我們知道,全向移動底盤是一個純線性系統,而剛性運動又可以將線性分解為三個值。那麼只需要計算出麥輪底盤在「沿X軸平移」、「沿Y軸平移」、「繞幾何中心自轉」時,四個輪子的速度,就可以通過簡單的加法,計算出這三種簡單運動所合成的「平動+旋轉」運動時所需要的四個輪子的轉速。而這三種簡單運動時,四個輪子的速度可以通過簡單的測試,或是推動底盤觀察現象得出。 將以上三個方程組相加,得到的恰好是根據「傳統」方法計算出的結果。這種計算方式不僅適用於O-長方形的麥輪底盤,也適用於任何一種全向移動的機器人底盤。 Makeblock 麥輪底盤的組裝 理論分析完成,可以開始嘗試將其付諸實踐了。 第一步,組裝矩形框架。 第二步,組裝馬達模塊。 由於麥輪底盤的四個輪子速度有約束關係,必須精確地控制每個輪子的速度,否則將會導致輥子與地面發生滑動摩擦,不僅會讓底盤運動異常,還會讓麥輪的壽命減少。所以必須使用編碼馬達。 第三步,將馬達模塊安裝到框架上。 第四步,將麥輪安裝到框架上。 第五步,安裝電路板並接線。 編碼馬達必須配上對應的主控板才能正常工作。這裡使用的 Makeblock 編碼馬達驅動板,每一塊模塊可以驅動兩個馬達。接線順序在下文中會提及,也可以隨意街上,在代碼中定義好對應的順序即可。 第六步,裝上電池。 至此,一個能獨立運行的麥輪底盤就完成了。 控制程序 根據麥輪的底盤運動學模型,要完全控制它的運動,需要有三個控制值:X軸速度、Y軸速度、自轉角速度。要產生這三個控制值,有很多種方法,本文將使用一個USB遊戲手把,左邊的搖桿產生平移速度,右邊的搖桿產生角速度。 首先將一個 USB Host 模塊 連接到 Orion 主控板 的 5 連接埠。 然後插上一個無線USB遊戲手把。 然後再添加其他細節,就大功告成啦! 其他細節: #include <Wire.h> #include <SoftwareSerial.h> #include "MeOrion.h" MeUSBHost joypad(PORT_3); // 手把代碼(紅燈亮模式) // 默認:128-127-128-127-15-0-0-128 // 左一:128-127-128-127-15-1-0-128 // 右一:128-127-128-127-15-2-0-128 // 左二:128-127-128-127-15-4-0-128 // 右二:128-127-128-127-15-8-0-128 // 三角:128-127-128-127-31-0-0-128 (0001 1111) // 方形:128-127-128-127-143-0-0-128 (1000 1111) // 叉號:128-127-128-127-79-0-0-128 (0100 1111) // 圓圈:128-127-128-127-47-0-0-128 (0010 1111) // 向上:128-127-128-127-0-0-0-128 (0000 0000) // 向下:128-127-128-127-4-0-0-128 (0000 0100) // 向左:128-127-128-127-6-0-0-128 (0000 0110) // 向右:128-127-128-127-2-0-0-128 (0000 0010) // 左上:128-127-128-127-7-0-0-128 (0000 0111) // 左下:128-127-128-127-5-0-0-128 (0000 0101) // 右上:128-127-128-127-1-0-0-128 (0000 0001) // 右下:128-127-128-127-3-0-0-128 (0000 0011) // 選擇:128-127-128-127-15-16-0-128 // 開始:128-127-128-127-15-32-0-128 // 搖桿:右X-右Y-左X-左Y-15-0-0-128 MeEncoderMotor motor1(0x02, SLOT2); MeEncoderMotor motor2(0x02, SLOT1); MeEncoderMotor motor3(0x0A, SLOT2); MeEncoderMotor motor4(0x0A, SLOT1); // 底盤:a = 130mm, b = 120mm float linearSpeed = 100; float angularSpeed = 100; float maxLinearSpeed = 200; float maxAngularSpeed = 200; float minLinearSpeed = 30; float minAngularSpeed = 30; void setup() { // 要通電才能動作,不能只是插上USB線來調試。 motor1.begin(); motor2.begin(); motor3.begin(); motor4.begin(); Serial.begin(57600); joypad.init(USB1_0); } void loop() { Serial.println("loop:"); //setEachMotorSpeed(100, 50, 50, 100); if(!joypad.device_online) { // 若一直輸出離線狀態,重新拔插 USB Host 的 RJ25 線試一下。 Serial.println("Device offline."); joypad.probeDevice(); delay(1000); } else { int len = joypad.host_recv(); parseJoystick(joypad.RECV_BUFFER); delay(5); } //delay(500); } void setEachMotorSpeed(float speed1, float speed2, float speed3, float speed4) { motor1.runSpeed(speed1); motor2.runSpeed(-speed2); motor3.runSpeed(-speed3); motor4.runSpeed(-speed4); } void parseJoystick(unsigned char *buf) //Analytic function, print 8 bytes from USB Host { // 輸出手把的數據,調試用 // int i = 0; // for(i = 0; i < 7; i++) // { // Serial.print(buf[i]); //It won't work if you connect to the Makeblock Orion. // Serial.print('-'); // } // Serial.println(buf[7]); // delay(10); // 速度增減 switch (buf[5]) { case 1: linearSpeed += 5; if (linearSpeed > maxLinearSpeed) { linearSpeed = maxLinearSpeed; } break; case 2: angularSpeed += 5; if (angularSpeed > maxAngularSpeed) { angularSpeed = maxAngularSpeed; } break; case 4: linearSpeed -= 5; if (linearSpeed < minLinearSpeed) { linearSpeed = minLinearSpeed; } break; case 8: angularSpeed -= 5; if (angularSpeed < minAngularSpeed) { angularSpeed = minAngularSpeed; } break; default: break; } if ((128 != buf[0]) || (127 != buf[1]) || (128 != buf[2]) || (127 != buf[3])) { // 處理搖桿 float x = ((float)(buf[2]) - 127) / 128; float y = (127 - (float)(buf[3])) / 128; float a = (127 - (float)(buf[0])) / 128; mecanumRun(x * linearSpeed, y * linearSpeed, a * angularSpeed); } else { switch (buf[4]) { case 0: mecanumRun(0, linearSpeed, 0); break; case 4: mecanumRun(0, -linearSpeed, 0); break; case 6: mecanumRun(-linearSpeed, 0, 0); break; case 2: mecanumRun(linearSpeed, 0, 0); break; case 7: mecanumRun(-linearSpeed/2, linearSpeed/2, 0); break; case 5: mecanumRun(-linearSpeed/2, -linearSpeed/2, 0); break; case 1: mecanumRun(linearSpeed/2, linearSpeed/2, 0); break; case 3: mecanumRun(linearSpeed/2, -linearSpeed/2, 0); break; default: mecanumRun(0, 0, 0); break; } } } void mecanumRun(float xSpeed, float ySpeed, float aSpeed) { float speed1 = ySpeed - xSpeed + aSpeed; float speed2 = ySpeed + xSpeed - aSpeed; float speed3 = ySpeed - xSpeed - aSpeed; float speed4 = ySpeed + xSpeed + aSpeed; float max = speed1; if (max < speed2) max = speed2; if (max < speed3) max = speed3; if (max < speed4) max = speed4; if (max > maxLinearSpeed) { speed1 = speed1 / max * maxLinearSpeed; speed2 = speed2 / max * maxLinearSpeed; speed3 = speed3 / max * maxLinearSpeed; speed4 = speed4 / max * maxLinearSpeed; } setEachMotorSpeed(speed1, speed2, speed3, speed4); }
1 評論
廖柏鈞
4/7/2017 05:39:24 pm
請問是什麼螺絲,有賣嗎
回覆
發表回覆。 |