مقدمه
ماژول ESP32 یکی از محبوبترین ماژولهای میکروکنترلر مبتنی بر Wi-Fi است و در بسیاری از برنامههای قابل حمل IoT کاربرد دارد. ESP32 یک کنترلر قدرتمند است که از برنامهنویسی دو هستهای پشتیبانی میکند و همچنین از بلوتوث کم مصرف (BLE) داخلی پشتیبانی میکند که انتخاب خوبی برای برنامههای قابل حمل مانند دستگاه های iBeacon، GPS Trackers و غیره است. نگرانی اصلی این پروژهها باتری پشتیبان و مصرف انرژی آن است. برای کاهش مصرف انرژی ESP32 در زمانی که نیازی به حالت فعال (Active mode) نیست، میتواند در جهت راه اندازی خواب عمیق (Deep Sleep) ESP32 عمل نماید. در نتیجه مصرف انرژی و باتری کاهش مییابد.

تجهیزات مورد نیاز
برای انجام این کار، از Devkit V4.0 مبتنی بر ESP32 از Espressif استفاده میکنیم که دارای رابط USB به UART و همچنین سایر پین اوت های ESP32 برای اتصال آسان است. برنامه نویسی آن نیز با Arduino IDE انجام خواهد شد.
پروژه به این ترتیب است که با فشار دادن یک دکمه، راه اندازی خواب عمیق (Deep Sleep) ESP32 سبب میشود و با فشار دادن دکمه دیگری از حالت خواب عمیق بیدار میشود. برای تشخیص وضعیت ESP32، یک LED با زمان روشن شدن 1000 میلی ثانیه چشمک میزند. در حالت خواب، خاموش خواهد شد.
بنابراین، اجزای اضافی مورد نیاز جهت مدار راه اندازی خواب عمیق (Deep Sleep) ESP32 به شرح ذیل میباشد:
LED یک عدد
دکمه فشاری (سوئیچ لمسی) دو عدد
مقاومت 4.7k دو عدد
مقاومت 680R یک عدد
بِرِد بُرد
آداپتور 5 ولت یا واحد منبع تغذیه
کابل میکرو یو اس بی
Arduino IDE با رابط برنامه نویسی ESP32 در رایانه شخصی
مدار نمونه جهت راهاندازی
شکل زیر مدار قرار دادن ESP32 به حالت خواب با کلید نشان مشاهده میکنیم.

در این شماتیک بسیار ساده، دو دکمه وجود دارد. یک کلید برای راه اندازی خواب عمیق (Deep Sleep) ESP32 و کلید دیگری برای بیدار کردن ESP32 از حالت خواب استفاده میشود. کلیدها در پین 16 و پین 33 وصل شدهاند. هر دو دکمه به صورت Pull-up پیکربندی میشوند. برای تشخیص مدار در حالت راهاندازی خواب عمیق ESP32 و یا حالت عادی، LED به پین IO 4 وصل میشود.
نمای کلی حالتهای خواب در ESP32
حالتهای مختلف برای ESP32 وجود دارد، یعنی حالت فعال، حالت خواب مودم، حالت خواب سبک، حالت خواب عمیق و حالت خواب زمستانی. در شرایط عادی کار، ESP32 در حالت فعال کار میکند. در حالت فعال ESP32، CPU، سختافزار WiFi/BT، حافظه RTC و لوازم جانبی RTC، پردازندههای کمکی ULP، همگی فعال میشوند (بستگی به حجم کاری دارد). با این حال، در حالتهای مختلف، یک یا چند دستگاه جانبی خاموش هستند. برای بررسی عملکردهای مختلف حالت، جدول زیر را دنبال کنید.

