Nhiệt kế bể cá có còi báo nước quá nóng/lạnh (Arduino Nano + DS18B20)

Đo nhiệt độ nước bằng đầu dò DS18B20 chống nước (sai số ±0,5°C), hiện lên màn LCD 16×2. Còi kêu và đèn đỏ nháy khi nước vượt ngưỡng cao/thấp; chỉnh ngưỡng bằng 2 nút nhấn, tự nhớ sau khi mất điện. Dùng cho bể cá, bể ươm, bồn nước nóng, thùng ủ.

⏱ 90 phút🔧 Biết hàn💰 Linh kiện ~170.400đ🧠 Arduino Nano⚡ 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 SPICE2/2 kịch bản · 12 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 48% bộ nhớarduino:avr:nano
Xem chi tiết 12 phép đo

Đang báo động (còi + đèn bật, 2 nút đang nhấn)

  • Q1: dẫn 29.2mA, Vce=0.03V (bão hòa tốt).
  • LED1: dòng 9.0mA (cho phép 20.0mA).
  • Chân D8 cấp 4.1mA (an toàn ≤ 20.0mA).
  • Chân D9 cấp 9.0mA (an toàn ≤ 20.0mA).
  • Tổng dòng đỉnh từ USB 103.8mA (đủ với cục sạc 5V 1A).
  • Dòng chạy qua còi: 29.2mA ✓
  • Dòng qua LED đỏ: 9.0mA ✓
  • Chân D3 khi nhấn nút CHỌN: 0.00V ✓
  • Áp đường tín hiệu DS18B20 (trở kéo lên): 5.00V ✓

Bình thường (còi tắt, không nhấn nút)

  • Tổng dòng đỉnh từ USB 61.5mA (đủ với cục sạc 5V 1A).
  • Còi im: 0.0mA ✓
  • Chân D3 khi thả nút (trở kéo lên trong chip): 4.98V ✓
Kiểm lần cuối: 17:06:11 12/11/2026

Nguyên lý hoạt động

DS18B20 truyền nhiệt độ số qua 1 dây (dây vàng) nên dây dài 1–3m vẫn chính xác; trở 4,7kΩ kéo dây này lên 5V là bắt buộc. Arduino đọc mỗi giây, hiện lên LCD qua 2 dây I2C (A4, A5). Khi nhiệt độ vượt ngưỡng, chân D8 mở transistor S8050 cho còi ăn ~30mA (chân Arduino chỉ cấp ~4mA), LED đỏ nháy qua trở 330Ω (~9mA). Ngưỡng lưu trong EEPROM nên mất điện không mất cài đặt.

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 D8điều khiển R2
Chân D9điều khiển R3
Nút SW1
Nút SW2

Đâ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.

5V5VGNDGNDDQDQSDASDASCLSCLBUZ_DRVBUZ_DRVBUZ_BASEBUZ_BASEBUZ_NEGBUZ_NEGLED_DRVLED_DRVLED_ALED_ABTN_SELBTN_SELBTN_ADJBTN_ADJU1 · Arduino Nano5VA4A5D2D3D4D8D9GNDU3 · Màn hình LCD 1602 kèm mạch I2CVCCSCLSDAGNDU2 · Cảm biến nhiệt độ DS18B20VDD · dây đỏDQ · dây vàngGND · dây đenR1 · Điện trở 4.7kΩ12SW1 · Nút nhấn12SW2 · Nút nhấn12R2 · Đ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ở 330Ω12LED1 · Đèn LED 5mmA · chân dài (+)K · chân ngắn (−)
#DâyNối các điểmÁp mô phỏng
1đỏ
5V
U1 5V ⟷ U2 VDD · dây đỏ ⟷ R1 2 ⟷ U3 VCC ⟷ BZ1 +—
2đen
GND
U1 GND ⟷ U2 GND · dây đen ⟷ U3 GND ⟷ Q1 E (phát) ⟷ LED1 K · chân ngắn (−) ⟷ SW1 2 ⟷ SW2 2—
3vàng
DQ
U2 DQ · dây vàng ⟷ R1 1 ⟷ U1 D2—
4xanh lá
SDA
U3 SDA ⟷ U1 A4—
5xanh dương
SCL
U3 SCL ⟷ U1 A5—
6tím
BUZ_DRV
U1 D8 ⟷ R2 1—
7xanh ngọc
BUZ_BASE
R2 2 ⟷ Q1 B (gốc)—
8nâu
BUZ_NEG
BZ1 - ⟷ Q1 C (góp)—
9hồng
LED_DRV
U1 D9 ⟷ R3 1—
10xám
LED_A
R3 2 ⟷ LED1 A · chân dài (+)—
11xanh rêu
BTN_SEL
SW1 1 ⟷ U1 D3—
12tím nhạt
BTN_ADJ
SW2 1 ⟷ U1 D4—

