[2026/03/29] First Start.
소나무 기운 , 전자제품 개발/생산
ESP32-C6-LCD-1.47 예제를 KODEX200 시세 표시기로 바꾸기 - 상
이번 프로젝트는 ESP32-C6-LCD-1.47 보드의 기본 예제를 바탕으로, KODEX200 ETF 값을 가져와 LCD에 표시하는 형태로 바꿔본 작업이다.
원래 예제는 디스플레이와 LVGL 구동 확인에 초점이 맞춰져 있었지만, 여기에 Wi-Fi 연결, HTTPS 통신, 주기적 데이터 갱신, UI 표시를 붙여 조금 더 실용적인 시세 보드 형태로 확장했다.
이 글에서는 예제 코드가 어떤 구조였는지, 그리고 어떤 부분을 바꿔 원하는 기능으로 발전시켰는지를 정리한다.
2026.03.30 - [ESP32] - ESP32-C6-LCD-1.47 예제를 KODEX200 시세 표시기로 바꾸기 - 하
📸 완성 화면

캡션: ESP32-C6-LCD-1.47 보드에서 KODEX200 시세를 표시하는 최종 화면. 예제 데모 UI를 정보 표시용 화면으로 바꿔 가격, 등락값, 등락률을 한 번에 확인할 수 있도록 구성했다.
이 프로젝트의 핵심은 새 애플리케이션을 처음부터 만드는 대신, 이미 동작하는 예제의 구조를 유지한 채 데이터 입력부와 화면 출력부를 목적에 맞게 바꿨다는 점이다. 결과적으로 LCD 출력, 무선 연결, 시세 갱신, 상태 표시가 하나의 흐름으로 묶인 작은 시세 보드가 되었다.
🎁 소스 코드
https://github.com/pineenergy/ESP32_kodex200

