第9章 ESP32 I2C 完整教學:I2C Scanner、LCD1602、OLED 與多裝置通訊

發布日期: |更新日期:
文章主圖:第9章 ESP32 I2C 完整教學:I2C Scanner、LCD1602、OLED 與多裝置通訊

前言:為什麼 ESP32 一定要學 I²C?

前面的 ESP32 教學中,我們已經學會:

GPIO
PWM
ADC
Touch Sensor
DHT11 / DHT22

接下來會開始使用越來越多模組,例如:

LCD1602
OLED
RTC
環境感測器
ADC
EEPROM
IMU
氣壓計
光線感測器

如果每個模組都要使用大量 GPIO,

ESP32 的 Pin 很快就不夠用了。

這時候就需要:

I²C

I²C 最大的特色之一,就是只需要:

SDA
+
SCL

兩條主要訊號線,就可以在同一個 Bus 上連接多個裝置。

例如:

┌── LCD1602
                │
ESP32 ── I²C ───┼── OLED
                │
                ├── RTC
                │
                └── Sensor

只要這些裝置具有不同的:

I²C Address

ESP32 就可以透過同一組 SDA / SCL 與它們通訊。

目前 Arduino-ESP32 官方 I²C API 使用 Wire.h,Master 模式可透過 Wire.begin() 啟動,也可以使用 Wire.begin(sdaPin, sclPin, frequency) 自行指定 SDA、SCL 與 Bus Frequency。

本章將完整學習:

I²C
↓
SDA / SCL
↓
Wire Library
↓
I²C Address
↓
I²C Scanner
↓
LCD1602 I²C
↓
SSD1306 OLED
↓
Multiple I²C Devices
↓
DHT11 + LCD1602

最後完成:

ESP32 + DHT11 + I²C LCD1602 溫溼度顯示器

一、什麼是 I²C?

I²C 常寫成:

I2C
I²C

全名:

Inter-Integrated Circuit

是一種很常見的同步式序列通訊介面。

典型 I²C Bus 使用:

SDA
SCL

兩條訊號線。

其中:

SDA
=
Serial Data

負責資料。

SCL
=
Serial Clock

負責 Clock。

基本架構:

ESP32
 │
 ├── SDA ───────────────┐
 │                      │
 └── SCL ───────────────┤
                        │
              ┌─────────┼─────────┐
              │         │         │
              ▼         ▼         ▼
            LCD       OLED      Sensor

這就是:

I²C Bus

二、SDA 是什麼?

SDA:

Serial Data

負責傳送:

Address
Command
Data
ACK / NACK

等資訊。

ESP32 和 I²C Device 的資料都會透過 SDA 傳輸。

三、SCL 是什麼?

SCL:

Serial Clock

負責提供同步時脈。

概念:

SCL
↓
Clock

SDA
↓
Data

接收端就能依照 Clock 判斷何時讀取 SDA 上的 Data Bit。

四、ESP32 預設 SDA / SCL 是哪兩腳?

這個問題非常常見。

對:

Generic ESP32

Arduino-ESP32 官方目前列出的常見預設 I²C Pin 是:

SDA
→ GPIO21

SCL
→ GPIO22

但官方同時提醒:

Default pins may vary from board to board.

也就是:

不同 ESP32 Board 不一定完全一樣。

因此本章使用:

const uint8_t SDA_PIN = 21;
const uint8_t SCL_PIN = 22;

作為:

常見原始 ESP32 / ESP32-WROOM-32

教材設定。

如果使用:

ESP32-S3
ESP32-C3
ESP32-C6
其他開發板

應確認實際 Board Pinout。

五、ESP32 I²C 腳位可以自行指定嗎?

可以。

這也是 ESP32 相較許多傳統 Arduino 開發板很方便的地方。

Arduino-ESP32 官方 API:

Wire.begin(
  sdaPin,
  sclPin,
  frequency
);

例如:

Wire.begin(
  21,
  22,
  100000
);

代表:

SDA = GPIO21
SCL = GPIO22
Frequency = 100 kHz

官方目前的 Master Mode API 確實提供:

bool begin(
  int sdaPin,
  int sclPin,
  uint32_t frequency
);

六、Wire Library 是什麼?

Arduino 使用 I²C 時,最重要的 Library 是:

#include <Wire.h>

Wire 就是 Arduino I²C 通訊的主要 API。

初始化:

Wire.begin();

或者:

Wire.begin(
  SDA_PIN,
  SCL_PIN
);

ESP32 Master Mode 可以利用:

Wire.beginTransmission();
Wire.write();
Wire.endTransmission();
Wire.requestFrom();
Wire.read();

等函式與 I²C Device 通訊。

七、I²C Master 與 Device

一個典型 Bus:

ESP32
↓
I²C Master

LCD1602
OLED
RTC
Sensor
↓
I²C Devices

Master 會先指定:

Address

再和特定 Device 通訊。

因此即使:

LCD
OLED
RTC

全部接在相同:

SDA
SCL

ESP32 還是知道現在要跟哪一個裝置說話。

八、什麼是 I²C Address?

每個 I²C Device 都有:

I²C Address(補充說明)

可以把它想像成同一條 Bus 上的:

裝置門牌號碼。

例如:

Device A
Address = 0x27

Device B
Address = 0x3C

Device C
Address = 0x68