Danh sách linh kiện

Ký hiệuLinh kiệnGiá tham khảo
U1Arduino Nano V3 (ATmega328P, chip nạp CH340) kèm cáp USB85.000đTìm mua
U2Cảm biến nhiệt độ DS18B20 dạng que inox chống nước, dây 1m (3 dây đỏ-vàng-đen)25.000đTìm mua
R1Điện trở 4,7kΩ 1/4W (vàng-tím-đỏ) (4.7k)200đTìm mua
U3Màn hình LCD 1602 xanh dương đã hàn sẵn mạch chuyển I2C (4 chân GND VCC SDA SCL)55.000đ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 có dán tem, chân dài là +)3.000đTìm mua
LED1LED đỏ 5mm (red)300đTìm mua
R3Điện trở 330Ω 1/4W (cam-cam-nâu)200đTìm mua
SW1Nút nhấn 4 chân 6×6mm (nút CHỌN)500đTìm mua
SW2Nút nhấn 4 chân 6×6mm (nút CHỈNH)500đTìm mua
Tổng (chưa gồm board hàn, dây, thiếc)170.400đ

Các bước làm

Code nạp cho mạch

Cài thư viện: OneWire, DallasTemperature, LiquidCrystal I2C (Arduino IDE → Library Manager). Chọn board Arduino Nano.

/*
  Mạch Gọn — Nhiệt kế bể cá có còi báo (Arduino Nano)
  Thư viện: OneWire, DallasTemperature, LiquidCrystal I2C
  Chân : D2 <- DS18B20 (trở 4,7k lên 5V) ; A4/A5 -> LCD1602 I2C
         D8 -> 1k -> S8050 -> còi ; D9 -> 330 -> LED đỏ
         D3 nút CHỌN, D4 nút CHỈNH (nối GND, dùng INPUT_PULLUP)
*/
#include <Arduino.h>
#include <Wire.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal_I2C.h>

const uint8_t PIN_DS = 2, PIN_SEL = 3, PIN_ADJ = 4, PIN_BUZ = 8, PIN_LED = 9;
const float DEF_HI = 30.0, DEF_LO = 24.0;     // ngưỡng mặc định bể cá nhiệt đới
const float LIM_MIN = 0.0, LIM_MAX = 60.0;
const uint32_t MUTE_MS = 10UL * 60UL * 1000UL;
const uint16_t EE_MAGIC = 0xB3C7;

OneWire ow(PIN_DS);
DallasTemperature ds(&ow);
LiquidCrystal_I2C* lcd = nullptr;

struct Cfg { uint16_t magic; float hi; float lo; } cfg;
float tempC = NAN;
bool sensorOk = false;
enum Mode { M_SHOW, M_HI, M_LO } mode = M_SHOW;
uint32_t convStart = 0, muteUntil = 0, lastDraw = 0;
bool converting = false;

uint8_t findLcdAddr() {
  const uint8_t addrs[] = {0x27, 0x3F};
  for (uint8_t a : addrs) { Wire.beginTransmission(a); if (Wire.endTransmission() == 0) return a; }
  return 0x27;
}

