2019년 4월 19일 금요일

ESP32 딥슬립과 깨어나기 (ESP32 Deep Sleep & Wake-up source)


 

ESP32에서 deep sleep이 왜 필요할까?


어떤 상태에 있는가에 따라 ESP32는 상대적으로 전력소모가 큰 디바이스가 될 수도 있다. 일반적인 동작시 약 75mA 정도의 전류를 소비하지만 WiFi로 데이터를 전송하는 경우는 240mA 정도까지 소비하게 된다.

개발하는것이 외부 아답터를 사용하는 경우라면 전력소모에 대해 별로 신경 쓰지 않겠지만 배터리로 동작하는 경우라면 수 mA라도 크게 신경을 써야 한다. 이 문제의 해결책으로 deep sleep 모드롤 활용해 ESP32의 전력 사용을 줄여줄 수 있다.

ESP32의 다른 슬립모드에 대한 자세한 내용은 이전 포스트를 참조하면 된다.

ESP32 Deep Sleep

Deep Sleep 모드에서는 CPU, 대부분의 RAM과 모든 디지털 페리페럴의 전원이 꺼진다. 전원이 공급되는 부분은 RTC controller, RTC 페리페럴(ULP 코프로세서를 포함), RTC 메모리(slow & fast) 뿐이다.

ULP를 사용하는 경우 약 0.15mA, ULP도 꺼져있는 경우 약 10uA의 전류를 소비한다.



Deep Sleep mode동안 메인 CPU는 전원이 꺼져 있지만 ULP 코프로세서가 센서 값을 읽어 측정된 값에 따라 메인 시스템을 깨울 수 있다. 이런 sleep pattern은 ULP sensor-monitored pattern이라고 한다.

CPU뿐 아니고 칩의 메인 메모리도 전원이 꺼지기 때문에 메모리에 저장된 모든 내용은 사라지고 억세스 할 수 없다.

하지만 RTC 메모리는 전원이 유지되기 때문에 deep sleep mode에서도 내용이 유지되고 메인 프로세서가 깨어난 다음에 내용을 읽을 수 있다. 그렇기 때문에 ESP32가 WiFi와 블루투스를 끄기 전에 연결 데이터를 RTC 메모리에 저장하는 것이다.

그러므로 리부팅 후에 데이터를 사용하려면 전역변수를 선언할 때 RTC_DATA_ATTR attribute를 사용해 RTC 메모리에 저장해야 한다. 예를 들어 RTC_DATA_ATTR int bootCount = 0; 같이 해 주면 된다.

Deep Sleep mode에서는 RTC 모듈을 제외한 칩 전체의 전원이 꺼지기 때문에 RTC recovery memory에 들어 있지 않은 내용은 모두 사라지고 RESET으로 칩을 재시작한다. 즉 프로그램 실행은 다시 한번 맨 처음부터 다시 시작하게 된다는 의미이다.

* TIP
ESP32는 deep sleep에서 깨어날 때 deep sleep wake stub을 실행하는걸 지원한다. 이 함수는 칩이 깨어나자 마자 다른 어떤 코드 (정상적인 초기화 또는 부트로더 코드)보다 먼저 곧바로 실행된다. Wake stub 이 실행된 후에 칩은 sleep mode로 돌아가거나 또는 정상적으로 시작할 수 있다.

다른 sleep mode와 달리 시스템이 deep sleep mode로는 자동으로 들어갈 수 없다. Wake-up 소스를 설정한 후 esp_deep_sleep_start() 함수를 호출하면 곧바로 deep sleep mode로 들어가게 된다.

기본적으로 ESP32는 wake-up 소스에 필요 없는 모든 페리페럴의 전원을 자동으로 꺼 버리지만 옵션으로 모든 페리페럴들애 대해 전원을 끌지 여부를 결정할 수도 있다.


ESP32 Deep Sleep Wake-up Sources

여러가지 방법으로 Deep Sleep mode에서 깨어나게 할 수 있다.

  • Timer
  • Touch pad
  • External wakeup (ext0 & ext1)
Wake-up source는 여러개를 동시에 사용할 수도 있다. 이 경우 여러개의 소스 중 하나가 트리거되면 칩이 깨어나게 된다.

이 소스들은 sleep modㄷ로 들어가기 전에 아무때나 설정할 수 있다.

* 주의
아무 Wake-up source를 설정하지 않고 deep sleep 모드로 들어갈 수도 있는데, 이 경우 칩은 외부에서 리셋을 시켜주기 전에는 무한히 deep sleep mode에 있게 된다.

ESP32 Wake-up Source : Timer

RTC controller는 타이머를 내장하고 있어 정해진 시간 후에 칩을 깨울 수 있다.

시간은 us 단위로 지정하지만 실제 resolution은 선택된 클럭 소스에 따라 결정된다.

타이머를 사용하는 경우 esp_sleep_enable_timer_wakeup() 함수로 sleep wake-up을 활성화 시킬 수 있다.

