2016年5月13日 星期五

Arduino - I2C

有在玩控制板的人,應該很常聽到I2C(Inter-Integrated Circuit)這個名詞吧?!

I2C,也有人稱為二線式介面(TWI,Two-Wire Interface),因為它只需要兩根Pin就能和其它支援I2C的元件進行通訊。此兩根Pin(SDA資料線和SCL時脈線)在標準的Arduino和Arduino Mega上,被定義的腳位不同:

  • 標準的Arduino:
    SDA -- Pin A4
    SCL -- Pin A5
  • Arduino Mega:
    SDA -- Pin 20
    SCL -- Pin 21
Arduino要使用I2C進行通訊,通常是透過"Wire"這個Library。在官網上看到一個簡單有趣,而且很實用的範例。使用二塊Arduino互相通訊。

硬體的連線方式可參考官網上的這張圖:

很簡單的連接法,就是SDA接SDA,SCL接SCL,但記得要連接共同的接地。由於這個範例我們需要由電腦顯示接收和傳送的訊息,所以Master Arduino需透過USB與電腦連接。
在供電的部份,由於Master Arduino透過電腦USB提供5V的電來啟動,但Slave Arduino沒有額外5V電源來啟動,所以也需要拉一條5V的電源給Slave Arduino。

程式碼如下:

<< Master Arduino >>

//     =========     //
//     Master Arduino
//     程式碼開始
//     =========     //

#include <Wire.h>

void setup()
{
     Wire.begin(); // join i2c bus (address optional for master)
}

byte x = 0;

void loop()
 {
     Wire.beginTransmission(8); // transmit to device #8
     Wire.write("x is "); // sends five bytes
     Wire.write(x); // sends one byte
     Wire.endTransmission(); // stop transmitting

     x++;
     delay(500);
}

//     =========     //
//     程式碼結尾     //
//     =========     //


<< Slave Arduino >>

//     =========     //
//     Slave Arduino
//     程式碼開始
//     =========     //

#include <Wire.h>

void setup()
{
     Wire.begin(8); // join i2c bus with address #8
     Wire.onReceive(receiveEvent); // register event
     Serial.begin(9600); // start serial for output
}

void loop()
{
     delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
     while (1 < Wire.available())
     { // loop through all but the last
          char c = Wire.read(); // receive byte as a character
          Serial.print(c); // print the character
     }
     int x = Wire.read(); // receive byte as an integer
     Serial.println(x); // print the integer
}

//     =========     //
//     程式碼結尾
//     =========     //



2016年5月12日 星期四

Arduino - 中斷(Interrupt)

中斷(Interrupt)其實是Arduino中,一個很好用的功能。只是當時剛入門,對它很不了解,不知道該如何使用它。接下來我會用一個故事來解釋如何使用中斷這個功能。

想像一下,有一棟剛蓋好的新大樓,而你買了二樓的位置。假設有一個賣消防器材業務員要來推銷他們公司的火災偵測器。為了安全起見,你買了一個並安裝在自己的家中。

這個故事,跟我們的中斷程序有什麼關聯呢?!

你可以把在家中的日常生活,看作是我們Arduino程式裡的Loop()。這個日常生活是一成不變的。有可能你回到家,就會先開電視看一下新聞,然後準備享用晚餐。吃完晚餐,洗完澡後就準備上床睡覺,結束這完美的一天。
假設在這個過程中,不幸地,大樓發生了火災。由於你家裡有裝了火災偵測器,而且它也沒有被觸發,所以你可以知道,火災發生的地方不在你家。

如果今天發生火災是在其它沒有安裝火災偵測器的房子呢?!屋主要怎麼知道究竟家裡是不是有發生火災?!

如果是在沒有安裝偵測器的情況下,該屋主必需在他日常生活內,安排一項"檢查是否失火"的日常工作。例如:回到家,到各個房間檢查是否失火,開電視看新聞...這樣的生活,不是很累嗎?!

如果以上這個情況,你已經能充分了解了,接下來我會說明中斷和這個故事的關聯性。


  1. 我們知道,家中的日常生活就像是Arduino裡面的Loop()
  2. 你買了一個火災偵測器,可以想像成以volatile宣告的變數。Volatile類型的變數,可以被sketch外的動作而改變,中斷就是其中一個例子。
  3. 將火災偵測器安裝在家中,可以想成將中斷初始化(attachInterrupt)。中斷初始化的格式如下:
    attachInterrupt(中斷編號, 呼叫的函數, 中斷模式)
  4. 火災偵測器在偵測到火災時要做的動作(例如:發出警報),就是中斷發生時要呼叫的函式
  5. 而火災偵測器是否被觸發,是利用平時監測屋中的狀態是否有所變動(例如:溫度、濃煙...)。這個監測的狀態,代表在中斷裡的四個模式(LOW, CHANGE, RISING, FALLING)
在上面的這個故事裡,我尚未提了一個中斷編號的概念。不是忘記寫到,而是每個Arduino版本的中斷編號並不一定相同。
中斷編號,你可以想像成火災偵測器的編號。例如1號偵測器你要裝在廚房,2號偵測器你要裝在臥室...。但中斷編號的數量是有限制的

標準的Arduino會有二個中斷:
  1. 中斷0 -- Pin 2
  2. 中斷1 -- Pin 3

Arduino Mega則有六個中斷:
  1. 中斷0 -- Pin 2
  2. 中斷1 -- Pin 3
  3. 中斷2 -- Pin 21
  4. 中斷3 -- Pin 20
  5. 中斷4 -- Pin 19
  6. 中斷5 -- Pin 18

