Hướng dẫn dùng Deep Sleep trên ESP32: tiết kiệm pin, đánh thức bằng timer và nút nhấn
Hướng dẫn đưa ESP32 vào Deep Sleep trên Arduino IDE để tiết kiệm pin: các chế độ nguồn, đánh thức bằng timer và nút nhấn, giữ biến trong bộ nhớ RTC và ước tính thời lượng pin.
Một thiết bị IoT chạy pin thường chỉ cần làm việc vài giây rồi nghỉ rất lâu: đọc cảm biến, gửi số đo, rồi chờ đến lần đo kế tiếp. Deep Sleep cho phép ESP32 ngắt điện gần như toàn bộ chip trong lúc chờ. Bài viết giải thích các chế độ nguồn của ESP32, cách đưa mạch vào Deep Sleep trên Arduino IDE, đánh thức bằng timer hoặc nút nhấn, giữ dữ liệu qua các lần ngủ và ước tính thời lượng pin.
1. Các chế độ nguồn của ESP32
ESP32 có năm chế độ nguồn. Chế độ càng sâu thì càng nhiều khối trong chip bị ngắt điện và dòng tiêu thụ càng nhỏ:
| Chế độ | Phần còn hoạt động | Dòng tiêu thụ của chip |
|---|---|---|
| Hoạt động, có Wi-Fi | CPU chạy, đang thu phát sóng | 95–240 mA |
| Modem Sleep | CPU chạy, tắt phần thu phát | 20–68 mA |
| Light Sleep | CPU tạm dừng, RAM còn giữ dữ liệu | 0,8 mA |
| Deep Sleep | Chỉ còn khối RTC | 10–150 µA |
| Hibernation | Chỉ còn timer RTC | 5 µA |
So với lúc thu phát Wi-Fi, Deep Sleep giảm dòng tiêu thụ của chip khoảng mười nghìn lần. Lưu ý đây là số của riêng chip ESP32. Trên mạch phát triển còn có IC ổn áp, chip USB–UART và LED nguồn, nên dòng của cả mạch lúc ngủ lớn hơn nhiều (xem mục 8).
2. Trong Deep Sleep, ESP32 còn làm được gì?
CPU, Wi-Fi, Bluetooth và RAM chính đều mất điện. Chỉ khối RTC còn chạy để chờ một nguồn đánh thức:
- Timer: thức dậy sau một khoảng thời gian đặt trước.
- Một chân RTC GPIO (ext0): thức dậy khi một chân đổi sang mức cao hoặc mức thấp, ví dụ khi nhấn nút.
- Nhiều chân RTC GPIO (ext1): giống ext0 nhưng theo dõi được nhiều chân cùng lúc.
- Chân cảm ứng (touch) và bộ xử lý phụ ULP: hai nguồn nâng cao, bài này không đi sâu.
Điểm khác biệt lớn nhất so với lệnh delay(): khi thức dậy, chương trình không chạy tiếp từ chỗ đã ngủ mà chạy lại từ đầu hàm setup(). Mọi biến thông thường trở về giá trị ban đầu; chỉ biến khai báo với RTC_DATA_ATTR còn giữ được giá trị.
3. Linh kiện cần chuẩn bị
- 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 nút nhấn thường hở (dùng từ mục 6), breadboard MB-102 và dây cắm test board.
- Khi chạy bằng pin: mô-đun ESP32 tích hợp pin 18650 và mạch quản lý pin, hoặc ESP32 DevKit V1 kèm mô đun pin Lithium-ion 18650.
- Máy tính đã cài Arduino IDE 2.x và 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. Thức dậy bằng timer
Chương trình đầu tiên chỉ cần mạch ESP32: mỗi lần thức, mạch bật LED, in số lần đã thức và lý do thức dậy, làm việc 2 giây rồi ngủ 10 giây.
/*
PYWORLD.VN - ESP32 Deep Sleep: tự thức dậy bằng timer
Chỉ cần mạch ESP32 DevKit V1; LED trên mạch (GPIO2) sáng trong lúc thức
Không cần cài thêm thư viện
*/
#define LED_PIN 2
#define THOI_GIAN_NGU_S 10 // Số giây ngủ mỗi chu kỳ
// Biến đặt trong bộ nhớ RTC: giữ được giá trị qua các lần Deep Sleep
RTC_DATA_ATTR int soLanThuc = 0;
void inLyDoThuc() {
switch (esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Thuc day do timer.");
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Thuc day do nut nhan (ext0).");
break;
default:
Serial.println("Khoi dong binh thuong (cap nguon hoac nhan EN).");
break;
}
}
void setup() {
Serial.begin(115200);
delay(300);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
soLanThuc++;
Serial.printf("\nLan thuc thu %d\n", soLanThuc);
inLyDoThuc();
// Việc cần làm mỗi lần thức: đọc cảm biến, gửi dữ liệu...
delay(2000);
Serial.printf("Ngu %d giay...\n", THOI_GIAN_NGU_S);
Serial.flush();
// Hẹn giờ thức dậy (đơn vị micro giây) rồi vào Deep Sleep
esp_sleep_enable_timer_wakeup((uint64_t)THOI_GIAN_NGU_S * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {
// Không bao giờ chạy tới đây: sau Deep Sleep, chương trình bắt đầu lại từ setup()
}
Chọn board DOIT ESP32 DEVKIT V1, nạp chương trình rồi mở Serial Monitor ở 115200 baud:
Giữa các lần thức, ESP32 còn in vài dòng khởi động của chính nó, trong đó có chữ DEEPSLEEP_RESET. Đó là dấu hiệu chip vừa thức dậy từ Deep Sleep, không phải lỗi. LED trên mạch sáng 2 giây rồi tắt 10 giây cho thấy rõ chu kỳ thức – ngủ.
5. Giải thích các lệnh chính
RTC_DATA_ATTR int soLanThuc = 0;: đặt biến vào bộ nhớ RTC, vùng nhớ vẫn có điện trong Deep Sleep. Biến này chỉ trở về 0 khi mất nguồn hoặc khi nhấn nút EN.esp_sleep_get_wakeup_cause(): cho biết vì sao chip vừa khởi động: do timer, do chân ext0, hay là khởi động bình thường.Serial.flush(): chờ gửi hết dữ liệu còn trong bộ đệm trước khi ngủ. Thiếu lệnh này, dòng cuối cùng có thể bị cắt.esp_sleep_enable_timer_wakeup(...): hẹn giờ thức dậy, đơn vị là micro giây. Phép nhân được ép sang kiểuuint64_tvì thời gian ngủ dài sẽ vượt quá phạm vi số nguyên 32 bit: một giờ đã là 3 600 000 000 micro giây.esp_deep_sleep_start(): vào Deep Sleep ngay. Các lệnh viết sau dòng này không được thực hiện.- Hàm
loop()để trống vì toàn bộ công việc nằm trongsetup().
Timer RTC chạy bằng bộ dao động bên trong chip nên thời gian ngủ có thể lệch đôi chút so với giá trị đặt. Thiết bị cần giờ chính xác nên đồng bộ lại thời gian qua mạng sau khi thức.
6. Thức dậy bằng nút nhấn
Nguồn đánh thức ext0 theo dõi một chân RTC GPIO. Trên ESP32, các chân RTC GPIO là GPIO0, 2, 4, 12–15, 25–27 và 32–39. Bài chọn GPIO4 vì chân này không ảnh hưởng tới quá trình khởi động và nằm gần chân 3V3 trên DevKit V1.
| Nút nhấn | ESP32 DevKit V1 | Ghi chú |
|---|---|---|
| Chân 1 | D4 / GPIO4 | Chân RTC GPIO dùng để đánh thức. |
| Chân 2 | 3V3 | Khi nhấn, GPIO4 được nối lên 3,3 V. |
Chương trình dưới đây bật cùng lúc hai nguồn đánh thức: nhấn nút thì mạch thức ngay, không nhấn thì timer đánh thức sau 60 giây.
/*
PYWORLD.VN - ESP32 Deep Sleep: thức dậy bằng nút nhấn (ext0) hoặc timer
Nút nhấn nối giữa GPIO4 và 3V3; LED trên mạch (GPIO2) sáng trong lúc thức
Không cần cài thêm thư viện
*/
#include "driver/rtc_io.h"
#define LED_PIN 2
#define NUT_PIN GPIO_NUM_4 // Phải là chân RTC: 0, 2, 4, 12-15, 25-27, 32-39
#define THOI_GIAN_NGU_S 60
// Hai biến trong bộ nhớ RTC: giữ được giá trị qua các lần Deep Sleep
RTC_DATA_ATTR int soLanThuc = 0;
RTC_DATA_ATTR int soLanNhanNut = 0;
void setup() {
Serial.begin(115200);
delay(300);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
soLanThuc++;
esp_sleep_wakeup_cause_t lyDo = esp_sleep_get_wakeup_cause();
if (lyDo == ESP_SLEEP_WAKEUP_EXT0) {
soLanNhanNut++;
Serial.printf("\nLan thuc thu %d: do nut nhan (da nhan %d lan)\n", soLanThuc, soLanNhanNut);
} else if (lyDo == ESP_SLEEP_WAKEUP_TIMER) {
Serial.printf("\nLan thuc thu %d: do timer\n", soLanThuc);
} else {
Serial.printf("\nLan thuc thu %d: khoi dong binh thuong\n", soLanThuc);
}
// Việc cần làm mỗi lần thức: đọc cảm biến, gửi dữ liệu...
delay(2000);
// Nguồn đánh thức 1: nút nhấn đưa GPIO4 lên mức cao
esp_sleep_enable_ext0_wakeup(NUT_PIN, 1);
rtc_gpio_pullup_dis(NUT_PIN);
rtc_gpio_pulldown_en(NUT_PIN); // Điện trở kéo xuống bên trong giữ chân ở mức thấp khi không nhấn
// Nguồn đánh thức 2: timer
esp_sleep_enable_timer_wakeup((uint64_t)THOI_GIAN_NGU_S * 1000000ULL);
Serial.printf("Ngu toi da %d giay, nhan nut de thuc som...\n", THOI_GIAN_NGU_S);
Serial.flush();
esp_deep_sleep_start();
}
void loop() {
// Không bao giờ chạy tới đây: sau Deep Sleep, chương trình bắt đầu lại từ setup()
}
esp_sleep_enable_ext0_wakeup(NUT_PIN, 1): thức dậy khi GPIO4 lên mức cao. Đổi số 1 thành 0 nếu muốn thức khi chân xuống mức thấp.rtc_gpio_pulldown_en(NUT_PIN): bật điện trở kéo xuống bên trong chip để GPIO4 ở mức thấp ổn định khi không nhấn nút. Nếu bỏ lệnh này, chân bị thả nổi và mạch có thể tự thức liên tục.- Có thể bật nhiều nguồn đánh thức cùng lúc; nguồn nào xảy ra trước sẽ đánh thức chip, và
esp_sleep_get_wakeup_cause()cho biết đó là nguồn nào.
Ví dụ kết quả khi nhấn nút hai lần rồi để yên (số minh họa):
Lan thuc thu 1: khoi dong binh thuong
Ngu toi da 60 giay, nhan nut de thuc som...
Lan thuc thu 2: do nut nhan (da nhan 1 lan)
Ngu toi da 60 giay, nhan nut de thuc som...
Lan thuc thu 3: do nut nhan (da nhan 2 lan)
Ngu toi da 60 giay, nhan nut de thuc som...
Lan thuc thu 4: do timer
Ngu toi da 60 giay, nhan nut de thuc som...
Một số lưu ý khi chọn chân đánh thức:
- GPIO34–39 chỉ là ngõ vào và không có điện trở kéo bên trong; nếu dùng các chân này phải gắn điện trở kéo ngoài khoảng 10 kΩ.
- GPIO0, 2, 12 và 15 tham gia quá trình khởi động của chip. Nên tránh gắn nút nhấn vào các chân này.
- Cần theo dõi nhiều nút thì dùng ext1 với lệnh
esp_sleep_enable_ext1_wakeup_io()và nên dùng điện trở kéo ngoài.
7. Ước tính thời lượng pin
Thời lượng pin phụ thuộc vào dòng trung bình của cả chu kỳ. Bảng dưới là một ví dụ tính với pin 18650 dung lượng dùng được 2000 mAh, giả sử lúc thức mạch tiêu thụ trung bình 120 mA:
| Cách chạy | Dòng trung bình | Thời lượng pin |
|---|---|---|
| Luôn thức và giữ kết nối Wi-Fi, giả sử 80 mA | 80 mA | Khoảng 25 giờ |
| Thức 5 giây mỗi 10 phút, lúc ngủ cả mạch còn 5 mA | Khoảng 6,0 mA | Khoảng 14 ngày |
| Thức 5 giây mỗi 10 phút, lúc ngủ cả mạch còn 0,1 mA | Khoảng 1,1 mA | Khoảng 76 ngày |
Cách tính dòng thứ hai: (120 × 5 + 5 × 595) / 600 ≈ 6,0 mA, rồi 2000 / 6,0 ≈ 336 giờ, tức khoảng 14 ngày. Hai dòng cuối cho thấy khi thời gian thức đã ngắn, dòng lúc ngủ của cả mạch mới là yếu tố quyết định. Con số thực tế còn phụ thuộc chất lượng pin, hiệu suất mạch nguồn và nhiệt độ.
8. Lưu ý khi làm thiết bị chạy pin
- Đo dòng lúc ngủ của mạch đang dùng. IC ổn áp, chip USB–UART và LED nguồn trên mạch phát triển vẫn tiêu thụ điện khi ESP32 ngủ. Mắc đồng hồ đo dòng nối tiếp với nguồn pin để biết con số thật.
- Cắt nguồn các module ngoài. Cảm biến, màn hình hay relay nối vào 3V3 vẫn tiêu thụ điện khi ESP32 ngủ. Có thể cấp nguồn cho chúng qua một chân GPIO (với tải vài mA) hoặc qua transistor.
- Rút ngắn thời gian thức. Kết nối Wi-Fi thường mất 1–3 giây mỗi lần thức. Chỉ bật Wi-Fi khi thật sự cần gửi dữ liệu.
- Chân GPIO không giữ trạng thái khi ngủ. Ngõ ra đang ở mức cao sẽ bị thả nổi; nếu cần giữ mức, gọi
gpio_hold_en()cho chân đó vàgpio_deep_sleep_hold_en()trước khi ngủ. - Dữ liệu cần giữ khi mất nguồn phải ghi vào bộ nhớ flash (thư viện Preferences), vì bộ nhớ RTC chỉ giữ được qua Deep Sleep.
- Với mô-đun ESP32 tích hợp pin 18650, LED trên mạch nối với GPIO16: đổi
LED_PINthành 16.
9. Xử lý lỗi thường gặp
- Mạch thức dậy liên tục, không chịu ngủ: chân đánh thức đang ở sẵn mức kích hoạt hoặc bị thả nổi. Kiểm tra dây nối nút nhấn, lệnh
rtc_gpio_pulldown_en()và mức đã chọn trongesp_sleep_enable_ext0_wakeup(). - Nhấn nút mà mạch không thức: chân đã chọn không phải chân RTC GPIO, hoặc nút nối nhầm sang GND thay vì 3V3.
- Biến đếm luôn bằng 1: thiếu
RTC_DATA_ATTR, hoặc mạch vừa bị mất nguồn hay vừa nhấn nút EN. - Serial Monitor thiếu dòng cuối trước khi ngủ: thêm
Serial.flush()trướcesp_deep_sleep_start(). - Arduino IDE không kết nối được khi mạch đang ngủ: giữ nút BOOT, nhấn rồi thả nút EN, sau đó bấm nạp lại.
- Dòng lúc ngủ vẫn cao: xem lại các linh kiện trên mạch và các module ngoài còn được cấp điện (mục 8).
Xem thêm: mạch hạ áp LM2596 và MP1584: cách chọn và cấp nguồn cho ESP32, ESP32 làm web server điều khiển relay qua Wi-Fi và dùng module thẻ nhớ Micro SD với ESP32 để ghi số đo mỗi lần thức.
Xem thêm