다음은 타이머를 wake-up 소스로 사용하고 리부팅 될 때 마다 사용하기 위해 RTC 메모리에 데이터를 저장하는 가장 기본적인 예제이다.

#define uS_TO_S_FACTOR 1000000  //Conversion factor for micro seconds to seconds
#define TIME_TO_SLEEP  5        //Time ESP32 will go to sleep (in seconds)

RTC_DATA_ATTR int bootCount = 0;

void setup(){
 Serial.begin(115200);
 delay(1000); //Take some time to open up the Serial Monitor

 //Increment boot number and print it every reboot
 ++bootCount;
 Serial.println("Boot number: " + String(bootCount));

 //Print the wakeup reason for ESP32
 print_wakeup_reason();

 //Set timer to 5 seconds
 esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
 Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
 " Seconds");

 //Go to sleep now
 esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
 esp_sleep_wakeup_cause_t wakeup_reason;
 wakeup_reason = esp_sleep_get_wakeup_cause();
 switch(wakeup_reason)
 {
  case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
  case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
  case 3  : Serial.println("Wakeup caused by timer"); break;
  case 4  : Serial.println("Wakeup caused by touchpad"); break;
  case 5  : Serial.println("Wakeup caused by ULP program"); break;
  default : Serial.println("Wakeup was not caused by deep sleep"); break;
 }
}

ESP32 Wake-up Source : Touch Pad

RTC IO 모듈은 터치센서 인터럽트가 발생할 때 wake-up 시키는 회로를 가지고 있다.

칩이 deep sleep모드로 들어가기 전에 touch pad interrupt를 설정해 줘야 한다.

이 wake-up source를 활성화 하려면 esp_sleep_enable_touchpad_wakeup() 함수를 사용한다.

다음은 touch pad를 wake-up 소스로 사용하고 리부팅 될 때 마다 사용하기 위해 RTC 메모리에 데이터를 저장하는 가장 기본적인 예제이다.

//Define touch sensitivity. Greater the value, more the sensitivity.
#define Threshold 40

RTC_DATA_ATTR int bootCount = 0;
touch_pad_t touchPin;

void callback(){
  //placeholder callback function
}

void setup(){
  Serial.begin(115200);
  delay(1000);

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32 and touchpad too
  print_wakeup_reason();
  print_wakeup_touchpad();

  //Setup interrupt on Touch Pad 3 (GPIO15)
  touchAttachInterrupt(T3, callback, Threshold);

  //Configure Touchpad as wakeup source
  esp_sleep_enable_touchpad_wakeup();

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}