ESP32 要和 0x3C 通訊時:

Wire.beginTransmission(
  0x3C
);

就會指定對應 Device。

九、為什麼 Address 常寫成 0x27、0x3C?

因為:

0x

代表:

Hexadecimal

也就是十六進位。

例如:

0x27
0x3C
0x68

都是常見的 I²C Address 表示方式。

程式通常直接使用:

#define LCD_ADDRESS 0x27

或:

#define OLED_ADDRESS 0x3C

十、LCD1602 的 Address 一定是 0x27 嗎?

不一定。

很多 LCD1602 I²C Backpack 常見:

0x27

也可能是:

0x3F

或其他 Address。

Address 會受到:

I²C Expander IC
A0 / A1 / A2
PCB 設計
焊接 Jumper
模組版本

影響。

所以不要只看到網路程式:

LiquidCrystal_I2C lcd(
  0x27,
  16,
  2
);

就認為:

我的 LCD 一定也是 0x27。

最可靠的方法:

I²C Scanner

十一、OLED Address 一定是 0x3C 嗎?

也不一定。

SSD1306 OLED 常見 Address:

0x3C

或:

0x3D

部分 Display 可以透過硬體 Address Pin 或 Jumper 選擇 Address。Adafruit 的 SSD1306 資料也列出常見的 0x3C / 0x3D 位址。

因此:

不管是 LCD 還是 OLED,先跑 Scanner 是最好的習慣。

十二、什麼是 I²C Scanner?

I²C Scanner 是 ESP32 初學者非常值得保留的一支工具程式。

它的工作流程:

ESP32
↓
Address 0x01
↓
有人回應嗎?
↓
Address 0x02
↓
有人回應嗎?
↓
...
↓
Address 0x7E

只要某個 Address:

ACK

就表示:

Bus 上有 Device 回應。

Scanner 就把 Address 印在 Serial Monitor。

十三、I²C Scanner 的原理

Scanner 主要使用:

Wire.beginTransmission(
  address
);

然後:

error =
  Wire.endTransmission();

官方 Arduino-ESP32 API 說明,endTransmission() 會將資料送至 beginTransmission() 所指定的 Address,並回傳 Error Code。

因此 Scanner 可以透過:

error == 0

判斷:

該 Address 有 I²C Device 回應。

十四、ESP32 I²C Scanner 完整程式

/*
  ESP32 I2C Scanner

  Generic ESP32:
  SDA -> GPIO21
  SCL -> GPIO22
*/

#include <Wire.h>


const uint8_t SDA_PIN = 21;
const uint8_t SCL_PIN = 22;


void setup()
{
  Serial.begin(115200);

  delay(500);

  Serial.println();
  Serial.println(
    "ESP32 I2C Scanner"
  );


  if (!Wire.begin(
        SDA_PIN,
        SCL_PIN))
  {
    Serial.println(
      "I2C initialization failed!"
    );

    while (true)
    {
      delay(1000);
    }
  }


  Serial.println(
    "I2C Ready"
  );
}


void loop()
{
  uint8_t
  deviceCount = 0;


  Serial.println();
  Serial.println(
    "Scanning..."
  );


  for (
    uint8_t address = 1;
    address < 127;
    address++
  )
  {
    Wire.beginTransmission(
      address
    );


    uint8_t error =
      Wire.endTransmission();


    if (
      error == 0
    )
    {
      Serial.print(
        "I2C device found at 0x"
      );


      if (
        address < 16
      )
      {
        Serial.print(
          "0"
        );
      }


      Serial.println(
        address,
        HEX
      );


      deviceCount++;
    }
  }


  if (
    deviceCount == 0
  )
  {
    Serial.println(
      "No I2C devices found."
    );
  }
  else
  {
    Serial.print(
      "Total devices: "
    );

    Serial.println(
      deviceCount
    );
  }


  Serial.println(
    "Scan complete."
  );


  delay(3000);
}

這種 Scanner 做法也是 Arduino 世界中很常見的 I²C Address 偵測方式。

十五、Scanner 顯示範例

如果只接 LCD:

ESP32 I2C Scanner

Scanning...

I2C device found at 0x27

Total devices: 1

就知道:

LCD Address
=
0x27

如果接 OLED:

I2C device found at 0x3C

則:

OLED Address
=
0x3C

十六、如果 Scanner 找不到任何 Device?

先檢查:

VCC
GND
SDA
SCL

再檢查:

SDA / SCL 是否接反

然後確認:

模組供電電壓
I²C Logic Level
Pull-up Resistor
Bus Frequency
GPIO Pin

不要第一時間就修改:

0x27
0x3C
0x3F

因為:

Scanner 一個 Address 都看不到時,通常首先是硬體 / Bus 問題。

十七、I²C 為什麼需要 Pull-up?

I²C 的 SDA / SCL 採用:

Open-drain / Open-collector

類型的 Bus 設計。

簡化概念:

Device
↓
可以把 Line 拉 LOW

但是 HIGH 通常由:

Pull-up Resistor

提供。

架構:

3.3V
 │
Pull-up
 │
 ├──── SDA
 │

3.3V
 │
Pull-up
 │
 └──── SCL

很多:

OLED Module
LCD Backpack
Sensor Board

本身已經具有 Pull-up。

十八、ESP32 I²C 最大的硬體注意事項:Logic Voltage