void loadCfg() {
  EEPROM.get(0, cfg);
  if (cfg.magic != EE_MAGIC || !(cfg.hi > cfg.lo) || cfg.hi > LIM_MAX || cfg.lo < LIM_MIN) {
    cfg = {EE_MAGIC, DEF_HI, DEF_LO};
    EEPROM.put(0, cfg);
  }
}

// Nút: trả về 1 = nhấn nhanh, 2 = giữ (lặp lại mỗi 300ms khi giữ)
struct Btn {
  uint8_t pin; bool last; uint32_t downAt, lastRep; bool longFired;
  explicit Btn(uint8_t p) : pin(p), last(HIGH), downAt(0), lastRep(0), longFired(false) {}
  int poll() {
    bool v = digitalRead(pin);
    uint32_t now = millis();
    int ev = 0;
    if (v == LOW && last == HIGH) { downAt = now; longFired = false; }
    if (v == LOW && now - downAt > 800 && now - lastRep > 300) { lastRep = now; longFired = true; ev = 2; }
    if (v == HIGH && last == LOW && now - downAt > 40 && !longFired) ev = 1;
    last = v;
    return ev;
  }
};
Btn bSel(PIN_SEL), bAdj(PIN_ADJ);

void draw() {
  if (!lcd) return;
  char l1[17], l2[17];
  if (mode == M_SHOW) {
    if (sensorOk) snprintf(l1, 17, "Nuoc: %s C", String(tempC, 1).c_str());
    else snprintf(l1, 17, "Loi cam bien!   ");
    bool alarm = sensorOk && (tempC > cfg.hi || tempC < cfg.lo);
    if (alarm) snprintf(l2, 17, "%s%s", tempC > cfg.hi ? "QUA NONG " : "QUA LANH ", millis() < muteUntil ? "(tat coi)" : "");
    else snprintf(l2, 17, "%s-%s C       ", String(cfg.lo, 1).c_str(), String(cfg.hi, 1).c_str());
  } else {
    snprintf(l1, 17, mode == M_HI ? "Nguong CAO:     " : "Nguong THAP:    ");
    snprintf(l2, 17, "  %s C  +/-    ", String(mode == M_HI ? cfg.hi : cfg.lo, 1).c_str());
  }
  lcd->setCursor(0, 0); lcd->print(l1); for (int i = strlen(l1); i < 16; i++) lcd->print(' ');
  lcd->setCursor(0, 1); lcd->print(l2); for (int i = strlen(l2); i < 16; i++) lcd->print(' ');
}

void readSensor() {
  uint32_t now = millis();
  if (!converting) { ds.requestTemperatures(); convStart = now; converting = true; return; }
  if (now - convStart < 800) return;   // 12-bit cần 750ms
  converting = false;
  float t = ds.getTempCByIndex(0);
  sensorOk = t != DEVICE_DISCONNECTED_C && t > -55 && t < 125 && t != 85.0;  // 85 = giá trị lúc mới cấp điện
  if (sensorOk) tempC = t;
  Serial.print("Nhiet do: "); Serial.println(sensorOk ? String(t, 2) : String("loi"));
}

void alarmOutput() {
  uint32_t now = millis();
  bool alarm = sensorOk && (tempC > cfg.hi || tempC < cfg.lo);
  bool muted = now < muteUntil;
  if (!sensorOk) {                      // lỗi cảm biến: LED sáng, bíp ngắn mỗi 10 giây
    digitalWrite(PIN_LED, HIGH);
    digitalWrite(PIN_BUZ, !muted && (now % 10000) < 80);
  } else if (alarm) {                   // vượt ngưỡng: nháy LED, bíp 2 tiếng/giây
    uint16_t ph = now % 1000;
    digitalWrite(PIN_LED, ph < 500);
    digitalWrite(PIN_BUZ, !muted && (ph < 120 || (ph > 250 && ph < 370)));
  } else {
    digitalWrite(PIN_LED, LOW);
    digitalWrite(PIN_BUZ, LOW);
  }
}

