Trạm đo nhiệt độ – độ ẩm có màn hình OLED và biểu đồ 24h trên điện thoại

Đo nhiệt độ, độ ẩm và chỉ số nóng bức (cảm giác thực tế) bằng DHT22, hiện trên màn OLED 0,96 inch và trang web điện thoại kèm biểu đồ 24 giờ. Sai số khoảng ±0,5°C và ±2–5% độ ẩm, dùng cho phòng ngủ, phòng máy, kho, chuồng trại.

⏱ 60 phút🔧 Mới học💰 Linh kiện ~230.000đ🧠 ESP32 DevKit V1⚡ Nguồn USB 5V

✅ Đã kiểm thử

Luật điệnKhông lỗiSai chân, chập nguồn, quá áp 5V vào chân 3,3V, thiếu đi-ốt, thiếu trở kéo…
Mô phỏng SPICE1/1 kịch bản · 4 phép đo đạtDòng/áp từng linh kiện ở trường hợp xấu nhất (ngspice)
Biên dịch codeĐạt · dùng 77% bộ nhớesp32:esp32:esp32doit-devkit-v1
Xem chi tiết 4 phép đo

Hoạt động bình thường (mọi tín hiệu ở mức cao — trường hợp áp lớn nhất)

  • Tổng dòng đỉnh từ USB 272.5mA (đủ với cục sạc 5V 1A).
  • Áp tín hiệu DHT22 vào ESP32: 3.30V ✓
  • Áp đường SDA màn hình vào ESP32: 3.30V ✓
  • Áp đường SCL màn hình vào ESP32: 3.30V ✓
Kiểm lần cuối: 17:08:43 12/11/2026

Nguyên lý hoạt động

DHT22 tự đo và gửi số liệu qua 1 dây DATA mỗi khi ESP32 hỏi (tối thiểu 2 giây/lần). Màn OLED nói chuyện với ESP32 qua 2 dây I2C (SDA–GPIO21, SCL–GPIO22). Cả 2 module được cấp 3,3V nên tín hiệu về ESP32 không vượt 3,3V, không cần mạch chuyển mức. Mạch chỉ ăn khoảng 25mA ngoài ESP32. ESP32 lưu 288 điểm đo (5 phút/điểm = 24 giờ) trong RAM để vẽ biểu đồ trên điện thoại.

Sơ đồ đấu dây

Mỗi màu là một dây. Chấm tròn là điểm hàn. Chạm vào tên dây trong bảng để làm nổi bật.

3V33V3GNDGNDDHT_DATADHT_DATASDASDASCLSCLU1 · ESP32 DevKit V13V3GPIO16 (RX2)GPIO21 (D21)GPIO22 (D22)GNDU2 · Cảm biến nhiệt độ – độ ẩm DHT22VCCDATAGNDU3 · Màn hình OLED 0.96" I2C SSD1306VCCSCLSDAGND
#DâyNối các điểmÁp mô phỏng
1cam
3V3
U1 3V3 ⟷ U2 VCC ⟷ U3 VCC—
2đen
GND
U1 GND ⟷ U2 GND ⟷ U3 GND—
3vàng
DHT_DATA
U2 DATA ⟷ U1 GPIO16 (RX2)—
4xanh lá
SDA
U3 SDA ⟷ U1 GPIO21 (D21)—
5xanh dương
SCL
U3 SCL ⟷ U1 GPIO22 (D22)—

Danh sách linh kiện

Ký hiệuLinh kiệnGiá tham khảo
U1ESP32 DevKit V1 30 chân (ESP32-WROOM-32)110.000đTìm mua
U2Module cảm biến DHT22 (AM2302) loại 3 chân có sẵn mạch đế65.000đTìm mua
U3Màn hình OLED 0.96 inch I2C SSD1306 128×64, 4 chân (GND VCC SCL SDA)55.000đTìm mua
Tổng (chưa gồm board hàn, dây, thiếc)230.000đ

Các bước làm

Code nạp cho mạch

Cài thư viện: DHT sensor library, Adafruit Unified Sensor, Adafruit SSD1306, Adafruit GFX Library (Arduino IDE → Library Manager). Chọn board DOIT ESP32 DEVKIT V1.