1. 개발 환경과 출발점
개발은 ESP-IDF 5.4.1 기준으로 진행했고, 대상 보드는 ESP32-C6-LCD-1.47이다. 플래시는 UART 방식으로 진행했고, LCD와 RGB LED, SD, Wi-Fi가 한 프로젝트 안에서 함께 동작하도록 구성했다. 이런 종류의 보드는 센서 없이도 네트워크 데이터와 UI를 바로 연결해볼 수 있다는 점에서, 예제 코드를 실용적인 결과물로 확장하기에 꽤 좋은 출발점이다.
[개발 환경]
VSCode
ESP-IDF 5.4.1
esp32c6 타깃
UART 플래시 방식
2. 원본 예제 구조는 어떻게 되어 있었나
프로젝트 구조를 이해하는 가장 빠른 방법은 main/main.c 를 먼저 보는 것이다.
이 파일에는 무선 기능 초기화, RGB 초기화, SD 초기화, LCD 초기화, LVGL 초기화, 그리고 최종 화면 생성까지의 흐름이 순서대로 들어 있다. 임베디드 프로젝트에서는 초기화 순서가 안정성과 직접 연결되기 때문에, 기능을 추가하더라도 이 순서를 크게 흔들지 않는 것이 중요했다.
특히 이 프로젝트에서는 Wireless_Init 이후 LCD와 LVGL을 순차적으로 올리고, 마지막 루프에서 lv_timer_handler 를 주기적으로 호출하는 구조를 유지했다. 이 부분은 단순한 보일러플레이트처럼 보여도 실제로는 UI가 계속 갱신되기 위한 핵심 조건이다. 예제 기반 프로젝트를 수정할 때는 바로 기능을 덧붙이기보다, 먼저 이런 앱 시작 흐름을 읽고 어디에 무엇을 끼워 넣을지 판단하는 것이 훨씬 안전하다.
코드를 조금 더 들여다보면 main/main.c 부근의 호출 순서는 사실상 의존 관계를 드러낸다. Wireless_Init 은 이후 화면에 표시할 데이터의 공급원을 준비하고, RGB_Init 과 RGB_Example 은 별도 태스크 기반 상태 표시를 시작한다. 그다음 SD와 LCD를 준비한 뒤 LVGL_Init 과 Lvgl_Example1 을 호출하는데, 이 순서 덕분에 화면 객체가 만들어지는 시점에는 이미 주변 장치 초기화가 끝난 상태가 된다. 즉, main.c 는 단순 시작 파일이 아니라 각 모듈의 책임이 어디서 연결되는지를 가장 압축적으로 보여주는 파일이다.
void app_main(void)
{
Wireless_Init();
Flash_Searching();
RGB_Init();
RGB_Example();
SD_Init(); // SD must be initialized behind the LCD
LCD_Init();
BK_Light(50);
LVGL_Init(); // returns the screen object
/********************* Demo *********************/
Lvgl_Example1();
// lv_demo_widgets();
// lv_demo_keypad_encoder();
// lv_demo_benchmark();
// lv_demo_stress();
// lv_demo_music();
while (1) {
// raise the task priority of LVGL and/or reduce the handler period can improve the performance
vTaskDelay(pdMS_TO_TICKS(10));
// The task running lv_timer_handler should have lower priority than that running `lv_tick_inc`
lv_timer_handler();
}
}
특히 마지막 while 루프에서 lv_timer_handler 를 반복 호출하는 부분은 단순 반복문이 아니라 LVGL 화면이 계속 살아 있도록 만드는 핵심 루프다. 이 한 장의 캡처만으로도 예제 기반 프로젝트를 어떻게 확장했는지를 꽤 설득력 있게 보여줄 수 있다.
2-1. 함수 호출 순서로 보는 전체 동작
ㅁ 함수별 설명 요약
1. 앱 진입과 모듈 연결
함수: app_main
위치: main/main.c
역할: 네트워크, RGB, LCD, LVGL을 초기화하고 메인 루프에서 lv_timer_handler 를 반복 호출한다.
2. 네트워크 시작점
함수: Wireless_Init
위치: main/Wireless/Wireless.c
역할: NVS 초기화 후 WIFI_Init 태스크를 생성한다.
3. Wi-Fi 연결과 시세 태스크 기동
함수: WIFI_Init
위치: main/Wireless/Wireless.c
역할: Wi-Fi 스택 초기화, 이벤트 핸들러 등록, 연결 시도, 시간 동기화 후 etf_price_task 생성.
4. 시세 수집 메인 루프
함수: etf_price_task
위치: main/Wireless/Wireless.c
역할: 주기적으로 fetch_etf_prices 를 호출하고 전역 상태를 갱신한다.
5. 데이터 소스 조회와 정규화
함수: fetch_etf_prices
위치: main/Wireless/Wireless.c
역할: Yahoo 조회 우선, 실패 시 Naver fallback, 결과를 공통 포맷으로 정리.
6. Yahoo/Naver 파싱 핵심
함수: parse_symbol_quote, parse_itemcode_quote
위치: main/Wireless/Wireless.c, main/Wireless/Wireless.c
역할: 문자열 기반 JSON 필드 추출, 가격/등락/등락률 값 변환.
7. UI 생성 시작점
함수: Lvgl_Example1, Onboard_create
위치: main/LVGL_UI/LVGL_Example.c, main/LVGL_UI/LVGL_Example.c
역할: 스타일 초기화 후 시세 표시 레이아웃을 만든다.
8. UI 실시간 갱신
함수: example1_increase_lvgl_tick
위치: main/LVGL_UI/LVGL_Example.c
역할: ETF_Update_Sequence 를 기준으로 진행 바와 텍스트/색상을 갱신한다.
9. 보조 상태 표시
함수: _RGB_Example
위치: main/RGB/RGB.c
역할: 데이터 유효 여부와 등락 방향에 따라 LED 색상을 변경한다.
3. KODEX200 데이터를 가져오기 위해 추가한 것들
실제 시세 표시기로 동작하려면 네트워크 계층이 먼저 안정적으로 붙어야 한다. 이 프로젝트에서는 main/Wireless/Wireless.c 에서 Wi-Fi 연결, SSID 후보 전환, 시간 동기화, HTTPS 요청과 같은 흐름을 담당한다. 단일 SSID만 고정해 두는 대신 여러 후보를 배열로 관리하고 실패 시 다음 후보로 넘어가도록 만든 점은 실제 사용성을 고려한 설계다. 공유기 위치나 설치 장소가 바뀌면 같은 장치라도 네트워크 조건이 꽤 달라질 수 있기 때문이다.
또 하나 중요한 포인트는 HTTPS 요청 전에 시간을 맞추는 처리다. main/Wireless/Wireless.c 에는 SNTP로 시스템 시간을 맞추는 로직이 들어 있는데, 인증서 검증이 필요한 연결에서는 시간이 크게 틀어져 있으면 요청 자체가 실패할 수 있다. 데스크톱 환경에서는 거의 의식하지 않는 부분이지만, 임베디드 장치에서는 꽤 자주 부딪히는 문제다.
실제 시세 조회는 Yahoo Finance 쿼리를 먼저 시도하고, 실패 시 Naver Finance 기반 조회로 fallback 하는 구조로 짜여 있다. 이 부분은 main/Wireless/Wireless.c 부근에서 확인할 수 있다. 즉, 단순히 값을 받아오는 것이 아니라 데이터 소스 실패에도 어느 정도 대응하도록 설계된 셈이다.
또한 종목 심볼과 갱신 주기는 코드에 하드코딩하지 않고 main/Kconfig.projbuild 에서 설정값으로 분리했다. 이런 구조 덕분에 KODEX200 외 다른 종목으로 바꾸거나 업데이트 주기를 조정할 때도 코드를 크게 수정할 필요가 없다.
여기서 흥미로운 점은 JSON 처리를 무거운 파서 라이브러리로 하지 않고, 문자열 검색 기반으로 필요한 필드만 직접 뽑아낸다는 것다. main/Wireless/Wireless.c 의 parse_symbol_quote 는 symbol, regularMarketPrice, regularMarketChange, regularMarketChangePercent 같은 키를 strstr 로 찾고 strtod 로 숫자로 변환한다. 구현은 단순하지만, 필요한 값이 몇 개 되지 않는 임베디드 환경에서는 메모리 사용량과 코드 복잡도를 낮출 수 있다는 장점이 있다. 대신 응답 JSON 구조가 바뀌면 쉽게 깨질 수 있다는 점은 분명한 trade-off 다.
fallback 쪽도 비슷하다. main/Wireless/Wireless.c 의 parse_itemcode_quote 는 Naver 응답에서 종목 코드와 현재가, 전일 대비, 등락률을 직접 찾아 해석한다. 특히 rf 플래그를 보고 상승과 하락 부호를 다시 맞추는 부분은, 단순 숫자 파싱만으로는 부족한 시장 데이터 해석 로직이 어디에 들어가야 하는지를 잘 보여준다. 즉, 이 모듈은 단순 HTTP 클라이언트가 아니라 “시세 데이터 정규화 계층”에 더 가깝다.
또 한 가지 눈에 띄는 건 상태 공유 방식이다. main/Wireless/Wireless.h 에 선언된 전역 상태 값들은 네트워크 태스크, UI, RGB 표시가 모두 참조하는 공용 데이터다. etf_price_task 가 가격과 등락폭, 상태 문자열, 업데이트 시퀀스를 갱신하면 LVGL 타이머와 RGB 태스크가 이를 읽어 각자 화면과 LED를 바꾼다. 구조는 단순하지만, "수집", "표시", "보조 표시"를 느슨하게 분리하면서도 별도 메시지 큐 없이 연결했다는 점에서 꽤 실용적이다.
static esp_err_t fetch_etf_prices(int32_t *kodex200,
int32_t *smr,
int32_t *kodex200_change,
int32_t *smr_change,
int32_t *kodex200_change_bp,
int32_t *smr_change_bp)
{
esp_err_t err;
char *response = NULL;
int total = 0;
char yahoo_url[256] = {0};
snprintf(yahoo_url,
sizeof(yahoo_url),
"https://query1.finance.yahoo.com/v7/finance/quote?symbols=%s,%s",
CONFIG_APP_STOCK_SYMBOL_KODEX200,
CONFIG_APP_STOCK_SYMBOL_SMR);
err = http_get_to_buffer(yahoo_url, 8000, &response, &total);
int32_t p1 = -1;
int32_t p2 = -1;
int32_t c1 = 0;
int32_t c2 = 0;
int32_t r1_bp = 0;
int32_t r2_bp = 0;
if (err == ESP_OK)
{
err = parse_symbol_quote(response, CONFIG_APP_STOCK_SYMBOL_KODEX200, &p1, &c1, &r1_bp);
if (err == ESP_OK)
{
err = parse_symbol_quote(response, CONFIG_APP_STOCK_SYMBOL_SMR, &p2, &c2, &r2_bp);
}
}
if (err == ESP_OK)
{
free(response);
*kodex200 = p1;
*smr = p2;
*kodex200_change = c1;
*smr_change = c2;
*kodex200_change_bp = r1_bp;
*smr_change_bp = r2_bp;
return ESP_OK;
}
ESP_LOGW("ETF", "Yahoo fetch/parse failed, trying Naver fallback: %s (%d)", esp_err_to_name(err), err);
if (response != NULL)
{
ESP_LOGW("ETF", "Yahoo response snippet: %.120s", response);
free(response);
response = NULL;
}
char code1[16] = {0};
char code2[16] = {0};
symbol_to_item_code(CONFIG_APP_STOCK_SYMBOL_KODEX200, code1, sizeof(code1));
symbol_to_item_code(CONFIG_APP_STOCK_SYMBOL_SMR, code2, sizeof(code2));
err = fetch_naver_price_by_code(code1, &p1, &c1, &r1_bp);
if (err == ESP_OK)
{
err = fetch_naver_price_by_code(code2, &p2, &c2, &r2_bp);
}
if (err != ESP_OK)
{
return err;
}
*kodex200 = p1;
*smr = p2;
*kodex200_change = c1;
*smr_change = c2;
*kodex200_change_bp = r1_bp;
*smr_change_bp = r2_bp;
ESP_LOGI("ETF", "Using Naver fallback data source");
return ESP_OK;
}
fetch_etf_price()는 먼저 Yahoo에 접속하여 데이터 취득을 시도하고 실패시 naver에 접속하여 데이터 취득을 추가 시도한다. getch_naver_price_by_code()를 사용하여 추가 시도를 하게 된다.
마무리
다음회에는 LCD의 표시 변경 사항과 부가기능으로 등락폭에 따라서 RGB 색상을 변경하는 기능을 소개하도록 한다.
프로그램은 바이브 코딩으로 제작되었으며 바이브 코딩으로 접근했던 방법에 대해서도 설명해 보고자 한다.
참고문헌
마무리5
틀린 부분이나 질문은 댓글 달아주세요.
즐거운 하루 보내세요. 감사합니다.
'ESP32' 카테고리의 다른 글
| ESP32-C6-LCD-1.47 예제를 KODEX200 시세 표시기로 바꾸기 - 하 (0) | 2026.03.30 |
|---|---|
| Wi-Fi AT 명령어들 (0) | 2025.08.24 |
| 🚀 ESP-AT 펌웨어와 AT 명령으로 간편하게 ESP32 제어하기 (0) | 2025.08.18 |
| ⌨️ AT Command Set — ESP-AT 명령어 세트 안내 (0) | 2025.08.14 |
| 📦 AT Binary Lists — ESP-AT 펌웨어 구성 & 선택 가이드 (0) | 2025.08.14 |
댓글