void setup() {
  pinMode(PIN_BUZ, OUTPUT); digitalWrite(PIN_BUZ, LOW);
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_SEL, INPUT_PULLUP);
  pinMode(PIN_ADJ, INPUT_PULLUP);
  Serial.begin(115200);
  loadCfg();
  Wire.begin();
  static LiquidCrystal_I2C l(findLcdAddr(), 16, 2);
  lcd = &l;
  lcd->init(); lcd->backlight();
  lcd->setCursor(0, 0); lcd->print("Mach Gon");
  lcd->setCursor(0, 1); lcd->print("Nhiet ke be ca");
  ds.begin();
  ds.setResolution(12);
  ds.setWaitForConversion(false);
  delay(800);
}

void loop() {
  readSensor();
  int s = bSel.poll(), a = bAdj.poll();
  if (s == 1) {
    if (mode == M_SHOW) mode = M_HI;
    else if (mode == M_HI) mode = M_LO;
    else { mode = M_SHOW; EEPROM.put(0, cfg); Serial.println("Da luu nguong"); }
    draw();
  }
  if (a) {
    if (mode == M_SHOW) { if (a == 1) muteUntil = millis() + MUTE_MS; }
    else {
      float step = a == 1 ? 0.5 : -0.5;
      if (mode == M_HI) cfg.hi = constrain(cfg.hi + step, cfg.lo + 0.5, LIM_MAX);
      else cfg.lo = constrain(cfg.lo + step, LIM_MIN, cfg.hi - 0.5);
    }
    draw();
  }
  alarmOutput();
  if (millis() - lastDraw > 500) { lastDraw = millis(); draw(); }
}

🔌 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 · Arduino NanoGND5VD2D3D4A5A4D8D9U2R1 4.7kU3Q1 S8050R2 1kBZ1+LED1 redR3 330SW1SW2
  • 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 board73,7×68,6 mm58 lỗ khoan · mũi 0,8 / 0,9 / 1mm
Đi dây 1 mặt29 đoạn · 558,7 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
U1U2R1U3Q1R2BZ1LED1R3SW1SW2D1D0RSTGNDD2D3D4D5D6D7D8D9D10D11D12VINGND2RST25VA7A6A5A4A3A2A1A0AREF3V3D13VDDDQGNDGNDVCCSDASCL
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: Nhiệt kế bể cá có còi báo nước quá nóng/lạnh (Arduino Nano + DS18B20)

Kích thước board: 73,7×68,6 mm · 58 lỗ khoan (mũi 0,8, 0,9 và 1 mm) · 29 đoạn mạch, tổng 558,7 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 — 9 lỗmũi khoan 0,9mm — 12 lỗmũi khoan 1mm — 37 lỗ

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

Giữa dây vàng (D2) và GND4,8 – 5,0V
Giữa VCC và GND của LCD4,6 – 5,1V
Nắm đầu dò trong tay 1 phútNhiệt độ tăng lên 30–34°C
Giữa chân C và E của S8050 khi còi kêudưới 0,2V

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

LCD báo "Loi cam bien" hoặc -127

Thiếu trở 4,7kΩ hoặc nối sai dây vàng/đỏ/đen; kiểm tra mối hàn đầu dò.

LCD sáng nhưng không có chữ

Vặn biến trở tương phản sau LCD; code tự dò địa chỉ 0x27 và 0x3F.

Còi không kêu khi vượt ngưỡng

Kiểm tra chiều còi (chân dài vào 5V) và chân E-B-C của S8050; có thể đang ở chế độ tắt còi 10 phút.

Không nạp được code

Cài driver CH340, thử Processor "ATmega328P (Old Bootloader)", đổi cáp USB có dây dữ liệu.

⚠️ An toàn

  • Mạch chạy 5V từ USB, an toàn.
  • Chỉ que inox của DS18B20 được nhúng nước; đặt Arduino và LCD ở nơi khô ráo, xa bể.