ESP32 GPIO 是:

3.3V Logic

因此不能因為 LCD1602 Backpack:

VCC = 5V

就直接假設:

SDA / SCL 拉到 5V 也一定安全。

有些 I²C Module 的 Pull-up 是接到 Module VCC。

如果:

Module VCC = 5V

可能造成:

SDA / SCL Pull-up
→ 5V

這並不是好的 ESP32 GPIO 設計。

較安全的方式包括:

確認模組可以 3.3V 工作

或:

確認 SDA / SCL Pull-up 是 3.3V

或使用:

Bidirectional I²C Level Shifter

十九、什麼是 LCD1602 I²C?

傳統 LCD1602 直接使用 Parallel Interface,

通常需要很多腳位:

RS
E
D4
D5
D6
D7

再加:

VCC
GND

使用 I²C Backpack 之後:

ESP32
↓
I²C
↓
I/O Expander
↓
LCD1602

對 ESP32 只需要:

SDA
SCL

即可控制 LCD。

這就是:

LCD1602 I²C

二十、LCD1602 I²C 常見接線

本章以:

SDA = GPIO21
SCL = GPIO22

為例。

ESP32 與 LCD1602 I²C 接線表
LCD1602 I²C ESP32
GND GND
VCC 依模組規格
SDA GPIO21
SCL GPIO22

建議:

先確認 Backpack 電壓與 SDA / SCL Pull-up 設計。

二十一、LCD1602 I²C Library

Arduino 中常見 Library 名稱:

LiquidCrystal_I2C

程式:

#include <LiquidCrystal_I2C.h>

建立:

LiquidCrystal_I2C lcd(
  0x27,
  16,
  2
);

意思是:

Address = 0x27
Columns = 16
Rows = 2

二十二、注意:LiquidCrystal_I2C Library 有很多不同版本

這是非常重要的一點。

網路上名稱都叫:

LiquidCrystal_I2C

但不同 Library Fork 的初始化 API 可能不同。

有人使用:

lcd.init();

有人則使用:

lcd.begin();

甚至:

lcd.begin(
  16,
  2
);

所以:

如果 lcd.init() 編譯錯誤,不一定是 ESP32 問題,而可能是你安裝的 Library API 不同。

本章以下範例使用常見:

lcd.init();
lcd.backlight();

寫法。

實際使用時應確認安裝 Library 的 API。

二十三、LCD1602 Hello World

假設 Scanner 找到:

0x27

程式:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>


const uint8_t SDA_PIN = 21;
const uint8_t SCL_PIN = 22;


LiquidCrystal_I2C lcd(
  0x27,
  16,
  2
);


void setup()
{
  Wire.begin(
    SDA_PIN,
    SCL_PIN
  );


  lcd.init();

  lcd.backlight();


  lcd.setCursor(
    0,
    0
  );

  lcd.print(
    "ESP32 I2C"
  );


  lcd.setCursor(
    0,
    1
  );

  lcd.print(
    "LCD1602 Ready"
  );
}


void loop()
{
}

LCD 顯示:

ESP32 I2C
LCD1602 Ready

二十四、setCursor() 是什麼?

LCD1602:

16 Columns
2 Rows

座標概念:

Column
0~15

Row:

0
1

所以:

lcd.setCursor(
  0,
  0
);

就是:

第一列
第一個字元

而:

lcd.setCursor(
  0,
  1
);

就是:

第二列
第一個字元

二十五、為什麼 LCD 有背光卻沒有文字?

很常見。

先確認:

Address

再確認 LCD Backpack 上的小型:

Contrast Potentiometer

如果對比度調得不對,

會出現:

背光有亮
但看不到字

可用小螺絲起子慢慢調整。

二十六、SSD1306 OLED 是什麼?

另一種非常適合 ESP32 的顯示器是:

SSD1306 OLED

常見:

0.96 inch
128 × 64

或:

128 × 32

OLED 不需要像 LCD1602 那樣只顯示固定字元。

它可以顯示:

文字
線條
矩形
圖示
Bitmap
圖表

因此更適合:

Sensor Dashboard
IoT 狀態
圖形介面

二十七、SSD1306 I²C Address

常見 SSD1306:

0x3C

或:

0x3D

部分模組可以依 Address Pin / Jumper 改變。

因此仍然:

先跑 Scanner。

二十八、SSD1306 OLED 接線

以四腳 I²C OLED 為例:

ESP32 與 SSD1306 OLED 接線表
OLED ESP32
GND GND
VCC 依模組規格
SCL GPIO22
SDA GPIO21

也就是和 LCD:

共用同一組
SDA / SCL

二十九、SSD1306 Arduino Library

常見使用:

Adafruit SSD1306
+
Adafruit GFX Library

程式:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

建立:

Adafruit_SSD1306 display(
  128,
  64,
  &Wire,
  -1
);

Adafruit 官方 SSD1306 範例也是將 Wire 傳給 Adafruit_SSD1306,由 Wire Library 的 SDA / SCL 操作 I²C Display。

三十、ESP32 SSD1306 OLED Hello World

假設 Scanner 找到:

0x3C

程式:

#include <Wire.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


const uint8_t SDA_PIN = 21;
const uint8_t SCL_PIN = 22;


#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64


#define OLED_RESET -1

#define OLED_ADDRESS 0x3C


