บทความ › ไมโครคอนโทรลเลอร์และโค้ด
อ่านอุณหภูมิ ความชื้น ความกดอากาศ จาก BME280 ผ่าน I2C ด้วย ESP32
ต่อสาย I2C สองเส้น หา address ด้วย I2C scanner อ่านค่าด้วยไลบรารี Adafruit แยก BME280 กับ BMP280 ที่หน้าตาเหมือนกัน และทำไมอุณหภูมิที่อ่านได้มักสูงเกินจริง
- I2C ใช้สายข้อมูลสองเส้น (SDA, SCL) ต่ออุปกรณ์หลายตัวร่วมกันได้ แยกกันด้วย address
- รัน I2C scanner ก่อนเขียนโค้ดจริง จะรู้ทันทีว่าสายถูกไหม และอุปกรณ์อยู่ address อะไร
- โมดูลราคาถูกหลายตัวที่ขายว่า BME280 เป็น BMP280 ซึ่งไม่มีเซนเซอร์ความชื้น
- เซนเซอร์ที่วางชิดบอร์ดอ่านอุณหภูมิสูงเกินจริง เพราะความร้อนจากบอร์ดเอง
BME280 เป็นเซนเซอร์ตัวเล็กที่วัดได้สามอย่าง อุณหภูมิ ความชื้นสัมพัทธ์ และความกดอากาศ คุยกับไมโครคอนโทรลเลอร์ผ่าน I2C ที่ใช้สายข้อมูลแค่สองเส้น เป็นเซนเซอร์ตัวแรกที่เหมาะมาก เพราะได้เรียนเรื่องสายสื่อสาร ไลบรารี และปัญหาจริงอย่างความร้อนจากบอร์ดในคราวเดียว
ตัวอย่างใช้ ESP32 DevKit (ESP32-WROOM-32) กับ Arduino IDE 2 และโมดูล BME280 แบบบอร์ดเล็กที่ขายทั่วไป ต่อจากบทที่แล้วที่เรื่อง GPIO
I2C ทำงานยังไง
I2C ใช้สายสองเส้น SDA ส่งข้อมูล และ SCL ส่งจังหวะ (clock) ESP32 เป็นฝ่ายควบคุม อุปกรณ์ทุกตัวต่อขนานกันบนสายคู่เดียว แต่ละตัวมีหมายเลขประจำตัวเรียกว่า address ESP32 เรียกหา address ไหน ตัวนั้นตอบ
สาย I2C ต้องมี ตัวต้านทาน pull-up ดึงสายขึ้นไปที่ไฟเลี้ยง เพราะอุปกรณ์ทำได้แค่ดึงสายลง โมดูลสำเร็จรูปส่วนใหญ่มี pull-up ติดมาบนบอร์ดแล้ว ปกติจึงไม่ต้องเพิ่ม
ต่อสาย
| ขาโมดูล BME280 | ต่อกับ ESP32 |
|---|---|
| VIN (หรือ VCC) | 3V3 |
| GND | GND |
| SCL | GPIO22 |
| SDA | GPIO21 |
GPIO21 และ GPIO22 เป็นขา I2C ค่าเริ่มต้นของ ESP32 รุ่นดั้งเดิมใน Arduino core ถ้าใช้ ESP32-S3 หรือ C3 ขาค่าเริ่มต้นต่างออกไป ให้ดู pinout ของบอร์ดนั้น
เรื่องไฟเลี้ยง: โมดูลบางแบบมีตัวควบคุมแรงดันในตัว รับ 5 V ได้ แต่บางแบบไม่มี ป้อน 5 V แล้วเซนเซอร์เสียได้ ตัวชิป BME280 ทำงานที่ไม่เกิน 3.6 V ต่อที่ 3V3 ปลอดภัยกับทั้งสองแบบ ถ้าไม่แน่ใจให้ใช้ 3V3
ขั้นแรก: หา address ด้วย I2C scanner
ก่อนเขียนโค้ดอ่านค่า ให้ตรวจก่อนว่าสายถูก และเซนเซอร์อยู่ address อะไร โค้ดนี้เรียกทุก address แล้วพิมพ์ตัวที่ตอบ
#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA, SCL
for (uint8_t addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.printf("found device at 0x%02X\n", addr);
}
}
Serial.println("scan done");
}
void loop() {}
เปิด Serial Monitor ที่ 115200 baud ควรเจอ 0x76 หรือ 0x77 ซึ่งขึ้นกับว่าขา SDO ของชิปต่อลงกราวด์ (0x76) หรือต่อไฟ (0x77) โมดูลทั่วไปจำนวนมากตั้งไว้ที่ 0x76 ถ้าไม่เจออะไรเลย ปัญหาอยู่ที่สาย ไม่ใช่โค้ด
อ่านค่าด้วยไลบรารี Adafruit
ใน Arduino IDE เปิด Library Manager ค้นหา Adafruit BME280 Library แล้วติดตั้ง ยอมรับให้ติดตั้งไลบรารีที่ต้องใช้ร่วม (Adafruit Unified Sensor และ Adafruit BusIO) ด้วย
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA, SCL
if (!bme.begin(0x76)) { // ถ้าไม่เจอ ลอง 0x77
Serial.println("BME280 not found: check wiring and address");
while (true) delay(1000);
}
}
void loop() {
float t = bme.readTemperature(); // °C
float h = bme.readHumidity(); // %RH
float p = bme.readPressure() / 100.0F; // Pa -> hPa
Serial.printf("T=%.2f C H=%.1f %% P=%.1f hPa\n", t, h, p);
delay(2000);
}
readPressure() คืนค่าเป็น pascal หารด้วย 100 ได้ hPa ซึ่งเป็นหน่วยที่พยากรณ์อากาศใช้ ความกดอากาศมาตรฐานที่ระดับน้ำทะเลคือ 1013.25 hPa ค่าที่อ่านได้จริงเปลี่ยนตามความสูงของพื้นที่และสภาพอากาศ
scanner เจอ แต่ไลบรารีบอกว่าไม่เจอ
อาการนี้เจอบ่อยมาก สาเหตุอันดับหนึ่งคือ ชิปบนโมดูลเป็น BMP280 ไม่ใช่ BME280 สองตัวนี้ใช้ address เดียวกัน ขาเหมือนกัน และบางร้านขายปนกัน แต่ BMP280 ไม่มีเซนเซอร์ความชื้น
ไลบรารีอ่านรหัสประจำชิป (chip ID) ก่อนเริ่ม BME280 คือ 0x60 ส่วน BMP280 คือ 0x58 ถ้าไม่ใช่ 0x60 ฟังก์ชัน begin() จะคืนค่า false ดูด้วยตาก็พอแยกได้ ชิป BME280 เป็นสี่เหลี่ยมจัตุรัสราว 2.5 × 2.5 mm ส่วน BMP280 เป็นสี่เหลี่ยมผืนผ้าราว 2.0 × 2.5 mm ถ้าได้ BMP280 มา ใช้ไลบรารี Adafruit BMP280 แทน อ่านได้แค่อุณหภูมิกับความกด
ทำไมอุณหภูมิสูงเกินจริง
BME280 แม่นพอสำหรับงานทั่วไป แต่มันวัดอุณหภูมิ ตรงที่มันอยู่ ถ้าวางชิด ESP32 ซึ่งอุ่นขึ้นตอนทำงาน โดยเฉพาะตอนเปิด Wi-Fi ค่าที่อ่านได้จะสูงกว่าอากาศในห้อง และเพราะความชื้นสัมพัทธ์ขึ้นกับอุณหภูมิ ค่าความชื้นก็จะต่ำกว่าจริงตามไปด้วย
เซนเซอร์อุณหภูมิวัดความร้อนรอบตัวมัน รวมถึงความร้อนจากบอร์ดที่มันติดอยู่
วิธีแก้ที่ได้ผล
- ต่อเซนเซอร์ด้วยสายแยกออกห่างจากบอร์ดหลัก
- ถ้าใส่กล่อง ให้เซนเซอร์อยู่ในช่องที่มีรูระบาย แยกจากส่วนของ ESP32
- อ่านค่าห่างขึ้น หรือให้ ESP32 เข้า deep sleep ระหว่างรอบ บอร์ดจะอุ่นน้อยลง
ข้อผิดพลาดที่เจอบ่อย
| อาการ | สาเหตุที่พบบ่อย | แก้ |
|---|---|---|
| scanner ไม่เจออะไรเลย | สลับ SDA กับ SCL หรือลืมต่อ GND | ตรวจสายทีละเส้น |
| เจอ 0x77 แต่โค้ดใช้ 0x76 | address ไม่ตรง | เปลี่ยนเป็น bme.begin(0x77) |
scanner เจอ แต่ begin() ล้ม | เป็น BMP280 | ใช้ไลบรารี BMP280 |
| อ่านได้บ้าง พลาดบ้าง | สายยาวเกินหรือเสียบหลวม | ใช้สายสั้นลง เสียบให้แน่น |
| อุณหภูมิสูงกว่าห้องหลายองศา | ความร้อนจากบอร์ด | แยกเซนเซอร์ออกห่าง |
บทถัดไปจะออกแบบกล่องพิมพ์ 3 มิติให้บอร์ดนี้ พร้อมช่องระบายที่แยกเซนเซอร์ออกจากความร้อนของ ESP32
คำถามที่พบบ่อย
ใช้ขาอื่นแทน GPIO21 และ GPIO22 ได้ไหม
ได้ ESP32 กำหนดขา I2C ได้เกือบทุกขาที่เป็น output ได้ ใส่ขาที่ต้องการใน Wire.begin(SDA, SCL) แต่เลี่ยง strapping pin และขา 34–39 ที่เป็น input อย่างเดียว
ต่อสายยาวได้แค่ไหน
I2C ออกแบบมาสำหรับระยะสั้นบนบอร์ดเดียวกัน สายระดับไม่กี่สิบเซนติเมตรมักใช้ได้ ถ้ายาวกว่านั้นเริ่มอ่านพลาด ต้องลดความเร็ว เพิ่ม pull-up ที่เหมาะสม หรือเปลี่ยนวิธีส่งสัญญาณ
จอ OLED กับ BME280 ใช้สายชุดเดียวกันได้ไหม
ได้ จอ SSD1306 ทั่วไปอยู่ที่ address 0x3C ส่วน BME280 อยู่ที่ 0x76 หรือ 0x77 ไม่ชนกัน ต่อขนานกันบนสาย SDA และ SCL ได้เลย
วงจรและโค้ดในบทความทดสอบกับบอร์ดตัวอย่าง บอร์ดต่างรุ่นอาจใช้ขาต่างกัน ตรวจ pinout ของบอร์ดที่ใช้ก่อนต่อไฟเสมอ