【教學】 Arduino 101結合 Processing 互動滾球遊戲

本文章要告訴您如何結合 Processing IDE 與 Arduino 101 開發板來設計一個滾球遊戲。藉由 Arduino 101 開發板上的加速度計與陀螺儀感測器來感測板子於空間中的傾斜、位移與轉動的狀態,藉此控制球在畫面上的四處移動,就好像零食乖乖附贈的小玩具一樣。

作者/攝影 曾吉弘
時間   2小時
難度

★★★★★

材料表
  • Arduino 101開發板(亦可使用KEYES UNO陀螺儀/加速度感測器 作為取代)
  • 個人電腦 (作業系統可用 Windows, Mac OSX 與 Linux,本範例使用 Windows 7)
  • Processing IDE  (2.0 or 3.0 皆可,本範例使用 2.2.1)
  • Arduino IDE 1.6.x 版以上

 

本範例是阿吉老師於海洋大學機械系學期課程的課堂小挑戰,修改 Arduino官網的範例來製作一個滾球遊戲,藉由搖擺 Arduino 101 開發板來控制 Processing 畫面上的球移動,這運用到了Arduino 101的加速度計與陀螺儀感測器,並理解如何透過序列通訊讓 Arduino 與 Processing (您的PC) 彼此溝通,後續要改成藍牙也是沒有問題的。

這類型的遊戲通稱為 labyrinth,玩法都差不多就是控制球走到終點,iOS 或 Android 都有非常多可以下載,您可以多載幾個來玩並把一些可怕的功能加入本範例中。

實際操作影片:

 

Arduino官網範例的實際執行影片:

 

Processing code (重要程式碼說明列於註解)

Processing 會讀取來自指定序列埠的 Arduino 所送來的資料,也就是加速度計與陀螺儀感測器值(Arduino 101 有內建的濾波器函式先處理過了,不然應該會抖動地相當嚴重)。

接著根據這些數值來決定灰球的XY位移,畫面上有陷阱(黑色球)與過關點(黃色球),當揮球與陷阱重疊達10像素(這個值您可以自行調整,不然一碰到邊邊就死掉也太嚴苛了吧……)時就會回到起點(畫面中央的紅色方塊)。

過關條件是灰球”完全”位在過關點中就會顯示紅色 WIN !!!」 字樣,當然這個條件您也可以自行調整。

Processing code
import processing.serial.*;

Serial myPort;
float x=0;

float y=0;

float yaw = 0.0; //陀螺儀Z軸

float pitch = 0.0; //陀螺儀X軸

float roll = 0.0; //陀螺儀Y軸
void setup()

{

size(600, 500, P3D);
myPort = new Serial(this, "COM3", 9600); //請改為您電腦上對應的COM port
// if you know the serial port name

//myPort = new Serial(this, "COM5:", 9600); // Windows

//myPort = new Serial(this, "/dev/ttyACM0", 9600); // Linux

//myPort = new Serial(this, "/dev/cu.usbmodem1217321", 9600); // Mac
textSize(16); // set text size

textMode(SHAPE); // set text mode to shape

}
void draw()

{

serialEvent(); //呼叫本函式,讀取序列埠訊息

background(255); //背景設為白色

lights();
translate(width/2, height/2); // set position to centre
pushMatrix(); // begin object
float c1 = cos(radians(roll));

float s1 = sin(radians(roll));

float c2 = cos(radians(pitch));

float s2 = sin(radians(pitch));

float c3 = cos(radians(yaw));

float s3 = sin(radians(yaw));

applyMatrix( c2*c3, s1*s3+c1*c3*s2, c3*s1*s2-c1*s3, 0,

-s2, c1*c2, c2*s1, 0,

c2*s3, c1*s2*s3-c3*s1, c1*c3+s1*s2*s3, 0,

0, 0, 0, 1);
popMatrix(); // end of object
//顯示座標訊息

print(x);

print("\t");

print(y);

println("\t");
background(255);

fill(255, 0, 0);
float dx=roll*0.11111;

float dy=-pitch*0.11111;

x=x+dx; //float x=0 out of void draw

y=y+dy; //float y=0 out of void draw
//邊緣偵測,在此設定讓球不會出界,但您可以改掉

if (x>285) {

x=285;

} else if (x<-285) {

x=-285;

}

if (y>235) {

y=235;

} else if (y<-235) {

y=-235;

}
fill(192, 192, 192); //灰色

ellipse(x, y, 30, 30); //根據x y變數值決定灰球位置

fill(255, 0, 0); //紅色

rect(-25, -25, 50, 50); //起始點

fill(255, 215, 0); //土黃色

ellipse(300-25, 250-25, 50, 50); //過關點位置l

fill(0, 0, 0); //黑色

ellipse(-300+25, -250+25, 50, 50);//陷阱位置
//是否掉入陷阱,如果重疊到一定比例則回到起始點重來

if (sqrt(pow((x+275), 2)+pow((y+225), 2))<=10) {

x=0;

y=0;

}
//是否過關,則顯示相關訊息

if (sqrt(pow((x-275), 2)+pow((y-225), 2))<=10) {

textSize(64);

fill(255, 0, 0);

text("WIN!!!", 130, -125); //顯示勝利訊息

}

}
void serialEvent()