//Function that prints the touchpad by which ESP32 has been awaken from sleep
void print_wakeup_touchpad(){
  touch_pad_t pin;
  touchPin = esp_sleep_get_touchpad_wakeup_status();
  switch(touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

ESP32 Wake-up Source : External Wake-up

ESP32를 Deep Sleep에서 깨어나게 해 주는 두가지 타입의 외부 트리거가 있다.
  • ext0 - 특정 핀에 의해서만 칩을 wake-up 하고 싶을 때 사용
  • ext1 - 여러 버튼으로 wake-up 하고 싶을 때 사용

ext0 External Wake-up Source

RTC controller는 특정 핀이 미리 지정된 로직 레벨로 될 때 wake-up 시키는 회로를 가지고 있다. 여기에는 RTC GPIO 핀들 (0, 2, 4, 12~15, 25~27, 32~39) 중에 하나를 사용할 수 있다.

이 wake-up source를 활성화 시키려면 esp_sleep_enable_ext0_wakeup(GPIO_PIN, LOGIC_LEVEL) 함수를 사용한다. 이 함수는 두개의 파라미터가 필요하다. 첫번째는 어떤 핀을 사용할 것인가이고 두번째는 핀 상태가 어떨 때  (HIGH 또는 LOW) wake-up 시킬까를 결정한다.

ext0는 wake-up을 위해 RTC IO를 사용하기 때문에 이 wake-up source를 사용ㅎ면 deep sleep 동안에도 RTC 페리페럴에 전원이 공급된다.

이 모드에서 RTC IO 모듈이 활성화 되어 있으므로 내부 pull-up/down 역시 사용할 수 있다. 이 경우 esp_sleep_start()를 호출하기 전에 어플리케이션에서 rtc_gpio_pullup_en(), rtc_gpio_pulldown_en() 함수를 사용해 설정해 줘야 한다.

아래는 푸쉬버튼을 ext0 wake-up source로 동작시키기 위해 어떻게 연결했는가를 보여주는 회로도이다.



다음은 ext0를 wake-up source로 사용하는 가장 기초적인 예제 코드이다.

RTC_DATA_ATTR int bootCount = 0;

void setup(){
  Serial.begin(115200);
  delay(1000); 

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO33 as ext0 wake up source for HIGH logic level
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}

ext1 External Wake-up Source

ESP32는 여러개의 GPIO 핀을 사용해 deep sleep mode에서 깨어나게 할 수도 있다. 이 경우 RTC GPIO핀들 (32~39)을 사용할 수 있다.

ext1 wake-up source는 RTC controller를 사용하기 때문에 RTC 페리페럴이나 RTC 메모리를 켜 줄 필요가 없다. 즉 내부 pull-up/down 저항을 사용할 수 없다.

내부 pull-up/down 저항을 사용하려면 deep sleep동안 RTC 페리페럴이 켜져 있도록 요청하고 sleep에 들어가기 전에 rtc_gpio_ 함수들을 사용해 pull-up/down 저항을 설정해 줘야 한다.

이 wake-up source를 활성화 시키려면 esp_sleep_enable_ext1_wakeup(BUTTON_PIN_MASK, LOGIC_LEVEL) 함수를 사용한다. 이 함수는 두개의 파라미터가 필요하다. 첫번째는 ESP32에게 어떤 핀들을 사용할지 알려주는 pin mask이다.

두번째는 핀 상태가 어떨 때  (HIGH 또는 LOW) wake-up 시킬까를 결정한다.

  • ESP_EXT1_WAKEUP_ANY_HIGH - 선택된 핀들 중 어느 하나가 HIGH일 때 wake-up
  • ESP_EXT1_WAKEUP_ANY_LOW - 선택된 핀들 중 어느 하나가 LOW일 때 wake-up
PIN MASK를 이해하는 가장 쉬운 방법은 바이너리 형태로 써 보는 것이다.




  • 0 - masked pin. 즉 이 핀은 외부 인터럽트 소스로 사용하지 않음
  • 1 - wake-up source로 활성화 된 핀
LSB가 GPIO0을 나타내고 MSB가 GPIO39를 나타낸다.

만일 어떤 GPIO핀도 wake-up source로 사용하고 싶지 않으면 모든 위치에 '0'을 써 넣으면 된다.

아래는 여러개의 푸쉬버튼을 ext1 wake-up source로 동작시키기 위해 어떻게 연결했는가를 보여주는 회로도이다.




다음은 ext1을 wake-up source로 사용하는 가장 기초적인 예제 코드이다.

//Pushbuttons connected to GPIO32 & GPIO33
#define BUTTON_PIN_BITMASK 0x300000000

RTC_DATA_ATTR int bootCount = 0;

void setup(){
  Serial.begin(115200);
  delay(1000); 

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO32 & GPIO33 as ext1 wake up source for HIGH logic level
  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}














댓글 11개:

  1. 타이머로 설정시 핀연결은 안해도 되나요?
    가령 gpio16핀과 rst핀 연결

    답글삭제
    답글
    1. 예...타이머로 하는 경우 내부적으로 연결되어 있어 별도 외부 핀 연결은 필요 없습니다.

      삭제
  2. i2c 로 들어오는 값이 일정 이상일때 깨어나게도 할 수 있나요?

    답글삭제
    답글
    1. 불가능하지는 않지만 ULP 코딩을 어셈블리로 해야 해서 쉽지 않습니다.
      관심 있으면 아래를 함고하세요.
      https://github.com/tomtor/ulp-i2c

      삭제
  3. 디지털 달력을 만들고 싶은데 ..가장 좋은 wake up방법은 어떤 방법이 될까요?
    1시간 delay주고 1시간마다 날짜를 확인하는 방법이 가장 나을까요?
    (그래야 밤 12시부터 새벽 1시 사이에 달력이 변하긴 할텐데..)
    아니면 특정 시간대에 wake up시킬수 있는 방법이 있을까요?

    답글삭제
  4. 혹시 RTC_DATA_ATTR int bootCount = 0; 이렇게 선언한 부분이 다른 데이터로 기록되지 않고 항상 0으로 되어 있다면 어떤이유일까요?
    중간에 bootcounter = aaa + 1; 이런식으로 되어 있는데.
    어째서인지 항상 0으로 되어 있어요..

    답글삭제
    답글
    1. 이건 해결됐습니다 배터리를 컴터 usb로 연결했더니 current 가 순간적으로 흘러서 리셋이 되어 그렇더군요..일반 배터리로 연결해서 해결했습니다

      삭제
  5. cds sensor로 sleep wakrup을 하려면 어떻게 해야 하나요 ??

    답글삭제
    답글
    1. 아마도 특정 밝기 이상 또는 이하일 때 wake up을 하고 싶은거 같은데 아날로그 신호는 wake-up 소스로 사용할 수 없습니다. 정 하고 싶으면 comparator 같은 별도 하드웨어를 추가해줘야 합니다.

      삭제
  6. DEEPSLEEP에서 깨어나면, esp_deep_sleep_start() 명령 다음부터 수행을 하는지?
    아니면 ESP32가 RESET되어서 처음부터 다시 구동을 하나요?

    답글삭제
    답글
    1. DeepSleep에서 깨어나면 기본적으로 reset되어 setup()부터 다시 시작됩니다. 물론 deep sleep에서 깨어날 때 deep sleep wake stub을 만들어 놓으면 일단 deep sleep wake stub이 먼저 실행되고 나서 setup()이 실행됩니다.

      삭제