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 ủ.
✅ Đã kiểm thử
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 ✓
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.
| # | Dây | Nối các điểm |
|---|---|---|
| 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 |
| 3 | vàng DQ | U2 DQ · dây vàng ⟷ R1 1 ⟷ U1 D2 |
| 4 | xanh lá SDA | U3 SDA ⟷ U1 A4 |
| 5 | xanh dương SCL | U3 SCL ⟷ U1 A5 |
| 6 | tím BUZ_DRV | U1 D8 ⟷ R2 1 |
| 7 | xanh ngọc BUZ_BASE | R2 2 ⟷ Q1 B (gốc) |
| 8 | nâu BUZ_NEG | BZ1 - ⟷ Q1 C (góp) |
| 9 | hồng LED_DRV | U1 D9 ⟷ R3 1 |
| 10 | xám LED_A | R3 2 ⟷ LED1 A · chân dài (+) |
| 11 | xanh rêu BTN_SEL | SW1 1 ⟷ U1 D3 |
| 12 | tím nhạt BTN_ADJ | SW2 1 ⟷ U1 D4 |
Danh sách linh kiện
| Ký hiệu | Linh kiện | Giá tham khảo | |
|---|---|---|---|
| U1 | Arduino Nano V3 (ATmega328P, chip nạp CH340) kèm cáp USB | 85.000đ | Tìm mua |
| U2 | Cả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 |
| U3 | Mà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 |
| Q1 | Transistor S8050 (TO-92) | 500đ | Tìm mua |
| R2 | Điện trở 1kΩ 1/4W (nâu-đen-đỏ) | 200đ | Tìm mua |
| BZ1 | Còi chip chủ động 5V (loại có dán tem, chân dài là +) | 3.000đ | Tìm mua |
| LED1 | LED đỏ 5mm (red) | 300đ | Tìm mua |
| R3 | Điện trở 330Ω 1/4W (cam-cam-nâu) | 200đ | Tìm mua |
| SW1 | Nút nhấn 4 chân 6×6mm (nút CHỌN) | 500đ | Tìm mua |
| SW2 | Nú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(); }
}
Đo kiểm bằng đồng hồ vạn năng
| Giữa dây vàng (D2) và GND | 4,8 – 5,0V |
| Giữa VCC và GND của LCD | 4,6 – 5,1V |
| Nắm đầu dò trong tay 1 phút | Nhiệt độ tăng lên 30–34°C |
| Giữa chân C và E của S8050 khi còi kêu | dướ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ể.