0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 自動化技巧
  3. 正文

ESP32 I2C 通訊實戰:連接 BME280 溫濕度氣壓感測器

2026 年 5 月 21 日 12點熱度 0人點贊 0條評論

ESP32 I2C 通訊是嵌入式開發中最實用的技能之一。I2C(Inter-Integrated Circuit)只需兩條線(SDA、SCL)就能連接多個感測器,BME280 感測器教學就是最經典的入門範例——它可以同時讀取溫度、濕度和氣壓資料。本文從 I2C 時序圖解讀開始,帶你一步步實作。

本篇以 ESP32 + BME280(溫濕度氣壓感測器)為例,從 I2C 協定原理、時序圖解讀、到完整程式碼實作,帶你一次搞懂 I2C 通訊。

目錄

  • 一、I2C 協定基礎
  • 二、I2C 時序圖完全解讀
  • 三、BME280 感測器介紹
  • 四、硬體接線
  • 五、ESP32 程式碼(Adafruit 函式庫)
  • 六、程式碼解讀:從原始暫存器操作到高階 API
  • 七、I2C 偵錯工具
  • 八、常見問題與排錯

一、I2C 協定基礎

I2C 由 Philips(現 NXP)在 1982 年發明,設計目標是用最少的引腳完成裝置間通訊:

訊號線 全名 說明
SDA Serial Data 雙向資料線
SCL Serial Clock 時脈線,由 Master 產生

I2C vs SPI 對比

特性 I2C SPI
引腳數 2(SDA + SCL) 4+(SCLK + MOSI + MISO + 每裝置一條 CS)
傳輸模式 半雙工 全雙工
速度 標準 100KHz,快速 400KHz,高速 3.4MHz 最高數十 MHz
多裝置 匯流排架構,最多 127 個裝置 每個裝置需要獨立 CS
通訊方式 裝置有 7-bit 位址,Master 定址 透過 CS 片選
硬體需求 需要上拉電阻 不需要上拉電阻

什麼時候用 I2C? 當你需要接多個感測器、引腳有限、速度要求不高的時候。反之,如果你需要高速傳輸大量資料(如相機、顯示器、Flash 記憶體),SPI 更適合。

二、I2C 時序圖完全解讀

以下是用 Python + WaveDrom 產生的 I2C 基本時序圖:

I2C 時序圖
圖 1:I2C 基本通訊時序

I2C 通訊有五個關鍵階段:

1. Start Condition(起始條件)

當 SCL 為高電位時,SDA 從高電位切換到低電位。這個邊緣變化告訴所有匯流排上的裝置:「準備開始通訊」。所有 I2C 指令都以 Start Condition 開頭。

2. 傳送從機位址(7-bit + R/W)

Master 送出 7-bit 從機位址 + 1 bit 讀寫標誌(0=寫,1=讀)。例如 BME280 的位址是 0x76(7-bit),加上寫標誌變成 0xEC。每個時脈週期傳送一個 bit,高位元(MSB)先傳。

3. ACK/NACK(應答)

第 9 個時脈週期由接收方控制。如果接收方將 SDA 拉低,表示 ACK(收到);如果 SDA 維持高電位,表示 NACK(未收到或結束)。

4. 資料傳輸

每 8 個 bit 資料後跟著 1 個 ACK bit。資料在 SCL 上升緣時被採樣,在下降緣時變化。Master 負責產生所有時脈訊號。

5. Stop Condition(停止條件)

當 SCL 為高電位時,SDA 從低電位切換到高電位,表示通訊結束。

三、BME280 感測器介紹

BME280 是 Bosch 生產的整合式環境感測器,一顆晶片同時測量三種數據:

參數 範圍 精度 解析度
溫度 -40°C ~ +85°C ±0.5°C 0.01°C
濕度 0% ~ 100% ±3% 0.008%
氣壓 300 ~ 1100 hPa ±1 hPa 0.18 Pa

BME280 的 I2C 位址有兩種:

  • 0x76(預設)— 當 SDO 接 GND 時
  • 0x77 — 當 SDO 接 VCC 時

大部分模組預設是 0x76,如果你買的是 breakout 板,通常不需要改。

四、硬體接線

ESP32 的 I2C 引腳可以任意指定(使用 Wire.begin() 時設定),但慣例上:

ESP32 BME280 說明
3.3V VCC 電源
GND GND 接地
GPIO21 SDA I2C 資料線
GPIO22 SCL I2C 時脈線

