指令 | 發出耆 | 作用 |
---|---|---|
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) 結合:
- 執行 beginTransmission 並指定接收指令的地址
- 以 Wire.write 把所需資料的指令發送出去
- 執行 endTransmission 完成指令發送
- 執行 beginTransmission 並指定連線地址
- 以 Wire.requestFrom 發出請求
- 不斷以 Wire.available 檢查是否有資源, 並以 Wire.read 把一個 byute 資料提出
- 執行 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 內, 看看會有什麼結果.
相關程式下載:
請問可以幫忙~用串口埠~點電容屏~7"我套您上面的範例,還是沒有辦法用?
回覆刪除不好意思,頂端表格 write 函數應為 master/slave 都適用吧?
回覆刪除