Adafruit_SSD1306 display(
  SCREEN_WIDTH,
  SCREEN_HEIGHT,
  &Wire,
  OLED_RESET
);


void setup()
{
  Serial.begin(115200);


  Wire.begin(
    SDA_PIN,
    SCL_PIN
  );


  if (!display.begin(
        SSD1306_SWITCHCAPVCC,
        OLED_ADDRESS))
  {
    Serial.println(
      "SSD1306 initialization failed!"
    );

    while (true)
    {
      delay(1000);
    }
  }


  display.clearDisplay();


  display.setTextColor(
    SSD1306_WHITE
  );


  display.setTextSize(
    2
  );


  display.setCursor(
    0,
    0
  );


  display.println(
    "ESP32"
  );


  display.println(
    "OLED"
  );


  display.display();
}


void loop()
{
}

三十一、為什麼 OLED 要 display.display()?

程式:

display.print();

通常只是先把內容畫到:

Display Buffer

最後需要:

display.display();

才真正送到 OLED。

所以流程:

clearDisplay()
↓
setCursor()
↓
print()
↓
draw...
↓
display()

三十二、LCD1602 與 OLED 怎麼選?

LCD1602 I²C 與 SSD1306 OLED 比較
項目 LCD1602 I²C SSD1306 OLED
顯示方式 16×2 字元 Pixel
圖形 有限 可
字體 字元型 可調整
Sensor 數值 很適合 很適合
圖示 不方便 適合
圖表 不適合 可
程式複雜度 較低 稍高
初學者 非常適合 適合

如果做:

Temperature
Humidity

LCD1602 已經很夠用。

如果做:

Temperature
Humidity
Wi-Fi
Battery
Trend Chart
Icon

OLED 會更方便。

三十三、一組 SDA / SCL 可以接兩個裝置嗎?

可以。

這就是 I²C 很重要的優點。

例如:

ESP32 GPIO21 SDA
  │
  ├──── LCD1602 SDA
  │
  ├──── OLED SDA
  │
  └──── RTC SDA


ESP32 GPIO22 SCL
  │
  ├──── LCD1602 SCL
  │
  ├──── OLED SCL
  │
  └──── RTC SCL

也就是:

Parallel Bus Connection

不是每個裝置各占一組 GPIO。

三十四、多個 I²C Device 如何區分?

靠:

Address(補充說明)

例如:

LCD1602
0x27

OLED
0x3C

RTC
0x68

ESP32:

0x27
→ LCD

0x3C
→ OLED

0x68
→ RTC

所以:

SDA 相同
SCL 相同
Address 不同

就可以共存。

三十五、如果兩個裝置 Address 一樣怎麼辦?

這就是 I²C 常見問題之一。

假設:

Device A
0x3C

Device B
0x3C

而它們又沒有:

Address Jumper

就會 Address Conflict。

常見解決方式:

1. 修改其中一個 Device Address
2. 使用 Address Jumper
3. 更換不同 Address Device
4. 使用 I²C Multiplexer

例如:

TCA9548A

就是常見 I²C Multiplexer。

三十六、多裝置時 Scanner 更重要

如果連接:

LCD1602
OLED
RTC

Scanner 應該可能看到:

Found 0x27
Found 0x3C
Found 0x68

這時很容易知道:

Device
Address
Connection

是不是正常。

因此建議養成:

每次第一次接新的 I²C Module,都先跑 I²C Scanner。

三十七、I²C Frequency 是多少?

常見:

100 kHz

也就是:

Standard Mode

ESP32 可以:

Wire.begin(
  SDA_PIN,
  SCL_PIN,
  100000
);

目前 Arduino-ESP32 官方 begin() API 就允許直接設定 Bus Frequency。

另外也可以:

Wire.setClock(
  400000
);

設定較高速 Bus。

但:

所有 I²C Device、線路與 Pull-up 都必須能支援該 Frequency。

初學教材使用:

100 kHz

即可。

三十八、I²C 線可以拉很長嗎?

I²C 原本比較適合:

PCB
同一設備
短距離模組

而不是:

數公尺
跨房間

因為線路越長:

Capacitance ↑
Noise ↑
Signal Rise Time ↑

就越容易通訊失敗。

如果距離很長,

應考慮:

RS-485
CAN
其他差動介面

而不是強迫 I²C 拉很遠。

三十九、本章實作:ESP32 + DHT11 + I²C LCD1602 溫溼度顯示器

現在把:

第 8 章
DHT11

與:

第 9 章
I²C LCD1602

結合。

架構:

DHT11
  │
GPIO4
  │
  ▼
ESP32
  │
  │ I²C
  ├──── SDA GPIO21
  │
  └──── SCL GPIO22
        │
        ▼
     LCD1602

顯示:

TEMP: 27.5 C
HUM : 63.2 %

這就是一台真正可以獨立顯示的:

ESP32 數位溫溼度計

四十、實作材料

ESP32 DHT11 與 I²C LCD1602 實作材料
元件 數量
ESP32 開發板 1
DHT11 1
I²C LCD1602 1
10KΩ 電阻 視 DHT Module 而定
麵包板 1
杜邦線 若干
USB 線 1

四十一、完整 GPIO 規劃

DHT11 DATA
→ GPIO4

LCD SDA
→ GPIO21

LCD SCL
→ GPIO22

整理:

