ESP32教學系列(十):I2C讀取SHT30溫濕度感測與OLED顯示

前言

I²C是一種相當常見且有名的傳輸協議,它被廣泛的應用在各種感測器的通訊界面上。本文將說明如何將ESP32操作在Master以及Slave Mode下,並以SHT3x溫溼度感測器,以及1.3吋OLED顯示器為例,說明ESP32如何分別使用這兩個I²C裝置;最後結合兩者,將測量到的溫溼度數值顯示在OLED上。

什麼是I2C

I²C(唸作I-Square-C),是 Inter-Integrated Circuit 的縮寫,有時也會寫作IIC、I2C 或 I²C bus,於1980年代由Philips公司(現為NXP)所設計出的一種通訊協議,最初的目的是為了讓同一片板子上的元件可以互相通訊。

I²C是一種具有同步訊號的串列通訊協議,支援多個主從設備進行雙向通訊,它允許由一個控制器(Controller)控制多個周邊設備(Peripheral),也可以由多個控制器控制一個周邊設備,擁有靈活的架構是它的優點之一。

I²C的另一個優點是硬體接線簡單,只需要兩條訊號線(Bus)就能與其它裝置通訊,這點與UART相同。不同的是,I²C可以連接多個設備,且不論是主設備或是周邊設備,所有設備都共享兩條相同的訊號線(稱作Bus),分別用來傳輸資料(data)與時脈訊號(clock)。控制器可以依照每個周邊設備的地址選擇要進行通訊的對象,每個周邊設備都有不重複的7位元地址。在嵌入式應用中,許多短距離傳輸的設備上都使用I²C傳輸協議,像是OLED顯示器、數位溫(溼)度感測器、DAC、MPU6050、LCD顯示器等都是常見的周邊裝置。

I²C的基本通訊協定

如何在ESP32使用I2C

I²C通訊時只需使用兩條Bus:SDA(serial data)與SCL(serial clock)。SDA用來傳輸資料,SCL則是用來同步設備通訊的時脈訊號,這兩條Bus都使用open-drain(或是 open-collector)的電路結構驅動。這種電路結構使得I²C可以只用一條資料線就達到雙向傳輸的功能,但同時這也表示設備沒有自主輸出高電壓準位的驅動能力。為了解決這個問題,我們會在open-drain的外部電路連接上拉電阻,讓資料線的狀態維持在高電位,這麼一來設備就可以按照實際情況選擇下拉(低電位)或釋放資料線(高電位)來改變電壓準位。

上拉電阻的數值可以經由計算得出,通常5V的設備會使用4.7kΩ,3V的設備會使用2.4kΩ。不過,大部分的模組在設計時都有加入上拉電阻,使用時不需要再另外連接。本篇所使用的模組都不需要加上拉電阻,只要將對應的接腳連接在一起就可以了。

當我們使用Arduino IDE開發時,預設的SDA接腳是GPIO 21,SDL接腳是GPIO 22。

I2C設備ESP32
VCC3.3V/5V
GNDGND
SDASDA (GPIO 21)
SCLSCL (GPIO 22)

ESP32的I²C介面可以做為主控端(Master Mode)或是做為周邊設備(Slave Mode)使用。多數情況下ESP32都會做為主控端,搭配周邊感測器/模組使用。若想瞭解如何當作周邊設備使用,可以自行參考Arduino IDE中的範例程式 (File>Examples>Wire>WireMaster,WireSlave)。在使用I²C模組時,通常也都要另外安裝函式庫,與I²C協議通訊過程的關聯度較低,所以下面接紹細節部分的內容僅提供給有興趣的讀者參考。若想瞭解如何使用SHT3x溫溼度感測器和OLED,可以直接跳到範例部分。

Master Mode

我們通常會將ESP32當作主控端連接各項感測器或控制其它的周邊設備,這時候ESP32就會操作在Master mode下,可以對周邊設備發起、結束傳輸,或是讀取周邊設備回傳的內容。

使用範例

關於Master Mode具體的使用範例可以在Arduino IDE的File>Examples>Wire>WireMaster中找到

下面是關於Master Mode 傳輸相關API的說明:

  • begin

使用預設I²C接腳的話,在setup()中呼叫Wire.begin()即可

void setup() {
  Wire.begin();
}

如果要使用其他接腳,可以在begin()前加上setPins(),並將指定的接腳當作參數傳入:

