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

Biểu đồ dòng tiêu thụ của chip ESP32 theo chế độ nguồn: hoạt động có Wi-Fi, Modem Sleep, Light Sleep, Deep Sleep và Hibernation
Dòng tiêu thụ của riêng chip ESP32 theo từng chế độ nguồn (hình vẽ theo số liệu datasheet, trục ngang là thang logarit).
Các chế độ nguồn của chip ESP32
Chế độPhần còn hoạt độngDòng tiêu thụ của chip
Hoạt động, có Wi-FiCPU chạy, đang thu phát sóng95–240 mA
Modem SleepCPU chạy, tắt phần thu phát20–68 mA
Light SleepCPU tạm dừng, RAM còn giữ dữ liệu0,8 mA
Deep SleepChỉ còn khối RTC10–150 µA
HibernationChỉ còn timer RTC5 µ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ì?

Sơ đồ khối ESP32 trong Deep Sleep: CPU, Wi-Fi, RAM và ngoại vi mất điện; timer RTC, bộ nhớ RTC, chân RTC GPIO và ULP vẫn có điện
Các khối bị ngắt điện và các khối còn hoạt động khi ESP32 ở Deep Sleep (hình vẽ minh họa).

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ị

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:

Hình minh họa Serial Monitor khi ESP32 thức dậy bằng timer sau mỗi 10 giây Deep Sleep, biến đếm tăng dần
Kết quả mong đợi trên Serial Monitor (hình minh họa theo định dạng in của chương trình).

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ểu uint64_t vì 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 trong setup().

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ối nút nhấn đánh thức với ESP32 DevKit V1
Nút nhấnESP32 DevKit V1Ghi chú
Chân 1D4 / GPIO4Chân RTC GPIO dùng để đánh thức.
Chân 23V3Khi nhấn, GPIO4 được nối lên 3,3 V.
Sơ đồ nối nút nhấn giữa chân D4 (GPIO4) và 3V3 của ESP32 DevKit V1 để đánh thức mạch khỏi Deep Sleep
Sơ đồ nối nút nhấn đánh thức ESP32 DevKit V1 (hình vẽ minh họa): một chân vào D4, chân còn lại vào 3V3.

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ị chu kỳ thức ngủ của ESP32: dòng tiêu thụ cao trong vài giây thức, rất thấp khi ngủ sâu, kèm công thức tính dòng trung bình
Chu kỳ thức – ngủ và công thức ước tính dòng trung bình (hình vẽ minh họa).

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:

Ví dụ ước tính thời lượng pin 2000 mAh (số giả định để so sánh)
Cách chạyDòng trung bìnhThời lượng pin
Luôn thức và giữ kết nối Wi-Fi, giả sử 80 mA80 mAKhoảng 25 giờ
Thức 5 giây mỗi 10 phút, lúc ngủ cả mạch còn 5 mAKhoảng 6,0 mAKhoảng 14 ngày
Thức 5 giây mỗi 10 phút, lúc ngủ cả mạch còn 0,1 mAKhoảng 1,1 mAKhoả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_PIN thà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 trong esp_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ước esp_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.


Tin tức liên quan

Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE
Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE

7 Lượt xem

Hướng dẫn nối load cell với module HX711 và ESP32 DevKit V1, gá load cell đúng cách, hiệu chuẩn bằng vật đã biết khối lượng và viết chương trình cân trên Arduino IDE.

Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch
Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch

5 Lượt xem

Hướng dẫn ESP32 lấy giờ Việt Nam từ Internet bằng NTP trên Arduino IDE, in ra Serial Monitor và làm đồng hồ hiển thị thứ, ngày, giờ trên màn hình OLED 0.96 inch SSD1306.

Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient
Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient

5 Lượt xem

Hướng dẫn dùng MQTT với ESP32 trên Arduino IDE: broker, topic, publish, subscribe, cài Mosquitto và PubSubClient, gửi dữ liệu và điều khiển module relay qua mạng nội bộ.

Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32
Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32

4 Lượt xem

Hướng dẫn dùng Bluetooth Classic của ESP32 trên Arduino IDE: ghép đôi với điện thoại Android, gửi nhận chữ bằng BluetoothSerial và điều khiển module relay bằng lệnh ON, OFF.

Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE
Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE

4 Lượt xem

Hướng dẫn bật OTA trên ESP32 bằng thư viện ArduinoOTA có sẵn, nạp chương trình qua Wi-Fi từ Arduino IDE 2.x, đặt mật khẩu và các quy tắc để không mất khả năng cập nhật từ xa.

Hướng dẫn dùng màn hình OLED 0.96 inch SSD1306 với Arduino Nano
Hướng dẫn dùng màn hình OLED 0.96 inch SSD1306 với Arduino Nano

4 Lượt xem

Hướng dẫn nối màn hình OLED 0.96 inch SSD1306 (I2C) với Arduino Nano, cài thư viện Adafruit, hiện chữ nhiều cỡ, vẽ thanh mức theo điện áp và lưu ý về bộ nhớ RAM.

Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp
Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp

5 Lượt xem

Hướng dẫn đọc điện áp analog bằng ADC của ESP32 trên Arduino IDE, dùng analogReadMilliVolts, làm cầu phân áp đo pin Li-ion 18650 và ước lượng phần trăm pin còn lại.

Hướng dẫn kết nối Wi-Fi trên ESP32: quét mạng, tự kết nối lại, IP tĩnh và phát Wi-Fi riêng
Hướng dẫn kết nối Wi-Fi trên ESP32: quét mạng, tự kết nối lại, IP tĩnh và phát Wi-Fi riêng

4 Lượt xem

Hướng dẫn các thao tác Wi-Fi cần dùng nhất trên ESP32 với Arduino IDE: quét mạng, kết nối có thời gian chờ, xem IP và cường độ sóng, tự kết nối lại, đặt IP tĩnh, phát Wi-Fi riêng.


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