⚠️ 關鍵:I2C 匯流排必須有上拉電阻!

SDA 和 SCL 都需要分別接到 VCC(3.3V)的上拉電阻,典型值 4.7kΩ。如果你買的是 BME280 模組(如 GY-BME280),模組上已經內建了上拉電阻,直接接線即可。

如果你是在麵包板上自己搭建,記得加上 4.7kΩ 電阻,否則 I2C 無法正常通訊。

五、ESP32 程式碼(Adafruit 函式庫)

在 Arduino IDE 中,先安裝以下函式庫(程式庫管理員搜尋):

  • Adafruit Unified Sensor
  • Adafruit BME280 Library

以下完整程式碼,ESP32 讀取 BME280 資料並透過 Serial 輸出:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;
unsigned long delayTime;

void setup() {
    Serial.begin(115200);
    while (!Serial) delay(10);
    
    Serial.println(F("BME280 測試開始"));
    
    // 初始化 I2C(指定引腳)
    Wire.begin(21, 22);  // SDA=GPIO21, SCL=GPIO22
    
    // 初始化 BME280(位址 0x76)
    if (!bme.begin(0x76, &Wire)) {
        Serial.println(F("找不到 BME280 感測器!"));
        Serial.println(F("請檢查:"));
        Serial.println(F("  1. 接線是否正確"));
        Serial.println(F("  2. 上拉電阻是否安裝"));
        Serial.println(F("  3. I2C 位址是否為 0x76"));
        while (1) delay(10);
    }
    
    Serial.println(F("BME280 初始化成功"));
    Serial.println();
    delayTime = 1000;
}

void loop() {
    printValues();
    delay(delayTime);
}

void printValues() {
    // 讀取三種資料
    float temperature = bme.readTemperature();
    float humidity    = bme.readHumidity();
    float pressure    = bme.readPressure() / 100.0F;
    float altitude    = bme.readAltitude(SEALEVELPRESSURE_HPA);
    
    // 顯示
    Serial.print("溫度 = ");
    Serial.print(temperature, 2);
    Serial.println(" °C");
    
    Serial.print("濕度 = ");
    Serial.print(humidity, 2);
    Serial.println(" %");
    
    Serial.print("氣壓 = ");
    Serial.print(pressure, 2);
    Serial.println(" hPa");
    
    Serial.print("海拔 ≈ ");
    Serial.print(altitude, 2);
    Serial.println(" m");
    Serial.println("-------------------");
}

六、程式碼解讀:從原始暫存器到高階 API

Adafruit BME280 函式庫封裝了所有底層操作,但了解底層有助於除錯。BME280 的溫度資料存放在以下暫存器:

暫存器 位址 說明
hum_lsb 0xFE 濕度低位元組
hum_msb 0xFD 濕度高位元組
temp_xlsb 0xFC 溫度最低位元組
temp_lsb 0xFB 溫度低位元組
temp_msb 0xFA 溫度高位元組
press_xlsb 0xF9 氣壓最低位元組
press_lsb 0xF8 氣壓低位元組
press_msb 0xF7 氣壓高位元組
config 0xF5 配置暫存器
ctrl_meas 0xF4 控制測量暫存器
hum_ctrl 0xF2 濕度控制暫存器
id 0xD0 晶片 ID(應為 0x60)
calib00..calib41 0x88..0xB0 校正參數(非常重要!)

讀取流程如下:

BME280 讀取時序
圖 2:BME280 暫存器讀取流程

如果要用原始 I2C 操作讀取溫度,程式碼會像這樣:

#include <Wire.h>

#define BME280_ADDR 0x76
#define TEMP_MSB    0xFA

void readBME280() {
    Wire.beginTransmission(BME280_ADDR);
    Wire.write(TEMP_MSB);          // 指定要讀取的暫存器
    Wire.endTransmission(false);   // 不發送 Stop(Repeated Start)
    
    Wire.requestFrom(BME280_ADDR, 3);  // 讀 3 個 bytes
    if (Wire.available() >= 3) {
        uint32_t msb = Wire.read();
        uint32_t lsb = Wire.read();
        uint32_t xlsb = Wire.read();
        
        // 組合 20-bit 原始值
        uint32_t raw_temp = (msb << 12) | (lsb << 4) | (xlsb >> 4);
        
        Serial.print("原始溫度值 = ");
        Serial.println(raw_temp);
    }
}

