Báo động nhiệt độ tăng bất thường cho bếp, kho, tủ điện

Theo dõi nhiệt độ liên tục bằng cảm biến DS18B20 chống nước; vượt ngưỡng cài đặt hoặc tăng quá nhanh là hú còi, nháy đèn đỏ và hiện cảnh báo trên web điện thoại. Xem được nhiệt độ hiện tại, mức cao nhất trong ngày và nhật ký lần báo gần nhất theo giờ Việt Nam.

⏱ 95 phút🔧 Biết hàn💰 Linh kiện ~142.400đ🧠 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 SPICE3/3 kịch bản · 22 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 22 phép đo

Đang báo động (còi hú, đèn đỏ sáng)

  • Q1: dẫn 29.2mA, Vce=0.04V (bão hòa tốt).
  • D1: dòng 5.9mA (cho phép 20.0mA).
  • Chân GPIO18 cấp 2.4mA (an toàn ≤ 20.0mA).
  • Chân GPIO25 cấp 5.9mA (an toàn ≤ 20.0mA).
  • Tổng dòng đỉnh từ USB 289.1mA (đủ với cục sạc 5V 1A).
  • Dòng qua còi: 29.2mA ✓
  • Áp trên transistor khi dẫn (càng nhỏ càng tốt): 0.04V ✓
  • Dòng chân GPIO18 mở transistor: 2.4mA ✓
  • Dòng LED đỏ: 5.9mA ✓
  • Áp trở kéo lên của cảm biến (phải là 3,3V): 3.30V ✓
  • Tổng dòng từ cổng USB: 289.1mA ✓

Bình thường (đèn xanh sáng, còi im)

  • D2: dòng 4.9mA (cho phép 20.0mA).
  • Chân GPIO26 cấp 4.9mA (an toàn ≤ 20.0mA).
  • Tổng dòng đỉnh từ USB 256.4mA (đủ với cục sạc 5V 1A).
  • Còi phải im: 0.0mA ✓
  • Dòng LED xanh: 4.9mA ✓
  • LED đỏ phải tắt: 0.0mA ✓

Đang bấm nút tắt tiếng lúc báo động

  • D1: dòng 5.9mA (cho phép 20.0mA).
  • Chân GPIO25 cấp 5.9mA (an toàn ≤ 20.0mA).
  • Tổng dòng đỉnh từ USB 257.4mA (đủ với cục sạc 5V 1A).
  • Áp chân nút khi nhấn: 0.00V ✓
  • Còi đã tắt nhưng đèn đỏ vẫn sáng: 0.0mA ✓
Kiểm lần cuối: 08:49:19 13/11/2026

Nguyên lý hoạt động

DS18B20 là cảm biến nhiệt độ số đo từ −55 đến 125°C với sai số 0,5°C, trả kết quả qua một dây dữ liệu duy nhất. Dây đó phải có điện trở 4,7kΩ kéo lên nguồn thì cảm biến mới trả lời được — ở đây kéo lên 3,3V chứ không phải 5V, vì chân ESP32 chỉ chịu 3,3V. ESP32 đọc nhiệt độ mỗi giây và xét hai điều kiện: vượt ngưỡng tuyệt đối (mặc định 60°C) hoặc tăng nhanh hơn ngưỡng tốc độ (mặc định 6°C trong 1 phút) — kiểu tăng nhanh thường xuất hiện sớm hơn khi có đám cháy bắt đầu. Khi báo động, GPIO18 mở transistor S8050 cho còi 5V kêu ngắt quãng (còi ăn khoảng 30mA, chân ESP32 chỉ cấp khoảng 2,5mA qua điện trở 1kΩ) và GPIO25 nháy đèn đỏ; lúc yên bình thì GPIO26 giữ đèn xanh sáng. Nút ở GPIO27 tắt tiếng còi trong 5 phút mà vẫn giữ đèn đỏ. ESP32 phát WiFi riêng và chạy trang web tiếng Việt để xem nhiệt độ, đổi ngưỡng và nhập WiFi nhà cho đúng giờ.

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.