ESP32 DHT11 與 I²C GPIO 規劃表
功能 ESP32
DHT11 DATA GPIO4
I²C SDA GPIO21
I²C SCL GPIO22

四十二、完整接線

DHT11

ESP32 3.3V
→ DHT VCC

GPIO4
→ DHT DATA

GND
→ DHT GND

裸 DHT Sensor 必須注意:

DATA Pull-up

LCD1602 I²C(補充說明)

ESP32 GPIO21
→ LCD SDA

ESP32 GPIO22
→ LCD SCL

ESP32 GND
→ LCD GND

VCC 則依 LCD Backpack 實際電氣規格設計。

如果 Backpack 必須使用 5V 才能正常顯示,

請確認:

SDA / SCL Pull-up Voltage

不要把 5V Logic 直接送入 ESP32 GPIO。

必要時使用:

I²C Level Shifter

四十三、先跑 Scanner

正式程式前,

先只接 LCD1602。

執行:

I²C Scanner(補充說明)

如果看到:

I2C device found at 0x27

就把正式程式:

#define LCD_ADDRESS 0x27

如果 Scanner 顯示:

0x3F

則改成:

#define LCD_ADDRESS 0x3F

不要猜。

四十四、需要的 Library

本實作使用:

Wire
LiquidCrystal_I2C
DHT

程式:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>

其中:

Wire

為 Arduino / ESP32 Core 內建 I²C Library。

DHT 使用上一章:

DHT sensor library by Adafruit
+
Adafruit Unified Sensor

LCD 則依你安裝的:

LiquidCrystal_I2C

版本確認初始化 API。

四十五、ESP32 + DHT11 + LCD1602 完整程式

/*
  第 9 章
  ESP32 I2C 通訊完整教學

  實作:
  ESP32 + DHT11
  + I2C LCD1602
  溫溼度顯示器


  GPIO:

  DHT DATA
  -> GPIO4

  I2C SDA
  -> GPIO21

  I2C SCL
  -> GPIO22


  LCD Address:
  請先使用 I2C Scanner 確認
*/


#include <Wire.h>

#include <LiquidCrystal_I2C.h>

#include <DHT.h>


// =====================================
// I2C
// =====================================
const uint8_t SDA_PIN =
  21;

const uint8_t SCL_PIN =
  22;


const uint32_t
I2C_FREQUENCY =
  100000;


// =====================================
// LCD
// =====================================
// 請依 Scanner 結果修改
const uint8_t
LCD_ADDRESS =
  0x27;


LiquidCrystal_I2C lcd(
  LCD_ADDRESS,
  16,
  2
);


// =====================================
// DHT
// =====================================
const uint8_t
DHT_PIN =
  4;


#define DHT_TYPE DHT11

// 如果使用 DHT22:
// #define DHT_TYPE DHT22


DHT dht(
  DHT_PIN,
  DHT_TYPE
);


// =====================================
// Sensor Interval
// =====================================
const unsigned long
READ_INTERVAL =
  2000;


unsigned long
lastReadTime =
  0;


// =====================================
// Clear LCD Line
// =====================================
void clearLine(
  uint8_t row
)
{
  lcd.setCursor(
    0,
    row
  );

  lcd.print(
    "                "
  );
}


// =====================================
// Show Sensor Error
// =====================================
void showSensorError()
{
  clearLine(0);
  clearLine(1);


  lcd.setCursor(
    0,
    0
  );

  lcd.print(
    "DHT SENSOR"
  );


  lcd.setCursor(
    0,
    1
  );

  lcd.print(
    "ERROR!"
  );
}


// =====================================
// Show Temperature
// =====================================
void showTemperature(
  float temperature
)
{
  clearLine(0);


  lcd.setCursor(
    0,
    0
  );


  lcd.print(
    "TEMP:"
  );


  lcd.print(
    temperature,
    1
  );


  lcd.print(
    " C"
  );
}


// =====================================
// Show Humidity
// =====================================
void showHumidity(
  float humidity
)
{
  clearLine(1);


  lcd.setCursor(
    0,
    1
  );


  lcd.print(
    "HUM :"
  );


  lcd.print(
    humidity,
    1
  );


  lcd.print(
    " %"
  );
}


// =====================================
// setup()
// =====================================
void setup()
{
  Serial.begin(
    115200
  );


  delay(
    500
  );


  Serial.println();
  Serial.println(
    "ESP32 DHT11"
  );

  Serial.println(
    "I2C LCD1602"
  );


  // -----------------------------
  // I2C
  // -----------------------------
  if (!Wire.begin(
        SDA_PIN,
        SCL_PIN,
        I2C_FREQUENCY))
  {
    Serial.println(
      "I2C initialization failed!"
    );


    while (true)
    {
      delay(1000);
    }
  }


  // -----------------------------
  // LCD
  // -----------------------------
  lcd.init();

  lcd.backlight();


  lcd.clear();


  lcd.setCursor(
    0,
    0
  );

  lcd.print(
    "ESP32 DHT11"
  );


  lcd.setCursor(
    0,
    1
  );

  lcd.print(
    "Starting..."
  );


  // -----------------------------
  // DHT
  // -----------------------------
  dht.begin();


  delay(
    1500
  );


  lcd.clear();


  Serial.println(
    "System Ready"
  );
}