以上的概念如果都懂了,那接下來看官網上的這個程式範例應該就很容易了解了:

/*
     中斷有四種模式:
  1. LOW : Pin在低電位時觸發中斷
  2. CHANGE : Pin值改變時觸發中斷
  3. RISING : Pin由LOW變成HIGH時觸發中斷
  4. FALLING : Pin由HIGH變成LOW時觸發中斷
*/

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
digitalWrite(ledPin, state);
}

void blink() {
state = !state;
}

/*     程式碼結尾     */


PS : 在官網上有提到一點值得注意的地方,在中斷發生時要呼叫的函數裡,不能有參數和傳回值,也不能使用delay()。而如果你在呼叫的函數裡,有使用到millis()這個方法也會無效,因為millis()本身就是依靠中斷來計時。






Arduino - 上拉電阻和下拉電阻

相信很多人在一開始學習Arduino時,對上拉電阻和下拉電阻感到一知半解。其實在Arduino的官網上已經有很詳細的說明,在此依官網的說明做個翻譯與註記,也希望能對上/下拉電阻能有更深一層的了解。
http://playground.arduino.cc/CommonTopics/PullUpDownResistor

 在邏輯電路裡,上拉電阻是被用來確保如果外部裝置是斷開或高阻抗的狀態,輸入到Arduino的訊號是穩定在預期的電位高(0V or 5V)。因為沒有在輸入端接任何元件,不代表輸入端的邏輯訊號就是"0"

上拉電阻(Pull-Up Resistor)


當電路裡其它元件是沒有作用時,上拉電阻會把相互連接線路的電壓,微弱地拉到和自己連接的來源電壓一樣的電位高。當在線路裡的開關是開路時,它會呈現高阻抗的狀態,就像是斷開的情況一樣。因為其它的元件就像是被斷開,所以電路就像是斷路,然後上拉電阻會把把線路升到高電位。當在線路裡其它的元件發生作用後,會將上拉電阻升上來的高電位撤銷。上拉電阻確保了線路電位是在被定義的電位,即使是連接其它未作用中的元件。

下拉電阻(Pull-Down Resistor)


下拉電阻的作用也是相同,但是是接到GND。它會在線路裡連接著未作用的元件時,保持邏輯訊號在0V左右。

上/下拉電阻值會依你的裝置而有所不同。(以Arduino而言,官方建議電阻值為10K ohm)



2016年5月10日 星期二

Arduino - 使用SPI與週邊設備進行通訊

SPI(Serial peripheral interface)是一種主從式架構的同步資料協定,可以讓多個裝置在短距離互相通訊。

在使用SPI最基本會有四個腳位:

  1. MISO:Master In, Slave Out
  2. MOSI:Master Out, Slave In
  3. SCK(SCLK or Clock):Serial 時脈
  4. SS/CS(Slave Select/Chip Select):LOW表示裝置可以與Master通訊;HIGH表示不與Master通訊
此四個腳位與Arduino UNO對應的腳位如下:
  • SS/CS -- Pin 10
  • MOSI -- Pin 11
  • MISO -- Pin 12
  • SCK -- Pin 13


對於SPI的使用,我們只要了解這幾個腳位即可。接下來,我們以SD卡模組來實作練習看看(因為SD卡也是使用SPI來和Arduino進行通訊)。需要準備的材料如下:
  1. Arduino控制板(我的版本是Arduino UNO)


  2. SD卡模組(這個是很久之前買的模組,現在很多都出Micro SD(小卡)的了,但使用方法大同小異)


  3. 六條連接線

硬體的接線方法,可參考在官網上找到的這張圖接線即可:



在程式碼的部份,我以Arduino 內建的範例來示範。請先開啟Arduino IDE,依序選擇File > Examples > SD > Datalogger




完整的程式碼如下:
/* ==================================================== */
/*
  SD card datalogger

 This example shows how to log data from three analog sensors
 to an SD card using the SD library.

 The circuit:
 * analog sensors on analog ins 0, 1, and 2
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 4

 created  24 Nov 2010
 modified 9 Apr 2012
 by Tom Igoe

 This example code is in the public domain.

 */

#include <SPI.h>
#include <SD.h>

const int chipSelect = 4; 

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("Initializing SD card...");
  pinMode(10,OUTPUT);          // 嚴謹一點的話,程式應該加上此行
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");
}

void loop() {
  // make a string for assembling the data to log:
  String dataString = "";

  // read three sensors and append to the string:
  for (int analogPin = 0; analogPin < 3; analogPin++) {
    int sensor = analogRead(analogPin);
    dataString += String(sensor);
    if (analogPin < 2) {
      dataString += ",";
    }
  }

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open("datalog.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(dataString);
    dataFile.close();
    // print to the serial port too:
    Serial.println(dataString);
    delay(1000);          // 增加此行,減少寫入失敗
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening datalog.txt");
  }
}

/* ==================================================== */

有幾個地方需要注意一下:
  1. const int chipSelect = 4;
    這個範例裡,我們指定Pin 4為CS Pin。在查詢官網的相關訊息發現,因為我們是引用SD Library,即使沒有使用到硬體的SS/CS Pin,也需要將SS/CS Pin保留為輸出模式(以UNO為例,就是Pin 10),否則SD Library無法正常執行。所以在setup()裡我加上了一行pinMode(10,OUTPUT) ,但實驗發現,此行沒加也可以正常執行。
  2. 在寫入到SD卡的程式裡,我有加上讓它延遲一秒再寫入。因為實驗發現,沒有加上這行,寫入失敗的機率好像蠻高的。