前言:為什麼 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
為例。
| 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 為例:
| 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 |
|---|---|---|
| 顯示方式 | 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 開發板 | 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 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 |
|---|---|---|
| 主要訊號線 | 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 模組「明明接好了卻完全沒反應」的問題。