本文章要告訴您如何結合 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;
}
Post Views: 452