😴 دیپ اسلیپ ESP32: خواب عمیق، بیدار شدن با تایمر و دکمه و ماهها کار با یک باتری
فرض کن میخواهی یک دماسنج کوچک را با باتری در گلخانه یا بالکن بگذاری و هر چند دقیقه یکبار دما را بفرستد. ESP32 وقتی بیدار است و وایفایش روشن است، بین ۱۰۰ تا ۲۴۰ میلیآمپر جریان میکشد و یک باتری معمولی را در یکی دو روز خالی میکند. راهحل، دیپ اسلیپ ESP32 (Deep Sleep) است: برد کارش را در چند ثانیه انجام میدهد، بعد به خواب عمیق میرود و در این حالت خود تراشه فقط حدود ۱۰ میکروآمپر مصرف دارد. در این آموزش یاد میگیریم برد را بخوابانیم، با تایمر یا دکمه بیدارش کنیم و یک شمارنده را با RTC_DATA_ATTR از خواب به خواب نگه داریم.
حالتهای مصرف ESP32
ESP32 چند حالت کاری دارد. عددها تقریبیاند و به برد، ولتاژ و کارهای روشن بستگی دارند، ولی ترتیبشان مهم است:
| حالت | چه چیزی روشن است | مصرف تقریبی |
|---|---|---|
| فعال با وایفای | همهچیز | ۱۰۰ تا ۲۴۰ میلیآمپر |
| Modem sleep | پردازنده روشن، رادیو خاموش | چند ده میلیآمپر |
| Light sleep | پردازنده متوقف، حافظه حفظ میشود | حدود ۰٫۸ میلیآمپر |
| Deep sleep | فقط بخش RTC: تایمر، حافظهی RTC و پینهای RTC | حدود ۱۰ میکروآمپر |
| Hibernation | فقط تایمر RTC | حدود ۵ میکروآمپر |
فرق دیپ اسلیپ با بقیه این است که پردازنده و حافظهی اصلی واقعاً خاموش میشوند. وقتی برد بیدار میشود، برنامه از اول setup شروع میشود؛ انگار دکمهی ریاستارت را زده باشی. همهی متغیرهای معمولی پاک میشوند، مگر آنهایی که در حافظهی کوچک RTC (حدود ۸ کیلوبایت) نگه داشته شدهاند.
چه میسازیم؟
برنامهای که هر بار بیدار میشود شمارهی بیداری را میشمارد، دلیل بیدار شدن (تایمر یا دکمه) را در سریال مانیتور مینویسد، یک چشمک کوتاه با LED روی برد میزند و دوباره میخوابد. برد هر ۳۰ ثانیه خودش بیدار میشود، یا هر وقت دکمه را فشار بدهی.
قطعهها و سیمکشی
- یک برد ESP32 (مدل معمولی ESP32 DevKit)
- یک دکمهی فشاری
- یک مقاومت ۱۰ کیلواهم برای بالاکش (Pull-up)
- برای پروژهی نهایی: یک باتری لیتیومپلیمر (LiPo) با رگولاتور مناسب
| از | به | توضیح |
|---|---|---|
| یک پایهی دکمه | GPIO33 | باید یکی از پینهای RTC باشد |
| پایهی دیگر دکمه | GND | فشار دادن، پین را LOW میکند |
| مقاومت ۱۰ کیلواهم | بین GPIO33 و 3V3 | در حالت عادی پین را HIGH نگه میدارد |
| LED آبی روی برد | GPIO2 (از قبل وصل است) | نشانهی بیدار بودن |
بیدار شدن با یک پین (روش ext0) فقط روی پینهای RTC کار میکند: GPIO0، 2، 4، 12 تا 15، 25 تا 27 و 32 تا 39. فهرست کامل پینها و کاربردشان را در پین اوت ESP32 ببین.
کد کامل
// ESP32 deep sleep: wake up every 30 s, or when the button is pressed.
// Real board only: the simulator does not model deep sleep.
#include "driver/rtc_io.h"
const int LED = 2; // built-in LED
const gpio_num_t WAKE_PIN = GPIO_NUM_33; // must be an RTC GPIO
const uint64_t SLEEP_SECONDS = 30;
RTC_DATA_ATTR int bootCount = 0; // lives in RTC memory: survives deep sleep
void printWakeReason() {
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause) {
case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Woke up: timer"); break;
case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Woke up: button"); break;
default: Serial.println("Woke up: power on or reset"); break;
}
}
void setup() {
Serial.begin(115200);
delay(300); // give the monitor a moment
bootCount++;
Serial.print("Boot number: ");
Serial.println(bootCount);
printWakeReason();
// The "work" of this wake-up. In a real project: read a sensor, send it.
pinMode(LED, OUTPUT);
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(LED, LOW);
// Wake-up source 1: a timer, in microseconds.
esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * 1000000ULL);
// Wake-up source 2: GPIO33 going LOW (button to GND).
esp_sleep_enable_ext0_wakeup(WAKE_PIN, 0);
rtc_gpio_pullup_en(WAKE_PIN); // keep the pin HIGH while asleep
rtc_gpio_pulldown_dis(WAKE_PIN);
Serial.println("Going to sleep now");
Serial.flush(); // let the text leave before sleeping
esp_deep_sleep_start();
}
void loop() {
// Never reached: every wake-up starts again from setup().
}
توضیح خط به خط
RTC_DATA_ATTR int bootCount = 0;این کلمهی کلیدی متغیر را بهجای حافظهی اصلی در حافظهی RTC میگذارد که در خواب عمیق روشن میماند. مقدار اولیهی صفر فقط بار اول (روشن شدن یا ریاستارت) اعمال میشود، نه بعد از هر بیداری.esp_sleep_get_wakeup_cause()میگوید چه چیزی برد را بیدار کرده است. این برای برنامهای که باید با دکمه کار دیگری بکند تا با تایمر، خیلی بهدرد میخورد.esp_sleep_enable_timer_wakeup(...)زمان خواب را بر حسب میکروثانیه میگیرد. پسوندULLمهم است؛ بدون آن ضرب ۳۰ در یک میلیون برای زمانهای طولانیتر سرریز میکند.esp_sleep_enable_ext0_wakeup(WAKE_PIN, 0)یعنی «وقتی GPIO33 به سطح LOW رسید بیدار شو». عدد1یعنی بیدار شدن با HIGH.rtc_gpio_pullup_enمقاومت بالاکش داخلی را در طول خواب هم روشن نگه میدارد. مقاومت ۱۰ کیلواهم بیرونی هم همین کار را با اطمینان بیشتر انجام میدهد و اگر داری، داشتن هر دو اشکالی ندارد.Serial.flush()صبر میکند تا آخرین متن واقعاً از پورت بیرون برود. بدون آن، جملهی آخر نصفه در سریال مانیتور میرسد.esp_deep_sleep_start()برد را میخواباند و هیچ خطی بعد از آن اجرا نمیشود. برای همینloopخالی است.
مصرف باتری را حساب کن
فرض کن برد هر ۱۰ دقیقه بیدار میشود، ۳ ثانیه با وایفای روشن کار میکند (بهطور میانگین ۱۲۰ میلیآمپر) و بقیهی ۵۹۷ ثانیه میخوابد. میانگین مصرف میشود: (۳ × ۱۲۰ + ۵۹۷ × مصرف خواب) تقسیم بر ۶۰۰.
- اگر در خواب فقط خود تراشه مصرف کند (۰٫۰۱ میلیآمپر): میانگین حدود ۰٫۶ میلیآمپر است و یک باتری ۲۰۰۰ میلیآمپرساعتی نزدیک چهار ماه کار میکند.
- اگر برد توسعه در خواب ۸ میلیآمپر بکشد (رگولاتور و چراغ پاور و تراشهی USB): میانگین حدود ۸٫۶ میلیآمپر است و همان باتری حدود ۱۰ روز دوام میآورد.
یعنی بخش بزرگ مصرف در خواب مال خود برد است، نه ESP32. برای این حسابها میتوانی از ماشینحساب عمر باتری استفاده کنی. سه راه برای کم کردن مصرف: بردهای مخصوص کممصرف که رگولاتور خوب و چراغ قابل قطع دارند، ماژول خام ESP32 با رگولاتوری مثل MCP1700، و کوتاه کردن زمان بیداری؛ مثلاً دادهها را چند بار در حافظهی RTC جمع کن و هر ده بار یکبار وایفای را روشن کن.
منبعهای دیگر بیدار شدن
- ext1: بیدار شدن با چند پین RTC با هم، مثلاً هر کدام از سه دکمه. با
esp_sleep_enable_ext1_wakeupو یک ماسک بیتی از پینها تنظیم میشود. - پین لمسی: با
esp_sleep_enable_touchpad_wakeupبرد با لمس یک سیم یا صفحهی فلزی بیدار میشود. پینهای لمسی را در آموزش پین لمسی ESP32 ببین. - ULP: یک پردازندهی خیلی کوچک داخل ESP32 که در خواب هم میتواند یک سنسور را بخواند و فقط وقتی لازم شد پردازندهی اصلی را بیدار کند. موضوع پیشرفتهای است و برای شروع لازم نیست.
این کد برای ESP32 معمولی نوشته شده است. مدلهای جدیدتر مثل ESP32-C3 روش ext0 ندارند و برای بیدار شدن با پین از تابعهای مخصوص خودشان استفاده میکنند.
در شبیهساز چه چیزی را تمرین کنیم؟
خواب عمیق و مصرف میکروآمپری را فقط روی برد واقعی و با مولتیمتر میشود دید. اما بخش «بیدار» برنامه، که معمولاً بیشتر اشکالها همانجاست، کاملاً در شبیهساز قابل ساختن است: خواندن سنسور، وصل شدن به شبکهی Wokwi-GUEST با تایماوت، و فرستادن داده. هنگام تست، بهجای esp_deep_sleep_start() یک delay بگذار و وقتی همهچیز درست بود، روی برد واقعی خواب را برگردان. نمونهی آمادهی وصل شدن به شبکه در تینکرکد فارسی پروژهی «وصل شدن ESP32 به وایفای» است. خواندن مثالها رایگان است و برای اجرای برد ESP32 در شبیهساز اشتراک Plus لازم است. اگر تازه با ESP32 آشنا شدهای، درس «سلام ESP32» شروع خوبی است.
اشکالهای رایج
- برد پشت سر هم بیدار میشود: پین دکمه شناور است یا مقاومت بالاکش ندارد، یا موقع خوابیدن دکمه هنوز فشرده است. سطح بیدار شدن را با سیمکشی دکمه هماهنگ کن.
- دکمه هیچ اثری ندارد: پینی که انتخاب کردهای پین RTC نیست. GPIO33 یا یکی دیگر از فهرست بالا را بگذار.
- دیگر نمیتوانم برنامه آپلود کنم: برد بیشتر وقت خواب است و پورت USB جواب نمیدهد. دکمهی BOOT را نگه دار و یکبار EN را بزن؛ توضیح کامل در خطای آپلود ESP32 است. ترفند خوب: هنگام توسعه، اگر
bootCountبرابر ۱ بود، ده ثانیه صبر کن و بعد بخواب. - شمارنده بعد از قطع برق صفر میشود: طبیعی است. حافظهی RTC فقط در خواب حفظ میشود؛ برای نگهداشتن بعد از قطع برق از Preferences یا حافظهی فلش استفاده کن.
- رله یا LED در خواب روشن میماند یا خودبهخود عوض میشود: پینها در خواب رها میشوند. برای ثابت ماندن وضعیت از
gpio_hold_enیا یک مقاومت بالاکش یا پایینکش بیرونی استفاده کن.
قدم بعدی این است که در همان چند ثانیهی بیداری داده را بفرستی: اتصال ESP32 به وایفای و بعد پروژهی MQTT و اینترنت اشیا. برای ایدههای باتریخور، سری به ایدههای پروژهی اینترنت اشیا با ESP32 بزن و همهی آموزشهای این برد را در صفحهی ESP32 ببین.
بخش بیداری پروژه را در تینکرکد فارسی بساز 🔋سؤالهای رایج
آیا دیپ اسلیپ ESP32 در شبیهساز کار میکند؟
نه. خواب عمیق و بیدار شدن با تایمر یا دکمه فقط روی برد واقعی امتحان میشود. در شبیهساز بخش «بیدار» برنامه را میسازی و تست میکنی، یعنی خواندن سنسور و وصل شدن به وایفای، و بهجای خواب موقتاً از delay استفاده میکنی.
چرا بعد از بیدار شدن، برنامه دوباره از setup شروع میشود؟
چون در دیپ اسلیپ پردازنده و حافظهی اصلی خاموش میشوند و بیدار شدن در عمل یک ریاستارت است. برای همین همهی کار را در setup مینویسی و loop هیچوقت اجرا نمیشود. متغیری که باید بماند را با RTC_DATA_ATTR تعریف کن.
برد ESP32 من در خواب هم چند میلیآمپر مصرف دارد؛ چرا؟
عدد ۱۰ میکروآمپر مال خود تراشه است. روی بردهای توسعه، رگولاتور ولتاژ، چراغ پاور و تراشهی USB به سریال همیشه روشناند و چند میلیآمپر میکشند. برای باتری از بردهای کممصرف یا ماژول خام با رگولاتور کممصرف استفاده کن.