همانطور که در جدول بالا میبینیم، در حالت راه اندازی خواب عمیق ESP32 که اغلب به عنوان الگوی نظارت بر حسگر ULP نامیده میشود – CPU، WiFi/BT، حافظه RTC و تجهیزات جانبی، پردازندههای ULP همگی خاموش هستند. فقط حافظه RTC و تجهیزات جانبی RTC روشن هستند. در طول وضعیت بیدار شدن، ESP32 باید توسط یک منبع بیدار کننده مطلع شود که ESP32 را از حالت خواب عمیق بیدار میکند. با این حال، از آن جایی که دستگاههای جانبی RTC روشن هستند، ESP32 را میتوان از طریق GPIO های دارای RTC فعال کرد. گزینههای دیگری نیز وجود دارد. این میتواند از طریق پینهای وقفه بیدار شدن خارجی یا استفاده از تایمر برای بیدار کردن ESP32 بیدار شود. در این پروژه از ext0 wakeup روی پین 33 استفاده میکنیم.
برنامه نویسی جهت راه اندازی خواب عمیق ESP32
برنامه کامل را میتوانید در پایین این صفحه مشاهده کنید. این کد برای Arduino IDE نوشته شدهاست و از این رو میتواند به راحتی با نیازهای شما سازگار شود. توضیح کد به شرح زیر است.
در ابتدای کد:
//Create a PushButton variable
PushBnt pushBtn = {GPIO_NUM_16, 0, false};
// define Led Pin
uint8_t led_pin = GPIO_NUM_4;
// define wake up pin
uint8_t wakeUp_pin = GPIO_NUM_33;
سه خط بالا پین بیداری، پین LED و پین حالت راه اندازی خواب عمیق ESP32 را مشخص میکند.
void setup() {
// put your setup code here, to run once:
// set the serial port at 115200
Serial.begin(115200);
delay(1000);
// set the pushButton pin as input with internal PullUp
pinMode(pushBtn.pin, INPUT_PULLUP);
// set the Interrupt handler with the pushButton pin in Falling mode
attachInterrupt(pushBtn.pin, isr_handle, FALLING);
// set the Led pin as ouput
pinMode(led_pin, OUTPUT);
//create a task that will be executed in the blinkLed() function, with priority 1 and executed on core 0
xTaskCreate(
blinkLed, /* Task function. */
"blinkLed", /* name of task. */
1024*2, /* Stack size of task */
NULL, /* parameter of the task */
5, /* priority of the task */
&taskBlinkled); /* Task handle to keep track of created task */
delay(500);
//Configure Pin 33 as ext0 wake up source with LOW logic level
esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeUp_pin, 0);
}
در بالا، وقفه توسط کد به حالت پایین رونده تنظیم میشود
attachInterrupt(pushBtn.pin, isr_handle, FALLING);
بنابراین، هر زمان که کلید فشرده شود، سطح منطقی از منطق 1 (3.3 ولت) به منطق 0 (0 ولت) تغییر میکند. ولتاژ پین دکمه کاهش مییابد و ESP32 تشخیص میدهد که کلید فشرده شدهاست. همچنین یک وظیفه برای چشمک زدن LED ایجاد شدهاست.
xTaskCreate(
blinkLed, /* Task function. */
"blinkLed", /* name of task. */
1024*2, /* Stack size of task */
NULL, /* parameter of the task */
5, /* priority of the task */
&taskBlinkled); /* Task handle to keep track of created task */
delay(500);
پین 33 نیز با استفاده از کد زیر به عنوان منبع بیدار کردن خارجی با شناسایی ext0 پیکربندی شدهاست.
esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeUp_pin, 0);
سپس، در حلقه while
void loop() {
// put your main code here, to run repeatedly:
if (pushBtn.pressed) {
Serial.printf("PushButton(%d) Pressed \n", pushBtn.pin);
Serial.printf("Suspend the 'blinkLed' Task \n");
// Suspend the blinkLed Task
vTaskSuspend( taskBlinkled );
digitalWrite(led_pin, LOW);
Serial.printf("Going to sleep..... \n", pushBtn.pin);
pushBtn.pressed = false;
//Go to sleep now
esp_deep_sleep_start();
}
esp_sleep_wakeup_cause_t wakeupReason;
wakeupReason = esp_sleep_get_wakeup_cause();
switch(wakeupReason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("using external signal ext0 for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("using external signal ext1 for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("using Timer signal for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("using TouchPad signal for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("using ULP signal for WakeUp From sleep"); break;
default : break;
Serial.printf("Resume the 'blinkLed' Task \n");
// restart the blinkLed Task
vTaskResume( taskBlinkled );
}
}
حلقه while دائماً چک میکند که آیا دکمه خواب فشرده شدهاست یا خیر. اگر دکمه فشرده شود، کار چشمک زدن LED را متوقف یا به حالت تعلیق در میآورد و عملکرد راه اندازی خواب عمیق (Deep Sleep) ESP32 را اجرا میکند.
esp_deep_sleep_start();
در این شرایط، اگر دکمه وقفه خارجی ext0 فشرده شود، بلافاصله از حالت خواب عمیق بیدار میشود و کار چشمک زدن led را از سر میگیرد. در آخر، عملکرد چشمک زدن LED را میتوان در کدهای زیر مشاهده کرد، این LED 1000 میلی ثانیه چشمک میزند.
void blinkLed(void* param){
while(1){
static uint32_t pin_val = 0;
// toggle the pin value
pin_val ^= 1;
digitalWrite(led_pin, pin_val);
Serial.printf("Led ----------------- %s\n" , pin_val? "On" : "Off");
/* Simply toggle the LED every 1000ms or 1sec */
vTaskDelay( 1000 / portTICK_PERIOD_MS );
}
taskBlinkled = NULL;
vTaskDelete( NULL );
}
کد کامل جهت راه اندازی خواب عمیق (Deep Sleep) ESP32 به شرح زیر است:
struct PushBnt{
const uint8_t pin;
uint32_t pressCount;
bool pressed;
};
//Create a PushButton variable
PushBnt pushBtn = {GPIO_NUM_16, 0, false};
// define Led Pin
uint8_t led_pin = GPIO_NUM_4;
// define wake up pin
uint8_t wakeUp_pin = GPIO_NUM_33;
// define taskBlinkled TaskHandle_t variable
TaskHandle_t taskBlinkled;
// define ISR function for PushButtton's Interrupt
void IRAM_ATTR isr_handle() {
pushBtn.pressed = true;
pushBtn.pressCount = pushBtn.pressCount + 1;
}
void setup() {
// put your setup code here, to run once:
// set the serial port at 115200
Serial.begin(115200);
delay(1000);
// set the pushButton pin as input with internal PullUp
pinMode(pushBtn.pin, INPUT_PULLUP);
// set the Interrupt handler with the pushButton pin in Falling mode
attachInterrupt(pushBtn.pin, isr_handle, FALLING);
// set the Led pin as ouput
pinMode(led_pin, OUTPUT);
//create a task that will be executed in the blinkLed() function, with priority 1 and executed on core 0
xTaskCreate(
blinkLed, /* Task function. */
"blinkLed", /* name of task. */
1024*2, /* Stack size of task */
NULL, /* parameter of the task */
5, /* priority of the task */
&taskBlinkled); /* Task handle to keep track of created task */
delay(500);
//Configure Pin 33 as ext0 wake up source with LOW logic level
esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeUp_pin, 0);
}
void loop() {
// put your main code here, to run repeatedly:
if (pushBtn.pressed) {
Serial.printf("PushButton(%d) Pressed \n", pushBtn.pin);
Serial.printf("Suspend the 'blinkLed' Task \n");
// Suspend the blinkLed Task
vTaskSuspend( taskBlinkled );
digitalWrite(led_pin, LOW);
Serial.printf("Going to sleep..... \n", pushBtn.pin);
pushBtn.pressed = false;
//Go to sleep now
esp_deep_sleep_start();
}
esp_sleep_wakeup_cause_t wakeupReason;
wakeupReason = esp_sleep_get_wakeup_cause();
switch(wakeupReason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("using external signal ext0 for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("using external signal ext1 for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("using Timer signal for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("using TouchPad signal for WakeUp From sleep"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("using ULP signal for WakeUp From sleep"); break;
default : break;
Serial.printf("Resume the 'blinkLed' Task \n");
// restart the blinkLed Task
vTaskResume( taskBlinkled );
}
}
void blinkLed(void* param){
while(1){
static uint32_t pin_val = 0;
// toggle the pin value
pin_val ^= 1;
digitalWrite(led_pin, pin_val);
Serial.printf("Led ----------------- %s\n" , pin_val? "On" : "Off");
/* Simply toggle the LED every 1000ms or 1sec */
vTaskDelay( 1000 / portTICK_PERIOD_MS );
}
taskBlinkled = NULL;
vTaskDelete( NULL );
}