{

int newLine = 13; //ASCII編碼中的換行字元: \n

String message;

do {

message = myPort.readStringUntil(newLine); //讀取序列埠訊息直到讀到 \n 為止

if (message != null) { //如果有訊息進來

String[] list = split(trim(message), " ");

//trim()會先移除 message字串頭尾的空白

//再由split()以” “來切割message並存入list,這與Arduino code中的 Serial.print()對應

//https://processing.org/reference/trim_.html

//https://processing.org/reference/split_.html

if (list.length >= 4 && list[0].equals("Orientation:")) {

//檢查分割後的陣列長度是否正確,且第一個元素是否等於”Orientation

//代表抓到第一筆訊息的位置,後續取 list[1]~list[3]才會正確

yaw = float(list[1]); //取得 yaw

pitch = float(list[2]); //取得 pitch

roll = float(list[3]); //取得 roll

}

}

}

while (message != null);

}

 

Arduino code

(來自https://www.arduino.cc/en/Tutorial/Genuino101CurieIMUOrientationVisualiser,未修改)

42
#include <CurieIMU.h>
#include <MadgwickAHRS.h> //濾波函式庫

//http://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
Madgwick filter; //宣告濾波器物件

unsigned long microsPerReading, microsPrevious;

float accelScale, gyroScale;
void setup() {
Serial.begin(9600); //建立對Processing的序列通訊
//啟動IMU與filter
CurieIMU.begin();
CurieIMU.setGyroRate(25);
CurieIMU.setAccelerometerRate(25);
filter.begin(25);
//將加速度計數值範圍設定為2G

CurieIMU.setAccelerometerRange(2);
//將陀螺儀數值範圍設定為 250 degrees/second

CurieIMU.setGyroRange(250);
// initialize variables to pace updates to correct rate

microsPerReading = 1000000 / 25;
microsPrevious = micros();
}

void loop() {
int aix, aiy, aiz;
int gix, giy, giz;
float ax, ay, az;
float gx, gy, gz;
float roll, pitch, heading;

unsigned long microsNow;
// check if it's time to read data and update the filter

microsNow = micros();
if (microsNow - microsPrevious >= microsPerReading) {
//讀取CurieIMU的raw值
CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);
//將 raw值分別轉為對應的單位:加速度計(g) / 陀螺儀(degrees/second)

ax = convertRawAcceleration(aix);
ay = convertRawAcceleration(aiy);
az = convertRawAcceleration(aiz);

gx = convertRawGyro(gix);
gy = convertRawGyro(giy);
gz = convertRawGyro(giz);
//要求 filter 取得 IMU值進行計算

filter.updateIMU(gx, gy, gz, ax, ay, az);
//顯示 filter 處理後的XYZ軸向訊息, X: pitch, Y:roll, Z: heading = yaw

roll = filter.getRoll();

pitch = filter.getPitch();

heading = filter.getYaw();

//以下開始發送資料

Serial.print("Orientation: ");

Serial.print(heading);

Serial.print(" ");

Serial.print(pitch);

Serial.print(" ");

Serial.println(roll);
// increment previous time, so we keep proper pace

microsPrevious = microsPrevious + microsPerReading;

}

}
float convertRawAcceleration(int aRaw) {

//由於將加速度計數值範圍設定為2G, 在此進行轉換

// -2g對應到 raw 值的 -32768, +2g 則是 32767
// since we are using 2G range

// -2g maps to a raw value of -32768

// +2g maps to a raw value of 32767



float a = (aRaw * 2.0) / 32768.0;

return a;

}
float convertRawGyro(int gRaw) {

//由於將陀螺儀數值範圍設定為250 degrees/seconds, 在此進行轉換

// -250 對應到 raw 值的 -32768, +250 則是 32767



float g = (gRaw * 250.0) / 32768.0;

return g;

}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *