DHT11 và DHT22 với ESP32: đọc nhiệt độ, độ ẩm không cần thư viện, giao thức 40 bit và điểm sương

So sánh DHT11 và DHT22, nối dây với ESP32, giải thích giao thức một dây 40 bit và checksum, chương trình đọc nhiệt độ, độ ẩm không cần thư viện, kèm điểm sương và bảng lỗi.

DHT11 và DHT22 là hai cảm biến nhiệt độ, độ ẩm rẻ và phổ biến nhất trong các dự án Arduino, ESP32: trạm thời tiết mini, theo dõi phòng ngủ, tủ chống ẩm, nhà kính. Cả hai chỉ cần một dây dữ liệu. Bài này giải thích giao thức của cảm biến, sau đó đọc nhiệt độ và độ ẩm bằng ESP32 không cần thư viện: tự đo độ dài từng xung, ghép thành 5 byte, kiểm tra checksum, đổi ra số đo và tính thêm điểm sương. Hiểu cách đọc này rồi thì gặp lỗi "không đọc được cảm biến" sẽ biết tìm ở đâu.

1. DHT11 và DHT22 khác nhau thế nào

Module cảm biến DHT11 vỏ xanh và module DHT22 vỏ trắng, mỗi module có ba chân ghi dấu cộng, out và dấu trừ
Module DHT11 (trái) và DHT22 (phải), loại hàn sẵn trên đế mạch 3 chân. Ảnh trái: Edwiyanto, CC BY-SA 4.0, Wikimedia Commons; ảnh phải: Suyash Dwivedi, CC BY-SA 4.0, Wikimedia Commons; đã cắt, thu nhỏ, làm mờ chữ in trên vỏ.

Hai cảm biến dùng cùng một kiểu giao tiếp, cùng cách nối dây. Khác nhau ở dải đo, độ chính xác và giá:

Thông số (theo datasheet)DHT11DHT22 (AM2302)
Màu vỏ thường gặpXanh dươngTrắng
Điện áp cấp3,3–5,5 V3,3–6 V
Dải độ ẩm20–90 %RH0–100 %RH
Sai số độ ẩm±5 %RH±2 %RH (tối đa ±5 %RH)
Dải nhiệt độ0–50 °C−40 đến 80 °C
Sai số nhiệt độ±2 °C±0,5 °C
Độ phân giải1 %RH, 1 °C (bản mới có 0,1 °C)0,1 %RH, 0,1 °C
Khoảng cách giữa hai lần đọcTừ 1 giâyTừ 2 giây

Chọn nhanh: đo trong nhà, cần biết "nóng hay mát, khô hay ẩm" thì DHT11 đủ dùng. Cần số đo sát thật, đo ngoài trời mùa lạnh hoặc độ ẩm trên 90 % (mùa nồm, nhà kính) thì dùng DHT22. Cả hai đều phản ứng chậm: thay đổi môi trường thì vài chục giây sau số đo mới ổn định.

2. Linh kiện cần chuẩn bị

3. Sơ đồ nối dây

Sơ đồ nối module DHT11 hoặc DHT22 với ESP32 DevKit V1: chân cộng vào 3V3, chân out vào D4, chân trừ vào GND
Nối module DHT 3 chân với ESP32: hai nhãn cùng tên là cùng một dây (hình minh họa).
Module DHTESP32 DevKit V1Ghi chú
+ (VCC)3V3Cấp 3,3 V để chân dữ liệu cũng ở mức 3,3 V, an toàn cho ESP32
out (DATA)D4 (GPIO4)Đổi chân khác thì sửa hằng CHAN_DHT
− (GND)GND

Thứ tự chân in trên đế mạch có thể khác nhau giữa các nhà sản xuất: luôn đọc chữ in cạnh chân trước khi cắm. Cảm biến 4 chân trần thì chân 1 là VCC, chân 2 là DATA, chân 3 bỏ trống, chân 4 là GND (nhìn từ mặt có lưới, chân hướng xuống), và cần thêm điện trở 10 kΩ từ DATA lên VCC.

Không cấp 5 V cho module rồi nối thẳng chân DATA vào ESP32: điện trở kéo lên trên module sẽ đưa 5 V vào GPIO. Lý do và cách chuyển mức an toàn có trong bài mức logic 3,3 V và 5 V.

4. Giao thức một dây của DHT

Giản đồ thời gian giao thức DHT: ESP32 kéo dây xuống thấp để bắt đầu, cảm biến trả lời 80 micro giây thấp và 80 micro giây cao, bit 0 có mức cao 26 đến 28 micro giây, bit 1 có mức cao khoảng 70 micro giây
Tín hiệu bắt đầu, lời trả lời của cảm biến và cách phân biệt bit 0, bit 1 (hình minh họa).

Đường dữ liệu bình thường ở mức cao nhờ điện trở kéo lên. Cả ESP32 lẫn cảm biến chỉ kéo đường dây xuống thấp rồi nhả ra, không bên nào đẩy lên cao. Một lần đọc gồm:

  1. Tín hiệu bắt đầu: ESP32 kéo dây xuống thấp ít nhất 18 ms (DHT11) hoặc 1 ms (DHT22), rồi nhả ra.
  2. Cảm biến trả lời: sau 20–40 µs, cảm biến kéo dây xuống thấp 80 µs rồi nhả lên cao 80 µs. Không thấy lời trả lời này nghĩa là sai dây, mất nguồn hoặc thiếu điện trở kéo lên.
  3. 40 bit dữ liệu: mỗi bit mở đầu bằng 50 µs mức thấp. Mức cao theo sau dài 26–28 µs là bit 0, dài khoảng 70 µs là bit 1. Bit gửi từ bit cao nhất của byte đầu tiên.

Giao thức này không phải 1-Wire của cảm biến DS18B20: DHT không có địa chỉ, mỗi chân GPIO chỉ nối được một cảm biến. Muốn so sánh hai cách làm, xem bài 1-Wire và cách đọc DS18B20.

5. Đọc 5 byte và kiểm tra checksum

Năm byte dữ liệu của DHT11 và DHT22: hai byte độ ẩm, hai byte nhiệt độ và một byte checksum, kèm ví dụ đổi ra 65 phần trăm và 29,3 độ C
40 bit được ghép thành 5 byte; byte thứ năm là checksum (số liệu ví dụ).

40 bit ghép lại thành 5 byte. Cách đổi ra số đo khác nhau giữa hai loại:

  • DHT11: byte 1 là phần nguyên độ ẩm, byte 2 là phần thập phân (thường bằng 0), byte 3 là phần nguyên nhiệt độ, 4 bit thấp của byte 4 là phần mười độ. Ví dụ 41 00 1D 03 là 65 % và 29,3 °C. Một số bản DHT11 mới dùng bit cao nhất của byte 4 để báo nhiệt độ âm.
  • DHT22: byte 1–2 là độ ẩm nhân 10, byte 3–4 là nhiệt độ nhân 10. Bit cao nhất của nhiệt độ là dấu âm. Ví dụ 02 8C = 652 là 65,2 %, 01 25 = 293 là 29,3 °C, còn 80 65 là −10,1 °C.
  • Checksum: cộng 4 byte đầu, lấy byte thấp của tổng. Kết quả phải bằng byte thứ năm; khác thì gói tin bị nhiễu, bỏ đi và đọc lại.

6. Chương trình đọc DHT không cần thư viện

Mở Arduino IDE 2, chọn bo ESP32 Dev Module (hoặc DOIT ESP32 DEVKIT V1), dán chương trình dưới đây và nạp. Chưa cài gói ESP32 thì làm theo bài cài ESP32 vào Arduino IDE 2. Bài dùng gói esp32 phiên bản 3.3.10; chương trình không cần cài thêm thư viện nào. Dùng DHT22 thì sửa LOAI_CAM_BIEN thành 22.

// PYWORLD.VN - Đọc cảm biến nhiệt độ, độ ẩm DHT11 / DHT22 bằng ESP32, không cần thư viện
// Chân DATA nối D4 (GPIO4), điện trở kéo lên 10 kΩ lên 3V3 (module 3 chân thường có sẵn điện trở này)
// Đổi LOAI_CAM_BIEN thành 22 khi dùng DHT22 (AM2302); in thêm điểm sương tính theo công thức Magnus

const int CHAN_DHT = 4;
const int LOAI_CAM_BIEN = 11;            // 11 = DHT11, 22 = DHT22
const uint32_t CHU_KY_DOC_MS = 2500;      // DHT22 cần ít nhất 2 giây giữa hai lần đọc

portMUX_TYPE khoaNgat = portMUX_INITIALIZER_UNLOCKED;

enum KetQua { DOC_DUOC, KHONG_TRA_LOI, LOI_THOI_GIAN, SAI_CHECKSUM };

// Chờ chân DATA rời khỏi mức `muc`. Trả về số micro giây đã chờ, hoặc -1 nếu quá `toiDa` micro giây.
static int32_t choHetMuc(int muc, uint32_t toiDa) {
  uint32_t batDau = micros();
  while (digitalRead(CHAN_DHT) == muc) {
    if (micros() - batDau > toiDa) return -1;
  }
  return (int32_t)(micros() - batDau);
}

// Đọc 5 byte: độ ẩm (2 byte), nhiệt độ (2 byte), checksum.
KetQua docDht(uint8_t du[5]) {
  // Tín hiệu bắt đầu: kéo DATA xuống thấp (DHT11 ít nhất 18 ms, DHT22 ít nhất 1 ms) rồi nhả ra.
  pinMode(CHAN_DHT, OUTPUT);
  digitalWrite(CHAN_DHT, LOW);
  delay(LOAI_CAM_BIEN == 11 ? 20 : 2);
  pinMode(CHAN_DHT, INPUT_PULLUP);

  uint16_t doDaiMucCao[40];
  KetQua kq = DOC_DUOC;
  // Cả gói tin dài khoảng 5 ms: tạm chặn ngắt để Wi-Fi, timer không làm lệch phép đo thời gian.
  portENTER_CRITICAL(&khoaNgat);
  // Cảm biến trả lời: chờ 20-40 us ở mức cao, kéo thấp 80 us, lên cao 80 us.
  if (choHetMuc(HIGH, 100) < 0 || choHetMuc(LOW, 100) < 0 || choHetMuc(HIGH, 100) < 0) {
    kq = KHONG_TRA_LOI;
  }
  for (int i = 0; i < 40 && kq == DOC_DUOC; i++) {
    if (choHetMuc(LOW, 80) < 0) {          // mỗi bit mở đầu bằng 50 us mức thấp
      kq = LOI_THOI_GIAN;
      break;
    }
    int32_t cao = choHetMuc(HIGH, 100);    // rồi mức cao: khoảng 27 us là bit 0, khoảng 70 us là bit 1
    if (cao < 0) {
      kq = LOI_THOI_GIAN;
      break;
    }
    doDaiMucCao[i] = (uint16_t)cao;
  }
  portEXIT_CRITICAL(&khoaNgat);
  if (kq != DOC_DUOC) return kq;

  for (int i = 0; i < 5; i++) du[i] = 0;
  for (int i = 0; i < 40; i++) {
    du[i / 8] = (du[i / 8] << 1) | (doDaiMucCao[i] > 48 ? 1 : 0);   // ngưỡng 48 us nằm giữa 27 và 70
  }
  if (((du[0] + du[1] + du[2] + du[3]) & 0xFF) != du[4]) return SAI_CHECKSUM;
  return DOC_DUOC;
}

// Đổi 5 byte thành độ ẩm (%) và nhiệt độ (độ C) theo loại cảm biến.
void doiGiaTri(const uint8_t du[5], float &doAm, float &nhietDo) {
  if (LOAI_CAM_BIEN == 11) {
    doAm = du[0] + du[1] * 0.1f;
    nhietDo = du[2] + (du[3] & 0x0F) * 0.1f;
    if (du[3] & 0x80) nhietDo = -nhietDo;                      // một số bản DHT11 mới báo nhiệt độ âm
  } else {
    doAm = ((du[0] << 8) | du[1]) * 0.1f;
    nhietDo = (((du[2] & 0x7F) << 8) | du[3]) * 0.1f;
    if (du[2] & 0x80) nhietDo = -nhietDo;                      // bit cao nhất là dấu âm
  }
}

// Điểm sương theo công thức Magnus (b = 17,62; c = 243,12 độ C).
float diemSuong(float nhietDo, float doAm) {
  float g = logf(doAm / 100.0f) + 17.62f * nhietDo / (243.12f + nhietDo);
  return 243.12f * g / (17.62f - g);
}

void setup() {
  Serial.begin(115200);
  pinMode(CHAN_DHT, INPUT_PULLUP);
  delay(1500);  // cảm biến cần khoảng 1 giây ổn định sau khi cấp nguồn
  Serial.printf("Doc DHT%d o chan GPIO%d\n", LOAI_CAM_BIEN, CHAN_DHT);
}

void loop() {
  static uint32_t lanDocTruoc = 0;
  if (millis() - lanDocTruoc < CHU_KY_DOC_MS) return;
  lanDocTruoc = millis();

  uint8_t du[5];
  KetQua kq = docDht(du);
  if (kq == KHONG_TRA_LOI) {
    Serial.println("Cam bien khong tra loi: kiem tra day DATA, nguon, dien tro keo len");
    return;
  }
  if (kq == LOI_THOI_GIAN) {
    Serial.println("Mat dong bo giua chung: doc lai o lan sau");
    return;
  }
  if (kq == SAI_CHECKSUM) {
    Serial.printf("Sai checksum: %02X %02X %02X %02X %02X\n", du[0], du[1], du[2], du[3], du[4]);
    return;
  }
  float doAm, nhietDo;
  doiGiaTri(du, doAm, nhietDo);
  Serial.printf("Byte: %02X %02X %02X %02X %02X | do am %.1f %%, nhiet do %.1f C, diem suong %.1f C\n",
                du[0], du[1], du[2], du[3], du[4], doAm, nhietDo, diemSuong(nhietDo, doAm));
}

7. Giải thích các phần chính

  • choHetMuc(muc, toiDa) chờ chân DATA rời khỏi một mức và trả về thời gian đã chờ tính bằng micro giây. Quá thời gian thì trả về −1, nhờ vậy chương trình không treo khi rút dây cảm biến.
  • portENTER_CRITICAL / portEXIT_CRITICAL tạm chặn ngắt trong khoảng 5 ms đọc 40 bit. Không có đoạn này, ngắt của Wi-Fi hay timer có thể chen vào giữa một bit, làm bit 0 bị đo dài thành bit 1 và checksum sai. Đoạn chặn ngắt phải ngắn: không đặt delay() hay Serial.print() bên trong.
  • Ngưỡng 48 µs nằm giữa 27 µs (bit 0) và 70 µs (bit 1), nên vẫn đúng khi xung lệch vài micro giây do dây dài hay nguồn yếu.
  • doiGiaTri() đổi 5 byte ra độ ẩm và nhiệt độ theo đúng cách ở mục 5.
  • diemSuong() tính điểm sương theo công thức Magnus (b = 17,62; c = 243,12 °C). Điểm sương là nhiệt độ mà hơi nước trong không khí bắt đầu đọng thành nước. Tường, sàn, đồ đạc lạnh hơn điểm sương sẽ bị đọng nước: đó là hiện tượng "nồm".
  • CHU_KY_DOC_MS = 2500: đọc 2,5 giây một lần. Đọc DHT22 dày hơn 2 giây thì cảm biến trả về số cũ hoặc không trả lời.

8. Kết quả trên Serial Monitor

Serial Monitor in năm byte đọc được, độ ẩm, nhiệt độ và điểm sương của DHT11 và DHT22, kèm thông báo khi rút dây DATA
Mở Serial Monitor ở tốc độ 115200 để xem kết quả (hình minh họa, số liệu ví dụ).

Mỗi dòng in 5 byte đọc được rồi mới tới giá trị đã đổi, nên khi số đo lạ có thể so lại từng byte với mục 5. Rút dây DATA ra thì chương trình báo "Cam bien khong tra loi" thay vì in số 0.

9. Đo cho đúng

  • Đặt cảm biến cách xa ESP32, mạch nguồn và đèn ít nhất 5–10 cm: linh kiện nóng làm nhiệt độ đo cao lên, độ ẩm đo thấp xuống.
  • Không đặt dưới nắng trực tiếp hay sát cửa gió máy lạnh. Ngoài trời thì cho vào hộp có khe thoáng, che mưa.
  • Sau khi cấp nguồn hoặc mang cảm biến sang phòng khác, chờ vài phút cho số đo ổn định.
  • So với đồng hồ nhiệt ẩm khác đặt cạnh nhau: DHT11 lệch 3–5 %RH là bình thường theo sai số của nó. Có thể cộng trừ một độ lệch cố định trong chương trình sau khi so.
  • Muốn biết vì sao cảm biến độ ẩm điện dung, điện trở cho số khác nhau, đọc bài cấu tạo và nguyên lý cảm biến độ ẩm.

10. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
"Cam bien khong tra loi" mọi lần đọcSai chân, cắm DATA vào GND, thiếu điện trở kéo lên (cảm biến 4 chân trần)Đọc lại chữ in cạnh chân; đo điện áp chân DATA lúc nghỉ phải gần 3,3 V
Thỉnh thoảng sai checksumDây dài, nhiễu từ relay, động cơ; nguồn 3,3 V sụt khi Wi-Fi phátDây ngắn dưới 1 m, xoắn dây DATA với GND, thêm tụ 100 nF sát chân VCC–GND của cảm biến
Đọc được DHT11 nhưng DHT22 không trả lờiQuên đổi LOAI_CAM_BIEN, đọc dày hơn 2 giâyĐặt LOAI_CAM_BIEN = 22, giữ CHU_KY_DOC_MS từ 2000 trở lên
Nhiệt độ cao hơn thực tế 2–5 °CCảm biến đặt sát ESP32 hoặc trong hộp kínĐưa cảm biến ra xa, khoan lỗ thoáng cho hộp
Độ ẩm đứng yên ở 95–99 %Cảm biến bị ướt hoặc đọng sươngĐể khô ở nơi thoáng vài giờ; đặt cảm biến ở chỗ không bị nước bắn
Số đo DHT22 âm vô lý, ví dụ −3276,7 °CĐổi byte nhiệt độ sai, coi bit dấu là bit giá trịTách bit cao nhất làm dấu như hàm doiGiaTri()

11. Xem thêm


Tin tức liên quan

Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker
Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker

Hướng dẫn làm hệ thống LoRa hoàn chỉnh với ESP32 và SX1278: node chạy pin đo điện áp, gửi gói rồi ngủ sâu; gateway nhận gói LoRa và publish dữ liệu JSON lên broker MQTT qua Wi-Fi.

Cánh tay robot 4 servo với ESP32: điều khiển bằng biến trở, ghi nhớ và phát lại động tác
Cánh tay robot 4 servo với ESP32: điều khiển bằng biến trở, ghi nhớ và phát lại động tác

Làm cánh tay robot 4 servo với ESP32: điều khiển từng khớp bằng biến trở, nhấn nút ghi tư thế, phát lại chuỗi động tác mượt, lưu vào flash khi mất điện và gợi ý STEM.

ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây
ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây

Hướng dẫn điều khiển động cơ bước 28BYJ-48 bằng ESP32 qua mạch ULN2003: chế độ nửa bước, 4076 bước một vòng, quay đúng góc theo lệnh, kim giây đồng hồ và thí nghiệm STEM.

Cấu hình LoRa trên ESP32: SF, băng thông, tỉ lệ mã, công suất và cách thử tầm xa
Cấu hình LoRa trên ESP32: SF, băng thông, tỉ lệ mã, công suất và cách thử tầm xa

Giải thích bốn tham số cấu hình LoRa (SF, băng thông, tỉ lệ mã, công suất), cách tính thời gian phát một gói và hướng dẫn làm bộ thử tầm xa bằng ESP32, SX1278 và màn hình OLED.

GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện
GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện

GPS làm việc thế nào, module NEO-6M, nối với ESP32 qua UART2, đọc câu NMEA RMC và GGA, kiểm checksum, đổi tọa độ, giờ Việt Nam, tốc độ, tính khoảng cách, không cần TinyGPS.

Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32
Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32

Hướng dẫn nối màn hình OLED 1.3 inch SH1106 với ESP32 DevKit V1 qua I2C, cài thư viện Adafruit SH110X, chạy code hiển thị chữ và xử lý lỗi thường gặp.

Hướng dẫn dùng LoRa SX1278 (Ra-02) với ESP32: nối dây SPI, gửi và nhận gói tin đầu tiên
Hướng dẫn dùng LoRa SX1278 (Ra-02) với ESP32: nối dây SPI, gửi và nhận gói tin đầu tiên

Hướng dẫn nối module LoRa SX1278 Ra-02 433 MHz với ESP32 qua SPI, cài thư viện LoRa trên Arduino IDE, viết chương trình gửi và nhận gói tin, đọc RSSI, SNR và xử lý lỗi thường gặp.

Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng
Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng

Phần 2 của loạt phased array siêu âm: thêm mạch đệm 12 V bằng hai module L298N, lắp mạch thu dùng LM358, đo biên độ 40 kHz bằng ADC của ESP32 và chạy chương trình tự quét góc lái để xem búp sóng.


Bình luận
Đã thêm vào giỏ hàng