2015年8月1日 星期六

Arduino 之間的 I2C 通訊 (5) master 向 slave 要求不同資料

相關指令:

指令發出耆作用
Wire.begin([<address>]);master / slave啟動 Wire (由於 i2c 是用 Wire 的, 這就等同啟動 i2c 了)
Wire.beginTransmission(<address>);master開始對 <address> 的連線
Wire.endTransmission();master 關閉之前的連線
Wire.requestFrom(<address>,<len>);master在線上向拍定地址發出回傳 <len> bytes 資料的請求
Wire.available();master檢查連線上是否有可接收的資料
Wire.read();master / slave讀取連線上的一個 byte 的資料
Wire.write(<array>,<size>);slave / slave在連線上送出 <size>個 byte 的資料
Wire.onReceive(<function>)slave 設定用來接收資料的函數
Wire.onRequest(<function>)slave 設定函數用來回應線上的請求

這個可以說是之前的一個初步小總結了.

從 (4) 已經說明了 slave 如果把資料回傳, 但大家可能會發覺, 某些 i2c device, 是可以選擇不同的資料的, 在處理請求的函數中, 如何得知 master 想要什麼?

對, 在 request 之中, 是沒有任何有關請求的資料提供的, 就只是一個空白的請求.

但就像 MPU6050 之類的模塊, 不是可以選擇不同的數據嗎?
如果大家有用過 I2CDev 的庫, 或許會發覺 readByte 的函數, 不是有一個 regAddr, 向 slave 要求不同的數據嗎?

    static int8_t readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);

大家有看過之前 (1) - (4) 的話, 難道想不出來嗎?  多思考一下, 把所知道的結合起來.

對了, 說穿了就只是把兩個工序結合起來.

  • master 先向 slave 發送所需資料的指令
  • slave 收到後, 先記下來, 不作處理
  • master 再向 slave 發出傳送資料的請求
  • slave 收到請求後, 先看看 master 剛發過來有關所需資料的指令, 再把相關資料回傳

是否很簡單呢?

master 向 slave 選擇所需資料

明白了原理, 步驟也很簡單, 就是把 (2) + (4) 結合:

  1. 執行 beginTransmission 並指定接收指令的地址
  2. 以 Wire.write 把所需資料的指令發送出去
  3. 執行 endTransmission 完成指令發送
  4. 執行 beginTransmission 並指定連線地址
  5. 以 Wire.requestFrom 發出請求
  6. 不斷以 Wire.available 檢查是否有資源, 並以 Wire.read 把一個 byute 資料提出
  7. 執行 endTransmission 作結束

當中 3. 4. 是否有必要, 我也不肯定.  但為了把 發送 / 接收 可獨立一點, 先把發送完結或者會好一點.  而且, 3. 4. 之後, 有需要或許要加入一點 delay, 好讓 slave 處理資料.  當然, 看實際情況決定, 並不是必須的.

以下是簡單的例子, master 發出請求時, 會先指定所需資料.


#include <Wire.h>

#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 57600 
#define DATA_SIZE 8
 
void setup()
{
  Wire.begin();
 
  Serial.begin(SERIAL_BAUD);
  Serial.println("I2C Master.05 started");
  Serial.println();
}
 
 
void loop()
{
  if (Serial.available()) {
    
    Wire.beginTransmission(SLAVE_ADDRESS);
    Wire.write(Serial.read());
    delay(1);
    Wire.endTransmission();

    Wire.beginTransmission(SLAVE_ADDRESS);
    Wire.requestFrom(SLAVE_ADDRESS, DATA_SIZE);
    if (Wire.available()) {
      Serial.print("Data returned: ");
      while (Wire.available()) Serial.print((char) Wire.read());
      Serial.println();
    }
    Wire.endTransmission();
    while(Serial.available()) Serial.read();  // Clear the serial buffer, in this example, just request data from slave

  }
}


salve 向 master 回傳所需資料

跟 master 一樣, 所需步驟, 同樣是把 (3) + (4) 結合起來就可以了.


  • 執行 Wire.onReceive 去設定接收資料的函數
  • 執行 Wire.onRequest 去設定處理請求的函數
  • 在 onRecevie 函數中, 以 Wire.available 檢查是否有資料需要處理.  
  • 要有複雜的程序, 先記下來, 留待主程式去做
  • 在主程序中檢查是否有 接收資料待處理
  • 在 onRequest 函數中, 跟據之前 onReceive 收到的指令, 以 Wire.write 把 所需資料回傳


但這裡有一點要留意, 之前沒提及的.  就是當 master 發出 requestFrom 時, slave 是會觸發一次 onReceive 而當中是沒有資料的.  所以在 onReceive 當中, 必須要先檢查 Wire.available.

以下是一個簡單例子, master 可以選擇 2 種資料, 而回傳結果如下:
0 - 回傳 "Super169"
1 - 回傳 "Apple II"
其他 - 回傳 "WhoAmI"


#include <Wire.h>

#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 57600 

uint8_t dataMode = '0';
boolean modeChanged = false;

void setup() {
  Wire.begin(SLAVE_ADDRESS);    // join I2C bus as a slave with address 1
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
 
  Serial.begin(SERIAL_BAUD);
  Serial.println("I2C Slave.05 started\n");
}

void loop() {
  if (modeChanged) {
    Serial.print("Change data mode to ");
    Serial.println((char) dataMode);
    modeChanged = false;
  }
}

void receiveEvent(int count) {
  if (Wire.available()) {
    dataMode = Wire.read();
    modeChanged = true;
    while (Wire.available()) Wire.read();
  }
}

void requestEvent()
{
  switch (dataMode) {
    case '0':
      Wire.write("Super169",8);
      break;
    case '1':
      Wire.write("Apple II",8);
      break;
    default:
      Wire.write("Who am I",8);
  }
}


額外測試項目

大家記得之前 (2) 時提及, 不要把 Serial.print 放在 receiveEvent 當中嗎?
在之前的例子, 由於只要 receiveEvent 在工作, 不會有任何影響.
但在今次的例子中, 大家看到為了把 data mode 的改變顯示出來, Serial.print 的部份放到主程式內.
有興趣的朋友可以試試把它修改一下, 放回 receiveEvent 內, 看看會有什麼結果.


相關程式下載:


2 則留言:

  1. 請問可以幫忙~用串口埠~點電容屏~7"我套您上面的範例,還是沒有辦法用?

    回覆刪除