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

Sơ đồ khối: quang trở trong cầu phân áp đưa tín hiệu vào ESP32 làm lọc, trễ, xác nhận; ESP32 điều khiển MOSFET bằng PWM 20 kHz; MOSFET bật dải LED 12 V sáng dần; adapter 12 V cấp cho dải LED và cho mạch hạ áp 12 V xuống 5 V LM2596 hoặc MP1584, mạch hạ áp đưa 5 V vào chân VIN của ESP32
Sơ đồ khối đèn tự động (hình tự vẽ).
KhốiLinh kiệnKiến thức dùng lại
Đo ánh sángQuang 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ảiMOSFET logic-level, điện trở gate 100 Ω, kéo xuống 10 kΩMOSFET và PWM (bài 20)
NguồnAdapter 12 V, mạch hạ áp xuống 5 VCuộn cảm, nguồn xung (bài 13), ổn áp (bài 24)
Lắp ráp, kiểm traBreadboard hoặc mạch hàn, đồng hồ vạn năngHàn (bài 8), tìm lỗi (bài 29)

2. Đo ánh sáng bằng quang trở

Đồ thị điện áp GPIO34 theo độ rọi thang log từ 0,5 đến 1000 lux: điện áp tăng dần từ khoảng 300 mV khi rất tối lên gần 3100 mV khi sáng; đường ngang 1500 mV là ngưỡng tắt ứng với khoảng 14 lux, đường ngang 1000 mV là ngưỡng bật ứng với khoảng 5 lux
Điện áp cầu phân áp theo độ rọi (hình tự vẽ, mô hình quang trở gần đúng).
  • 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

Mô phỏng 30 phút lúc chạng vạng: điện áp quang trở giảm dần từ khoảng 2000 mV xuống dưới 500 mV với nhiễu dao động, có một xung cao do xe chiếu đèn 2 giây ở phút 21; hai đường ngưỡng 1500 và 1000 mV; bên dưới trạng thái đèn khi chỉ dùng một ngưỡng 1000 mV bật tắt 49 lần quanh phút 13 đến 15 và một lần khi xe chiếu đèn, còn chương trình chỉ bật một lần
Vì sao cần ngưỡng có trễ và thời gian chờ (hình tự vẽ, mô phỏng đúng logic chương trình với ánh sáng giả lập).

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ệ:

  1. 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).
  2. 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".
  3. 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ý

Sơ đồ nguyên lý: adapter 12 V qua jack DC cấp cực dương cho mạch hạ áp 12 V xuống 5 V và cho đầu dương dải LED; ngõ ra 5 V của mạch hạ áp vào chân VIN của ESP32; đầu âm dải LED nối drain MOSFET Q1 AO3400 hoặc IRLZ44N, source xuống đất; GPIO25 qua điện trở 100 ôm vào gate, 10 kilôôm từ gate xuống đất; quang trở LDR từ 3V3 xuống GPIO34, 10 kilôôm từ GPIO34 xuống đất; các ký hiệu đất nối chung
Sơ đồ nguyên lý đèn tự động (hình tự vẽ).

Chuẩn bị

  • 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ằng millis(), 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ăng HE_SO_LOC thì phản ứng nhanh hơn nhưng nhiễu hơn.
  • Biến muonBat chọ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.
  • batDauDoi ghi 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 doSang tă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.
Đồ thị độ rộng xung theo thời gian sau khi bật từ 0 đến 2,5 giây: đường nét đứt là biến doSang tăng đều tới 100 phần trăm ở khoảng 2 giây, đường cam là độ rộng xung thật bằng doSang bình phương chia 1023 tăng chậm lúc đầu rồi nhanh dần, cũng đạt 100 phần trăm ở khoảng 2 giây
Sáng dần bằng PWM (hình tự vẽ, số tính theo bước và công thức của chương trình).

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

Hình minh họa Serial Monitor: dòng giới thiệu; điện áp quang trở giảm dần từ 1167 mV, đèn tắt, độ sáng 0 phần trăm; khi xuống 988 mV dòng ghi đang chờ xác nhận; sau ba giây có dòng BAT đèn ở 939 mV; các dòng sau độ sáng 29 phần trăm, 78 phần trăm rồi 100 phần trăm
Kết quả mong đợi trên Serial Monitor (mô phỏng đúng logic chương trình, số liệu giả lập).
  1. 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.
  2. 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.
  3. 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_MV bằng giá trị đó, NGUONG_TAT_MV cao hơn khoảng 300–600 mV.
  4. 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ượngNguyên nhân thường gặpCách xử lý