// =====================================
// loop()
// =====================================
void loop()
{
  unsigned long
  currentTime =
    millis();


  // -----------------------------
  // Read every 2 seconds
  // -----------------------------
  if (
    currentTime
    - lastReadTime
    < READ_INTERVAL
  )
  {
    return;
  }


  lastReadTime =
    currentTime;


  // -----------------------------
  // Read Sensor
  // -----------------------------
  float humidity =
    dht.readHumidity();


  float temperature =
    dht.readTemperature();


  // -----------------------------
  // Sensor Error
  // -----------------------------
  if (
    isnan(humidity)
    ||
    isnan(temperature)
  )
  {
    Serial.println(
      "DHT Sensor Error!"
    );


    showSensorError();


    return;
  }


  // -----------------------------
  // LCD
  // -----------------------------
  showTemperature(
    temperature
  );


  showHumidity(
    humidity
  );


  // -----------------------------
  // Serial Monitor
  // -----------------------------
  Serial.print(
    "Temperature = "
  );

  Serial.print(
    temperature,
    1
  );

  Serial.print(
    " C"
  );


  Serial.print(
    " | Humidity = "
  );

  Serial.print(
    humidity,
    1
  );

  Serial.println(
    " %RH"
  );
}

四十六、LCD 顯示結果

正常:

TEMP:27.5 C
HUM :63.2 %

Sensor Error:

DHT SENSOR
ERROR!

這樣使用者不用:

USB
Serial Monitor
PC

就可以直接看環境數據。

四十七、為什麼更新 LCD 前先清除整列?

假設原來:

TEMP:100.0 C

下一次變:

TEMP:9.8 C

如果沒有把舊文字清掉,

LCD 可能留下:

TEMP:9.8 C0

之類的殘字。

因此建立:

clearLine();

先輸出:

16 個空白

再寫新內容。

這比每次:

lcd.clear();

更好。

因為整個 LCD 不斷 Clear:

可能閃爍

而局部更新比較平順。

四十八、LCD 更新太快會怎樣?

DHT 本身每:

2 seconds

才有必要讀一次,

所以 LCD 也不需要:

每幾毫秒刷新

本章架構:

每 2 秒
↓
Read DHT
↓
Update LCD

就很合理。

這也減少:

I2C Traffic
LCD Flicker
不必要重繪

四十九、如何改成 DHT22?

只需要改:

#define DHT_TYPE DHT11

成:

#define DHT_TYPE DHT22

接線與 LCD 程式大致不變。

因此同一支程式可以成為:

ESP32 + DHT11 + LCD1602

或:

ESP32 + DHT22 + LCD1602

五十、如何把 LCD 改成 OLED?

整體 Sensor 架構不用重寫。

原本:

DHT
↓
ESP32
↓
LCD1602

改成:

DHT
↓
ESP32
↓
SSD1306 OLED

Sensor Reading:

dht.readTemperature();
dht.readHumidity();

完全可以保留。

只需要替換:

Display Layer

五十一、DHT11 + OLED 核心顯示範例

假設 OLED Address:

0x3C

可以:

display.clearDisplay();


display.setTextSize(1);

display.setTextColor(
  SSD1306_WHITE
);


display.setCursor(
  0,
  0
);

display.println(
  "ESP32 ENV MONITOR"
);


display.setTextSize(2);


display.setCursor(
  0,
  20
);

display.print(
  temperature,
  1
);

display.println(
  " C"
);


display.setCursor(
  0,
  44
);

display.print(
  humidity,
  1
);

display.println(
  " %"
);


display.display();

就可以製作較漂亮的:

OLED Environment Monitor

五十二、OLED 還能做什麼?

相比 LCD1602,

OLED 可以進一步加入:

Temperature Icon
Humidity Icon
Wi-Fi Icon
Battery
Alarm
Trend
Graph

例如:

┌────────────────────┐
│ ESP32 ENV MONITOR  │
│                    │
│ TEMP    27.5 C     │
│ HUM     63.2 %     │
│                    │
│ WiFi: OK           │
└────────────────────┘

後面加入 Wi-Fi 後就很適合。

五十三、LCD1602 + OLED 可以同時接嗎?

可以,

如果:

Address 不衝突

例如:

LCD1602
0x27

OLED
0x3C

可以:

GPIO21 SDA
├── LCD SDA
└── OLED SDA

GPIO22 SCL
├── LCD SCL
└── OLED SCL

程式:

LiquidCrystal_I2C lcd(
  0x27,
  16,
  2
);


Adafruit_SSD1306 display(
  128,
  64,
  &Wire,
  -1
);

共用:

Wire

即可。

五十四、I²C Bus 上可以接多少 Device?

理論上 I²C Address 空間可以容納很多 Device,

但實際數量還受到:

Address 衝突
Bus Capacitance
Pull-up
線長
Frequency
供電
Device Loading

限制。

因此不要只問:

I²C 理論可以多少個 Address?

更重要的是:

這條實際 Bus 的 Signal Integrity 能不能正常工作?

五十五、兩塊相同 OLED 可以同時接嗎?

假設兩塊 SSD1306:

OLED A
0x3C

OLED B
0x3C

直接接同一 Bus:

Address Conflict

如果模組可以:

0x3C
0x3D

修改其中一塊即可:

OLED A
0x3C

OLED B
0x3D

如果完全無法改 Address,

可以使用:

I2C Multiplexer

