화면이 살아 움직이기 시작하면 생기는 문제
2편 까지 오면 원하는 글자를 원하는 자리에 띄울 수 있다. 이제 실제로 쓸 수 있게 만들 차례다. 시계가 돌아야 하고, 날씨를 받아와야 하고, 와이파이가 없어도 안전 수칙은 계속 흘러야 한다.
그 셋을 붙이면서 나온 문제 세 가지를 정리한다.
문제 1 — 날씨를 받아오면 화면이 멈춘다
기상청 공공데이터 API를 HTTP GET으로 호출하도록 붙였다. 그랬더니 호출할 때마다 화면이 최대 15초씩 얼어붙었다. 시계 바늘도 멈추고 스크롤도 멈춘다.
loop() 안에서 HTTP GET을 부르면, 응답이 올 때까지 그 함수가 반환하지 않는다. 그동안 아래에 있는 화면 갱신 코드는 한 줄도 실행되지 않는다. 네트워크가 느리거나 서버가 늦게 답할수록 정지 시간이 길어진다.ESP32는 코어가 두 개다
여기서 ESP32의 구조가 답이 됐다. ESP32는 듀얼코어고, FreeRTOS가 기본으로 올라가 있다. 태스크를 특정 코어에 고정할 수 있다.
| 코어 | 맡은 일 |
|---|---|
| 코어 0 | 기상청 API 수신 태스크. 느려도 상관없다 |
| 코어 1 | 메인 루프 — I2S DMA 화면 갱신, 시계, 스크롤. 절대 멈추면 안 된다 |
xTaskCreatePinnedToCore 로 수신 태스크를 코어 0에 붙였다. 그리고 받아온 값을 메인 루프로 바로 넘기지 않고 스테이징 버퍼(pendingTempStr, weatherPending)를 거치게 했다.
문제 2 — 와이파이가 두 종류다
이 전광판은 테스트 환경과 실제 공사 현장 두 곳에서 돌아야 한다. 와이파이 SSID가 다르다. 설치할 때마다 코드를 고쳐 다시 업로드하는 건 현실적이지 않다.
WiFiMulti 라이브러리로 AP를 여러 개 등록해 두고, 켜졌을 때 잡히는 쪽으로 자동 접속하게 했다.
라디오가 전압을 흔든다
와이파이를 붙이고 나니 화면에 미세한 노이즈가 돌아왔다. 1편 에서 승압 모듈로 잡았던 그 증상과 비슷했다.
와이파이 라디오는 송신할 때 전류를 순간적으로 크게 끌어간다. 그러면 3.3V 레일이 순간 내려앉고, 그 위에서 만들어지는 신호가 흔들린다. WiFi.setSleep(WIFI_PS_MIN_MODEM) 으로 모뎀 슬립을 켜 라디오가 항상 켜져 있지 않도록 했다.
WIFI_PS_NONE 으로 되돌린다.비밀번호가 틀리면
인증에 실패해도 라이브러리는 계속 재시도한다. 그동안 리소스를 쓰고, 오프라인 화면으로 넘어가는 것도 늦어진다. WL_CONNECT_FAILED 를 감지하면 빨간 Password Err! 토스트를 한 번 띄우고 즉시 라디오를 끄고 오프라인 스크롤 모드로 고정하게 했다.
이 판단도 4편에서 뒤집힌다. 전파가 약해도 같은 코드가 뜨기 때문에, 한 번 삐끗하면 영영 오프라인으로 굳어 버린다.
문제 3 — 배경에 푸르스름한 게 남는다
부팅 초반에 배경이 완전한 검정이 아니라 푸르스름하게 떠 있었다. 아무것도 그리지 않은 영역인데도 그랬다.
당시 내린 진단은 이랬다. ICN2038S 드라이버 칩 내부의 static 래치에 잔류 전하가 남아 있고, 시프트 레지스터 초기화가 미흡해서 생긴다.

우연히 발견한 현상
정적 화면에서는 잔상이 계속 유지되는데, 가로 스크롤이 패널 256px 전체를 한 번 훑고 지나가는 순간 배경이 순수 검정으로 밀려나고 그 뒤로는 정상이었다.
이걸 연출로 만들었다. 와이파이가 붙은 순간 WiFi Connected! 토스트를 띄우는데, 그 토스트가 4px 세로 레이저 선을 앞세워 1.8초에 걸쳐 화면을 가로지르도록 짰다. 지나간 자리는 0x0000 PURE BLACK으로 덮인다.
if (elapsed < 1800UL) {
// 1단계 [완벽 퍼지]: 4px 레이저 수직선이 PURE BLACK(0x0000)을 훑으며 잔류 전하 100% 방전
int sweepX = (int)((elapsed * 256UL) / 1800UL);
if (sweepX > 0) {
display.fillRect(0, 0, sweepX, 32, boxBg);
display.drawRect(2, 2, sweepX > 4 ? sweepX - 4 : 0, 28, boxBorder);
display.setTextSize(2);
display.setTextColor(C_WHITE, boxBg);
display.setCursor(textCursorX, 8);
display.print(toastText);
}
if (sweepX < 256) {
display.fillRect(sweepX, 0, 256 - sweepX, 32, C_BLACK);
display.drawFastVLine(sweepX, 0, 32, C_CYAN);
display.drawFastVLine(sweepX + 1, 0, 32, C_WHITE);
}
}
전체는 5단계로 짰다. 퍼지 스위핑 → 팝업 유지 → 역방향 스위핑으로 지우기 → 구분선 정착 → 시계와 안전수칙 슬라이드. 비밀번호 오류일 때는 3단계에서 바로 오프라인 모드로 빠진다.
여기서 초기 버전이 끝났다

정리하면 이렇다.
| 문제 | 당시 해결책 |
|---|---|
| API 호출 시 화면 15초 정지 | FreeRTOS 코어 0에 수신 태스크 분리 + 스테이징 버퍼 |
| 환경마다 다른 와이파이 | WiFiMulti 자동 스캔 · 모뎀 슬립으로 전압 안정 |
| 비밀번호 오류 시 무한 재시도 | 1회 감지로 확정하고 오프라인 고정 |
| 배경 푸른 잔상 | 가로 스위핑으로 PURE BLACK 강제 방전 |
네 가지 모두 화면에서 증상이 사라졌다. 하드웨어 전원부터 펌웨어, 폰트 에디터까지 1인으로 완성했고, 공장 설치를 준비하기 시작했다.
4편은 그 이야기다. 이 시리즈에서 가장 쓰고 싶었던 편이기도 하다.