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 :

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

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

2013年9月11日 星期三

光感應小夜燈臨界亮度調整

買了一個小夜燈,當環境變暗時會自動點亮 ~
LED小夜燈














但是在使用時卻出了小問題,由於客廳有兩個燈,
當離小夜燈較遠的燈亮時小夜燈也會點亮,
所以這裡遇到的問題是 : 

Q : 如何讓小夜燈對明滅切換的臨界亮度下降 ?

打開小夜燈,研究裡面的結構

小夜燈內部電路














可以發現正中間的CdS,也就是光敏電阻,
是控制小夜燈明滅的關鍵!

上一篇有提到,光敏電阻的電阻值會隨著環境光的亮度而改變,
當環境愈暗時,光敏電阻的阻值愈大,
當阻值大到一定程度時,LED燈就亮了。

知道原理後問題就很好解決了,
只要將光敏電阻額外並連一個電阻器,
就能降低整體電阻阻值,讓其需要更低的亮度才能達到臨界電阻值。

那接下來的問題是 : 

Q : 要並聯多少歐姆的電阻 ?

解決辦法是先將光敏電阻並聯一可變電阻,
給予固定亮度,並調整可變電阻至明滅切換的臨界點,
此時可變電阻阻值就是所求。

用可變電阻尋找所需阻值














測試後阻值是103.7k歐姆,取近似選擇100k歐姆的碳模電阻,
將電阻焊到電路上 : 
焊上100k歐姆的電阻














最後將小夜燈重新組裝好就完成了~

試燈 :




























自己動手改進日用品的電路很有趣 =目
不過深深覺得需要買一個焊架了,
不然每次焊接時電路板跑來跑去的頗考驗技術......

2013年9月8日 星期日

Arduino - 光感應LED製作

只要有光敏電阻,配上Arduino就能做出隨環境光源變化的有趣東西!

光敏電阻會隨著入射光的強度而調降電阻值,所以又稱光依賴電阻器(LDR)以及
光導體(Photoconductor)。光敏電阻中最流行的就是CdS(硫化鎘)元件,
恰好手邊就有一個 ~
俯視圖

側視圖
材料清單:
  • Arduino UNO REV3
  • USB接線
  • 光敏電阻        X1
  • 10K.歐姆電阻X1
  • 跳線若干
  • LED手電筒     X1
利用光敏電阻建構以下電路 :

右上方是被拆解掉的LED手電筒,預期會隨環境光源改變亮度(環境愈亮燈愈暗)。
手電筒是吃1.54V乾電池的,但實際輸出因為電阻關係,光強度會比較暗。

現在來點簡單的code :

const unsigned int OUTPUT_PIN = 9;

void setup() {
  // put your setup code here, to run once:  
}

void loop() {
  // put your main code here, to run repeatedly: 
  const int ligh = analogRead(A0);                //讀取A0的類比輸入,值預設為0~1023
  int intensity = map(ligh, 0, 1023, 255, 0);   //將0~1023的值轉換成255~0
  analogWrite(OUTPUT_PIN, intensity);     //以PWM的型式由第8數位PIN類比輸出
  delay(100);
}

將程式驗證後上傳Arduino : 

這次的光感測LED是拿手邊現成的LED手電筒改裝來的,
以後有機會入手小燈座&燈泡,就可以自己DIY一個光感測小夜燈了!
或者接繼電器變成當光線暗到一定程度就會自動開啟的小燈也不錯。

2013年9月7日 星期六

Arduino-UNO R3

暑假的新玩具~
Arduino 原型開發版 Uno 系列
Arduino Uno Rev3

Arduino Uno Specs :
  • Microcontroller :      ATMEGA328P
  • Operating Voltage :  5V
  • Input Voltage :          7~12V
  • Digital I/O :              14 (6 Power PWM Outputs)
  • Analog Input :           6
  • Memory :                 Flash 32 KB, SRAM 2 KB, EEPROM 1 KB
  • Clock Speed :          16 MHZ

USB port 可提供 5V 的電源,可外接AC變壓器(2.1mm圓頭,中心為正極),
也可使用電池作為外部電源(連接到Vin , GND)

IDE可在官網找到(http://arduino.cc/en/Main/Software)
目前(2013/9/5)最新版本是 1.0.5,
驅動程式則是將Arduino插入USB就能自動安裝
Arduino IDE 介面
Arduino 上有TX/RX 指示燈,除此之外還有狀態LED,與13號I/O針腳相連,
用個簡單的 code 來測試Arduino的運作~

void setup() {
  // put your setup code here, to run once:
  pinMode(13,OUTPUT); //將13號針腳設為OUTPUT
}

void loop() {
  // put your main code here, to run repeatedly: 
  digitalWrite(13, HIGH); //將13號針腳設為HIGH
  delay(500);                    //等待500ms
  digitalWrite(13, LOW);  //將13號針腳設為LOW
  delay(500);
}

setup()中pinMode()將13號針腳設為OUTPUT,
loop()中則以DiditalWrite()和設定輸出電壓,
並delay(500)讓其以500ms的頻率切換,
在IDE編輯區鍵入code並上傳至Arduino,
實際解果應會看到狀態LED以0.5Hz的頻率閃爍

Test Video : 



Arduino 是個有趣的東西,操作容易,且和PIC、FPGA比起來,
IDE中已經有很棒的library可以使用,而且還有完整的examples教學!
跟以前用Quartus玩FPGA時比起來真是便利太多了!
而且Arduino的語法基本上就是 C/C++ ,學起來一點也不耗腦力,
對於那些有滿滿的創意卻受限於code的人來說真是一大福音阿 (茶)


2013年9月4日 星期三

Working Space

DIY時常會遇到這樣那樣的問題,
於是決定將每一次的嘗試記錄下來,
為未來的自己提供一些database 

小倉儲 :