五十六、Wire.beginTransmission() 做什麼?

例如:

Wire.beginTransmission(
  0x3C
);

代表:

接下來準備和 Address 0x3C 的 Device 通訊。

資料先透過:

Wire.write();

放進 Buffer。

最後:

Wire.endTransmission();

才真正送出。

Arduino-ESP32 官方文件也特別說明:

Wire.write() 並不是立即直接傳給 Device,而是加入 I²C Buffer;endTransmission() 才會將 Buffer 送出。

五十七、I²C 如何讀取 Device?

一般流程:

Wire.requestFrom(
  address,
  size
);

然後:

Wire.read();

官方 Arduino-ESP32 Master API 目前也提供:

requestFrom()
read()
readBytes()

用於從指定 Device 取得資料。

五十八、常見錯誤:LCD 沒畫面

依序檢查:

1. VCC
2. GND
3. SDA
4. SCL
5. I2C Address
6. Contrast
7. Library
8. init() / begin()
9. Backlight
10. Logic Voltage

最重要:

先跑 Scanner。(補充說明)

五十九、常見錯誤:Scanner 找到 0x27,但是 LCD 沒字

如果:

Scanner
→ 0x27

證明:

I2C Backpack 有回應

但 LCD 還沒字,

再檢查:

Contrast
Library Compatibility
LCD Initialization
Backlight
Backpack Pin Mapping

這時問題就不再只是:

SDA / SCL

六十、常見錯誤:OLED 顯示初始化失敗

如果:

SSD1306 allocation failed

或 Display 初始化失敗,

檢查:

Address
0x3C / 0x3D

以及:

128×32
128×64

是否與實際 OLED 相符。

Adafruit 的不同 SSD1306 Display 範例也會依 Panel 型號使用不同 Screen Size 與 Address。

六十一、常見錯誤:把 SDA / SCL 接反

正確:

ESP32 SDA
→ Device SDA

ESP32 SCL
→ Device SCL

不是:

SDA → SCL
SCL → SDA

雖然這個錯誤很基本,

但在 I²C Scanner 顯示:

No I2C devices found

時非常常見。

六十二、常見錯誤:所有網路教學都寫 GPIO21 / 22,所以每塊 ESP32 都一樣

不對。

Generic ESP32 常見:

SDA = GPIO21
SCL = GPIO22

但官方已明確提醒:

Board default pins may vary.

更可靠的做法:

Wire.begin(
  SDA_PIN,
  SCL_PIN
);

明確指定自己的 Pin。

六十三、常見錯誤:LCD Address 永遠寫死 0x27

不要。

應先:

I2C Scanner
↓
Address
↓
程式

而不是:

網路文章說 0x27
↓
直接複製

OLED 也一樣。

六十四、常見錯誤:每多一個 I²C Device 就換一組 SDA / SCL

一般不需要。

I²C 的目的就是:

一組 SDA
+
一組 SCL
↓
多 Device

只要:

Address 不衝突

就可以共用同一 Bus。

六十五、I²C Scanner 應該保留下來

我很建議 ESP32 開發資料夾固定留一支:

ESP32_I2C_Scanner.ino

每次拿到新模組:

LCD
OLED
RTC
IMU
Sensor
ADC
EEPROM

第一件事:

接線
↓
Scanner
↓
確認 Address
↓
再寫正式程式

這會省下非常多 Debug 時間。

六十六、ESP32 I²C 可以應用在哪些模組?

常見包括:

LCD1602

ESP32
↓
I2C
↓
LCD

OLED

ESP32
↓
SSD1306

RTC

DS3231

IMU

例如:

MPU6050

ADC

例如:

ADS1115

環境 Sensor

例如:

BME280
SHT31
BH1750

EEPROM

例如:

24Cxx

這也是為什麼 I²C 是 ESP32 IoT 教材中非常重要的一章。

六十七、I²C 與 SPI 有什麼差別?

簡單比較:

I²C 與 SPI 通訊比較
項目 I²C SPI
主要訊號線 SDA + SCL SCK + MOSI + MISO + CS
Address 有 通常靠 CS
多裝置 很方便 每裝置通常需要 CS
GPIO 使用 少 較多
通訊速度 通常較低 通常較高
LCD / Sensor 常見 也常見
初學難度 低~中 中

本章先掌握:

I²C(補充說明)

後面再進入:

ESP32 SPI

六十八、FAQ:ESP32 I²C 常見問題

Q1:ESP32 I2C 的 SDA 是哪一腳?

Generic ESP32 常見預設:

GPIO21

但不同 Board 可能不同。

Q2:ESP32 I2C 的 SCL 是哪一腳?

Generic ESP32 常見:

GPIO22

不同 ESP32 Board 仍應查看實際 Pinout。

Q3:ESP32 可以自行指定 SDA / SCL 嗎?

可以。

例如:

Wire.begin(
  21,
  22,
  100000
);

Arduino-ESP32 官方 Master API 支援自訂 SDA、SCL 與 Bus Frequency。

Q4:ESP32 I2C Scanner 有什麼用途?

用來找出:

Bus 上有哪些 I²C Address。

因此拿到:

LCD
OLED
RTC
Sensor

時可以先確認裝置是否正常回應。

Q5:LCD1602 I2C Address 是多少?

沒有唯一答案。

常見可能是:

0x27
0x3F