Điện áp quang trở luôn gần 0 hoặc gần 3,3 VThiế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ốiQuang trở nhìn thấy ánh sáng của chính đènChe quang trở, đổi hướng; nới rộng khoảng trễ
Đèn sáng yếu, MOSFET nóngMOSFET không phải loại logic-levelDùng AO3400, IRLZ44N, IRLB8721 (bài 20)
Đèn chớp lên khi cắm điệnThiếu điện trở 10 kΩ kéo gate xuốngThêm điện trở từ gate xuống GND
ESP32 khởi động lại khi đèn bậtSụt áp trên dây GND chung, adapter thiếu dòngDâ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óngCắm ngược cực adapter hoặc chưa chỉnh điện áp raKiể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.


Tin tức liên quan

Jack Type-C đực: sơ đồ chân, điện trở CC và cách hàn dây
Jack Type-C đực: sơ đồ chân, điện trở CC và cách hàn dây

Jack Type-C đực (đầu cắm) chỉ có một chân CC ở A5 và một cặp D+ D−. Bài trình bày sơ đồ chân đầu đực, điện trở 56 kΩ hoặc 5,1 kΩ cần gắn ở chân CC cho từng kiểu dây và các bước hàn dây vào pad.

Sạc pin Li-ion 1S ở dòng 1 A: chọn dung lượng pin, thời gian sạc và an toàn
Sạc pin Li-ion 1S ở dòng 1 A: chọn dung lượng pin, thời gian sạc và an toàn

Dòng sạc 1 A hợp với pin 18650 và pin Li-po từ khoảng 2000 mAh, quá lớn với pin vài trăm mAh. Bài hướng dẫn chọn dung lượng pin, ước tính thời gian sạc, cách mắc pin và quy tắc an toàn.

Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32
Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32

Giải thích Bluetooth Low Energy: quảng bá và kết nối, cấu trúc GATT gồm dịch vụ, đặc tính, mô tả, UUID. Hướng dẫn cho ESP32 làm thiết bị BLE có đặc tính đọc, ghi, thông báo và thử bằng điện thoại.

Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi
Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi

Giải thích Modbus TCP/IP: phần đầu MBAP, cổng 502, điểm khác với Modbus RTU. Kèm chương trình ESP32 làm máy chủ Modbus TCP qua Wi-Fi với 10 thanh ghi giữ và 8 cuộn, tự phân tích khung, không dùng thư viện.

Nguồn điện, đi dây, công tắc hành trình và nút dừng khẩn cho máy CNC tự chế
Nguồn điện, đi dây, công tắc hành trình và nút dừng khẩn cho máy CNC tự chế

Chọn nguồn 24 V đủ công suất, sơ đồ đi điện có nút dừng khẩn, nối công tắc hành trình thường mở, thường đóng với GRBL, chống nhiễu và chọn tiết diện dây cho máy CNC, máy laser tự chế.

Hướng dẫn nạp file .bin vào ESP32 bằng Flash Download Tool
Hướng dẫn nạp file .bin vào ESP32 bằng Flash Download Tool

Hướng dẫn lấy đúng file và offset theo từng ESP32, nạp bằng Flash Download Tool, xử lý lỗi boot và xóa flash an toàn.

I2C là gì: START, STOP, địa chỉ, ACK và cách đọc ghi thanh ghi trên ESP32
I2C là gì: START, STOP, địa chỉ, ACK và cách đọc ghi thanh ghi trên ESP32

Giải thích giao tiếp I2C ở mức từng bit: hai dây SDA, SCL, điện trở kéo lên, START, địa chỉ, ACK, STOP. Kèm hai chương trình ESP32: tự tạo tín hiệu I2C bằng GPIO và đọc ghi thanh ghi bằng Wire.

Trẻ mấy tuổi nên học lập trình? Lộ trình từ 4 đến 15 tuổi cho phụ huynh
Trẻ mấy tuổi nên học lập trình? Lộ trình từ 4 đến 15 tuổi cho phụ huynh

Trẻ mấy tuổi nên học lập trình: lộ trình năm bước từ trò chơi không màn hình, ScratchJr, Scratch đến mạch ESP32 và Python, kèm dấu hiệu trẻ sẵn sàng và câu hỏi thường gặp.


Bình luận
Đã thêm vào giỏ hàng