2013年9月14日 星期六

Arduino - 動作感測控制器製作

動作感測能讓裝置感應到使用者的動作,像是手機、Wii都是
而這次要做的是使用加速度感測器製作的控制器。

首先要來看看主角 ADXL335三軸加速度感測器 :

俯視圖














這是Analog Devices公司的ADXL335感測器,其上有六個連接插頭分別
標示為GND、Z、Y、X、3V、TEST,將其焊上0.1"排插使用
焊上0.1"排插














接下來就是接線囉,感測器是類比裝置,所以將X、Y、Z接到Arduino
上的類比針腳

將ADXL335連接至Arduino














線路接好了,接著來看看它會丟出甚麼資料吧~

code : 
讀取三個軸向的輸入值、並將之輸出到序列埠上


















result :
















這三排數值代表著平放時三軸感測到的加速度,
沿著軸移動,則相對應的數值會跟著變動。

但觀察上圖,在平放時三軸偵測到的值仍然會跳動,
而且若要進一步的運用,還需要確立感測器的最大值和最小值。

解決方法是修改code讓其自動記錄最大最小值,
並設緩衝,連續紀錄幾筆資料後取平均避免跳動。

最後在電路上加上一個按鈕,
控制器就完成了~
加上下拉式電阻按鈕,輸入至數位針腳7














控制器 code :
//////////////////////////////////////////////////////////////
#include <Bounce.h>

const unsigned int BUTTON_PIN = 7;
const unsigned int X_AXIS_PIN = 2;
const unsigned int Y_AXIS_PIN = 1;
const unsigned int Z_AXIS_PIN = 0;
const unsigned int BAUD_RATE = 19200;
const unsigned int NUM_AXES = 3;
const unsigned int PINS[NUM_AXES] = {X_AXIS_PIN, Y_AXIS_PIN, Z_AXIS_PIN};
const unsigned int BUFFER_SIZE = 16;

int buffer[NUM_AXES][BUFFER_SIZE];
int buffer_pos[NUM_AXES] = {0};

Bounce button(BUTTON_PIN, 20);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(BAUD_RATE);
  pinMode(BUTTON_PIN, INPUT);
}

int get_axis(const int axis){
  delay(1);
  buffer[axis][buffer_pos[axis]] = analogRead(PINS[axis]);
  buffer_pos[axis] = (buffer_pos[axis] + 1) % BUFFER_SIZE;
  
  long sum = 0;
  for(int i = 0; i < BUFFER_SIZE; i++){
    sum += buffer[axis][i];
  }
  return round(sum / BUFFER_SIZE);
}

int get_x(){return get_axis(2);}
int get_y(){return get_axis(1);}
int get_z(){return get_axis(0);}

void loop() {
  // put your main code here, to run repeatedly: 
  Serial.print(get_x());
  Serial.print(" ");
  Serial.print(get_y());
  Serial.print(" ");
  Serial.print(get_z());
  Serial.print(" ");
  if(button.update())
    Serial.println(button.read() == HIGH ? "1" : "0");
  else
    Serial.println("0");
}
///////////////////////////////////////////////////////////////////////////////////

將code上載到Arduino,控制器就完成了,
他會向電腦輸出X、Y、Z及按鈕的0/1值

為了測試,
用Processing寫了個小遊戲來試試手~

Game code :
//////////////////////////////////////////////////////////////////////////////////////
import processing.serial.*;

Serial arduinoPort;

final int COLUMNS = 7;
final int ROWS = 4;
final int BALL_RADIUS = 8;
final int BALL_DIAMETER = BALL_RADIUS * 2;
final int MAX_VELOCITY = 8;
final int PADDLE_WIDTH = 60;
final int PADDLE_HEIGHT = 15;
final int BRICK_WIDTH = 40;
final int BRICK_HEIGHT = 20;
final int MARGIN = 10;
final int WIDTH = COLUMNS * BRICK_WIDTH + 2 * MARGIN;
final int HEIGHT = 300;
final int X_AXIS_MIN = 252;
final int X_AXIS_MAX = 443;
final int LINE_FEED = 10;
final int BAUD_RATE = 19200;

int px, py;
int vx, vy;
int xpos = WIDTH/2;
int[][] bricks = new int[COLUMNS][ROWS];

boolean buttonPressed = false;
boolean paused = true;
boolean done = true;

void setup(){
  size(WIDTH, HEIGHT);
  noCursor();
  textFont(loadFont("Verdana-Bold-36.vlw"));
  initGame();
  println(Serial.list());
  arduinoPort = new Serial(this, Serial.list()[0], BAUD_RATE);
  arduinoPort.bufferUntil(LINE_FEED);
}

void initGame(){
  initBricks();
  initBall();
}