#define I2C_SDA 4
#define I2C_SCL 5

void setup() {
  Wire.setPins(I2C_SDA, I2C_SCL);
  Wire.begin();
}
  • beginTransmission

beginTransmission用來對周邊設備發起傳輸

函式原型:
void beginTransmission(uint16_t address)

使用範例:
Wire.beginTransmission(0x55);

參數說明:

address: 周邊設備的I2C地址

  • write

write可以將資料寫入緩衝區(buffer),它會回傳內添加到緩衝區的資料大小。

函式原型:
size_t write(uint8_t);

使用範例:
Wire.write(x);
  • endTransmission

使用endTransmission將資料從緩衝區發送到周邊設備,並結束傳輸。這個函式會回傳錯誤代碼(error code)

函式原型:
uint8_t endTransmission(bool sendStop);

使用範例:
Wire.endTransmission(true);

參數說明:

sendStop: 啟用 (true) 或停用 (false) 停止的訊號。預設參數為true。

error code:

// error code
typedef enum {
    I2C_ERROR_OK=0,
    I2C_ERROR_DEV,
    I2C_ERROR_ACK,
    I2C_ERROR_TIMEOUT,
    I2C_ERROR_BUS,
    I2C_ERROR_BUSY,
    I2C_ERROR_MEMORY,
    I2C_ERROR_CONTINUE,
    I2C_ERROR_NO_BEGIN
} i2c_err_t;
  • requestFrom

如果要讀取裝置回傳的內容,可以呼叫requestFrom它會回傳讀取到的資料大小(number of bytes)。

函式原型:
uint8_t requestFrom(uint16_t address, uint8_t size, bool sendStop)

使用範例:
Wire.requestFrom(0x55, 16);

參數說明:

address: 周邊設備的I2C地址

size: 所需的記憶體大小

sendStop: 啟用 (true) 或停用 (false) 停止的訊號。預設參數為true

  • readBytes

readBytes用來查看資料

函式原型:
virtual size_t readBytes(char *buffer, size_t length);

使用範例:
uint8_t bytesReceived = Wire.requestFrom(0x55, 16);
uint8_t temp[bytesReceived];

Wire.readBytes(temp, error);

如何掃描I2C設備地址

I²C bus上的周邊設備都會有一組用16進位表示的設備地址,通常地址會標註在模組上。在使用設備前,我們可以先掃描一次,以確認設備實際的地址。

範例1: 使用SHT3x測量溫濕度

這個範例會使用Circus SHT3x在Arduino IDE的序列埠監控視窗顯示溫溼度數值。

本範例使用的是Circus SHT3x 溫溼度感測器模組。相較於另一款常見的溫溼度感測器DHT22,這款感測器(SHT30晶片)的溫度量測範圍更廣,也有更好的測量精度,體積小、價錢也更便宜,不妨可以將其作為取代DHT22的另一項選擇。

以下是SHT3x模組與DHT22的簡易比較表:

參數SHT3x模組DHT22模組
輸入電壓 (DC)3.3V ~ 5V3.3V ~ 5.5V
工作範圍(溫度)-40°C~125°C-40°C ~ 80°C
工作範圍(濕度)0%~100% RH0% ~ 99.9% RH
測量精度 (溫度)±0.3℃±0.5℃
測量精度 (濕度)±0.3%±2%
接腳數量43
價格 (參考自iCShop販售價格)140元260元

材料清單

連接示意圖

SHT3xESP32
SCLGPIO 22
SDAGPIO 21
VCC3.3V
GNDGND

安裝函式庫

首先,到Arduino IDE的Library Manager搜尋並安裝Adafruit SHT31函式庫

掃描設備地址

我們可以透過前面的範例程式確認設備地址:

這片模組預設的I2C地址是0x44。透過更改模組上的焊盤連接,也可以將地址改成0x45。

範例程式碼

複製下方的程式碼進行測試

程式碼說明

上面的範例修改自函式庫的範例測試程式。

一開始我們先建立一個 sht3x 物件,由於使用的是預設接腳,因此Adafruit_SHT31()不需要傳入參數。

Adafruit_SHT31 sht3x = Adafruit_SHT31();

初始化物件時,使用begin,並將設備的地址當作參數傳入函式。

sht3x.begin(0x44);

完成初始化以後就可以用readTemperaturereadHumidity直接讀取目前的溫度和濕度

