常看到影片中的製造工廠自動化,都是透過機器手臂或無人搬運車來完成。今天的實作要接續上一篇:Arduino筆記(92):Wii Nunchcuk控制伺服馬達,將多個伺服馬達,使用固定架組合成機器手臂,控制其轉向及擺動動作,當伺服馬達數量越多時,手臂的動作會越細緻。我利用 PCA9685 16路電機控制板,連接 4 個伺服馬達,控制其轉向、上臂彎曲、手腕彎曲及爪子抓物品。過去曾做過使用 PCA9685 16路電機控制板來控制伺服馬達的實作:Arduino筆記(74):PCA9685控制Servo伺服馬達,這次要將這個實作,再結合 Wii nunchuck 控制轉向及手臂擺動。
實作過程中,控制伺服馬達轉動的角度,程式中的 pwm.setPWM()函式是將伺服馬達轉動到控制的角度,以為控制範圍的參數是 0 ~ 180,上傳程式到 Arduino 執行時,伺服馬達轉動的角度很小。一查 Adafruit_PWM Servo Driver 的函式庫說明,才知道 pwm.setPWM(channel,on,off) 的的 on, off 值可以從 0 到 4095,經過幾次嘗試找到適合的轉動角度,這個角度會因為伺服馬達的廠牌不同而有差異,只能多試幾次,得知大約的轉動位置是否適合你的環境。
得知轉動位置後,可設定為伺服馬達的最大跟最小值,並利用 map()函式進行轉換,將 nunchuck 取得的搖桿及加速度值,map成介於上述伺服馬達最小與最大值之間的值,再透過 pwm.setPWM()控制伺服馬達轉動的角度。
由於 4 個伺服馬達的控制角度不同,對於校準伺服馬達,我一次只接一個伺服馬達進行調校,以伺服馬達A來說,他在機器手臂最下方,控制轉動方向。我設定最小值為 0,最大值為 550,為什麼會是這個值呢?只因為這個值會讓 Arduino 啟動時,手臂的角度剛好與底座成 90度垂直,稍微改一下最大與最小值,都會讓起動的角度偏移。例如第 4 個馬達,設定介於 150 與 600 之間,若將 600 減少的 350左右,爪子剛好可以閉合,可是沒有抓緊的力道,將最大值稍微設定高一點,可以增加爪子緊縮的力道。
第 2 個伺服馬達控制大臂的彎曲角度,我使用 z 軸的傾斜角度來控制,第 3 個伺服馬達控制爪子手腕的彎曲角度,我用搖桿的 y 軸來控制,往下按時,爪子手腕也會往下,至於設定的值是根據實際測試得到,讓手臂不要因彎曲角度過大敲到桌面,或是因最大與最小值的角度範圍太大變得不好控制等,這需要多幾次嘗試,才能知道該設定角度的大小範圍。
我沒有將組裝機器手臂的過程放上來,如有需要瞭解如何安裝的朋友,可以參考 這篇 文章。
實作過程中,控制伺服馬達轉動的角度,程式中的 pwm.setPWM()函式是將伺服馬達轉動到控制的角度,以為控制範圍的參數是 0 ~ 180,上傳程式到 Arduino 執行時,伺服馬達轉動的角度很小。一查 Adafruit_PWM Servo Driver 的函式庫說明,才知道 pwm.setPWM(channel,on,off) 的的 on, off 值可以從 0 到 4095,經過幾次嘗試找到適合的轉動角度,這個角度會因為伺服馬達的廠牌不同而有差異,只能多試幾次,得知大約的轉動位置是否適合你的環境。
得知轉動位置後,可設定為伺服馬達的最大跟最小值,並利用 map()函式進行轉換,將 nunchuck 取得的搖桿及加速度值,map成介於上述伺服馬達最小與最大值之間的值,再透過 pwm.setPWM()控制伺服馬達轉動的角度。
由於 4 個伺服馬達的控制角度不同,對於校準伺服馬達,我一次只接一個伺服馬達進行調校,以伺服馬達A來說,他在機器手臂最下方,控制轉動方向。我設定最小值為 0,最大值為 550,為什麼會是這個值呢?只因為這個值會讓 Arduino 啟動時,手臂的角度剛好與底座成 90度垂直,稍微改一下最大與最小值,都會讓起動的角度偏移。例如第 4 個馬達,設定介於 150 與 600 之間,若將 600 減少的 350左右,爪子剛好可以閉合,可是沒有抓緊的力道,將最大值稍微設定高一點,可以增加爪子緊縮的力道。
第 2 個伺服馬達控制大臂的彎曲角度,我使用 z 軸的傾斜角度來控制,第 3 個伺服馬達控制爪子手腕的彎曲角度,我用搖桿的 y 軸來控制,往下按時,爪子手腕也會往下,至於設定的值是根據實際測試得到,讓手臂不要因彎曲角度過大敲到桌面,或是因最大與最小值的角度範圍太大變得不好控制等,這需要多幾次嘗試,才能知道該設定角度的大小範圍。
我沒有將組裝機器手臂的過程放上來,如有需要瞭解如何安裝的朋友,可以參考 這篇 文章。
[安裝Adafruit-PWM-Servo-Driver-Library]
本實作需要安裝以下程式庫:- adafruit /Adafruit-PWM-Servo-Driver-Library
程式庫(Library)安裝方法請參考另一篇文章: Arduino筆記:安裝 Arduino IDE 程式庫(Library)。
[材料]
- Arduino Uno x1
- WiiChuck 轉接板 x1
- Wii 擴充搖桿 x1
- 麵包板 x1
- 伺服馬達 MG996R x4
- 18650電池 x2
- 18650電池座 x1
- 可調變壓轉換器 x1
- U型固定架 x2
- MG996R固定架 x3
- 長軸U型固定架 x2
- MG996R控制爪子組件 x1
- 排線 x n 條
[接線圖]
Aruino Uno | Wii Nunchuck | PCA9685 |
---|---|---|
3V3 | + | N/A |
5V | N/A | VCC |
GND | - | GND |
A4(SDA) | D(Data) | N/A |
A5(SCL) | C(Clock) | N/A |
SCL | N/A | SCL |
SDA | N/A | SDA |
[程式]
#include <Wire.h> #include <Servo.h> #include <Adafruit_PWMServoDriver.h> #define TWI_FREQ 400000L #define SERVO_A_MIN 0 // 伺服馬達A最小脈寬值 #define SERVO_A_MAX 550 // 伺服馬達A最大脈寬值 #define SERVO_B_MIN 150 #define SERVO_B_MAX 600 #define SERVO_C_MIN 300 #define SERVO_C_MAX 900 #define SERVO_D_MIN 150 #define SERVO_D_MAX 600 #define MIN_PULSE_WIDTH 544 #define MAX_PULSE_WIDTH 2400 #define FREQUENCY 50 // 呼叫伺服驅動程式函數,預設I2C位址為 0x40 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); // 定義伺服機使用的輸出Port int ServoA = 0; int ServoB = 1; int ServoC = 2; int ServoD = 3; // 從nunchuck回傳的資料是一個 6位元的值 #define BUFFERSIZE 6 uint8_t outbuf[BUFFERSIZE]; int bufPos = 0; int joy_x_axis = outbuf[0]; int joy_y_axis = outbuf[1]; int accel_x_axis = outbuf[2] * 2 * 2; int accel_y_axis = outbuf[3] * 2 * 2; int accel_z_axis = outbuf[4] * 2 * 2; int z_button = 0; int c_button = 0; long refreshTime = 1000 / 30; // 更新時間,以為秒計 millisecs long lastTime = 0; void setup() { Serial.begin (57600); Wire.begin (); pwm.begin(); pwm.setPWMFreq(FREQUENCY); //最大的 PWM 頻率 nunchuck_init (); Serial.println("\r\n"); Serial.print ("Start Wii Nunchuk demo\n"); Serial.print ("joy_x\tjoy_y\tacc_x\tacc_y\tacc_z\tz_but\tc_but\r\n"); } void loop() { long now = millis(); // get the current time // 最後一次取得資料到這次取得的時間大於更新時間 if ( now - lastTime >= refreshTime) { lastTime = now; //儲存現在時間 Wire.requestFrom (0x52, BUFFERSIZE); // 從 nunchuck 取得資料 // 將收到的資料放到 buffer while (Wire.available () && bufPos < BUFFERSIZE) { outbuf[bufPos] = nunchuk_decode_byte(Wire.read()); bufPos++; } if (bufPos == BUFFERSIZE) { // 收到 buffer 滿了 printNunchuckData(); ServoStart(); } // 更新 buffer 位址 bufPos = 0; send_zero(); } } void nunchuck_init() { Wire.beginTransmission (0x52); Wire.write (0x40); Wire.write (0x00); Wire.endTransmission (); } void send_zero() { Wire.beginTransmission (0x52); Wire.write (0x00); Wire.endTransmission (); } void printNunchuckData() { joy_x_axis = outbuf[0]; joy_y_axis = outbuf[1]; accel_x_axis = outbuf[2] * 2 * 2; accel_y_axis = outbuf[3] * 2 * 2; accel_z_axis = outbuf[4] * 2 * 2; z_button = 0; c_button = 0; // byte outbuf[5] 包含 z 和 c 按鍵的 bits,也包含加速度的值 if ((outbuf[5] >> 0) & 1) { z_button = 1; } if ((outbuf[5] >> 1) & 1) { c_button = 1; } if ((outbuf[5] >> 2) & 1) { accel_x_axis += 2; } if ((outbuf[5] >> 3) & 1) { accel_x_axis += 1; } if ((outbuf[5] >> 4) & 1) { accel_y_axis += 2; } if ((outbuf[5] >> 5) & 1) { accel_y_axis += 1; } if ((outbuf[5] >> 6) & 1) { accel_z_axis += 2; } if ((outbuf[5] >> 7) & 1) { accel_z_axis += 1; } // 顯示搖桿資料,顯示搖桿上方方向鍵的 X, Y座標 Serial.print(joy_x_axis, DEC); Serial.print("\t"); Serial.print(joy_y_axis, DEC); Serial.print("\t"); // 顯示X,Y,Z三軸的加速度值 Serial.print(accel_x_axis, DEC); Serial.print("\t"); Serial.print(accel_y_axis, DEC); Serial.print("\t"); Serial.print(accel_z_axis, DEC); Serial.print("\t"); // 顯示前方的兩個按鍵狀態 Serial.print(z_button, DEC); Serial.print("\t"); Serial.print(c_button, DEC); Serial.print("\t"); // Serial.print("\r\n"); } void ServoStart() { // 使用搖桿控制伺服馬達,依照實測的搖桿及z水平加速值範圍設定map值, // 轉成介於伺服馬達最小與最大值之間 int valueA = map(joy_x_axis, 25, 220, SERVO_A_MAX, SERVO_A_MIN); int valueB = map(accel_z_axis, 25, 990, SERVO_B_MIN, SERVO_B_MAX); int valueC = map(joy_y_axis, 25, 220, SERVO_C_MIN, SERVO_C_MAX); int valueD = 0; //第四個伺服馬達控制爪子,按下閉合 if (z_button == 1) { valueD = SERVO_D_MIN; } if (z_button == 0) { valueD = SERVO_D_MAX; } // 讓伺服馬達移動到該位置 pwm.setPWM(ServoA, 0, valueA); pwm.setPWM(ServoB, 0, valueB); pwm.setPWM(ServoC, 0, valueC); pwm.setPWM(ServoD, 0, valueD); // 在串列埠顯示值 Serial.print(valueA); Serial.print("\t"); Serial.print(valueB); Serial.print("\t"); Serial.print(valueC); Serial.print("\t"); Serial.print(valueD); Serial.print("\r\n"); } char nunchuk_decode_byte (char x) { x = (x ^ 0x17) + 0x17; return x; }
[結果]
[參考資料]
- Sunfounder:PCA9685 16 Channel 12 Bit PWM Servo Driver
- adafruit.com:Adafruit PCA9685 16-Channel Servo Driver
- instructables.com:Nunchuk Controlled Robotic Arm (with Arduino)
張貼留言