2017年8月30日 星期三

Arduino 之間的 I2C 通訊 (9) 實例(一) 簡單傳感數據收集

完成以上 8 章之後, 要進行 arduino 之間的 i2c 通訊, 應該沒有難度吧.

但有個問題, 為什麼要以 i2c 通訊呢?

因為有很多原因, 單一片 arduino 不足以滿足要求, 比如 I/O 不足, 又或有些程式太煩覆, 希望可以拆開, 以多片 arduino 合成一個大的系統.

比如你想建立一個超多傳感的測量系統, arduino 的 I/O 絕對不能滿足.  而且, 要安裝不同的庫, arduino 的記憶體也是一個限制.

如果可以把工作分開, 由多個 arduino 板子組成一個更大的系統, 當中由一個 master 板子, 執行主程式, 其他 slave 去配合讀取不同的資料.

當然, 現在的 arduino, 一般只有一組 i2c, 所以整個系統就只可以有一組 i2c bus.
而在  i2c bus 上只可以有一個 master, 所以 i2c 有關的設備, 都只可以靠 master 去處理.
另外, 相同 i2c 地址的設備, 不會因為加入多塊 arduino 板子, 而可以同時使用.
這個缺憾, 需要有另一個系統去配合.
比如在  i2c 之外, 再加上SPI 的通訊, 就可以把 i2c 都分開了.
這個複雜的系統, 之後再去探討吧.  現在以一個 i2c bus 為基礎去開始.

首先, 嘗試做一個簡單系統.  假設你的板子 I/0 都用盡了, 你還想加入一個 DHT11 的溫濕度測量.  你可以簡單加入一片 mini 或 nano 的板子, 以 i2c  跟主板連上, 去擴充你的 I/O.

最簡單是修改 "由 master 向 slave 要求資料回傳" 的例子.
當 master 向 slave 發出請求時, 配合 "(8) 浮點的傳送" 的例子, 就可以把 溫濕度 數據以直接回傳.

由於某些 設備的庫, 不一定可以在 interrupt 內執行的, 可以嘗試在 slave 的 loop 內不斷更新, 當收到 master 的請求時, 就把最後的資料發過去.

詳情大家可以在相關程式中看到, 執行後, 在 master 隨意發送一些資料, slave 就會回傳.



而 slave 的 serial monitor 亦會同時顯示出已發送的資料供對比:



用類似的方法, 就可以把 非 i2c 的傳感, 放到另一片 arduino 中去讀取數據, 甚至先進行簡單分析.  例如 濾波.
一片 arduino slave 板子, 可以同時接上多個 非 i2c device 呢.  其主力工作, 就是為主程式準備所需的資料.

當 主程式需要時, 就可以向 slave 直接讀取了.  主程式亦不需要加入有關的庫, 會變得簡單一點.

當然, 這不是沒有付出的, 系統上, 就要增加了 arduion 板子.  是否值得, 還是看需要吧.


相關程式下載:




Arduino 之間的 I2C 通訊 (8) 浮點的傳送

之前有點忙, 在準備 "Arduino 之間的 I2C 通訊 (7) 單片機有效傳送數據的選擇" 中, 突然停了一下, 沒想到一停就停了一年多.

當中留下了一個問題給看官思考的, 就是有關 浮點的傳送.

網上經常會看到, 有人問用 串口通訊時, 如何把 浮點 的數據送出去?
一般的答案, 都會是轉成字串發送吧, 例如 "123.45".
如果需要的浮點, 有固定的大小, 精確度有限, 這個有可能失真的方法還不錯吧.
為什麼說"失真'呢?  因為原本的數值, 小點後可能是連續很長的, 要轉成字串, 就要把長度限制了, 自然會有失真.

但大家有否考慮過, 可以簡單又完全不失真的傳送呢?
注意, 這裡的"不失真", 只是跟原來儲存的數值比較, 並非所有數值都可以.
如果原來儲存的數值已經是有"失真"的話, 傳送時也只會原原本本的發過去.
這是什麼意思呢?
簡單說, 要記錄 pi, 用 float 本身就有精確度的限制, 發送出去的 float, 絕不可能突然變成 double 的精度吧.  可以做到的, 就是你給我一個 float, 我就把這個 float 原整的發出去.  詳細情況, 可以在最後的例子中看到.

如果要轉成字串, 要用多少個字符才足夠呢? 100? 1000? 就是一萬也不一定是完全一樣.

但如果有考慮過電腦內的記憶體的運作,  無論發送什麼數值的 float, 也只需 4 個 byte 就可以了.
為什麼? 發送 123.45 有 5 個數字, 還要記下小數位, 也只需 4 bytes?

沒錯, 就是 4 bytes, 這沒有什特別, 因為原本用來儲存 float 的記憶體, 就只需要 4 bytes.
只要我們把這 4 個 bytes 發出去, 就可以原原本本的把那個數值傳過去了.