正式使用請跑:

I²C Scanner。

Q6:SSD1306 OLED Address 是多少?

常見:

0x3C

或:

0x3D

實際仍以 Scanner / 模組設定為準。

Q7:LCD1602 和 OLED 可以一起接嗎?

可以,

如果 Address 不衝突。

例如:

LCD
0x27

OLED
0x3C

兩者可共用:

SDA
SCL

Q8:I²C 可以接很多裝置嗎?

可以,

但除了 Address 數量,

還要考慮:

Bus Capacitance
Pull-up
線長
Frequency
Address Conflict

Q9:Scanner 找不到裝置怎麼辦?

檢查:

VCC
GND
SDA
SCL
GPIO
Logic Voltage
Pull-up

先解決 Bus 通訊,

再處理 Library。

Q10:LCD 有背光但是沒有字怎麼辦?

先確認:

Scanner 找得到 Address

再調整:

Contrast Potentiometer(補充說明)

並確認:

LiquidCrystal_I2C Library

的初始化 API。

Q11:ESP32 可以接 5V LCD1602 I²C 嗎?

要看完整 Module 電路。

特別需要確認:

SDA / SCL Pull-up

是否被拉到 5V。

ESP32 為 3.3V Logic,

所以不能只因為 LCD VCC 使用 5V,就忽略 I²C Logic Level。

必要時使用:

Bidirectional Level Shifter。

Q12:Wire.h 要另外安裝嗎?

一般 Arduino-ESP32 Core 已經提供:

#include <Wire.h>

不需要另外安裝第三方 Wire Library。

六十九、本章學習重點整理

第一個核心:

I²C 只需要兩條主要訊號線

SDA
+
SCL

第二個核心:

Generic ESP32 常見:

SDA
GPIO21

SCL
GPIO22

但不同 Board 可能不同。

第三個核心:

ESP32 可以:

Wire.begin(
  SDA,
  SCL,
  frequency
);

自行指定 I²C Bus。

第四個核心:

I²C Device 透過:

Address(補充說明 2)

區分。

例如:

0x27
0x3C
0x68

第五個核心:

拿到任何新的 I²C Module,

先使用:

I²C Scanner(補充說明 2)

而不是猜 Address。

第六個核心:

多個 Device:

LCD
OLED
RTC
Sensor

可以共用:

SDA
SCL

前提:

Address 不衝突
+
Bus 電氣條件正常

第七個核心:

LCD1602 適合:

簡單文字
Sensor 數值

SSD1306 OLED 則更適合:

文字
圖示
圖形
Dashboard

七十、本章總結

在第 9 章中,我們正式學習 ESP32 最重要的通訊介面之一:

I²C(補充說明 2)

整體架構:

ESP32
 │
 ├── SDA
 │
 └── SCL
      │
      ▼
    I²C Bus
      │
 ┌────┼────────┬────────┐
 ▼    ▼        ▼        ▼
LCD  OLED     RTC      Sensor

首先學會:

#include <Wire.h>

以及:

Wire.begin();

對 Generic ESP32,本章使用:

SDA = GPIO21
SCL = GPIO22

但這只是常見預設;Arduino-ESP32 官方明確指出不同 Board 的 Default Pins 可能不同。

接著認識:

I²C Address(補充說明 2)

並完成非常重要的工具程式:

ESP32 I²C Scanner

讓 ESP32 自動找出:

0x27
0x3C
0x68
...

等 Bus 上的 Device Address。

之後實作:

LCD1602 I²C(補充說明 2)

學會:

Address
Backlight
Cursor
16×2 Character Display

再進一步認識:

SSD1306 OLED(補充說明)

以及:

128×64
Pixel Display
Text
Graphics

最後把上一章:

DHT11

與本章:

I²C LCD1602

整合成:

ESP32 + DHT11 + I²C LCD1602 溫溼度顯示器(補充說明)

完整資料流程:

Temperature
Humidity
      │
      ▼
    DHT11
      │
    GPIO4
      │
      ▼
    ESP32
      │
      │ I²C
      ├── SDA GPIO21
      └── SCL GPIO22
            │
            ▼
         LCD1602
            │
            ▼
   TEMP:27.5 C
   HUM :63.2 %

至此,ESP32 已經從單純:

Serial Monitor

進一步具備:

獨立顯示介面

而 I²C 的真正價值還不只 LCD。

後面可以繼續加入:

OLED
RTC DS3231
MPU6050
BME280
BH1750
ADS1115
EEPROM

形成:

ESP32
+
Multiple Sensors
+
Multiple I²C Devices

的完整系統。

因此如果只記住本章一個實作流程,就是:

接好 SDA / SCL → 先跑 I²C Scanner → 確認 Address → 再安裝對應 Library → 最後整合正式程式。

這個習慣可以解決大量 ESP32 I²C 模組「明明接好了卻完全沒反應」的問題。

下一步學習

不要停在這一篇,接著把內容做成自己的實作

目前主題:ESP32 / IoT。從完整學習路線、免費資源、會員下載,再接到實作所需材料。

同系列:ESP32 |  已是會員?直接前往 Getfile
相關資源與會員文章下載
文章可免費閱讀;下載完整 DOCX 或 PDF 需登入免費基本會員。
查看相關資源 會員免費下載 DOCX 會員免費 PDF/列印

← 回文章列表