[程式教學] Arduino 101結合 Processing 互動滾球遊戲

本文章要告訴您如何結合 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 都有非常多可以下載,您可以多載幾個來玩並把一些可怕的功能加入本範例中。

實際操作影片:

 

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

 

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

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

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

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

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);

}
Processing code

 

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/
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;

}
42

Leave a comment

Your email address will not be published.


*