Dự án tổng hợp: đèn tự bật khi trời tối với quang trở, MOSFET và ESP32
Dự án cuối loạt điện tử căn bản: quang trở trong cầu phân áp, lọc nhiễu, ngưỡng có trễ và chờ xác nhận, MOSFET điều khiển dải LED 12 V sáng dần bằng PWM, nguồn 12 V qua mạch hạ áp cho ESP32.
Bài cuối của loạt "Điện tử căn bản" ghép những gì đã học thành một thiết bị dùng được thật: đèn tự bật khi trời tối cho cổng nhà, ban công hay bể cá. Quang trở đo ánh sáng qua một cầu phân áp, ESP32 đọc bằng ADC, lọc nhiễu, so với hai ngưỡng có trễ và chờ xác nhận vài giây, rồi điều khiển MOSFET cho dải LED 12 V sáng dần bằng PWM. Toàn bộ chạy từ một adapter 12 V, ESP32 lấy nguồn qua mạch hạ áp. Mỗi khối trong bài đều đã có một bài riêng trong loạt; ở đây tập trung vào cách ghép chúng lại và những vấn đề chỉ xuất hiện khi ghép: nhiễu, bật tắt liên tục, nguồn chung.
1. Tổng quan hệ thống

| Khối | Linh kiện | Kiến thức dùng lại |
|---|---|---|
| Đo ánh sáng | Quang trở và điện trở 10 kΩ | Cầu phân áp (bài 4), quang trở (bài 10) |
| Xử lý | ESP32: ADC, lọc, ngưỡng có trễ | Mạch so sánh và trễ (bài 26) |
| Đóng cắt tải | MOSFET logic-level, điện trở gate 100 Ω, kéo xuống 10 kΩ | MOSFET và PWM (bài 20) |
| Nguồn | Adapter 12 V, mạch hạ áp xuống 5 V | Cuộn cảm, nguồn xung (bài 13), ổn áp (bài 24) |
| Lắp ráp, kiểm tra | Breadboard hoặc mạch hàn, đồng hồ vạn năng | Hàn (bài 8), tìm lỗi (bài 29) |
2. Đo ánh sáng bằng quang trở

- Quang trở nối từ 3,3 V xuống GPIO34, điện trở 10 kΩ từ GPIO34 xuống GND. Trời sáng, quang trở chỉ còn vài kΩ, điện áp cao; trời tối, quang trở lên hàng trăm kΩ, điện áp thấp.
- Điện trở 10 kΩ được chọn gần bằng điện trở của quang trở ở vùng ánh sáng cần phân biệt (vài lux đến vài chục lux), để điện áp thay đổi nhiều nhất đúng ở vùng chạng vạng.
- Ngưỡng đặt bằng điện áp, không cần biết chính xác lux: bật khi dưới 1000 mV, tắt khi trên 1500 mV. Quang trở mỗi con một khác, nên sau khi lắp xong nên xem số in ra lúc trời chạng vạng thật rồi sửa hai hằng số này.
- GPIO34 thuộc ADC1, đọc được cả khi bật Wi-Fi (nếu sau này thêm điều khiển qua mạng).
3. Ngưỡng có trễ và thời gian chờ xác nhận

Cách đơn giản nhất là "dưới 1000 mV thì bật, trên thì tắt". Thực tế ánh sáng lúc chạng vạng thay đổi rất chậm và có nhiễu (mây, lá cây lay, đèn xe), nên điện áp đi qua ngưỡng nhiều lần: đèn nhấp nháy bật tắt hàng chục lần trong vài phút, relay hay MOSFET và dải LED đều chịu thiệt. Chương trình dùng ba lớp bảo vệ:
- Lọc trung bình trượt: mỗi lần đọc chỉ lấy 10 % giá trị mới, 90 % giá trị cũ, nên nhiễu nhanh bị làm mượt (bộ lọc số này giống mạch RC ở bài 12).
- Ngưỡng có trễ: đang tắt thì so với ngưỡng bật 1000 mV, đang bật thì so với ngưỡng tắt 1500 mV. Khoảng 500 mV ở giữa là vùng "không đổi trạng thái".
- Chờ xác nhận 3 giây: điều kiện đổi trạng thái phải đúng liên tục 3 giây. Xe chiếu đèn qua 2 giây không làm đèn tắt.
Một vấn đề nữa chỉ xuất hiện khi lắp thật: nếu quang trở nhìn thấy chính dải LED, đèn bật lên làm quang trở thấy sáng, đèn tắt, trời lại tối, đèn lại bật. Đặt quang trở hướng lên trời, có mái che, không để ánh sáng đèn chiếu vào; khoảng trễ rộng cũng giúp chống hiện tượng này.
4. Sơ đồ nguyên lý

