차량 OBD-II에서 읽은 속도(또는 그에 대응하는 값)에 따라 TFT 위 캐릭터 애니메이션이 바뀌는 대시보드형 IoT 펌웨어의 Arduino/ESP32 프로토타입입니다. macOS RunCat(부하에 따라 달리는 고양이)에서 아이디어를 가져와, 자동차 속도에 맞추는 방향으로 설계했습니다.
현재 저장소의 sketch_prototype은 실제 OBD 블루투스 연동 전 단계로, 시리얼 모니터에서 속도를 넣어 OBD를 흉내 내고, GIF를 RGB565 PROGMEM 헤더로 빌드에 넣어 재생합니다.
- 동작 요약
- 아키텍처
- 디렉터리 구조
- 하드웨어
- 빌드·실행
- 애니메이션 파이프라인 (GIF → C 헤더)
- 캐릭터 팩·플래시 절약 (
CAT_EMBED_PACK) - 재생 규칙 (정지/주행 클립 선택)
- 시리얼 명령
- 설정 매크로 (
cat_anim_profile.h) - 트러블슈팅
- 로드맵
- TFT_eSPI + TFT_eSprite로 더블 버퍼링에 가깝게 그린 뒤
pushSprite로 화면에 올립니다. - 애니메이션 데이터는 파일시스템이 아니라 Python으로 생성한 C 헤더 안의
PROGMEM배열입니다. - 캐릭터는
CatCharacterPack단위로 묶이며, 슬롯마다sleep/run/ (선택)idle/error클립을 둘 수 있습니다. - 내부 변수
g_catAnimObdKmh(km/h로 간주)가 0 이하면 정지 계열, 0 초과면 주행 계열 클립을 우선합니다.sketch_prototype.ino는 시리얼로 받은 속도를 이 값에 반영합니다. - 프레임 간격은 속도에 따라
map으로 짧아지게 해 두었습니다(느리면 느리게, 빠르면 빠르게 넘김).
flowchart LR
subgraph build [빌드 시]
GIF[GIF 소스]
PY[tools/gif_to_rgb565.py]
HDR[generated/*.h PROGMEM]
GIF --> PY --> HDR
end
subgraph fw [펌웨어]
REG[cat_anim_registry.h]
DRV[cat_anim_driver.h]
ANI[cat_animation.h]
INO[sketch_prototype.ino]
HDR --> REG
REG --> DRV
REG --> ANI
DRV --> ANI
INO --> ANI
ANI --> TFT[TFT_eSprite / TFT]
end
cat_anim_types.h:CatAnimAsset,CatAnimSlot,CatCharacterPack정의.cat_anim_registry.h: 생성된 헤더를 include하고,kCatCharacters[]에 각 팩의 슬롯 포인터를 연결.CAT_EMBED_PACK에 따라 포함 캐릭터가 달라집니다.cat_anim_profile.h: 플래시 팩 선택, 색 보정, 레이아웃( contain/cover ), 디폴트 캐릭터 등 빌드 타임 튜닝.cat_anim_driver.h:g_catAnimObdKmh와 로드된 슬롯 수에 따라 어떤CatAnimAsset*을 재생할지 결정.cat_anim_picker.h: 시리얼에서 캐릭터/클립 선택, 목록 출력.cat_animation.h: 현재 에셋 조회, 프레임 인덱스, RGB565 픽셀 스케일·그리기(drawPixel또는 옵션의pushImage버퍼).sketch_prototype.ino: 디스플레이 초기화, 스프라이트 생성(RAM 부족 시 축소 폴백), 시리얼 루프, HUD(Speed:xxx).
| 경로 | 설명 |
|---|---|
sketch_prototype.ino |
엔트리 스케치 |
cat_anim_*.h, cat_animation.h, cat_layout.h |
애니메이션·레지스트리·프로필 |
generated/<캐릭터_id>/ |
gif_to_rgb565.py로 만든 anim_*.h (용량 큼) |
data/animations/ |
소스 GIF 배치 권장 위치, ASSETS.txt에 워크플로 문서 |
tools/gif_to_rgb565.py |
GIF → RGB565 헤더 변환기 (Pillow 필요) |
| 부품 | 예시 |
|---|---|
| MCU | ESP32 (WROOM-32 등) |
| 디스플레이 | Waveshare 2.0" 240×320 ST7789 IPS |
| OBD2 (향후) | 블루투스 Classic SPP 어댑터 등 |
| ESP32 | Waveshare |
|---|---|
| GPIO 23 (MOSI) | SDA |
| GPIO 18 (SCK) | SCL |
| GPIO 2 | DC |
| GPIO 15 | CS |
| GPIO 4 | RST |
| 3.3V | VCC, BL |
| GND | GND |
실제 핀은 사용 중인 **TFT_eSPI User_Setup.h**와 일치해야 합니다. sketch_prototype.ino 주석에 적힌 것처럼, 물리 해상도·회전이 설정과 다르면 화면 일부만 노이즈처럼 보일 수 있습니다.
- Arduino IDE 또는 PlatformIO 등으로 ESP32 보드 패키지 설치.
- 라이브러리: TFT_eSPI (ST7789 등 보드에 맞게
User_Setup.h설정). - 이 폴더를 스케치 폴더로 열고
sketch_prototype.ino컴파일·업로드. - 시리얼 115200 baud. 부팅 후
TFT: WxH로그로 논리 해상도 확인.
tools/gif_to_rgb565.py는 GIF의 각 프레임을 지정 크기로 리사이즈한 뒤, 픽셀을 RGB565로 변환해 PROGMEM 배열과 프레임 포인터 테이블이 담긴 .h 파일을 생성합니다.
의존성: Python 3, Pillow (pip install pillow).
예시 (data/animations/ASSETS.txt와 동일한 패턴):
python tools/gif_to_rgb565.py data/animations/nyan/sleep.gif generated/nyan/anim_nyan_sleep.h --width 112 --height 98
python tools/gif_to_rgb565.py data/animations/nyan/run.gif generated/nyan/anim_nyan_run.h --width 112 --height 98--symbol: 출력 C 심볼 접두어(미지정 시 파일명 기반으로 정제).--resample:nearest,bilinear,bicubic,lanczos(기본lanczos).
생성 후 **cat_anim_registry.h**에 해당 헤더를 include하고 CatAnimAsset·kCatCharacters[] 한 줄을 추가해야 빌드에 반영됩니다. 자세한 슬롯 순서와 권장 폴더 레이아웃은 data/animations/ASSETS.txt를 참고하세요.
cat_anim_profile.h의 CAT_EMBED_PACK과 cat_anim_registry.h의 #if 분기가 맞물립니다.
| 값 | 의미 |
|---|---|
0 |
mario + nyan + cjemoj 전부 (개발용, 플래시 큼) |
1 |
mario만 |
2 |
nyan만 |
3 |
cjemoj만 |
#include되지 않은 generated/.../*.h는 바이너리에 올라가지 않습니다. 배포용으로는 팩 하나만 켜고 해당 캐릭터 GIF만 변환해도 됩니다.
로직은 cat_anim_driver.h의 catAnimResolvePlayingAsset에 정리되어 있습니다.
- 팩에 로드된 슬롯이 1개뿐이면: OBD 속도와 무관하게 그 클립만 반복.
- 2개 이상이면:
- 속도 ≤ 0:
SLEEP→IDLE→RUN→ERROR순으로, 존재하는 첫 클립. - 속도 > 0:
RUN→IDLE→SLEEP→ERROR순.
- 속도 ≤ 0:
슬롯 enum 순서는 cat_anim_types.h의 CatAnimSlot을 따릅니다.
| 입력 | 동작 |
|---|---|
list 또는 clist |
캐릭터·슬롯·현재 클립 파일명, OBD 흉내 속도 출력 |
c0, c1, … |
캐릭터 인덱스 선택 후 애니메이션 리셋·다시 그리기 |
n0, n1, … |
이전 API 호환: 캐릭터 인덱스 선택 |
n파일명.gif |
해당 파일명(대소문자 무시)을 가진 클립이 속한 캐릭터로 전환. sleep/idle이면 내부 속도 0, 그 외 1로 힌트 |
-120 ~ 120 (정수 한 줄) |
currentSpeed로 반영 후 catAnimSetObdKmh에 전달 (내부적으로 0~120으로 clamp) |
애니메이션 프레임 레이트는 속도가 높을수록 짧은 간격으로 presentAnimFrame이 호출되도록 map 되어 있습니다.
주요 항목만 요약합니다. 상세 주석은 헤더 파일을 참고하세요.
CAT_EMBED_PACK: 위 표 참고.CAT_ANIM_SWAP_RB: R/B 채널 스왑. TFT_eSPITFT_RGB_ORDER와 이중 적용되면 색이 어긋날 수 있어 보통0부터 시도.CAT_ANIM_INVERT_COLORS,CAT_ANIM_DIM_PERCENT: 색 반전·전체 밝기(100=원본).CAT_ANIM_USE_PUSH_IMAGE:0이면 픽셀 단위drawPixel,1이면 RAM에 전체 프레임을 채운 뒤pushImage(RAM 2배 사용).CAT_ANIM_FIT_CONTAIN:1이면 비율 유지·레터박스,0이면 cover(중앙 크롭).CAT_ANIM_LETTERBOX_FROM_ANIM_BORDER: 레터박스 배경을 고정색 대신 0번 프레임 모서리 평균색에 가깝게.CAT_DEFAULT_CHARACTER_INDEX,CAT_DEFAULT_GIF_LEAF: 기본 캐릭터·파일명 힌트 (CAT_EMBED_PACK1~3일 때 기본 인덱스는 0으로 재정의됨).
- 화면 오른쪽만 잡음·깨짐:
User_Setup.h의TFT_WIDTH/TFT_HEIGHT가 패널 물리 픽셀과 맞는지,setRotation후tft.width()/height()가 기대와 같은지 확인 (sketch_prototype.ino상단 주석 참고). - 색이 바뀜(하늘색·노랑 뒤바뀜 등):
tft.setSwapBytes/sprite.setSwapBytes와CAT_ANIM_SWAP_RB,TFT_RGB_ORDER조합을drawPixel모드에서 맞춘 뒤CAT_ANIM_USE_PUSH_IMAGE에도 동일하게 적용. - 스프라이트 생성 실패: 로그
Sprite fail (RAM 부족). 스케치는 스프라이트 크기를 단계적으로 줄이는 폴백이 있음. 해상도·CAT_ANIM_USE_PUSH_IMAGE·다른 RAM 사용을 점검. - 플래시 부족:
CAT_EMBED_PACK으로 캐릭터 한 종만 넣기, GIF 해상도·프레임 수 줄이기.
- Veepeak 등 OBD2 블루투스 페어링 후 실시간 속도를
catAnimSetObdKmh에 연결 - 실차 테스트
- SPIFFS/LittleFS 등으로 런타임 에셋 교체(선택)
- 모바일 앱·웹 설정 UI (별도 저장소 계획 시)
- 캐릭터 폴더 규칙, 레지스트리 수정 예:
data/animations/ASSETS.txt
OBDPet is an ESP32 + TFT_eSPI firmware prototype: GIFs are converted offline to RGB565 headers in flash (PROGMEM), grouped as character packs with sleep/run (and optional) slots. Serial input simulates OBD speed; catAnimResolvePlayingAsset picks the clip. See sections above for build steps, CAT_EMBED_PACK, and serial commands.