/*
  Mạch Gọn — Trạm nhiệt độ/độ ẩm DHT22 + OLED + biểu đồ 24h (ESP32)
  Board : DOIT ESP32 DEVKIT V1
  Thư viện: DHT sensor library, Adafruit Unified Sensor, Adafruit SSD1306, Adafruit GFX
  Chân  : GPIO16 <- DATA DHT22 (3V3) ; GPIO21 SDA, GPIO22 SCL -> OLED (3V3)
  Lần đầu: WiFi "MachGon-NhietDo" (mật khẩu machgon123) -> 192.168.4.1
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Preferences.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>

const uint8_t PIN_DHT = 16, PIN_SDA = 21, PIN_SCL = 22;
const char* AP_SSID = "MachGon-NhietDo";
const char* HOSTNAME = "nhietdo";
const uint32_t READ_MS = 3000;            // DHT22 cần >= 2 giây giữa 2 lần đọc
const uint32_t LOG_MS = 5UL * 60UL * 1000UL;
const int HIST = 288;                     // 24h x 12 điểm/giờ

DHT dht(PIN_DHT, DHT22);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
WebServer server(80);
Preferences prefs;
bool oledOk = false, sensorOk = false;
float tNow = NAN, hNow = NAN, hiNow = NAN, tOff = 0, hOff = 0;
float tMin = NAN, tMax = NAN;
int16_t histT[HIST], histH[HIST];
int histCount = 0, histHead = 0;
uint32_t lastRead = 0, lastLog = 0;
int failCount = 0;

void addHistory(float t, float h) {
  histT[histHead] = (int16_t)lroundf(t * 10);
  histH[histHead] = (int16_t)lroundf(h * 10);
  histHead = (histHead + 1) % HIST;
  if (histCount < HIST) histCount++;
}

void readSensor() {
  float h = dht.readHumidity(), t = dht.readTemperature();
  if (isnan(h) || isnan(t)) {
    if (++failCount >= 3) sensorOk = false;   // 3 lần lỗi liên tiếp mới báo
    Serial.println("Doc DHT22 loi");
    return;
  }
  failCount = 0; sensorOk = true;
  tNow = t + tOff; hNow = constrain(h + hOff, 0.0f, 100.0f);
  hiNow = dht.computeHeatIndex(tNow, hNow, false);
  if (isnan(tMin) || tNow < tMin) tMin = tNow;
  if (isnan(tMax) || tNow > tMax) tMax = tNow;
  if (histCount == 0 || millis() - lastLog >= LOG_MS) { lastLog = millis(); addHistory(tNow, hNow); }
}

void drawOled() {
  if (!oledOk) return;
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  if (!sensorOk) {
    oled.setTextSize(2); oled.setCursor(0, 8); oled.println("Loi");
    oled.setTextSize(1); oled.println("cam bien DHT22");
    oled.println("Kiem tra day OUT");
  } else {
    oled.setTextSize(3); oled.setCursor(0, 0); oled.printf("%.1f", tNow);
    oled.setTextSize(1); oled.print(" o"); oled.setTextSize(2); oled.print("C");
    oled.setTextSize(2); oled.setCursor(0, 28); oled.printf("%.0f%% RH", hNow);
    oled.setTextSize(1); oled.setCursor(0, 46); oled.printf("Cam giac: %.1fC", hiNow);
  }
  oled.setTextSize(1); oled.setCursor(0, 56);
  oled.print(WiFi.status() == WL_CONNECTED ? WiFi.localIP() : WiFi.softAPIP());
  oled.display();
}

const char PAGE[] PROGMEM = R"HTML(<!doctype html><html lang="vi"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Nhiệt độ – độ ẩm</title>
<style>
body{margin:0;font-family:system-ui,sans-serif;background:#0b1120;color:#e2e8f0}
header{padding:14px;background:#111827;font-weight:700}.row{display:flex;gap:10px;margin:12px}
.c{flex:1;background:#1f2937;border-radius:14px;padding:14px;text-align:center}.c b{display:block;font-size:34px}
.c small,small{color:#94a3b8}.card{margin:12px;background:#1f2937;border-radius:14px;padding:14px}
canvas{width:100%;height:220px}input{width:80px;padding:7px;border-radius:8px;border:0;font:inherit}
button{font:inherit;border:0;border-radius:10px;padding:9px 14px;background:#38bdf8;color:#082f49;font-weight:700;margin-top:6px}
.err{background:#7f1d1d;margin:12px;padding:10px;border-radius:10px;display:none}
</style></head><body><header>🌡 Nhiệt độ – độ ẩm <small id="ip"></small></header>
<div class="err" id="err">⚠ Không đọc được DHT22. Kiểm tra dây OUT/VCC/GND.</div>
<div class="row"><div class="c"><small>Nhiệt độ</small><b id="t">--</b><small id="mm"></small></div>
<div class="c"><small>Độ ẩm</small><b id="h">--</b><small id="lv"></small></div></div>
<div class="row"><div class="c"><small>Cảm giác như</small><b id="hi">--</b></div></div>
<div class="card"><b>24 giờ qua</b> <small>— đỏ: nhiệt độ, xanh: độ ẩm</small><canvas id="cv" width="700" height="300"></canvas></div>
<div class="card"><b>Cài đặt</b><br><small>Bù sai số nếu so với nhiệt kế chuẩn bị lệch.</small><br>
Nhiệt độ ± <input id="to" type="number" step="0.1"> °C &nbsp; Độ ẩm ± <input id="ho" type="number" step="1"> %<br><button onclick="saveOff()">Lưu</button>
<p><small id="ws"></small></p>WiFi <input id="ssid" style="width:40%"> Mật khẩu <input id="pass" type="password" style="width:30%"><br><button onclick="wifi()">Lưu WiFi & khởi động lại</button></div>
<script>
const $=i=>document.getElementById(i);let first=true;
function level(h){return h<40?'Khô':h<=60?'Dễ chịu':h<=75?'Hơi ẩm':'Rất ẩm'}
async function tick(){try{const s=await (await fetch('/api/state')).json();$('err').style.display=s.ok?'none':'block';
if(s.ok){$('t').textContent=s.t.toFixed(1)+'°C';$('h').textContent=s.h.toFixed(0)+'%';$('hi').textContent=s.hi.toFixed(1)+'°C';$('lv').textContent=level(s.h);$('mm').textContent='thấp '+s.tmin.toFixed(1)+' · cao '+s.tmax.toFixed(1)}
$('ip').textContent=s.ip;$('ws').textContent=s.sta?'Đã nối WiFi nhà: '+s.ssid:'Chưa nối WiFi nhà';if(first){$('to').value=s.to;$('ho').value=s.ho;first=false}}catch(e){}}
async function chart(){const d=await (await fetch('/api/history')).json();const c=$('cv'),x=c.getContext('2d');x.clearRect(0,0,c.width,c.height);
if(d.t.length<2){x.fillStyle='#94a3b8';x.font='22px sans-serif';x.fillText('Đang thu thập số liệu (5 phút/điểm)…',20,150);return}
const P=40,W=c.width-P*2,H=c.height-P*1.5;function line(a,mn,mx,col){x.strokeStyle=col;x.lineWidth=3;x.beginPath();a.forEach((v,i)=>{const X=P+i*W/(a.length-1),Y=P/2+H-(v-mn)/(mx-mn||1)*H;i?x.lineTo(X,Y):x.moveTo(X,Y)});x.stroke()}
const tm=Math.min(...d.t)-1,tM=Math.max(...d.t)+1;line(d.t,tm,tM,'#f87171');line(d.h,0,100,'#38bdf8');
x.fillStyle='#94a3b8';x.font='18px sans-serif';x.fillText(tM.toFixed(0)+'°',2,P/2+12);x.fillText(tm.toFixed(0)+'°',2,P/2+H);x.fillText((d.t.length*5/60).toFixed(1)+' giờ trước',P,c.height-6);x.fillText('bây giờ',c.width-P-60,c.height-6)}
async function saveOff(){await fetch('/api/offset',{method:'POST',body:new URLSearchParams({t:$('to').value,h:$('ho').value})});alert('Đã lưu')}
async function wifi(){await fetch('/api/wifi',{method:'POST',body:new URLSearchParams({ssid:$('ssid').value,pass:$('pass').value})});alert('Mạch khởi động lại. Nối WiFi nhà rồi mở http://nhietdo.local')}
tick();chart();setInterval(tick,3000);setInterval(chart,60000);
</script></body></html>)HTML";

void setupWeb() {
  server.on("/", []() { server.send_P(200, "text/html; charset=utf-8", PAGE); });
  server.on("/api/state", []() {
    bool sta = WiFi.status() == WL_CONNECTED;
    char buf[400];
    snprintf(buf, sizeof(buf), "{\"ok\":%s,\"t\":%.2f,\"h\":%.1f,\"hi\":%.2f,\"tmin\":%.1f,\"tmax\":%.1f,\"to\":%.1f,\"ho\":%.0f,\"sta\":%s,\"ssid\":\"%s\",\"ip\":\"%s\"}",
             sensorOk ? "true" : "false", sensorOk ? tNow : 0, sensorOk ? hNow : 0, sensorOk ? hiNow : 0,
             isnan(tMin) ? 0 : tMin, isnan(tMax) ? 0 : tMax, tOff, hOff, sta ? "true" : "false",
             prefs.getString("ssid", "").c_str(), (sta ? WiFi.localIP() : WiFi.softAPIP()).toString().c_str());
    server.send(200, "application/json", buf);
  });
  server.on("/api/history", []() {
    String t = "[", h = "[";
    int start = (histHead - histCount + HIST) % HIST;
    for (int i = 0; i < histCount; i++) {
      int k = (start + i) % HIST;
      if (i) { t += ','; h += ','; }
      t += String(histT[k] / 10.0, 1); h += String(histH[k] / 10.0, 1);
    }
    server.send(200, "application/json", "{\"t\":" + t + "],\"h\":" + h + "]}");
  });
  server.on("/api/offset", HTTP_POST, []() {
    tOff = constrain(server.arg("t").toFloat(), -10.0f, 10.0f);
    hOff = constrain(server.arg("h").toFloat(), -30.0f, 30.0f);
    prefs.putFloat("to", tOff); prefs.putFloat("ho", hOff);
    server.send(200, "text/plain", "ok");
  });
  server.on("/api/wifi", HTTP_POST, []() {
    prefs.putString("ssid", server.arg("ssid")); prefs.putString("pass", server.arg("pass"));
    server.send(200, "text/plain", "ok"); delay(500); ESP.restart();
  });
  server.onNotFound([]() { server.sendHeader("Location", "/"); server.send(302); });
  server.begin();
}

void setup() {
  Serial.begin(115200);
  prefs.begin("nhietdo", false);
  tOff = prefs.getFloat("to", 0); hOff = prefs.getFloat("ho", 0);
  dht.begin();
  Wire.begin(PIN_SDA, PIN_SCL);
  oledOk = oled.begin(SSD1306_SWITCHCAPVCC, 0x3C) || oled.begin(SSD1306_SWITCHCAPVCC, 0x3D);
  if (!oledOk) Serial.println("Khong tim thay OLED (0x3C/0x3D)");
  else { oled.clearDisplay(); oled.setTextColor(SSD1306_WHITE); oled.setCursor(0, 0); oled.println("Mach Gon"); oled.println("Dang khoi dong..."); oled.display(); }
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(AP_SSID, "machgon123");
  String ssid = prefs.getString("ssid", "");
  if (ssid.length()) {
    WiFi.begin(ssid.c_str(), prefs.getString("pass", "").c_str());
    uint32_t t = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - t < 15000) delay(250);
  }
  if (MDNS.begin(HOSTNAME)) MDNS.addService("http", "tcp", 80);
  setupWeb();
  delay(2000);
  readSensor(); drawOled();
}

void loop() {
  server.handleClient();
  if (millis() - lastRead >= READ_MS) { lastRead = millis(); readSensor(); drawOled(); }
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > 30000) {
    lastCheck = millis();
    if (prefs.getString("ssid", "").length() && WiFi.status() != WL_CONNECTED) WiFi.reconnect();
  }
}

🔌 Cắm thử trên breadboard

Cắm nháp trên board cắm trước cho chắc, chạy đúng rồi hãy làm mạch in. Sơ đồ này do máy tự xếp từ chính netlist ở trên và đã đối chiếu lại: hai chân cùng một cột (cùng nửa board) là thông nhau, không có net nào bị chập.

+−+−551010151520202525JIHGFEDCBAU1 · ESP32 DevKit V1 (30 chân)222116GND3V3U2U3
  • Mỗi cột 5 lỗ trong cùng một nửa board thông nhau; hai nửa cách nhau bởi rãnh giữa.
  • Hai đường kẻ dọc mép board là đường nguồn: hàng + (vạch đỏ) và hàng − (vạch xanh). Nhớ bắc dây nối đường nguồn trên với đường nguồn dưới nếu dùng cả hai nửa.
  • Màu dây trên hình trùng với màu trong bảng nối dây ở mục Sơ đồ, cắm theo màu cho dễ dò.

🧲 Mạch in để ủi (PCB 1 mặt)

Bản mạch in một mặt đồng, tự xếp linh kiện và tự đi dây, đã kiểm quy tắc khoảng cách và đối chiếu đúng 100% với sơ đồ nguyên lý ở trên. In tỉ lệ 1:1 rồi ủi lên tấm đồng.

Kích thước board55,9×55,9 mm37 lỗ khoan · mũi 1mm
Đi dây 1 mặt6 đoạn · 116,8 mm · 1 cầu nhảyCầu nhảy = đoạn dây hàn ở mặt linh kiện
Kiểm tra bản mạchĐạt DRC · khớp 100% netlistĐường ≥0,8mm, khe ≥0,5mm; mọi net đối chiếu lại từ hình học thật
Mặt đồng — đã lật gương sẵn để in rồi ủi úp xuống tấm đồng
Mặt linh kiện — vị trí, chiều cắm và tên từng chân
U1U2U3EN363934353233252627141213GNDVIN232213211918517164215GND23V3VCCDATAGNDGNDVCCSCLSDA
Cầu nhảy cần hàn (dây đơn ở mặt linh kiện):
  • net DHT_DATA: U1.GPIO16 → U2.DATA
Hướng dẫn ủi – ăn mòn – khoan – hàn (đầy đủ)

Hướng dẫn làm mạch in bằng phương pháp ủi

Mạch: Trạm đo nhiệt độ – độ ẩm có màn hình OLED và biểu đồ 24h trên điện thoại

Kích thước board: 55,9×55,9 mm · 37 lỗ khoan (mũi 1 mm) · 6 đoạn mạch, tổng 116,8 mm · 1 cầu nhảy.

Quy tắc đã kiểm: đường mạch rộng 0,8mm (nguồn và GND 1mm), khe cách tối thiểu 0,5mm. Mạch in đã được máy đối chiếu đúng 100% với sơ đồ nguyên lý.

1. In đúng tỉ lệ 1:1

  • In trang "MẶT ĐỒNG" bằng máy in LASER, đặt tỉ lệ 100%, KHÔNG chọn "Fit to page" hay "Scale to fit".
  • Dùng thước trên bản in đo lại đoạn 100mm: phải đúng 100mm mới dùng được.
  • In trên giấy in ảnh bóng, giấy decal bóc, hoặc giấy tạp chí láng. Mực càng dày càng tốt.
  • Trang "MẶT ĐỒNG" đã được LẬT GƯƠNG sẵn: úp mặt có mực vào tấm đồng là đúng chiều.

2. Chuẩn bị tấm đồng

  • Cắt phíp đồng (board đồng một mặt) lớn hơn kích thước mạch mỗi bề khoảng 5mm.
  • Chà mặt đồng bằng giấy nhám mịn (P800–P1200) rồi rửa sạch, lau khô. Mặt đồng phải sáng, không vết dầu mỡ, không chạm tay vào.

3. Ủi mực sang đồng

  • Úp mặt có mực vào tấm đồng, dùng băng keo giấy dán cố định một cạnh.
  • Bàn ủi để mức nóng nhất (cotton/linen), KHÔNG dùng chế độ phun hơi nước.
  • Ủi đè mạnh và đều 3–5 phút, tập trung từng vùng nhỏ, chú ý các góc và mép.
  • Để nguội hẳn rồi ngâm cả tấm vào nước ấm 5–10 phút, bóc giấy thật nhẹ. Giấy decal thì bóc khi còn ấm.
  • Soi kỹ: chỗ nào mực bị thiếu hoặc đứt đường thì tô lại bằng bút lông dầu (bút CD) rồi để khô.

4. Ăn mòn (etching)

  • Pha dung dịch sắt(III) clorua FeCl3 theo hướng dẫn trên bao bì, hoặc dung dịch oxy già + muối ăn + a-xít; dùng hộp nhựa, KHÔNG dùng đồ kim loại.
  • Nhiệt độ dung dịch khoảng 40–50°C thì ăn nhanh hơn. Lắc nhẹ hộp cho đều, thường 10–25 phút là xong.
  • An toàn: đeo găng tay cao su và kính bảo hộ, làm ở nơi thoáng. Dung dịch ăn mòn làm hỏng quần áo, bồn rửa và gây bỏng rát da.
  • Xong thì rửa board bằng nước sạch, lau khô, dùng xăng thơm hoặc axeton lau sạch lớp mực.
  • Dung dịch đã dùng KHÔNG đổ xuống bồn rửa hay cống; đựng vào can kín và bỏ đúng nơi thu gom chất thải.

5. Khoan lỗ

  • Đối chiếu trang "BẢN VẼ LỖ KHOAN" để biết lỗ nào dùng mũi nào.
  • Dùng mũi khoan 1mm, khoan từ MẶT ĐỒNG vào để mũi tự định tâm giữa pad.
  • Khoan tốc độ cao, đẩy nhẹ; mũi nhỏ rất dễ gãy. Khoan xong chà nhẹ bavia hai mặt.

6. Hàn linh kiện

  • Linh kiện cắm ở MẶT KHÔNG CÓ ĐỒNG, hàn ở mặt đồng. Theo trang "MẶT LINH KIỆN" để đặt đúng vị trí và đúng chiều.
  • Hàn linh kiện thấp trước (điện trở, đi-ốt), cao sau (tụ, hàng chân, board vi điều khiển).
  • Chú ý chiều: LED chân dài là (+); đi-ốt có vạch trắng là K; tụ hoá có vạch là (−); transistor theo mặt chữ như nét vẽ đỏ.
  • Mạch này còn 1 CẦU NHẢY: hàn đoạn dây đơn ở mặt linh kiện theo nét đỏ gạch trên bản vẽ:
  • cầu nhảy net DHT_DATA: từ U1.GPIO16 sang U2.DATA
  • Trước khi cấp điện: đo thông mạch giữa chân nguồn và GND — KHÔNG được kêu (không được chập).

Lưu ý với board vi điều khiển

  • Board nhái có thể khác thứ tự chân. Đối chiếu tên chân in cạnh từng lỗ trên trang "MẶT LINH KIỆN" với chữ in trên board thật TRƯỚC KHI ăn mòn. Nếu khác, đừng hàn mà báo lại để sửa bản vẽ.
Bản vẽ lỗ khoan
BẢN VẼ LỖ KHOAN (nhìn từ mặt linh kiện)mũi khoan 1mm — 37 lỗ

Đo kiểm bằng đồng hồ vạn năng

Giữa VCC và GND của DHT22 và của OLED3,2 – 3,3V
Giữa DATA (OUT) của DHT22 và GND lúc nghỉkhoảng 3,3V
Giữa SDA/SCL và GND lúc nghỉkhoảng 3,3V
Màn OLED sau khi nạp codeHiện nhiệt độ, độ ẩm và địa chỉ IP

Sửa lỗi thường gặp

Màn OLED tối đen

Kiểm tra SDA/SCL có bị đảo không; mở Serial Monitor 115200 xem log "Khong tim thay OLED". Code tự thử cả địa chỉ 0x3C và 0x3D.

Hiện "Lỗi cảm biến"

Kiểm tra dây OUT vào GPIO16, chân + và −; nếu dùng DHT22 trần 4 chân phải có trở 10kΩ kéo lên 3V3.

Nhiệt độ cao hơn thực tế 1–3°C

Cảm biến đang đặt sát ESP32 hoặc trong hộp kín; đưa ra xa, khoan lỗ thoáng hộp. Có thể chỉnh bù sai số trong mục Cài đặt.

Mất biểu đồ sau khi rút điện

Biểu đồ lưu trong RAM để bảo vệ bộ nhớ flash, mất điện sẽ đo lại từ đầu.

⚠️ An toàn

  • Mạch chạy 5V từ USB, an toàn.
  • Không đặt cảm biến ngoài trời mưa nếu không có hộp che có lỗ thoáng.