Chuẩn bị
- ESP32 DevKit V1, breadboard, dây cắm.
- Mạch nguồn MP1584 hoặc mạch hạ áp LM2596 để hạ 12 V xuống 5 V cho ESP32.
- Adapter 12 V (dòng tối thiểu bằng dòng dải LED cộng khoảng 0,3 A cho ESP32), jack nguồn DC gắn hộp để nối adapter khi lắp vào hộp.
- Quang trở (loại 5 mm phổ biến), MOSFET logic-level (AO3400, IRLZ44N hoặc IRLB8721), điện trở 100 Ω, hai điện trở 10 kΩ, một đoạn dải LED 12 V.
- Chỉnh mạch hạ áp ra 5 V trước (đo bằng đồng hồ, chưa nối ESP32), rồi mới nối vào chân VIN. Không cấp thẳng 12 V vào VIN: ổn áp AMS1117 trên bo sẽ quá nhiệt (bài 24).
- GND của adapter, mạch hạ áp, source MOSFET và ESP32 nối chung. Dây từ source MOSFET về cực âm adapter nên ngắn và to, vì dòng dải LED chạy qua đó.
- Điện trở 10 kΩ từ gate xuống GND giữ MOSFET tắt lúc ESP32 khởi động, tránh đèn chớp lên khi cấp điện.
- Dòng dải LED trên khoảng 1 A thì không đi qua breadboard: nối dây trực tiếp hoặc hàn trên mạch.
Cách chỉnh điện áp ra và nối mạch hạ áp vào ESP32 có trong bài mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32.
5. Chương trình
/*
PYWORLD.VN - Dự án đèn tự bật khi trời tối: quang trở, ngưỡng có trễ, chờ xác nhận và sáng dần dải LED 12 V qua MOSFET
Quang trở từ 3V3 xuống GPIO34, điện trở 10 kΩ từ GPIO34 xuống GND (trời tối thì điện áp GPIO34 giảm)
GPIO25 qua 100 Ω vào gate MOSFET logic-level, 10 kΩ từ gate xuống GND; dải LED 12 V giữa cực dương adapter và drain
*/
#define CHAN_LDR 34
#define CHAN_MOSFET 25
const uint32_t TAN_SO_PWM = 20000; // 20 kHz: ngoài vùng tai nghe
const uint8_t DO_PHAN_GIAI = 10; // 0..1023
const int MUC_TOI_DA = 1023;
const uint32_t NGUONG_BAT_MV = 1000; // Điện áp dưới mức này: trời đủ tối để bật
const uint32_t NGUONG_TAT_MV = 1500; // Điện áp trên mức này: trời đủ sáng để tắt
const uint32_t THOI_GIAN_XAC_NHAN_MS = 3000; // Điều kiện phải giữ liên tục chừng này mới đổi trạng thái
const uint32_t THOI_GIAN_SANG_DAN_MS = 2000; // Thời gian sáng dần, tắt dần
const uint32_t CHU_KY_MS = 50;
const float HE_SO_LOC = 0.1; // Lọc trung bình trượt: càng nhỏ càng mượt, càng chậm
float uLoc = 0;
bool denBat = false;
uint32_t batDauDoi = 0; // Thời điểm điều kiện đổi trạng thái bắt đầu đúng (0: chưa có)
int doSang = 0; // 0..MUC_TOI_DA, tăng giảm dần
uint32_t lanDoTruoc = 0, lanInTruoc = 0;
uint32_t docLdrMv() {
uint32_t tong = 0;
for (int i = 0; i < 16; i++) tong += analogReadMilliVolts(CHAN_LDR);
return tong / 16;
}
void setup() {
Serial.begin(115200);
analogReadResolution(12);
ledcAttach(CHAN_MOSFET, TAN_SO_PWM, DO_PHAN_GIAI);
ledcWrite(CHAN_MOSFET, 0);
uLoc = docLdrMv(); // Bắt đầu bộ lọc từ giá trị thật, tránh báo sai lúc khởi động
Serial.println("Den tu dong: bat duoi 1000 mV, tat tren 1500 mV, xac nhan 3 s");
}
void loop() {
uint32_t bayGio = millis();
if (bayGio - lanDoTruoc < CHU_KY_MS) return;
lanDoTruoc = bayGio;
uLoc += HE_SO_LOC * ((float)docLdrMv() - uLoc);
// Ngưỡng có trễ: đang tắt thì so với ngưỡng bật, đang bật thì so với ngưỡng tắt
bool muonBat = denBat ? (uLoc < NGUONG_TAT_MV) : (uLoc < NGUONG_BAT_MV);
if (muonBat != denBat) {
if (batDauDoi == 0) {
batDauDoi = bayGio;
} else if (bayGio - batDauDoi >= THOI_GIAN_XAC_NHAN_MS) {
denBat = muonBat;
batDauDoi = 0;
Serial.printf(">>> %s den (U LDR %4u mV)\n", denBat ? "BAT" : "TAT", (unsigned)uLoc);
}
} else {
batDauDoi = 0; // Điều kiện mất trước khi đủ 3 s: bỏ qua (xe chiếu đèn, mây che)
}
// Sáng dần hoặc tắt dần về mức đích
int dich = denBat ? MUC_TOI_DA : 0;
int buoc = MUC_TOI_DA * CHU_KY_MS / THOI_GIAN_SANG_DAN_MS;
if (doSang < dich) doSang = min(dich, doSang + buoc);
if (doSang > dich) doSang = max(dich, doSang - buoc);
// Mắt cảm nhận độ sáng không tuyến tính: bình phương độ rộng xung để sáng dần trông đều hơn
ledcWrite(CHAN_MOSFET, (uint32_t)doSang * doSang / MUC_TOI_DA);
if (bayGio - lanInTruoc >= 1000) {
lanInTruoc = bayGio;
Serial.printf("U LDR %4u mV | den %s | do sang %3d %%%s\n", (unsigned)uLoc, denBat ? "BAT" : "TAT",
doSang * 100 / MUC_TOI_DA, batDauDoi ? " | dang cho xac nhan" : "");
}
}
- Vòng lặp chạy mỗi 50 ms nhưng không dùng
delay(): so thời gian bằngmillis(), nên sau này thêm nút nhấn, Wi-Fi vẫn phản hồi ngay. uLoc += HE_SO_LOC * (doc − uLoc)là bộ lọc trung bình trượt; tăngHE_SO_LOCthì phản ứng nhanh hơn nhưng nhiễu hơn.- Biến
muonBatchọn ngưỡng tùy trạng thái hiện tại: đó là toàn bộ ý tưởng của ngưỡng có trễ, chỉ trong một dòng. batDauDoighi thời điểm điều kiện đổi bắt đầu đúng; điều kiện mất trước 3 giây thì xóa về 0.- Độ sáng
doSangtăng giảm mỗi bước 25 trên 1023, khoảng 2 giây từ tắt tới sáng hết. Độ rộng xung thật làdoSang² / 1023.