⚡ Mô phỏng tương tácBấm thử — mạch được giải ngay trên máy bạn, điện áp và dòng hiện lên sơ đồ bên dưới.
Tình huống mẫu
Chân GPIO18điều khiển R2
Chân GPIO25điều khiển R3
Chân GPIO26điều khiển R4
Nút SW1

Đây là mô phỏng một chiều (DC) ở trạng thái tĩnh: dùng để kiểm tra mức áp, dòng và công suất. Hiệu ứng nhấp nháy, PWM hay giao tiếp số do code quyết định nên không chạy ở đây.

5V5V3V33V3GNDGNDDQDQBZ_DRVBZ_DRVBZ_BASEBZ_BASEBZ_NEGBZ_NEGRED_DRVRED_DRVRED_ARED_AGRN_DRVGRN_DRVGRN_AGRN_ABTNBTNU1 · ESP32 DevKit V13V3VINGPIO4 (D4)GPIO18 (D18)GPIO25 (D25)GPIO26 (D26)GPIO27 (D27)GNDU2 · Cảm biến nhiệt độ DS18B20VDD · dây đỏDQ · dây vàngGND · dây đenR1 · Điện trở 4.7kΩ12R2 · Điện trở 1kΩ12Q1 · Transistor NPN S8050B (gốc)C (góp)E (phát)BZ1 · Còi chip chủ động 5V-+R3 · Điện trở 220Ω12D1 · Đèn LED 5mmA · chân dài (+)K · chân ngắn (−)R4 · Điện trở 220Ω12D2 · Đèn LED 5mmA · chân dài (+)K · chân ngắn (−)SW1 · Nút nhấn12
#DâyNối các điểmÁp mô phỏng
1đỏ
5V
U1 VIN ⟷ BZ1 +—
2cam
3V3
U1 3V3 ⟷ U2 VDD · dây đỏ ⟷ R1 2—
3đen
GND
U1 GND ⟷ U2 GND · dây đen ⟷ Q1 E (phát) ⟷ D1 K · chân ngắn (−) ⟷ D2 K · chân ngắn (−) ⟷ SW1 2—
4vàng
DQ
U2 DQ · dây vàng ⟷ R1 1 ⟷ U1 GPIO4 (D4)—
5xanh lá
BZ_DRV
U1 GPIO18 (D18) ⟷ R2 1—
6xanh dương
BZ_BASE
R2 2 ⟷ Q1 B (gốc)—
7tím
BZ_NEG
BZ1 - ⟷ Q1 C (góp)—
8xanh ngọc
RED_DRV
U1 GPIO25 (D25) ⟷ R3 1—
9nâu
RED_A
R3 2 ⟷ D1 A · chân dài (+)—
10hồng
GRN_DRV
U1 GPIO26 (D26) ⟷ R4 1—
11xám
GRN_A
R4 2 ⟷ D2 A · chân dài (+)—
12xanh rêu
BTN
SW1 1 ⟷ U1 GPIO27 (D27)—

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
U2Cảm biến nhiệt độ DS18B20 dạng đầu dò chống nước, dây 1m25.000đTìm mua
R1Điện trở 4,7kΩ 1/4W (vàng-tím-đỏ) (4.7k)200đTìm mua
Q1Transistor S8050 (TO-92)500đTìm mua
R2Điện trở 1kΩ 1/4W (nâu-đen-đỏ)200đTìm mua
BZ1Còi chip chủ động 5V (loại to, kêu lớn)4.000đTìm mua
D1LED đỏ 5mm (red)300đTìm mua
R3Điện trở 220Ω 1/4W (đỏ-đỏ-nâu)200đTìm mua
D2LED xanh lá 5mm (green)300đTìm mua
R4Điện trở 220Ω 1/4W (đỏ-đỏ-nâu)200đTìm mua
SW1Nút nhấn 4 chân 6×6mm1.500đTìm mua
Tổng (chưa gồm board hàn, dây, thiếc)142.400đ

Các bước làm

Code nạp cho mạch

Cài thư viện: OneWire, DallasTemperature (Arduino IDE → Library Manager). Chọn board DOIT ESP32 DEVKIT V1.

