Category (Click)
개발보드 덕질하기

[ESP-IDF] ESP32 BOD 활용 Brownout Interrupt 구현

 차완기 - @10/8/2023, 3:50:00 PM
최근 ESP32 MCU를 활용해 IoT 조명을 만드는 프로젝트를 진행하고 있었습니다. 그러던 중 조명의 상태를 Flash(NVS)에 저장할 필요가 있었는데요, 밝기를 바꿀 때 마다 Flash에 쓰기보다는 전원이 차단될 때 Flash에 쓰는 쪽이 조금 더 바람직해 보여 이러한 방식으로 구현해보기로 하였습니다.
그러기 위해서는 전원의 차단을 감지할 필요가 있었고 이를 위해 ESP-IDF의 Brownout Detector을 활용해보기로 하였습니다.
여기서는 ESP-IDF v5.1을 사용하며 작성 시점의 stable 최신 버전인 v5.1.1을 사용하였습니다.

ESP-IDF 들여다보기

ESP-IDF에는 기본적으로 BOD(Brownout Detector)가 내장되어 있으며, SDK Configuration의 “Brownout Detector”를 켜면 자동으로 적용됩니다.
esp-idf/components/esp_system/startup.c - do_core_init() 함수 내부
내부적으로는 esp-idf/components/esp_system/startup.cdo_core_init() 함수에서 SDK Config의 brownout detector 설정 여부에 따라 esp_brownout_init() 함수를 호출하게 됩니다.
esp-idf/components/esp_system/port/brownout.c - esp_brownout_init() 함수 내부
함수 내부를 살펴보면 BOD를 enable한 후 ISR을 설정하는 것을 볼 수 있습니다.
이 코드를 가져와 활용해보기로 했습니다.

예제 들여다보기

프로젝트 파일은 아래 리포지토리에서 확인할 수 있습니다.
esp-idf_brownout_interrupt_example
varofla
BOD Interrupt를 테스트하기 위해 작성한 예제 코드는 아래와 같은 동작을 수행합니다.
전원 공급 시
1.
BOD Interrupt Enable
2.
NVS에서 brownout_det Key의 데이터 읽어 콘솔에 출력 (bool 타입)
3.
true인 경우 GPIO12에 연결된 LED를 켬
4.
무한 대기
BOD ISR
1.
NVS에서 brownout_det Key의 데이터를 Toggle(0→1, 1→0)
2.
무한 대기
전원이 공급될 때 마다 NVS의 brownout_det 데이터가 Toggle되고, 12번 GPIO핀에 연결된 LED가 Toggle될 것입니다.
파일은 크게 둘로 나뉘어 있습니다.
1.
main.c: App 코드
2.
brownout_init.c: BOD와 관련된 모든 코드(Init, ISR Call)

 main.c

 extern

brownout_with_interrupt_init() 함수는 brownout_init.c 파일에서 정의된 init 함수입니다. 이 함수에 대한 설명은 뒤에서 다룹니다.

 app_main(void)

Default Task에서 호출되는 app_main 함수입니다. 뒤에서 살펴볼 brownout_with_interrupt_init() 함수를 호출한 후 NVS의 데이터를 읽어 이에 맞게 LED를 켭니다.

 brownout_isr(void)

BOD ISR(Interrupt Service Routine)입니다.
NVS에서 storage라는 공간의 brownout_det key의 int8 데이터를 Toggle합니다.
이후에는 전원이 차단될 때 까지(꺼질 때 까지) 대기합니다.

 s_read_nvs(void)

NVS에서 storage라는 공간의 brownout_det key의 int8 데이터를 읽습니다. 디버깅을 위해 콘솔에 데이터도 출력하였습니다.

 s_init_led(void), s_set_led(bool val)

12번 핀에 연결된 LED를 위해 GPIO를 init하는 함수와 GPIO level을 설정하는 함수입니다. esp-idf api를 app 함수에서 바로 호출하는게 썩 마음에 들지 않아 분리해주었습니다.

 brownout_init.c

 brownout_with_interrupt_init(void (*brownout_isr)(void))

BOD Interrupt를 enable하는 함수입니다. 참고로 이 코드는 앞서 app에서 호출되었습니다.
31번줄을 보면 BOD의 설정을 진행하는데요, brownout_hal_config_t의 각 옵션은 다음과 같습니다.
threshold: BOD 임계점. SDK Configuration에 따르면 아래와 같습니다.
7: 2.51 V 6: 2.64 V 5: 2.76 V 4: 2.92 V 3: 3.10 V 2: 3.27 V
enabled: BOD Enable 여부
reset_enabled: 자동 reset 여부
flash_power_down: Flash 전원 차단 여부
rf_power_down: RF circuit 전원 차단 여부
이후 ISR을 등록하게 되며, SoC에 따라 다른 함수를 사용하는 것을 볼 수 있습니다.
ISR 등록에 대한 부분은 원본 코드에서 TODO로 남겨져 있기 때문에 ESP-IDF 버전에 따라 위 코드가 작동하지 않을 수 있습니다. v5.1.1 기준 ESP32-C6와 H2에서 rtc_isr_register() 함수는 정의되지 않은 상태로, esp_intr_alloc() 함수로 ISR을 등록합니다.
ISR로는 아래에서 설명할 s_brownout_handler(void *arg) 함수를 사용하며, argument로 함수 호출 시 전달받았던 brownout_isr 함수 포인터, 즉 main.c에 작성되어 있는 BOD ISR 함수를 넣어주게 됩니다.

 s_brownout_handler(void *arg)

BOD ISR입니다. 14번 줄에서 BOD 인터럽트 Flag를 clear하였는데요, 원본 코드에 따르면 RTC 인터럽트 Flag는 ISR에서 clear한다고 합니다.
이후로 듀얼 코어를 사용하는 경우 나머지 한 코어를 꺼주고,
argument로 전달받은 함수 포인터를 이용해 main.c의 brownout_isr() 함수를 호출합니다.
함수에서 반환된 경우 의도치 않은 동작을 방지하기 위해 무한 반복에 집어넣습니다.

동작 확인