sht3x.readTemperature();  // 讀取目前溫度
sht3x.readHumidity();     // 讀取目前濕度

程式每間隔1秒就會讀取溫溼度數值,並顯示在序列埠監控視窗。

SHT3x系列的感測器內部都有加熱器功能,用意是提高濕度量測準確度,我們可以透過軟體控制加熱器的開關

sht3x.heater(true);  // 開啟加熱器
sht3x.heater(false); // 關閉加熱器

也可以用reset重置感測器。

sht3x.heater(true);  // 開啟加熱器
sht3x.heater(false); // 關閉加熱器

執行結果

範例2: 驅動1.3吋OLED

本範例使用Circus 1.3吋OLED螢幕顯示模組,它是一款由SSD1306晶片驅動,解析度128 x 64,顯示顏色為白色,使用I2C通訊協議的OLED螢幕。

這個範例會示範如何在畫面中心顯示circus pi的圖片,並在螢幕最下方顯示1.3” OLED的文字。

材料清單

  • ESP32開發板 x1
  • Circus 1.3吋OLED螢幕顯示模組 x1
  • micro USB 傳輸線 x1
  • 麵包板 x1
  • 杜邦線(公-公) x3

連接示意圖

連接方式與範例1相同。

安裝函式庫

首先,到Arduino IDE的Library Manager搜尋並安裝U8g2函式庫

圖片轉換

與之前使用的電子紙相同,要在OLED上顯示圖案時我們可以用Image2Lcd這個軟體把圖片轉換成C語言的陣列資料

轉換完成後,就可以將產生的陣列貼到程式中。

範例程式碼

程式碼說明

首先匯入Wire和U8g2的函式庫。我們使用U8g2函式庫來驅動OLED。

#include <U8g2lib.h>
#include <Wire.h>

定義圖片大小為56×56 pixel

#define ImgWIDTH 56
#define ImgHEIGHt 56

接著選擇適合的建構函式。U8g2這套函式庫能支援非常多種類的顯示器與型號,以這個模組為例,要選擇的建構函式為:

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

在setup()函式中,先初始化螢幕

  u8g2.begin();

然後設定要顯示的字型

u8g2.setFont(u8g2_font_luRS08_tr); // 設定字型

可以到這裡選擇支援的字型以及字體大小。在這個範例中,我們使用的是8 pixel大小的字型。

使用drawBMP設定圖片:

u8g2.drawXBMP(32,0, ImgWIDTH, ImgHEIGHt, circus_logo);  // 設定圖片

參數分別為圖片左上角的xy位置、圖片的寬度和高度,以及要顯示的圖片陣列。

使用drawStr設定文字:

u8g2.drawStr(32, 64, "1.3\" OLED");        // 設定文字

參數分別為文字的xy位置,以及要顯示的文字。注意xy位置是從文字的左下方開始算。

最後使sendBuffer顯示文字和圖片:

u8g2.sendBuffer();

進階範例: 連接多個i2c設備

經過前面的2個例子,我們已經知道要如何驅動溫溼度感測器與OLED顯示模組。接下來我們可以將它們連接在一起,在OLED上顯示目前的溫濕度資訊。

連接示意圖

SHT3x1.3” OLEDESP32
SCLSCLGPIO 22
SDASDAGPIO 21
VCCVCC3.3V
GNDGNDGND

圖片轉換

範例中使用的圖片是從Flaticon這個網站上下載的,圖片來源: 濕度 溫度

下載後將圖片匯入Image2Lcd,將圖片大小設為28×28,其他選項都與範例2相同,不用更改。

儲存後,我們一樣將轉換好的陣列放到程式中。

範例程式碼

程式說明

基本上程式都與前兩個範例一樣,只是這個範例的寫法會比sendBuffer還要節省記憶體空間。print函式用來顯示數字,參數分別是變數名稱,以及小數點位數。

例如:

u8g2.print(t, 2); // 顯示溫度t 以及小數點後兩位的數值。

執行結果

小結

在這篇文章中,我們介紹了如何使用ESP32的I2C,以及掃描I2C設備的方式。也分別用兩個範例說明溫溼度感測器以及OLED模組的使用方式,最後說明如何在同一條I2C Bus上使用多個I2C設備。

下一篇我們會使用1.9吋的段碼電子紙螢幕搭配SHT3x顯示溫溼度數值,比較不同的顯示方式。