Phát hiện chuyển động bằng cảm biến PIR HC-SR501 với ESP32: tự bật đèn qua relay
Hướng dẫn dùng cảm biến chuyển động PIR HC-SR501 với ESP32: nguyên lý, cách chỉnh module, đọc tín hiệu bằng ngắt và ghép relay làm đèn tự bật khi có người, tự tắt sau 60 giây.
Cảm biến PIR HC-SR501 phát hiện người hoặc vật ấm di chuyển trong tầm vài mét, giá rẻ và tiêu thụ rất ít điện, nên được dùng nhiều trong đèn cầu thang, đèn hành lang và báo động. Bài viết hướng dẫn dùng HC-SR501 với ESP32 trên Arduino IDE: đọc tín hiệu bằng ngắt, rồi ghép với module relay để làm đèn tự bật khi có người và tự tắt sau một khoảng thời gian.
1. Cảm biến PIR hoạt động thế nào?
- PIR là viết tắt của Passive Infrared, cảm biến hồng ngoại thụ động. Cảm biến không phát ra tia nào, chỉ thu bức xạ hồng ngoại từ các vật ấm.
- Bên trong có hai phần tử nhạy nhiệt mắc ngược nhau. Khi cả hai nhận cùng một lượng nhiệt (phòng yên tĩnh), tín hiệu triệt tiêu. Khi một nguồn nhiệt di chuyển, hai phần tử nhận lệch nhau và tạo ra xung.
- Vòm nhựa trắng là thấu kính Fresnel, chia vùng quan sát thành nhiều dải. Vì thế PIR nhạy nhất với chuyển động cắt ngang trước cảm biến và kém nhạy hơn khi người đi thẳng về phía cảm biến.
- PIR chỉ phát hiện chuyển động. Người ngồi yên lâu sẽ không còn được nhận ra.
2. Tìm hiểu module HC-SR501
| Thông số | Giá trị thường gặp |
|---|---|
| Điện áp nguồn | 4,5 đến 20 V DC, thường dùng 5 V |
| Mức tín hiệu chân OUT | Mức cao khoảng 3,3 V, mức thấp 0 V |
| Tầm phát hiện | Khoảng 3 đến 7 m, chỉnh bằng biến trở độ nhạy |
| Góc phát hiện | Khoảng 110 độ |
| Thời gian giữ mức cao | Khoảng 3 giây đến 5 phút, chỉnh bằng biến trở thời gian |
| Thời gian khóa | Khoảng 2,5 giây sau khi OUT xuống thấp, trong lúc này cảm biến không nhận chuyển động mới |
| Thời gian ổn định | Khoảng 30 đến 60 giây sau khi cấp nguồn |
| Dòng tiêu thụ lúc chờ | Rất nhỏ, dưới 0,1 mA |
Các số trên là thông số thường đi kèm module HC-SR501; module của từng lô hàng có thể khác đôi chút. Vì chân OUT ra mức 3,3 V nên nối thẳng vào chân GPIO của ESP32 được, không cần mạch chuyển mức.
3. Chuẩn bị
- Một mạch ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) và cáp USB có truyền dữ liệu.
- Một cảm biến chuyển động PIR HC-SR501.
- Module relay bản 5 V (dùng từ mục 6).
- Breadboard và dây cắm test board.
- Arduino IDE 2.x với gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Không cần cài thêm thư viện.
4. Sơ đồ nối dây
| Linh kiện | Chân | ESP32 DevKit V1 |
|---|---|---|
| Cảm biến PIR | VCC | VIN (5 V từ cổng USB) |
| OUT | D27 (GPIO27) | |
| GND | GND | |
| Module relay | VCC | VIN (5 V từ cổng USB) |
| IN1 | D18 (GPIO18) | |
| GND | GND |
PIR và relay cùng lấy 5 V từ chân VIN, nên chia nguồn qua hàng nguồn của breadboard. Chương trình 1 chỉ cần cảm biến PIR; có thể nối relay sau.
5. Chương trình 1: đọc PIR bằng ngắt
Chương trình in ra lúc bắt đầu và lúc hết chuyển động, đồng thời bật LED trên mạch (GPIO2) khi chân OUT ở mức cao:
/*
PYWORLD.VN - Cảm biến chuyển động PIR HC-SR501 với ESP32: đọc tín hiệu bằng ngắt
In ra lúc bắt đầu và lúc hết chuyển động, kèm thời gian chân OUT ở mức cao
Nối dây: VCC = VIN (5 V), GND = GND, OUT = D27
*/
#define PIR_PIN 27
#define LED_PIN 2
const unsigned long THOI_GIAN_ON_DINH_MS = 30000; // Cảm biến cần khoảng 30 đến 60 giây để ổn định sau khi cấp nguồn
volatile bool coThayDoi = false;
bool dangCoNguoi = false;
unsigned long lucBatDau = 0;
// Hàm ngắt: chỉ đặt cờ, việc in và xử lý làm trong loop()
void IRAM_ATTR khiPirDoiMuc() {
coThayDoi = true;
}
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
Serial.println("Dang cho cam bien on dinh (30 giay), tranh di chuyen truoc cam bien...");
delay(THOI_GIAN_ON_DINH_MS);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), khiPirDoiMuc, CHANGE);
Serial.println("San sang");
}
void loop() {
if (!coThayDoi) {
return;
}
coThayDoi = false;
bool mucCao = (digitalRead(PIR_PIN) == HIGH);
if (mucCao && !dangCoNguoi) {
dangCoNguoi = true;
lucBatDau = millis();
digitalWrite(LED_PIN, HIGH);
Serial.println("Phat hien chuyen dong");
} else if (!mucCao && dangCoNguoi) {
dangCoNguoi = false;
digitalWrite(LED_PIN, LOW);
Serial.printf("Het chuyen dong, OUT o muc cao %.1f giay\n", (millis() - lucBatDau) / 1000.0);
}
}
Trong 30 giây đầu, đứng yên hoặc rời khỏi vùng quan sát để cảm biến ổn định. Sau đó đi ngang trước cảm biến và quan sát Serial Monitor. Thời gian chân OUT ở mức cao cho biết biến trở thời gian đang đặt ở đâu.
6. Chương trình 2: đèn tự bật, tự tắt
Thay vì dùng thời gian giữ của module, chương trình tự đếm thời gian. Cách này cho phép đổi thời gian giữ ngay trong code và dễ thêm điều kiện khác về sau.
/*
PYWORLD.VN - Đèn tự bật khi có người: PIR HC-SR501 + relay với ESP32
Có chuyển động thì đóng relay; relay tự nhả sau một khoảng thời gian không còn chuyển động
PIR: VCC = VIN (5 V), GND = GND, OUT = D27
Module relay bản 5 V: VCC = VIN, GND = GND, IN1 = D18
*/
#define PIR_PIN 27
#define RELAY_PIN 18
// true: module kích mức thấp (IN = LOW thì relay đóng)
// false: module kích mức cao (IN = HIGH thì relay đóng)
const bool RELAY_ACTIVE_LOW = true;
const unsigned long THOI_GIAN_ON_DINH_MS = 30000; // Bỏ qua tín hiệu trong lúc cảm biến ổn định
const unsigned long THOI_GIAN_GIU_MS = 60000; // Giữ đèn thêm 60 giây sau lần chuyển động cuối
volatile bool coChuyenDong = false;
bool denDangBat = false;
bool daSanSang = false;
unsigned long lanChuyenDongCuoi = 0;
void IRAM_ATTR khiPirLenCao() {
coChuyenDong = true;
}
void datRelay(bool bat) {
bool muc = RELAY_ACTIVE_LOW ? !bat : bat;
digitalWrite(RELAY_PIN, muc ? HIGH : LOW);
denDangBat = bat;
}
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
// Đặt mức "tắt" trước khi chuyển chân thành ngõ ra để relay không đóng lúc khởi động
digitalWrite(RELAY_PIN, RELAY_ACTIVE_LOW ? HIGH : LOW);
pinMode(RELAY_PIN, OUTPUT);
datRelay(false);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), khiPirLenCao, RISING);
Serial.println("Dang cho cam bien on dinh (30 giay)...");
}
void loop() {
// Trong 30 giây đầu cảm biến có thể báo sai nên không điều khiển đèn
if (!daSanSang) {
if (millis() < THOI_GIAN_ON_DINH_MS) {
coChuyenDong = false;
return;
}
daSanSang = true;
coChuyenDong = false;
Serial.println("San sang");
}
// Chân OUT còn ở mức cao nghĩa là vẫn đang có chuyển động
if (coChuyenDong || digitalRead(PIR_PIN) == HIGH) {
coChuyenDong = false;
lanChuyenDongCuoi = millis();
if (!denDangBat) {
datRelay(true);
Serial.println("Co chuyen dong -> BAT den");
}
}
if (denDangBat && millis() - lanChuyenDongCuoi >= THOI_GIAN_GIU_MS) {
datRelay(false);
Serial.printf("Khong con chuyen dong %lu giay -> TAT den\n", THOI_GIAN_GIU_MS / 1000);
}
}
7. Kết quả mong đợi
- Chương trình 2: đi ngang trước cảm biến, relay đóng (nghe tiếng "tách", LED kênh 1 trên module relay sáng).
- Tiếp tục di chuyển trong phòng: relay giữ nguyên, bộ đếm được đặt lại sau mỗi lần phát hiện.
- Rời khỏi vùng quan sát: khoảng 60 giây sau lần phát hiện cuối, relay nhả.
8. Giải thích các điểm chính
attachInterrupt(digitalPinToInterrupt(PIR_PIN), ham, CHANGE): gọi hàm ngắt mỗi khi chân OUT đổi mức. Chương trình 2 dùngRISINGvì chỉ cần biết lúc bắt đầu có chuyển động.IRAM_ATTRvà biếnvolatile: hàm ngắt đặt trong RAM và chỉ đặt một cờ; mọi việc in, đóng relay làm trongloop(). Cách viết này giống bài ngắt GPIO trên ESP32.- Kết hợp ngắt và đọc mức: ngoài cờ ngắt, chương trình 2 còn đọc
digitalRead(PIR_PIN). Chừng nào chân OUT còn ở mức cao, thời điểm chuyển động cuối vẫn được cập nhật. - Đếm thời gian bằng
millis(): không dùngdelay()dài, nên chương trình vẫn phản ứng ngay với chuyển động mới trong lúc đèn đang bật. - Bỏ qua 30 giây đầu: lúc mới cấp nguồn, HC-SR501 thường kéo chân OUT lên cao một hai lần dù không có ai.
9. Chỉnh module và chọn vị trí lắp
- Biến trở thời gian: khi đã để chương trình đếm thời gian, vặn biến trở thời gian về mức nhỏ nhất (khoảng 3 giây) để cảm biến sớm sẵn sàng cho lần phát hiện sau.
- Jumper: đặt ở H (kích lại) để chân OUT giữ mức cao suốt lúc còn chuyển động.
- Biến trở độ nhạy: bắt đầu ở giữa, tăng dần cho đến khi phủ đủ khu vực cần quan sát. Độ nhạy quá cao dễ báo giả.
- Vị trí lắp: đặt cao khoảng 2 đến 2,5 m, hướng sao cho người đi ngang qua vùng nhìn. Tránh hướng thẳng vào cửa sổ có nắng, máy lạnh, quạt sưởi, bếp.
- Tránh nhiễu: đặt cảm biến cách mạch ESP32 và anten ít nhất một gang tay. Sóng Wi-Fi phát gần có thể làm HC-SR501 báo giả.
An toàn điện: khi dùng relay đóng cắt đèn 220 V, phải ngắt điện trước khi đấu dây, đặt mạch trong hộp cách điện và không chạm vào mạch lúc có điện. Xem cách đấu tải và các lưu ý trong bài ESP32 điều khiển module relay.
10. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Báo có chuyển động liên tục dù không có ai | Cảm biến chưa ổn định, độ nhạy quá cao, đặt sát mạch ESP32 hoặc hướng vào nguồn nhiệt | Chờ đủ một phút sau khi cấp nguồn; giảm độ nhạy; dời cảm biến ra xa mạch và nguồn nhiệt |
| Không bao giờ báo chuyển động | Nối nhầm thứ tự ba chân hoặc cấp nguồn 3,3 V thay vì 5 V | Tháo vòm trắng để đọc tên chân; nối VCC vào VIN |
| Đèn tắt khi người vẫn ngồi trong phòng | PIR chỉ phát hiện chuyển động, người ngồi yên không tạo tín hiệu | Tăng THOI_GIAN_GIU_MS; đặt cảm biến gần chỗ ngồi hơn |
| Sau khi đèn tắt, đi vào ngay nhưng đèn không bật lại liền | Cảm biến đang trong thời gian khóa khoảng 2,5 giây | Hiện tượng bình thường của HC-SR501 |
| Relay đóng ngược: có người thì nhả, không có người thì đóng | Module relay kích mức cao | Đổi RELAY_ACTIVE_LOW thành false |
| Mạch khởi động lại khi relay đóng | Nguồn 5 V từ cổng USB yếu | Dùng nguồn 5 V riêng cho relay và nối chung GND; xem bài mạch hạ áp LM2596 và MP1584 |
11. Xem thêm
- ESP32 điều khiển module relay 4 kênh an toàn.
- Ngắt GPIO trên ESP32: đọc nút nhấn, chống dội và đếm xung.
- Deep Sleep trên ESP32: dùng tín hiệu PIR để đánh thức ESP32 chạy pin.
- Dùng MQTT với ESP32: gửi thông báo có chuyển động lên điện thoại.
- Các mạch ESP32 tại PYWORLD: danh mục ESP32.
Xem thêm