ESP8266은 최근 ESP32로 또 한번 진화하며 Wi-Fi를 사용하는 여러 어플리케이션에 킬러MCU로 자리 매김 해 가는 듯 합니다.
필자도 인터넷라디오 정도의 간단한 프로젝트만 진행하다 최근 복잡도가 다소 높은 프로젝트를 진행하며 스택오버플로 리셋 현상 때문에 골머리를 앓았었습니다.
이 글에서는 아두이노 IDE상에서 에러가 발생한 라인을 추적하는 기술과 에러를 회피하는 방법에 대해 나름의 경험을 토대로 서술해 보고자 합니다.
MCU들 간의 특성도 있고 같은 회사라 하더라도 8bit, 32bit에 따른 특성도 있고, 같은 조건인 듯해 보이지만 어플리케이션의 복잡도에 따른 특성도 있는게 현실 이라고 봅니다.
같은 실수를 되풀이 하지 않도록 하자는 차원도 곁들여 기록으로 남겨둡니다.
1. 상태 관찰을 위한 기본 여건
- 아두이노 IDE : 아두이노 스케치 1.8.9
- ESP8266보드 : ESP-12F
- UART Speed : 115200bps
(※ 스택오버플로 메시지를 뿌릴때 115200bps를 사용하므로 에러 디버깅시 권장 속도)
2. 워치독 타임아웃 에러
ESP8266계열(ESP-01, ESP-12E, ESP-12F, NODE MCU, WeMOS 등등)은 모두 기본적으로 워치독 타이머가 적용되어 있습니다.
아무 생각 없이 프로그래밍 하다보면 영락 없이 워치독 타임아웃에 걸립니다.
어떤 이유에 의해 약 2초가량 홀딩되면 발생되며 HW상 문제로 인한 리셋과 SW상 원인에 기인한 2가지 타입이 있습니다.
- 리셋원인 : 2초 이상 워치독타이머를 리셋 시켜주지 않음
- 조치방법 : wdt_reset() 함수를 2초가 되기전에 자주 호출해 준다.
- 워치독 시간 변경 가능여부 : 변경은 되나 반영은 안됨(즉, 효과가 없음)
- 특이점 : 다운로드를 막 마친 상태와 리셋버튼을 누르거나 전원이 재투입된 직후가 서로 양상이 약간 다르며 이는 ESP8266의 알려진 버그(특성) 임
3. 스택 오버플로 에러에 의한 리셋
매우 다양한 원인에 의해 발생되며 경험상 메모리 침범에 기인하는 경우가 많았습니다.
주로 어레이를 선언해 놓고 어레이 범위를 초과해 억세스 하는 경우 흔히 발생됩니다.
일반적인 8bit계열은 이런 오류를 범해도 프로그램은 어찌어찌 굴러 갑니다만 그냥 대충 지나치면 결정적인 순간에 Hang상태에 빠져 버리는 등 신뢰성 저하로 이어질 공산이 큽니다.
반면 ESP8266은 이런 경우 스택 오버플로 에러를 뿌리며 재부팅이 되기에 문제를 금방 알아 차릴 수 있습니다.
암튼 이런 경우 원인 분석도구인 ESP8266제조사에서 제공하는 익셉션 디코더(예외처리 원인 분석기)를 설치하여 추적하는 방법을 소개하고자 합니다.
3.1. Exception Decoder 설치하기
아래 주소에서 프로그램 jar파일을 다운로드 받거나 첨부파일 에서 다운 받습니다.
https://github.com/me-no-dev/EspExceptionDecoder/releases/tag/1.1.0
압축을 풀어 EspExceptionDecoder.jar를 아래 경로에 둡니다.
C:\Arduino\arduino-1.8.9\tools\EspExceptionDecoder\tool\EspExceptionDecoder.jar
위에서 반드시 tools\EspExceptionDecoder\tool\EspExceptionDecoder.jar 이 경로는 준수 하셔야 합니다.
재차 아두이노 IDE를 구동 시키면 아래와 같이
아두이노IDE>툴>ESP Exception Decoder 항목이 생성된 것을 볼 수 있습니다.
3.2. Exception Decoder 사용해 보기
ESP8266은 잘 아시다 싶이 32비트 MCU이기에 바이트 타입의 데이터를 다룰때는 주의해야 합니다. 별 생각없이 다루게 되면 세그먼트 침범 오류를 일으키게 되고 이는 8bit만 주로 사용하던 개발자가 32bit로 넘어오면 당혹스러워 하는 부분 중 하나가 됩니다.
즉, 32비트 머신은 데이터를 억세스 할때 4바이트 단위로 입출력을 하므로 프로그래머는 이점에 주의를 기울여야 하는 것입니다.
본 글에서는 이런 경우에 발생될 수 있는 상황을 어떻게 추적하여 해결하는지 다뤄 보고자 합니다.
ㅇ 우선 문제가 있는 것으로 의심이 되는 코드 직전에다 브레이크 코드를 삽입해 두고 보드로 프로그램을 내려 보내봅니다.
아래에서 "printf_LCD("SYSTEM STARTUP");" 라인이 스택오버플로를 일으키는 것으로 의심이 되는 코드입니다.
필자가 즐겨 사용하는 브레이크 코드는 "while (1) { wdt_reset(); }" 입니다.
ㅇ 시리얼 모니터창에 뜨는 스택오버플로 메시지를 갈무리 합니다.
아래에서 보듯이 정확히 갈무리 하여야 하며 아무곳에서나 대충 갈무리를 하면 자동 해독이 안됩니다.
반드시 "Fatal exception 3(LoadStoreErrorCause): Fatal exception 부터 시작이 되어야 제대로 분석을 해줍니다.
갈무리의 끝라인 은 "<<<stack<<<" 직전까지가 됩니다.
필자의 프로그램에서 발생된 실제 오류 코드를 갈무리한 내용
Fatal exception 3(LoadStoreErrorCause): epc1=0x4020128c, epc2=0x00000000, epc3=0x00000000, excvaddr=0x40242769, depc=0x00000000 Exception (3): epc1=0x4020128c epc2=0x00000000 epc3=0x00000000 excvaddr=0x40242769 depc=0x00000000 >>>stack>>> ctx: cont sp: 3ffff930 end: 3fffffc0 offset: 01a0 3ffffad0: 3a747352 72614820 72617764 00000006 3ffffae0: 00000000 0000ffff 00000000 2d2d2d2d 3ffffaf0: 0d2d2d2d 0000019f 00000008 3ffefb58 3ffffb00: 3ffe8832 3ffffbe0 3ffef38c 3ffefb58 3ffffb10: 3ffef268 3ffef26e 3ffef270 402013c8 3ffffb20: 3ffe8832 3ffefa18 3ffffb51 4020156a 3ffffb30: 00000011 00000002 00000000 00000004 3ffffb40: 3ffef970 3ffffb94 3ffe9452 40201614 3ffffb50: 54535953 53204d45 54524154 40005055 3ffffb60: 3ffffe9e 00000000 7fffffe7 3ffefb58 3ffffb70: 3ffe8832 3ffe8300 3ffffba0 3ffffb90 3ffffb80: 00000008 00000000 00000000 3ffefb58 3ffffb90: 3ffffbe0 00000011 00000046 00000000 3ffffba0: 3ffffbc0 3ffffbb0 00000004 4020d794 3ffffbb0: 3ffe8832 3ffffbe0 00000000 00000001 3ffffbc0: 00000001 00000000 000001e6 4020b890 3ffffbd0: 3ffffbf0 3ffffbe0 00000004 40201820 3ffffbe0: 30373030 00000000 3ffef270 00000006 3ffffbf0: 00000000 00000000 3fffff10 402092a8 3ffffc00: 3fffdad0 00000000 3fffff10 40202868 3ffffc10: 61482e31 61776472 53206572 75746174 3ffffc20: 0a0d2e73 00000000 00000000 00000000 3ffffc30: 00000000 00000000 00000000 00000000 3ffffc40: 00000000 00000000 00000000 00000000 3ffffc50: 00000000 00000000 00000000 00000000 3ffffc60: 00000000 00000000 64530000 7265566b 3ffffc70: 2e32203a 28312e32 34646663 29336638 3ffffc80: 00000a0d 00000000 00000000 00000000 3ffffc90: 00000000 00000000 00000000 00000000 3ffffca0: 00000000 00000000 00000000 00000000 3ffffcb0: 00000000 00000000 00000000 00000000 3ffffcc0: 00000000 65726f43 3a726556 355f3220 3ffffcd0: 0a0d325f 00000000 00000000 00000000 3ffffce0: 00000000 00000000 00000000 00000000 3ffffcf0: 00000000 00000000 00000000 00000000 3ffffd00: 00000000 00000000 00000000 00000000 3ffffd10: 00000000 00000000 00000000 6f420000 3ffffd20: 6556746f 35203a72 00000a0d 00000000 3ffffd30: 00000000 00000000 00000000 00000000 3ffffd40: 00000000 00000000 00000000 00000000 3ffffd50: 00000000 00000000 00000000 00000000 3ffffd60: 00000000 00000000 00000000 00000000 3ffffd70: 00000000 00000000 646f4d42 31203a65 3ffffd80: 00000a0d 00000000 00000000 00000000 3ffffd90: 00000000 00000000 00000000 00000000 3ffffda0: 00000000 00000000 00000000 00000000 3ffffdb0: 00000000 00000000 00000000 00000000 3ffffdc0: 00000000 00000000 00000000 00000000 3ffffdd0: 50430000 38203a55 7a484d30 00000a0d 3ffffde0: 00000000 00000000 00000000 00000000 3ffffdf0: 00000000 00000000 00000000 00000000 3ffffe00: 00000000 00000000 00000000 00000000 3ffffe10: 00000000 00000000 00000000 00000000 3ffffe20: 00000000 00000000 00000000 73616c46 3ffffe30: 34203a68 33343931 79423430 0a0d6574 3ffffe40: 00000000 00000000 00000000 00000000 3ffffe50: 00000000 00000000 00000000 00000000 3ffffe60: 00000000 00000000 00000000 00000000 3ffffe70: 00000000 00000000 00000000 00000000 3ffffe80: 00000000 73520000 48203a74 77647261 3ffffe90: 20657261 63746157 676f6468 00000a0d 3ffffea0: 00000000 00000000 00000000 00000000 3ffffeb0: 00000000 00000000 00000000 00000000 3ffffec0: 00000000 00000000 00000000 00000000 3ffffed0: 00000000 00000000 00000000 00000000 3ffffee0: 00000000 00000000 00000000 00000000 3ffffef0: 00000000 00000000 00000000 00000000 3fffff00: 00000000 00000000 00000000 00000000 3fffff10: 00000000 00000000 00000000 00000000 3fffff20: 00000000 00000000 00000000 00000000 3fffff30: 00000000 00000000 00000000 00000000 3fffff40: 00000000 00000000 00000000 00000000 3fffff50: 00000000 00000000 00000000 00000000 3fffff60: 00000000 00000000 00000000 00000000 3fffff70: 00000000 00000000 00000000 00000000 3fffff80: 00000000 00000000 00000000 00000000 3fffff90: 00000000 00000000 00000000 ffefeffe 3fffffa0: 3fffdad0 00000000 3ffefb28 4020a350 3fffffb0: feefeffe feefeffe 3ffe8558 40100bed |
위 갈무리된 값을 아두이노IDE>툴>ESP Exception Decoder를 실행 시켜 아래와 같이 붙여 넣으면 자동으로 디코딩(해독)이 되어 문제가 있는 프로그램의 라인이 찍혀져 나옵니다.
해석을 좀 해보자면
- 오류의 종류? : Exception 3
- 오류의 원인? : 데이터를 로딩하거나 저장하는 중 메모리 침범이 발생함
- 오류를 일으킨 프로그램?: drv_Adafruit_GFX.cpp의 985번 라인
그럼 해당 프로그램의 해당 라인으로 가 봅니다.
5*7폰트 테이블에 접근하는 부분인데 PIC32, PIC18 등에서는 문제가 없었는데 gcc를 사용하는 ESP8266에서는 pgm메모리 접근하는 방식을 변경해 주어야 문제가 해결 될 듯 합니다.
변경된 코드는 다음과 같습니다.
직접 값을 가져오는 방식을 버리고 pgm_read_byte 매크로를 이용하여 원하는 폰트가 저장된 번지를 넘겨 주는 방식으로 변경함으로써 문제가 해결되었습니다.
4. 끝으로
프로그램 작성시 워닝에러에 민감하게 반응하는 것이 오류를 예방하는 좋은 습관이라고 생각합니다.
아두이노 IDE의 파일 > 환경설정 > 다음동작중 자세한 출력보이기 [v]컴파일을 켜 놓는 것이 좋다고 봅니다.
이상으로 ESP8266에서 스택오버플로에러가 났을때 문제 원인을 추적하는 요령을 살펴 봤습니다. ESP8266을 사용하며 스택오버플로 등 느닷없이 발생되는 리셋현상으로 몇날 몇일을 고생하시는 분들께 작은 실마리 라도 되셨으면 합니다.
감사합니다.
* 자료참고
1. https://www.youtube.com/watch?v=323CS87h6WU
2. https://arduino-esp8266.readthedocs.io/en/latest/faq/a02-my-esp-crashes.html
*2020.1.10 추가
ESP Exception Decoder 항목을 선택하면 elf 파일이 존재하는 위치를 물어옵니다.
이 위치는 vMicro를 사용하는 경우 아래 경로에 생성되므로 이곳이 지정되어야 정상적으로 분석이 이뤄집니다.