2016年5月5日 星期四

Arduino 之間的 I2C 通訊 (6) 由 master 提供參數, 再由 slave 作出相應的回復

看了(5) 之後, 這個章節其實也沒大意義, 只是把 master 發出的東西加點變化吧.
重點將會是下一章節中所提及, 如何有效地把數據送出 (不論是 master 或 slave 都一樣).

為什麼說 (6) 是沒意義呢?  請大家再看看 (5) 吧:




第 (5) 是 slave 可以提供不同的資料供 master 去選擇.
而 (6) 是 由 master 把需要的參數提供給 slave 應用.

如果認真去想, (5) 的範例中, 不是已經由 master 提供了一個參數嗎?
對, 就是用來選擇有關資訊的 dataMode 吧.
只不過, (5) 接收到那個 參數後, 只是用來分辨要回傳的資料, 而不是用來計算.
但實際的作用是沒分別的, 就是由 master 提供了一個數據給 slave 吧.

那為什麼又要多開一個沒意義的章節呢?

首先, 很多朋友會覺得, 參數的作用會有所不同.
其次, 在 (5) 的 選擇服務功能上, 基本上只是一個 byte 的數據, 簡單的送出去就行了.
但 參數可以超過一個 byte, 有不同的類形(int, float, char[],...), 亦可以同時有多個參數.

所以, 發送參數, 就有需要考慮到發送的方式.
流程上都是一樣, 把要發送的東西, 用 Wire.write 送出去, 而接收的, 也就是不斷用 Wire.read 去接收.

重點在於, 如何把不同類型的數據, 都用 Wire.write 發出去.

同樣的做法, 也可以應用到串口通訊的.

如果在桌面電腦上, 要作出通訊, 基本上用什麼形式也沒大問題, 因為基本上不需要考慮速度以及記憶體的問題.
就以現在比較常見的 XML 通訊為例, 小小的幾個 byte, 做一個很大的 XML 發出去, 也是很平常的事.
但在 MCU 上, 要準備及處理一個 XML, 需要浪費很大的資源, 實在是不值得的.

下一個章節, (7) 單片機有效傳送數據的選擇, 將會針對 單片機的資源限制, 尋求既有效率又簡單的通訊方法.


以下先用一個簡單的例子, 說明如何可以在 master 向 slave 提供參數.

假設有一台 slave 服務機, 可以提供 AND , OR  及 XOR 的計算.
由 master 送出 3 個 byte, 中間一個作為運算的選擇, 'A' = AND, 'O' = OR, 'X' = XOR.
如運算不在 'A', 'O' 及 'X' 之內, 就會把結果定為 0xFF.
而 slave 會回傳接收到的資料, 再加上運算結果.

比如發出 0x31 0x41 0x35 就是要計算 0x31 AND 0x35 的結果 (0x31 & 0x35 = 0x31).
Slave 就會回傳 0x31 0x41 0x35 0x31

Master 的程式:


#include <Wire.h>
 
#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 57600 
#define DATA_SIZE 4

void setup()
{
  Wire.begin();
  
  Serial.begin(SERIAL_BAUD);
  Serial.println("I2C Master.06 started");
  Serial.println();
}

byte data[4];
  
void loop()
{
  if (Serial.available()) {
     
    Wire.beginTransmission(SLAVE_ADDRESS);
    for (int i = 0; i < 3; i++) {
      data[i] = Serial.read();
      delay(1);
    }
    Wire.write(data, 3);
    Wire.endTransmission();
    while(Serial.available()) Serial.read();  // Clear the serial buffer, in this example, just request data from slave
    
    Serial.print("Data sent: ");
    for (int i = 0; i < 3; i++) {
      Serial.print(data[i], HEX);
      Serial.print(" ");
    }
    Serial.println();

    Wire.beginTransmission(SLAVE_ADDRESS);
    Wire.requestFrom(SLAVE_ADDRESS, DATA_SIZE);
    if (Wire.available()) {
      for (int i = 0; i < 4; i++) data[i] = Wire.read();
      while (Wire.available()) Serial.print((char) Wire.read());
      Serial.println();
    }
    Wire.endTransmission();

    Serial.print("Data returned: ");
    for (int i = 0; i < 4; i++) {
      Serial.print(data[i], HEX);
      Serial.print(" ");
    }
    Serial.println();

  }
}

Slave 的程式:

#include <Wire.h>

 
#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 57600 
 
byte data[4];
boolean dataReceived = false;
boolean dataReady = false;
boolean dataReturned = 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.06 started\n");
}
 
void loop() {
  if (dataReturned) {
    dataReturned = false;
    Serial.print("Data received: ");
    for (int i = 0; i < 3; i++) {
      Serial.print(data[i], HEX);
      Serial.print(" ");
    }
    Serial.print(" => ");
    Serial.println(data[3], HEX);
  }
}
 
void receiveEvent(int count) {
  if (Wire.available()) {
    for (int i = 0; i < 3; i++) data[i] = Wire.read();
    while (Wire.available()) Wire.read();
    dataReceived = true;
    dataReturned = false;
  }
}
 
void requestEvent()
{
  char op = (char) data[1];
  Serial.print("op = ");
  Serial.println(op);
  switch (op) {
    case 'A':
      data[3] = data[0] & data[2];
      break;
    case 'O':
      data[3] = data[0] | data[2];
      break;
    case 'X':
      data[3] = data[0] ^ data[2];
      break;
    default:
      data[3] = 0xFF;
      break;
  }
  Wire.write(data, 4);
  dataReceived = false;
  dataReturned = true;
}

以上範例, 只在於說明如何發送參數給 slave 使用, 當中並沒有加入錯誤的檢測.
而且只用了簡單參數, 下一個章節將會探討給何發送不同的數據.

相關程式下載:

2 則留言:

  1. 萬分感謝,獲益良多
    一處不明白,slave 中的dataReceived 做為何用?

    回覆刪除
    回覆
    1. 謝謝你的意見, 那裡真是有點亂. 可能當初想把每個狀況都分清楚, 就定了多個變量.
      結果那個 dataReceived 用不著, 是做多餘了.
      此外, 現在的 dataReturned 的作用應該是表示有 data 可以 return, 但名稱上好像是 data 已經 return 了.
      有時間我再清理一下, 現在的變量名稱也有點怪.

      刪除