/*
  Mạch Gọn — Báo động nhiệt độ tăng bất thường (ESP32 + DS18B20)
  Board : ESP32 DevKit V1 ("DOIT ESP32 DEVKIT V1")
  Thư viện: OneWire, DallasTemperature
  Chân  : GPIO4 <- DQ của DS18B20 (trở 4,7kΩ kéo lên 3V3)
          GPIO18 -> 1kΩ -> B của S8050 (còi 5V ở cực C)
          GPIO25 -> 220Ω -> LED đỏ ; GPIO26 -> 220Ω -> LED xanh
          GPIO27 nút tắt tiếng, nối GND (INPUT_PULLUP)
  Lần đầu: WiFi "MachGon-BaoNhiet" (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 <OneWire.h>
#include <DallasTemperature.h>
#include <time.h>

const uint8_t PIN_DS = 4, PIN_BUZ = 18, PIN_RED = 25, PIN_GRN = 26, PIN_NUT = 27;
const char* AP_SSID  = "MachGon-BaoNhiet";
const char* HOSTNAME = "baonhiet";
const uint32_t DOC_MS   = 1000;          // nhịp đọc cảm biến
const uint32_t TAT_TIENG_MS = 5UL * 60UL * 1000UL;  // tắt tiếng 5 phút
const uint8_t  LOG_MAX = 20;

OneWire ow(PIN_DS);
DallasTemperature ds(&ow);
WebServer server(80);
Preferences cfg;

float nhiet = NAN, nhietMax = -999;
float nguongC = 60.0;          // ngưỡng tuyệt đối
float nguongToc = 6.0;         // °C tăng trong 60 giây thì coi là bất thường
bool  baoDong = false, tatTieng = false, camBienOk = false;
uint32_t mocDoc = 0, mocTatTieng = 0, mocMau = 0;
float nhietMauTruoc = NAN;
uint8_t soLanLoi = 0;

struct LogItem { time_t t; uint32_t ms; float nhiet; bool toc; };
LogItem logs[LOG_MAX];
uint8_t logHead = 0, logCount = 0;

bool gioOk() { return time(nullptr) > 1700000000; }

void ghiLog(float t, bool theoToc) {
  logs[logHead] = { gioOk() ? time(nullptr) : 0, millis(), t, theoToc };
  logHead = (logHead + 1) % LOG_MAX;
  if (logCount < LOG_MAX) logCount++;
}

String dinhDangLog(const LogItem& l) {
  char b[48];
  if (l.t) { struct tm tmv; localtime_r(&l.t, &tmv); strftime(b, sizeof b, "%d/%m %H:%M:%S", &tmv); }
  else snprintf(b, sizeof b, "+%lus sau khi bật", (unsigned long)(l.ms / 1000));
  return String(b);
}

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>Báo nhiệt</title>
<style>
body{margin:0;font-family:system-ui,sans-serif;background:#0f172a;color:#e2e8f0}
header{padding:14px;background:#1e293b;font-weight:700}
.card{margin:14px;padding:14px;background:#1e293b;border-radius:14px}
.t{font-size:46px;font-weight:800;margin:4px 0}
.st{font-size:18px;margin-bottom:8px}.muted{color:#94a3b8;font-size:13px}
.alarm{color:#fca5a5}.ok{color:#86efac}
.btn{background:#334155;color:#e2e8f0;border:0;border-radius:10px;padding:12px 14px;margin-top:8px;width:100%;font-size:16px}
.mute{background:#dc2626;color:#fff;font-weight:700}
ul{list-style:none;padding:0;margin:0}li{padding:8px 0;border-bottom:1px solid #334155;font-size:14px}
input{width:100%;padding:9px;margin:4px 0 8px;border-radius:8px;border:1px solid #475569;background:#0b1220;color:#e2e8f0;box-sizing:border-box}
</style></head><body>
<header>🌡️ Báo động nhiệt độ</header>
<div class="card"><div class="st" id="st">…</div><div class="t" id="nh">--</div>
<p class="muted" id="info"></p>
<button class="btn mute" id="mute">🔕 Tắt tiếng 5 phút</button></div>
<div class="card"><b>Nhật ký báo động</b><ul id="log"></ul></div>
<div class="card"><b>Cài đặt</b>
<label class="muted">Ngưỡng nhiệt độ (°C)</label><input id="ng" type="number" min="30" max="110" step="1">
<label class="muted">Tăng nhanh bao nhiêu °C trong 1 phút thì báo</label><input id="toc" type="number" min="2" max="40" step="1">
<button class="btn" onclick="luu()">Lưu</button><hr style="border-color:#334155">
<label class="muted">WiFi nhà (để có giờ chính xác)</label><input id="ssid" placeholder="Tên WiFi"><input id="pass" type="password" placeholder="Mật khẩu">
<button class="btn" onclick="wifi()">Lưu WiFi & khởi động lại</button></div>
<script>
const $=s=>document.querySelector(s);let S={};
async function load(){const r=await fetch('/api/state');S=await r.json();
 $('#nh').textContent=S.ok?S.nhiet.toFixed(1)+' °C':'--';
 $('#nh').className='t '+(S.baoDong?'alarm':'ok');
 $('#st').textContent=S.baoDong?'🚨 NHIỆT ĐỘ BẤT THƯỜNG':(S.ok?'✅ Bình thường':'⚠️ Không đọc được cảm biến');
 $('#info').textContent='Cao nhất: '+(S.max>-900?S.max.toFixed(1)+'°C':'--')+' · '+(S.tatTieng?'đang tắt tiếng':'còi sẵn sàng')+' · '+(S.sta?'WiFi nhà '+S.ip+' — http://baonhiet.local':'chưa nối WiFi nhà');
 $('#log').innerHTML=S.log.map(l=>`<li class="alarm">🚨 ${l.t} — ${l.nhiet.toFixed(1)}°C ${l.toc?'(tăng nhanh)':'(vượt ngưỡng)'}</li>`).join('')||'<li class="muted">Chưa có lần nào</li>';
 if(document.activeElement.tagName!='INPUT'){$('#ng').value=S.nguong;$('#toc').value=S.toc}}
$('#mute').onclick=async()=>{await fetch('/api/mute',{method:'POST'});load()};
async function luu(){await fetch('/api/opt?ng='+$('#ng').value+'&toc='+$('#toc').value,{method:'POST'});load()}
async function wifi(){await fetch('/api/wifi',{method:'POST',body:new URLSearchParams({ssid:$('#ssid').value,pass:$('#pass').value})});alert('Đã lưu, mạch khởi động lại')}
load();setInterval(load,2000);
</script></body></html>)HTML";

void handleState() {
  bool sta = WiFi.status() == WL_CONNECTED;
  String j = "{\"ok\":" + String(camBienOk ? "true" : "false") +
             ",\"nhiet\":" + String(isnan(nhiet) ? 0 : nhiet, 2) +
             ",\"max\":" + String(nhietMax, 2) +
             ",\"baoDong\":" + (baoDong ? "true" : "false") +
             ",\"tatTieng\":" + (tatTieng ? "true" : "false") +
             ",\"nguong\":" + String(nguongC, 0) + ",\"toc\":" + String(nguongToc, 0) +
             ",\"sta\":" + (sta ? "true" : "false") +
             ",\"ip\":\"" + (sta ? WiFi.localIP().toString() : WiFi.softAPIP().toString()) + "\",\"log\":[";
  for (uint8_t i = 0; i < logCount; i++) {
    const LogItem& l = logs[(logHead + LOG_MAX - 1 - i) % LOG_MAX];
    if (i) j += ',';
    j += "{\"t\":\"" + dinhDangLog(l) + "\",\"nhiet\":" + String(l.nhiet, 2) + ",\"toc\":" + (l.toc ? "true" : "false") + "}";
  }
  j += "]}";
  server.send(200, "application/json", j);
}

void setupWeb() {
  server.on("/", []() { server.send_P(200, "text/html; charset=utf-8", PAGE); });
  server.on("/api/state", HTTP_GET, handleState);
  server.on("/api/mute", HTTP_POST, []() {
    tatTieng = true; mocTatTieng = millis();
    digitalWrite(PIN_BUZ, LOW);
    server.send(200, "text/plain", "ok");
  });
  server.on("/api/opt", HTTP_POST, []() {
    nguongC = constrain(server.arg("ng").toFloat(), 30, 110);
    nguongToc = constrain(server.arg("toc").toFloat(), 2, 40);
    cfg.putFloat("nguong", nguongC);
    cfg.putFloat("toc", nguongToc);
    server.send(200, "text/plain", "ok");
  });
  server.on("/api/wifi", HTTP_POST, []() {
    cfg.putString("ssid", server.arg("ssid"));
    cfg.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);
  pinMode(PIN_BUZ, OUTPUT); digitalWrite(PIN_BUZ, LOW);
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_GRN, OUTPUT);
  pinMode(PIN_NUT, INPUT_PULLUP);

  cfg.begin("cfg", false);
  nguongC = cfg.getFloat("nguong", 60.0);
  nguongToc = cfg.getFloat("toc", 6.0);

  ds.begin();
  ds.setResolution(11);
  ds.setWaitForConversion(false);
  ds.requestTemperatures();

  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(AP_SSID, cfg.getString("appass", "machgon123").c_str());
  String ssid = cfg.getString("ssid", "");
  if (ssid.length()) {
    WiFi.begin(ssid.c_str(), cfg.getString("pass", "").c_str());
    uint32_t t = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - t < 15000) delay(250);
  }
  configTime(7 * 3600, 0, "pool.ntp.org", "time.google.com");   // giờ Việt Nam UTC+7
  if (MDNS.begin(HOSTNAME)) MDNS.addService("http", "tcp", 80);
  setupWeb();
  Serial.printf("San sang. AP %s IP %s\n", AP_SSID, WiFi.softAPIP().toString().c_str());
}

void loop() {
  server.handleClient();
  uint32_t now = millis();

  // 1. Đọc nhiệt độ
  if (now - mocDoc >= DOC_MS) {
    mocDoc = now;
    float t = ds.getTempCByIndex(0);
    ds.requestTemperatures();
    if (t <= -100 || t >= 120) {
      if (++soLanLoi >= 3) { camBienOk = false; nhiet = NAN; }
    } else {
      soLanLoi = 0;
      camBienOk = true;
      nhiet = t;
      if (t > nhietMax) nhietMax = t;
    }

    // 2. Xét báo động: vượt ngưỡng hoặc tăng quá nhanh
    bool vuotNguong = camBienOk && nhiet >= nguongC;
    bool tangNhanh = false;
    if (now - mocMau >= 60000) {
      if (camBienOk && !isnan(nhietMauTruoc) && nhiet - nhietMauTruoc >= nguongToc) tangNhanh = true;
      nhietMauTruoc = nhiet;
      mocMau = now;
    }
    bool canBao = vuotNguong || tangNhanh;
    if (canBao && !baoDong) {
      ghiLog(nhiet, tangNhanh && !vuotNguong);
      tatTieng = false;
      Serial.printf("BAO DONG %.1fC (%s)\n", nhiet, tangNhanh ? "tang nhanh" : "vuot nguong");
    }
    if (!canBao && baoDong) Serial.println(F("Da ve binh thuong"));
    baoDong = canBao;

    Serial.printf("T=%.2fC max=%.2f bao=%d\n", nhiet, nhietMax, baoDong ? 1 : 0);
  }

  // 3. Nút tắt tiếng
  if (digitalRead(PIN_NUT) == LOW && baoDong && !tatTieng) {
    tatTieng = true;
    mocTatTieng = now;
    digitalWrite(PIN_BUZ, LOW);
    Serial.println(F("Da tat tieng 5 phut"));
  }
  if (tatTieng && now - mocTatTieng >= TAT_TIENG_MS) tatTieng = false;

  // 4. Còi và đèn
  if (baoDong && !tatTieng) digitalWrite(PIN_BUZ, (now / 400) % 2);
  else digitalWrite(PIN_BUZ, LOW);
  digitalWrite(PIN_RED, baoDong ? ((now / 200) % 2) : LOW);
  digitalWrite(PIN_GRN, (!baoDong && camBienOk) ? HIGH : ((now % 2000) < 60));

  // 5. Nối lại WiFi nhà nếu rớt
  static uint32_t mocKiemTra = 0;
  if (now - mocKiemTra > 30000) {
    mocKiemTra = now;
    if (cfg.getString("ssid", "").length() && WiFi.status() != WL_CONNECTED) WiFi.reconnect();
  }
  delay(5);
}

🔌 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.

+−+−551010151520202525303035354040JIHGFEDCBAU1 · ESP32 DevKit V1 (30 chân)182526274GNDVIN3V3U2R1 4.7kQ1 S8050R2 1kBZ1+D1 redR3 220D2 greenR4 220SW1
  • 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 board68,6×66,0 mm54 lỗ khoan · mũi 0,8 / 0,9 / 1mm
Đi dây 1 mặt22 đoạn · 519,4 mmKhông cần cầu nhảy nào
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
U1U2R1Q1R2BZ1D1R3D2R4SW1EN363934353233252627141213GNDVIN232213211918517164215GND23V3VDDDQGND
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: Báo động nhiệt độ tăng bất thường cho bếp, kho, tủ điện

Kích thước board: 68,6×66,0 mm · 54 lỗ khoan (mũi 0,8, 0,9 và 1 mm) · 22 đoạn mạch, tổng 519,4 mm · 0 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 0,8mm, 0,9mm và 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ẽ đỏ.
  • 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 0,8mm — 11 lỗmũi khoan 0,9mm — 10 lỗmũi khoan 1mm — 33 lỗ

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

Giữa 3V3 và GND3,2 – 3,4V
Giữa GPIO4 và GND lúc nghỉkhoảng 3,3V (nhờ trở kéo lên)
Giữa C và E của S8050 khi còi imkhoảng 3V trở lên
Giữa C và E khi còi đang kêudưới 0,4V
Trang webNhiệt độ hiện tại chênh không quá 1°C so với nhiệt kế thường

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

Nhiệt độ hiện −127°C

Thiếu hoặc hỏng điện trở kéo lên 4,7kΩ giữa dây dữ liệu và 3V3, hoặc dây vàng chưa vào GPIO4. Đo điện trở giữa GPIO4 và 3V3 phải khoảng 4,7kΩ.

Nhiệt độ hiện 85°C đứng yên

Đó là giá trị mặc định khi cảm biến chưa hoàn tất lần đo đầu: thường do nguồn yếu hoặc dây quá dài. Rút ngắn dây dưới 3m, dùng cục sạc 5V ≥1A.

Còi kêu dù không nóng

Ngưỡng tốc độ tăng đang quá nhạy khi mang cảm biến từ phòng lạnh ra. Tăng hằng số tốc độ trên trang Cài đặt hoặc chờ 2 phút cho ổn định.

Không thấy WiFi MachGon-BaoNhiet

ESP32 chưa chạy: xem Serial Monitor 115200. Nếu dùng cổng USB máy tính mà mạch khởi động lại liên tục thì đổi sang cục sạc 5V ≥1A.

Còi kêu nhỏ

Phải là còi chủ động 5V (cấp 5V trực tiếp là kêu ngay). Còi thụ động cần xung nên sẽ rất nhỏ hoặc im.

⚠️ An toàn

  • Mạch này là thiết bị tham khảo, KHÔNG thay thế đầu báo cháy, báo khói đạt chuẩn phòng cháy chữa cháy. Nhà xưởng, kho hàng vẫn phải lắp thiết bị có chứng nhận.
  • Không đặt mạch, dây hay cảm biến tiếp xúc trực tiếp với lửa, bếp đang nấu hay bề mặt trên 100°C — vỏ dây chỉ chịu được khoảng 85°C.
  • Mạch chạy 5V USB, an toàn khi chạm; nên cắm vào cục sạc dự phòng để còn hoạt động khi mất điện.
  • Không dùng mạch này để canh thiết bị bắt buộc phải có người trông coi.