但怎樣可以把這 4 個 bytes 發出去, 在 接收端得到一個 float 呢?

看看 wire 或 serial 有關的 function, 都沒有以 float 作為參數或結果的, 那可以怎樣做到.

學習 c 的人, 應該不會有問題吧.  c 的 pointer 可是超好用的東西.

大家可以看看 wire 的庫, write 的方法之中, 其中一個就是:
virtual size_t write(const uint8_t *, size_t);
只要把 const uint8_t * 指到要發出的 float 去, size_t 設定為 4, 不就可以把 float 的 4 個 byte 發出去了嗎?

比如 你的資料儲存在   float data = 123.45678, 當中 data 是一個 float.
只要用  wire.write((uint8_t *) &data, 4)  就可以把它的值, 原原本本的發出去.

注意:  由於 data 是 float, &data 是 (float *), c 是不會主動把 (float *) 轉成 (uint8_t *) 的, 所以需要自己指定轉換.


之後, 在接收端收到 4 個 byte, 用相反的方法, 把 4 個 byte 用 pointer 放進 float 對應的記憶體, 就可以還原了.

文字很難清楚說明, 還是直接看程式碼吧.


slave 回傳 float

以下是一個收到任何請求, 都回傳一個 float 數值的例子,

#include 
 
#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 115200 
 
#define I2C_BUFFER_SIZE 32  
uint8_t i2cBuffer[I2C_BUFFER_SIZE];
uint8_t i2cBufferCnt = 0;

float data = 123.45678;
 
void setup() {
  Wire.begin(SLAVE_ADDRESS);    // join I2C bus as a slave with address 1
  Wire.onRequest(requestEvent); // register event
  
  Serial.begin(SERIAL_BAUD);
  Serial.println("I2C Slave.08 started\n");
}
 
void loop() {
}
 
void requestEvent()
{
  Wire.write((uint8_t *) &data,4);
}



master 接收 float

下面是對應的 master 程式例子:

#include 
 
#define SLAVE_ADDRESS 0x12
#define SERIAL_BAUD 115200 
#define DATA_SIZE 4
 
void setup()
{
  Wire.begin();
  
  Serial.begin(SERIAL_BAUD);
  Serial.println("I2C Master.08 started");
  Serial.println();
}

float data = 0;
  
void loop()
{
  if (Serial.available()) {
 
    Wire.requestFrom(SLAVE_ADDRESS, DATA_SIZE);
    Wire.beginTransmission(SLAVE_ADDRESS);
    data = 0;
    uint8_t *ptr = (uint8_t *) &data;
    if (Wire.available()) {
      Serial.print("Data returned: ");
      while (Wire.available()) {
        uint8_t b = Wire.read();
        *ptr++ = b;
        Serial.print(b, HEX);
        Serial.print(" ");
      }
      Serial.println();
      Serial.print("Data value: ");
      Serial.println(data, 6);
    }
    Wire.endTransmission();
    while(Serial.available()) Serial.read();  // Clear the serial buffer, in this example, just request data from slave
  }
 
}


以上範例, 只在於說明如何發送參數給 slave 使用, 當中並沒有加入錯誤的檢測.

執行之後, 你會得到以下結果:

Data returned: DF E9 F6 42
Data value: 123.456779


DF E9 F6 42 就是記憶體中, 用來儲存 123.45678 的 4 個 bytes 了.

但...為什麼 Data value 是 123.456779, 而不是 123.456780 呢?  不是說不會"失真"嗎?

這裡那 0.000001 的差別, 並非傳送時的失真, 而是 float 本身的失真的.
在 arduino 的 float, 是不能準確記下 123.456780 的, 其精確度所限, 會記錄成 123.456779.
正如上面說過, 傳送過程是會原原本本的發過去, 結果就是 123.456779 了.

不信的話, 你可以試試在 slave 的程式中, 把 data 以 6 位小數印出來看看吧.




相關程式下載:


2017年8月22日 星期二

顯示器(一) Nokia 5110 (84x48 LCD)

Nokia 5110 既 LCD 都有好多唔同款既, 而 pin 位都有少少分別.
小弟買左既就係呢款, 接線亦會以呢款為跟據:




基本資料:



相關資料下載:



    接線方法:

    除特定連接 (例 D3-D7) 外, 一般例子都以 SPI 接法, 轉化如下:

    5110MappedUNO
    RSTRSTD11
    CECSD12
    DCDCD10
    DinMOSID9
    ClkSCKD8
    VccVcc3.3V/5V
    BLBL3,3V
    GndGndGND

    (以上連接, 同一般 SPI 庫用 D10-D13 有D唔同, 但搵唔番出處了, 有待再驗證.)


    好可惜...我塊 5110 唔知乜事死左, 要買過塊再試.