void initBricks(){
  for(int x = 0; x < COLUMNS; x++)
    for(int y = 0; y < ROWS; y++)
      bricks[x][y] = 1;
}

void initBall(){
  px = width / 2;
  py = height / 2;
  vx = int(random(-MAX_VELOCITY, MAX_VELOCITY));
  vy = -2;
}

void draw(){
  background(0);
  stroke(255);
  strokeWeight(3);
  
  done = drawBricks();
  if(done){
    paused = true;
    printWinMessage();
  }
  
  if(paused)
    printPauseMessage();
  else
    updateGame();
    
  drawBall();
  drawPaddle();
}

boolean drawBricks(){
  boolean allEmpty = true;
  for(int x = 0; x < COLUMNS; x++){
    for(int y = 0; y < ROWS; y++){
      if(bricks[x][y] > 0){
        allEmpty = false;
        fill(0, 0, 100 + y*8);
        rect(MARGIN + x * BRICK_WIDTH,
             MARGIN + y * BRICK_HEIGHT,
             BRICK_WIDTH,
             BRICK_HEIGHT);
    }
  }
 }
 return allEmpty;
}
void drawBall(){
  strokeWeight(1);
  fill(128, 0, 0);
  ellipse(px, py, BALL_DIAMETER, BALL_DIAMETER);
}

void drawPaddle(){
  int x = xpos - PADDLE_WIDTH / 2;
  int y = height - (PADDLE_HEIGHT + MARGIN);
  strokeWeight(1);
  fill(128);
  rect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
}

void printWinMessage(){
  fill(255);
  textSize(36);
  textAlign(CENTER);
  text("YOU WIN!", width / 2, height * 2 / 3); 
}

void printPauseMessage(){
  fill(128);
  textSize(16);
  textAlign(CENTER);
  text("Press Button to Continue", width / 2, height * 5 / 6);
}

void updateGame(){
  if(ballDropped()){
    initBall();
    paused = true;
  }
  else{
    checkBrickCollision();
    checkWallCollision();
    checkPaddleCollision();
    px += vx;
    py += vy;
  }
}

boolean ballDropped(){
  return py + vy > height - BALL_RADIUS;
}

boolean inXRange(final int row, final int v){
  return px + v > row * BRICK_WIDTH &&
         px + v < (row + 1) * BRICK_WIDTH + BALL_DIAMETER;
}

boolean inYRange(final int col, final int v){
  return py + v > col * BRICK_HEIGHT &&
         py + v < (col + 1) * BRICK_HEIGHT + BALL_DIAMETER;
}

void checkBrickCollision(){
  for(int x = 0; x < COLUMNS; x++){
    for(int y = 0; y < ROWS; y++){
      if(bricks[x][y] > 0){
        if(inXRange(x, vx) && inYRange(y, vy)){
          bricks[x][y] = 0;
          if(inXRange(x, 0))
            vy = -vy;
          if(inYRange(y, 0))
            vx = -vx;
        }
      }
    }
  }
}

void checkWallCollision(){
  if(px + vx < BALL_RADIUS || px + vx > width - BALL_RADIUS)
    vx = -vx;
  if(py + vy < BALL_RADIUS || py + vy > height - BALL_RADIUS)
    vy = -vy;
}

void checkPaddleCollision(){
  final int cx = xpos;
  if(py + vy >= height - (PADDLE_HEIGHT + MARGIN + 6) &&
     px >= cx - PADDLE_WIDTH / 2 &&
     px <= cx + PADDLE_WIDTH / 2){
       vy = -vy;
       vx = int(map(px-cx,
                    -(PADDLE_WIDTH / 2), PADDLE_WIDTH / 2,
                    -MAX_VELOCITY, MAX_VELOCITY));
     }
}

void SerialEvent(Serial port){
  final String arduinoData = port.readStringUntil(LINE_FEED);
  if(arduinoData != null){
    final int[] data = int(split(trim(arduinoData), ' '));
    if(data.length == 4){
      buttonPressed = (data[3] == 1);
      if(buttonPressed){
        paused = !paused;
        if(done){
          done = false;
          initGame();
        }
      }
      
      if(!paused){
        xpos = int(map(data[0], X_AXIS_MIN, X_AXIS_MAX, 0, WIDTH));
      }
    }
  }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

關於code和Processing的資料可以參考下列網址 :

Code :          http://pragprog.com/titles/msard/source_code
Processing :  http://processing.org

這是一個打磚塊的小遊戲,
可以用控制器控制衡感進行遊戲~

Test Video :

動作感測器可以應用在很多地方,
像是在加一個按鈕就能充當滑鼠了~
總的來說是個很實用的小東西,
可以擴充成很多有趣的電子產品。

這次的小遊戲只是測試用,比較單調,
以後有空會再多增加一些東西,讓他變得更有趣 =目

沒有留言:

張貼留言