Mắt người nhạy với thay đổi ở vùng tối hơn vùng sáng: tăng độ rộng xung đều đặn thì đèn có vẻ sáng vọt lên lúc đầu rồi gần như đứng yên. Lấy bình phương làm phần đầu tăng chậm lại, cảm giác sáng lên đều hơn. Tần số PWM 20 kHz nằm ngoài vùng tai nghe nên dải LED và MOSFET không kêu rít.
6. Kết quả mong đợi và cân chỉnh

- Nạp chương trình, mở Serial Monitor. Lấy tay che quang trở: điện áp giảm, xuất hiện "dang cho xac nhan", 3 giây sau đèn bật và sáng dần trong khoảng 2 giây.
- Bỏ tay ra: điện áp phải lên trên 1500 mV và giữ 3 giây thì đèn mới tắt dần.
- Cân chỉnh: để mạch ở vị trí lắp thật, ghi lại điện áp in ra vào đúng lúc muốn đèn bật (thường lúc chạng vạng). Đặt
NGUONG_BAT_MVbằng giá trị đó,NGUONG_TAT_MVcao hơn khoảng 300–600 mV. - Muốn tránh đèn bật vì mây đen ban ngày, tăng thời gian xác nhận lên 30–60 giây.
7. Mở rộng
- Hẹn giờ tắt: đèn chỉ sáng tới nửa đêm. Lấy giờ qua Wi-Fi (NTP) và thêm điều kiện giờ vào
muonBat. - Cảm biến chuyển động: buổi tối đèn sáng mờ 20 %, có người thì sáng 100 % trong một phút.
- Tải 220 V (bóng đèn thường): thay MOSFET bằng module relay và bỏ phần sáng dần; phần 220 V phải lắp trong hộp kín theo đúng nguyên tắc an toàn ở bài 5.
- Cảm biến ánh sáng số như BH1750 đo ra lux trực tiếp, chính xác hơn quang trở; xem bài cảm biến ánh sáng: quang trở, photodiode, BH1750.
8. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Điện áp quang trở luôn gần 0 hoặc gần 3,3 V | Thiếu điện trở 10 kΩ, hoặc nối nhầm vị trí quang trở và điện trở | Kiểm tra cầu phân áp; che, mở quang trở và đo bằng đồng hồ |
| Đèn bật rồi tắt liên tục khi trời tối | Quang trở nhìn thấy ánh sáng của chính đèn | Che quang trở, đổi hướng; nới rộng khoảng trễ |
| Đèn sáng yếu, MOSFET nóng | MOSFET không phải loại logic-level | Dùng AO3400, IRLZ44N, IRLB8721 (bài 20) |
| Đèn chớp lên khi cắm điện | Thiếu điện trở 10 kΩ kéo gate xuống | Thêm điện trở từ gate xuống GND |
| ESP32 khởi động lại khi đèn bật | Sụt áp trên dây GND chung, adapter thiếu dòng | Dây GND tải ngắn và to, nối về thẳng cực âm adapter; adapter đủ dòng; thêm tụ 470 µF ở ngõ vào mạch hạ áp |
| Không có gì chạy, mạch hạ áp nóng | Cắm ngược cực adapter hoặc chưa chỉnh điện áp ra | Kiểm tra cực tính jack; chỉnh và đo 5 V trước khi nối ESP32 |
9. Tổng kết loạt bài
Từ định luật Ohm tới một thiết bị hoàn chỉnh, loạt 30 bài đã đi qua điện trở, tụ, cuộn cảm, diode, transistor, MOSFET, IC, op-amp, cổng logic, flip-flop và cách tìm lỗi. Với nền tảng đó, các bài hướng dẫn ESP32 và cảm biến trong mục Góc kỹ thuật của pyworld.vn đều có thể đọc hiểu đến từng linh kiện: tại sao có con điện trở này, tụ kia để làm gì, và khi mạch không chạy thì bắt đầu đo từ đâu.
- Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32.
- Cảm biến ánh sáng: quang trở, photodiode, BH1750.
- Bài đầu của loạt: lộ trình tự học điện tử căn bản (bài 1).
Xem thêm