本文章要告訴您如何結合 Processing IDE 與 Arduino 101 開發板來設計一個滾球遊戲。藉由 Arduino 101 開發板上的加速度計與陀螺儀感測器來感測板子於空間中的傾斜、位移與轉動的狀態,藉此控制球在畫面上的四處移動,就好像零食乖乖附贈的小玩具一樣。
作者/攝影 | 曾吉弘 |
時間 | 2小時 |
成本 | ❊ Arduino101開發板 $1,575 (購買連結) |
難度 | * * * * * * |
材料表 | ❊個人電腦 (作業系統可用 Windows, Mac OSX 與 Linux,本範例使用 Windows 7)
❊Processing IDE (2.0 or 3.0 皆可,本範例使用 2.2.1) ❊Arduino IDE 1.6.x 版以上 ❊Arduino 101開發板 |
本範例是阿吉老師於海洋大學機械系學期課程的課堂小挑戰,修改 Arduino官網的範例來製作一個滾球遊戲,藉由搖擺 Arduino 101 開發板來控制 Processing 畫面上的球移動,這運用到了Arduino 101的加速度計與陀螺儀感測器,並理解如何透過序列通訊讓 Arduino 與 Processing (您的PC) 彼此溝通,後續要改成藍牙也是沒有問題的。
這類型的遊戲通稱為 labyrinth,玩法都差不多就是控制球走到終點,iOS 或 Android 都有非常多可以下載,您可以多載幾個來玩並把一些可怕的功能加入本範例中。
實際操作影片:www.youtube.com/watch?v=wxrCQRF8FZ4
Arduino官網範例的實際執行影片:www.youtube.com/embed/WyVhrwHfmvI
Processing code (重要程式碼說明列於註解)
Processing 會讀取來自指定序列埠的 Arduino 所送來的資料,也就是加速度計與陀螺儀感測器值(Arduino 101 有內建的濾波器函式先處理過了,不然應該會抖動地相當嚴重)。
接著根據這些數值來決定灰球的XY位移,畫面上有陷阱(黑色球)與過關點(黃色球),當揮球與陷阱重疊達10像素(這個值您可以自行調整,不然一碰到邊邊就死掉也太嚴苛了吧……)時就會回到起點(畫面中央的紅色方塊)。
過關條件是灰球”完全”位在過關點中就會顯示紅色 「WIN !!!」 字樣,當然這個條件您也可以自行調整。
import processing.serial.*;
Serial myPort; float y=0; float yaw = 0.0; //陀螺儀Z軸 float pitch = 0.0; //陀螺儀X軸 float roll = 0.0; //陀螺儀Y軸 { size(600, 500, P3D); //myPort = new Serial(this, “COM5:”, 9600); // Windows //myPort = new Serial(this, “/dev/ttyACM0”, 9600); // Linux //myPort = new Serial(this, “/dev/cu.usbmodem1217321”, 9600); // Mac textMode(SHAPE); // set text mode to shape } { serialEvent(); //呼叫本函式,讀取序列埠訊息 background(255); //背景設為白色 lights(); 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); print(x); print(“\t”); print(y); println(“\t”); fill(255, 0, 0); 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; } 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); //顯示勝利訊息 } } { 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,未修改)
#include <CurieIMU.h>
#include <MadgwickAHRS.h> //濾波函式庫 //http://x-io.co.uk/open-source-imu-and-ahrs-algorithms/ unsigned long microsPerReading, microsPrevious; float accelScale, gyroScale; Serial.begin(9600); //建立對Processing的序列通訊 CurieIMU.begin(); CurieIMU.setGyroRate(25); CurieIMU.setAccelerometerRate(25); filter.begin(25); CurieIMU.setAccelerometerRange(2); //將陀螺儀數值範圍設定為 250 degrees/second CurieIMU.setGyroRange(250); microsPerReading = 1000000 / 25; microsPrevious = micros(); } int aix, aiy, aiz; int gix, giy, giz; float ax, ay, az; float gx, gy, gz; float roll, pitch, heading; unsigned long microsNow; microsNow = micros(); if (microsNow – microsPrevious >= microsPerReading) { CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz); ax = convertRawAcceleration(aix); ay = convertRawAcceleration(aiy); az = convertRawAcceleration(aiz); gx = convertRawGyro(gix); gy = convertRawGyro(giy); gz = convertRawGyro(giz); filter.updateIMU(gx, gy, gz, ax, ay, az); 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); microsPrevious = microsPrevious + microsPerReading; } } //由於將加速度計數值範圍設定為2G, 在此進行轉換 // -2g對應到 raw 值的 -32768, +2g 則是 32767 // -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; } //由於將陀螺儀數值範圍設定為250 degrees/seconds, 在此進行轉換 // -250 對應到 raw 值的 -32768, +250 則是 32767
float g = (gRaw * 250.0) / 32768.0; return g; } |