不過實際轉換成攝氏需要套用 BME280 的校正公式(需要讀取 calib00~calib41 共 42 bytes 的校正參數),這就是為什麼使用 Adafruit 函式庫比自己從頭寫方便很多——它已經幫你處理好所有數學。

七、I2C 偵錯工具

當 I2C 通訊有問題時,以下是有效的偵錯方法:

1. I2C 掃描(I2C Scanner)

這是最基本的診斷工具——掃描匯流排上所有 I2C 裝置:

#include <Wire.h>

void setup() {
    Serial.begin(115200);
    Wire.begin(21, 22);
    Serial.println("I2C Scanner 啟動");
}

void loop() {
    byte error, address;
    int nDevices = 0;
    
    Serial.println("掃描 I2C 匯流排...");
    
    for (address = 1; address < 127; address++) {
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
        
        if (error == 0) {
            Serial.printf("發現裝置,位址: 0x%02X\r\n", address);
            nDevices++;
        }
    }
    
    if (nDevices == 0) {
        Serial.println("找不到任何 I2C 裝置!");
    }
    
    delay(5000);
}

2. 用邏輯分析儀抓波形

如果掃描不到裝置,接上邏輯分析儀(如 Saleae)看 SDA/SCL 波形:

  • Start Condition 有沒有出現?(SCL 高時 SDA 下降)
  • SCL 有沒有脈波? 沒有脈波可能是程式沒跑到 I2C 初始化
  • ACK 有沒有拉低? 沒有 ACK 表示裝置沒回應
  • SDA/SCL 波形是否乾淨? 上升緣太緩慢表示上拉電阻太大

3. 用三用電表量測

  • 量 SDA 對 GND 電壓:空閒時應為 3.3V
  • 量 SCL 對 GND 電壓:空閒時應為 3.3V
  • 如果接近 0V,表示沒有上拉電阻或 GPIO 被拉低

八、常見問題與排錯

Q1:I2C 掃描不到 BME280

最常見的原因依序是:

  • 缺少上拉電阻(約占 60% 的案例)— SDA 和 SCL 必須有 4.7kΩ 上拉到 3.3V
  • 位址錯誤(約 20%)— 試試 0x76 和 0x77 兩個位址
  • 接線錯誤(約 15%)— SDA 接 SDA,SCL 接 SCL,不要交叉
  • 感測器損壞(約 5%)— 換一顆試試

Q2:讀到的溫度固定不變

BME280 的測量模式可能設為「睡眠模式」。確認 ctrl_meas 暫存器(0xF4)的值為 0x27(正常模式,IIR 濾波關閉)。Adafruit 函式庫在 begin() 時會自動設定,但如果你用原始 I2C 操作,記得要設定。

Q3:氣壓值偏高或偏低

氣壓讀數需要根據當地海拔校正。BME280 回傳的是絕對氣壓,如果你住在高海拔地區,數值會偏低。可以用 bme.readAltitude(SEALEVELPRESSURE_HPA) 來計算海拔,但需要傳入正確的海平面氣壓參考值(可從氣象局查詢)。

Q4:SDA 或 SCL 波形上升緣很緩慢

這表示上拉電阻阻抗太高。4.7kΩ 是標準值,如果你的 I2C 匯流排上有很多裝置(增加寄生電容),可能需要降到 2.2kΩ 甚至 1kΩ。反之如果上升緣太快(過衝),則需要增加電阻值。

Q5:多個 I2C 裝置衝突

確認所有裝置的 I2C 位址不同。BME280 有 0x76 和 0x77 兩個選項。如果兩個裝置位址相同,可以買 I2C 多工器(如 TCA9548A)來解決。

總結

I2C 是嵌入式開發的必備技能,兩條線就能搞定多感測器通訊:

📖 延伸閱讀:SPI 通訊教學 · MQTT 實戰教學

  • I2C 只需要 SDA + SCL 兩條線,加上 4.7kΩ 上拉電阻
  • BME280 是目前 C/P 值最高的環境感測器,一顆測三種資料
  • 使用 Adafruit 函式庫可用幾行程式完成讀取
  • I2C Scanner 是除錯的第一步,邏輯分析儀是第二步
  • 90% 的通訊問題來自上拉電阻或接線錯誤
標籤: BME280 ESP32 I2C 感測器 教學 氣壓 溫濕度
最後更新:2026 年 5 月 21 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang