ESP32-S3 기반 아날로그 필름 현상용 로터리 프로세서 제어 펌웨어입니다. 스테퍼 모터로 현상 탱크를 정/역 교반하고, 약품 온도를 실시간 측정하며, 웹 UI와 물리 디스플레이(TFT + 로터리 인코더)로 레시피/수동 운전을 제어합니다.
v4.0에서 단일 .ino 모놀리식 구조를 PlatformIO 기반 C++ OOP로 전면 이관했습니다.
v4.4~v4.9.3: 안정성 전수 개선(PCNT 패닉 수정·노이즈 가드·운전 중 플래시 차단), 웹 OTA, 보드별 핀 배치(칩 MAC 자동 선택),
무디스플레이·N8R2 지원, WiFi 검색, 온도 보정, 정전·리셋 복구, 하드웨어 감시(DIAG/VM),
기존 제어 화면에서 여러 대 동시 제어(자동 탐색), AP 이름 = 접속 주소(이름.local).
🧾 현재 상태·남은 작업·실측 기록·변경 이력:
ISSUES.md(프로젝트 현황의 단일 기준)
📖 전체 설명서(HTML):
docs/manual.html— 브라우저로 열면 사이드바 네비게이션이 있는 단일 파일 설명서. 하드웨어·전원·아키텍처·모듈 API·상태머신·크래시 분석·웹 API·트러블슈팅을 한 곳에 정리.
| 항목 | 사양 |
|---|---|
| MCU | ESP32-S3 N16R8 (16MB Flash / 8MB OPI PSRAM) |
| 모터 | NEMA17 + 3.71:1 유성기어, 1.8°/스텝 (200 step/rev) |
| 드라이버 | TMC2209 (STEP/DIR/EN), StealthChop, 1/8 마이크로스텝 → 1600 step/rev |
| 온도센서 | MAX31865 + PT100, 3선식, RREF 412Ω(실측) / RNOMINAL 100Ω — 현재 보드 불량으로 격리(Cfg::TEMP_SENSOR_PRESENT=false) |
| 디스플레이 | ST7789 320×240 TFT (하드웨어 FSPI) — 패널 색반전 보정 invertDisplay(false) |
| 입력 | EC11 로터리 인코더(A/B/PUSH) + 확정 버튼(KO) |
| 전원 | USB-PD 100W → CH224K 디코이로 20V 트리거 → 모터 직결 + 벅(20V→5V)→ESP (↓ 전원 아키텍처) |
- 출력축 RPM → 모터 RPM × 3.71 → step/s
- 출력 80RPM(최대) = 모터 297RPM = 7,915 step/s (
MAX_SPEED) - 80 초과 시 탈조는 코드 결함이 아니라 토크-속도 한계(back-EMF). 권장 운전 75RPM.
정방향 rotIntSec 구동 → 감속 정지 → REST(EN=HIGH, 코일 전류 차단 = 관성 안정 + 발열 억제) 2초 →
역방향 구동 → … 무한 반복. 레시피 모드에서는 각 단계가 속도/지속시간/방향전환주기를 가집니다.
REST는 가속도 곡선 감속이 완전히 끝난 뒤(!isRunning()) 시작하므로, 2초는 관성 정지 후의 순수 휴식 구간입니다.
USB-C PD 충전기 (100W)
│ USB-C
▼
[ CH224K ] USB-PD 디코이/트리거 — CFG 저항으로 20V 선택
│ 20V
├───────────────────────────────► TMC2209 VMOT (모터 전원, 직결)
│
├─[ 470µF 캐패시터 ] 벌크 — 정/역 전환 트랜지언트 흡수
│
└──► [ 벅 컨버터 MP1584EN ] 20V → 5V 고정
│ 5V
▼
ESP32-S3 DevKit ─ 온보드 LDO → 3.3V ─ 로직 / 센서 / 디스플레이 / 인코더
- CH224K (USB-PD 디코이): PD 소스와 협상해 20V 고정 출력. 전압 선택은 CFG 핀 저항으로 설정. 100W(20V/5A) 소스라 모터+로직 피크 전류에 여유 충분.
- 470µF 캐패시터: 20V 레일 벌크 캐패시터. 스테퍼 정/역 전환 시 역기전력·인덕티브 트랜지언트를 흡수. (계산상 모터발 레일 상승은 +1~2V 수준 — 캡이 충분히 억제)
- 벅 컨버터 (20V→5V): ESP+디스플레이 공급(부하 ~0.5A). 부하는 가볍지만 20V→5V 강하비가 커 발열·입력마진(MP1584 최대 26V)이 빠듯 → 양품 + 입력정격 여유 부품(XL4015/LM2596HV 등 36V급) 권장. (과부하가 아니라 개체 불량·발열로 사망 사례 있음)
- 인러시/핫플러그: PD가 5V→20V로 부드럽게 램프업 + 전류제한 → 별도 NTC/소프트스타트/안티스파크 불필요. 단, 살아있는 20V에 커넥터 직삽은 지양.
- 전원 스위치(선택): 비상정지는 모터 전원(20V→TMC) 차단용으로 두는 게 안전(펌웨어 소프트 e-stop의 물리 백업). 꽂을 땐 ON 유지, 비상시에만 OFF.
모터는 Core 1 + FastAccelStepper 하드웨어 ISR이라 디스플레이/웹 부하와 무관하게 타이밍 보장. 벅이 죽어도 모터 전원(20V 직결)은 영향 없음.
─ A변 (3v3 쪽) ──────────────────────────────────────────────────
ST7789 SCK=GPIO12 MOSI=GPIO11 CS=GPIO10 DC=GPIO9 RST=GPIO8 BL=GPIO18 (FSPI)
EC11 A=GPIO17 B=GPIO16 PUSH=GPIO15
KO 버튼 GPIO7
─ B변 (V5in 쪽) ─────────────────────────────────────────────────
MAX31865 CS=GPIO13 MOSI=GPIO14 MISO=GPIO21 CLK=GPIO47 (소프트웨어 SPI, 연속 4핀)
TMC2209 EN=GPIO40 DIR=GPIO41 STEP=GPIO42 (EN: LOW=코일활성, HIGH=차단, 연속 3핀)
위는 기본 배치(DEFAULT_PINS, 바디1)입니다. 보드마다 배선이 달라 핀은 src/PinMap.h 한 파일에서 관리합니다.
- 부팅 시 칩 MAC으로
BOARDS[]에서 배치를 자동 선택, 미등록 보드는DEFAULT_PINS. 같은.bin을 어느 보드에 OTA해도 안전. - 항목: 핀 17개 +
hasDisplay(false면 TFT/인코더 핀을 전혀 구동하지 않고 화면 태스크 미기동) +rtdWires(0=온도센서 격리, 2/3/4=결선)diagPin·vmPin·vmRatio(하드웨어 감시, 0=미배선 — 아래 「안전 설계」).
- 금지 GPIO(strapping 0/3/45/46, USB 19/20, Flash·PSRAM 26~37, UART0 43/44, RGB LED 48)·중복 핀·인코더 핀 ≥32는
static_assert로 빌드 단계에서 차단. - 새 보드 등록: 웹 설정 → 디바이스 정보의 MAC 확인 →
BOARDS[]에 한 줄 추가 → 빌드·OTA.
| 보드 | MAC | 사양 | 배치 |
|---|---|---|---|
| 바디1 | A0:F2:62:E5:D9:B0 |
N16R8, 디스플레이 | 기본 배치, 온도센서 격리(불량 보드) |
| 바디2 | D0:CF:13:59:14:78 |
N8R2, 무디스플레이 | STEP 5 · DIR 6 · EN 7 · MAX CS 2 / MOSI 38 / MISO 39 / CLK 40, PT100 2선 모드 |
시간 크리티컬한 모터 제어와 비차단 통신/UI를 코어로 분리합니다.
| 코어 | 담당 |
|---|---|
Core 1 (loop()) |
모터 상태머신 + 레시피 타이밍 (시간 크리티컬). FastAccelStepper 하드웨어 ISR이 스텝 펄스를 독립 생성 |
| Core 0 | WiFi 스택 + AsyncWebServer + tempTask(온도 1Hz) + displayTask(UI 25Hz) |
핵심 규약: 웹 핸들러·디스플레이(Core 0)는 모터/레시피 상태를 직접 건드리지 않습니다.
모든 조작은 CommandQueue로 enqueue → Core 1의 loop()이 dispatch하여 단일 코어에서만 상태를 변경합니다.
이로써 mCtx/rCtx/stepper 레이스를 원천 제거합니다.
- CommandQueue — 일반 명령(시작/정지/일시정지/확인/수동)을 직렬 전달
- 비상정지 플래그 — 큐가 가득 차도 유실되면 안 되므로 큐를 우회하는
volatile플래그 - 레시피 스테이징 —
/api/start는 뮤텍스 보호 버퍼에 적재만 하고,loop()이 안전한 시점에 꺼내 적용 - 뮤텍스 — 온도 스냅샷, 레시피
steps벡터, 스테이징 버퍼 각각 보호
main.cpp App 코디네이터 — 객체 소유·배선, setup/loop, 상태 전이
Config.h 물리 상수 (Cfg::, constexpr) · FW_VERSION · 노이즈/온도 임계값
PinMap.* 보드별 핀 배치 + hasDisplay + rtdWires (칩 MAC 자동 선택, static_assert 검증)
NoiseGuard.* 입력 노이즈 버스트 감시 → 패널 입력 차단/모터 정지, 연속 비정상 리셋 시 모터 잠금
HwSafety.* TMC2209 DIAG / 모터 전원 VM 감시 → 즉시 차단·잠금 (PinMap에 배선된 보드만)
Types.h 공유 도메인 타입 (MotorState, StepInfo, CmdType, Cmd)
CommandQueue.h 크로스코어 명령 큐 + 비상정지 플래그
ISaver.h 화면보호기 설정 인터페이스 (+ 무디스플레이용 NullSaver)
MotionController.* TMC2209 스테퍼 상태머신 (레시피 무관)
RecipeRunner.* 레시피 단계 시퀀서 (steps + 뮤텍스 소유)
TemperatureSensor.* MAX31865 — Core 0 태스크, 뮤텍스 보호
WifiManager.* AP(+STA, 항상 DHCP), AP 이름=mDNS 이름, 자체 DNS 태스크, Preferences
WebServer.* ESPAsyncWebServer 라우트 + 상태 JSON + 스테이징
web_assets.h 웹 UI HTML/CSS/JS (PROGMEM 임베드)
web_multi.h (구) 멀티 보드 페이지 /multi — IP 수동 입력. 집 공유기 공유 구성용으로 유지
DisplayUI.* ST7789 + 인코더 + 버튼 UI (Core 0 태스크, ISaver 구현)
모든 서브시스템 객체를 소유하고 참조를 주입해 배선합니다. 시스템 상태 전이는 여기에 집약됩니다.
stopAll()— 비상정지(모터 즉시 정지 + 레시피 초기화 + 모드 리셋)safeStop()— 가속도 곡선 따라 감속 후 정지 (탈조/관성 충격 없음)manualStart()— 수동 운전 시작dispatch()— 큐에서 꺼낸 명령을 위 함수로 분기setup()— 부팅 핀 안전 고정 → 서브시스템 초기화 → 태스크 기동loop()— 비상정지 확인 → 명령 dispatch → 레시피 스테이징 적용 → DNS/모터/레시피 업데이트
FastAccelStepper로 펄스를 생성하고 EN 핀은 수동 제어(REST 구간 코일 차단)합니다.
레시피를 모릅니다 — 단계 종료는 requestStepStop()으로 받고, 감속 완료는 takeStepStopped() 이벤트로 알립니다.
상태: IDLE → RUN_FWD → STOP_FWD → REST → RUN_REV → STOP_REV → REST → …
레시피 단계 종료 시 STOP_RECIPE, 안전 정지 시 STOP_SAFE.
steps 벡터를 뮤텍스로 보호(웹 buildStatus가 Core 0에서 동시 읽기)하며,
모터는 MotionController 경유로만 구동합니다.
load()→startStep(0)→ 단계 지속시간 경과 시requestStepStop()- → 감속 완료(
takeStepStopped) →waitConfirm(약품 교체 대기) →confirm()→ 다음 단계 pauseToggle()/snapshot()(상태 표시용 스냅샷)
Core 0 전용 태스크에서 MAX31865를 1Hz 폴링(소프트웨어 SPI). 모터 코어에 영향 없음. fault는 즉시 clear하지 않고 연속 5회 누적 후에만 시도 → 단선/접촉불량을 UI에서 끊김 없이 관찰.
- 결선 모드: 보드별
PinMap.hrtdWires(2/3/4) — 보드 솔더점퍼와 일치 필수. - 진단: 부팅 시 threshold 레지스터 SPI 라운드트립 자가진단(칩·통신 검증) + fault 비트 디코드 + raw RTD 값 출력.
- 격리:
rtdWires=0이면 init·폴링 태스크 미생성 →temperature()=TEMP_UNREAD, fault 없음(화면--.-). 바디1은 센서 보드 불량으로 격리. - 오류 판정: SPI 미통신(0x01), 유효범위(-20~100°C) 밖(0x02)을 칩 fault 비트와 함께 처리. 1초에 2°C 넘는 점프는 스파이크로 버림.
- 보정: 측정저항에
R = gain·R_meas + offset선형 보정(NVStcal, 보드별). 웹 설정 "온도 보정"에서 1점/2점(예: 0°C 얼음물 + 38°C).
홈 WiFi(STA) 미설정이면 AP 단독, 설정 시 AP+STA(항상 DHCP — 고정 IP 옵션 없음). 설정은 Preferences wifi에 영구 저장.
- AP 이름 = 접속 주소: AP SSID를 mDNS 호스트 이름으로 씀 →
http://이름.local. 새 SSID는 규칙(영문 소문자·숫자·-, 1~32자, 하이픈 시작·끝 금지)을 웹 입력에서 변환·서버에서 검증(위반 시 400). 기존 규칙 밖 SSID는 유지하고 이름만 변환(FilmProcessor→filmprocessor.local). - 자체 DNS(Core 0 태스크): AP 클라이언트 질의에 자기 이름 → AP IP, 연결된 다른 보드 이름 → mDNS로 찾아 대리 응답(60초 캐시), 그 외 NXDOMAIN.
.local을 못 푸는 안드로이드도 이름으로 접속. - 보드 번호(1~9): AP 대역
192.168.(3+N).1— 1번=4.1, 2번=5.1. 다른 보드 AP에 STA로 붙어도 대역 충돌 없음. AP 최대 접속 10. - STA 재연결 백오프 30s→최대 5분(즉시 재시도 시 채널 스캔으로 AP 통신이 끊기던 문제 방지), WiFi 절전 OFF.
- 슬레이브 보드: 설정 → 홈 WiFi [검색] → 마스터 AP 선택 → 비밀번호 → 저장 (DHCP로 연결).
- 폰을 마스터 AP에 연결 → 마스터 웹 화면 상태 탭 🔗 동시 제어 켜기 →
/api/peers(마스터 AP 접속 기기 IP) + 각 IP의/api/status로 보드를 자동 발견(MAC으로 기억). - 이 화면의 시작·정지(재시도)·안전정지·일시정지·다음 단계·건너뛰기·단계 이동·수동 운전이 선택된 보드 전체에 전달. 일시정지는 보드별 상태에 맞춰(멈출 보드만/재개할 보드만), 다음 단계는 확인대기 보드에만 → 보드 간 상태 엇갈림 방지. 실패한 보드는 알림.
- 모든 보드가 집 공유기에 함께 붙은 구성은 자동 탐색 대상이 아님 →
/multi에서 IP 입력.
ESPAsyncWebServer 기반. HTML은 web_assets.h에 PROGMEM 임베드.
의존성(motion/recipe/temp/cmd/wifi/saver)을 begin()에서 주입받아 소유하지 않습니다.
대용량 JSON(/api/start, 레시피 저장)은 청크 분할 수신 + reserve로 힙 단편화를 억제합니다.
ST7789 320×240 + EC11 인코더 + KO 버튼. U8g2로 한글 UTF-8 폰트 출력.
캐시 기반 부분 렌더링으로 깜빡임 제거. initTft에서 invertDisplay(false)로 패널 색반전 보정(이 패널은 INVON이 색을 네거티브로 반전).
ISaver를 구현해 웹에서 화면보호기 설정을 읽고 씁니다.
화면보호기 — RGB565 애니메이션 (온디바이스 GIF 디코드 완전 제거):
- 온디바이스 GIF 디코드(AnimatedGIF)는 이 빌드에서 실패 → 라이브러리·코드 모두 제거하고 브라우저에서 사전 인코딩한 RGB565 프레임을 기기는 디코드 없이 blit.
- 업로드 시 **브라우저(
web_assets.h)**가 GIF를 160×120 RGB565ANM1애니메이션으로 트랜스코드(최대 8프레임, 비율 유지 레터박스) 후 전송. 기기는 순수 blit만. - 우선순위:
/saver.anim(업로드) >/saver.jpg(정적) > 임베드 Nyan(기본). /saver.anim포맷:"ANM1"+ W,H,frames,delay(u16 LE) + frames×W×H×2 RGB565(LE). 기기는 160×120 프레임을 PSRAM 로드 후 2×→320×240로 blit.- 임베드 Nyan은
tools/gen_nyan.py가 생성한nyan_frames.h(flash 상수, RAM 0).
크래시 방지 — 플래시op ↔ 인코더 인터럽트 게이팅: WiFi NVS·LittleFS 쓰기 등 플래시 연산이 캐시를 끄는 순간 인코더 인터럽트(모터 EMI로 유발)가 뜨면 "Cache disabled but cached memory region accessed" Core 1 panic이 난다. encISR은 REG_READ+DRAM_ATTR 테이블로 IRAM-safe화했지만 Arduino attachInterrupt 디스패처가 플래시를 건드려 잔존. 최종 차단 = 모터 비-IDLE이면 모든 플래시op 금지 — 화면보호기 진입/렌더 안 함, 업로드는 시작 거부 + 진행 중 모터 기동 시 중단·부분파일 삭제, 삭제는 409. (PCNT 인코더로의 전환은 FastAccelStepper가 PCNT를 점유해 불가)
페이지: STATUS / 레시피경고 / 수동메뉴 / 속도·주기·화면보호기시간 편집 / 정보 / 화면보호기.
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | / |
웹 UI (HTML) |
| GET | /api/status |
모터/레시피/온도/STA 상태 JSON |
| POST | /api/start |
레시피 시작 (steps 배열, 청크 수신 → 스테이징) |
| POST | /api/stop |
비상정지 (큐 우회 플래그) |
| POST | /api/pause |
일시정지/재개 토글 |
| POST | /api/confirm |
다음 단계 확인 (약품 교체 후 진행) |
| POST | /api/manual |
수동 운전 (속도/방향/사이클/주기) |
| GET·POST | /api/settings |
WiFi 설정 조회/저장(저장 시 재부팅) |
| GET·POST | /api/recipes/load·/save |
레시피 JSON 불러오기/저장(LittleFS, 원자적 교체) |
| GET·POST | /api/saver/settings |
화면보호기 활성/타임아웃 |
| POST | /api/skip · /api/safestop |
단계 건너뛰기 / 감속 정지 (둘 다 감속 후 동작) |
| POST | /api/goto |
{"step":N} 지정 단계로 이동 (진행도 바 클릭 → 확인창) |
| GET | /api/recipe |
실행 중 레시피의 단계 목록(이름·시간·RPM) — 모든 클라이언트가 같은 진행도 바 |
| GET | /multi |
멀티 보드 제어 페이지 (상태·정지·일시정지·다음단계·레시피 동시 시작) |
| POST | /api/ota |
펌웨어 업로드(X-OTA 헤더 필수). 사양(플래시 용량) 불일치·운전 중·동시 업로드 거부. 크래시 루프 시 이전 펌웨어로 자동 롤백 |
| GET | /api/wifi/scan |
주변 WiFi 비동기 검색(?refresh=1 강제). 같은 SSID 병합·신호순 |
| POST | /api/temp/cal |
온도 보정 {"gain","offset"} 또는 {"reset":true} |
| GET | /api/peers |
이 보드 AP에 접속한 기기 IP 목록 {"ap":[…],"noIp":n} (동시 제어 자동 탐색) |
| POST | /api/recovery |
정전 복구 선택 `{"action":"resume" |
| POST | /api/hwfault/clear |
하드웨어 고장 해제 (원인이 사라졌을 때만 실제 해제) |
| POST·DELETE | /api/saver/image |
화면보호기 업로드(매직바이트로 JPEG/GIF/ANM1 판정 → /saver.{jpg,gif,anim}, 파일핸들 유지로 대용량 안정)/삭제. 모터 운전 중 차단(업로드 거부·진행 중 중단, 삭제 409 — 플래시op 크래시 회피) |
/api/status 추가 키: fw·variant·name·boardId·mac·pinProfile·upSec·heapFree/Min/Max·guard·resetReason·rssi·apClients·tempFaultCode·rtdWires·rtdOhm·calGain/calOffset·hwFault·vm·recovery(복구 대기 시 이름·단계·경과·상태).
운전 중 차단: 모터 회전·감속 중이거나 레시피 진행(일시정지·확인대기 포함) 중이면 플래시 쓰기·재부팅이 따르는 요청(설정 저장, 레시피 저장, 화면보호기, OTA, 온도 보정)은 모두 409. 웹은 레시피 편집을 "저장 대기"로 미뤘다가 정지 후 자동 저장.
칩 사양이 2종이라 빌드 환경도 2개입니다. 플래시 용량·PSRAM 종류는 빌드 타임 설정이라 바이너리가 다릅니다.
| 환경 | 칩 | 파티션 | 대상 |
|---|---|---|---|
esp32-s3-devkitc-1 |
N16R8 (16MB / 8MB Octal PSRAM) | partitions.csv |
바디1 |
n8r2 |
N8R2 (8MB / 2MB Quad PSRAM) | partitions_8MB.csv |
바디2 |
pio run -e esp32-s3-devkitc-1 -e n8r2 # 두 사양 모두 빌드
pio run -e n8r2 -t upload --upload-port COMx # USB 업로드 (최초 1회 또는 파티션 변경 시)
pio device monitor --dtr 0 --rts 0 # 시리얼 모니터 — ★DTR/RTS 끄기★-
OTA(권장): 웹 설정 → "펌웨어 업데이트"에
.pio/build/<환경>/firmware.bin업로드. 사양이 다른.bin은 보드가wrong board variant로 거부. -
⚠ USB 시리얼 포트를 닫으면 보드가 리셋됩니다(USB-Serial-JTAG). 운전 중 모니터를 닫거나 케이블을 뽑지 마세요. 크래시 로그는 UART0(GPIO43/44) TTL로 받는 게 안전합니다.
-
firmware/,backups/(보드 플래시·레시피 백업)는 git 제외. -
data/폴더가 없으므로uploadfs는 불필요합니다(레시피·화면보호기는 런타임 생성). -
시리얼 로그가 안 보이면 보드 EN/RESET을 한 번 누르세요(USB-CDC 재연결 타이밍).
| 플래그 | 의미 |
|---|---|
UI_DISPLAY_PRESENT=1 |
화면 코드 포함 여부. 보드별 화면 유무는 PinMap.h hasDisplay로 런타임 결정 |
FW_VARIANT |
칩 사양 태그(N16R8/N8R2) — 웹·로그 표시 |
BOARD_HAS_PSRAM + memory_type=qio_opi |
N16R8 OPI PSRAM 활성 |
ARDUINO_USB_CDC_ON_BOOT=1 |
네이티브 USB 포트로 Serial 출력 |
CORE_DEBUG_LEVEL=0 |
코어 로그 억제(디버깅 시 3) |
partitions.csv — 3MB APP ×2 (OTA) + 9.9MB LittleFS (16MB). partitions_8MB.csv — 3MB APP ×2 + 1.9MB LittleFS (8MB).
gen_nyan.py — GIF → RGB565 프레임 헤더(src/nyan_frames.h) 생성 (임베드 Nyan 화면보호기). LANCZOS로 160×120 리사이즈, 프레임/딜레이 자동 추출. Pillow 필요: python -m pip install Pillow.
- 부팅 즉시 TMC2209 EN=HIGH — 리셋~setup 구간의 floating으로 코일이 통전된 채 정지 → 발열/소손 방지. 권장 HW 풀업: EN→3.3V 10K.
- 부팅 즉시 panel 신호 핀 idle 고정 — 부팅 직후 floating noise가 패널 컨트롤러를 비정상 state로 몰아 ESD/latch-up 손상 누적 방지. 권장 HW 풀업: TFT CS·RST→3.3V 10K.
- 디스플레이 배선 주의 — 신호선을 5V(VBUS) 근처로 라우팅하지 말 것. 5V가 신호핀에 닿으면 내부 ESD 다이오드를 통해 VCC 레일로 역주입되어 컨트롤러/백라이트가 손상될 수 있음(실손상 사례 있음).
- 냉각 — 75RPM 연속 2h+ 시 모터·전자부 ≈50°C(실측). 발열은 I²R 지배 → VREF 최소 유지 정책.
- PCNT 패닉 회피 — IDF 4.4 pcnt 드라이버가 객체를 PSRAM에 잡아, 모터 운전 중 플래시 접근 시
Cache disabled패닉(reset 4). FAS 초기화 동안 PSRAM을 점유해 내부 RAM 할당 강제(MotionController.cpp). - 운전 중 플래시 쓰기·재부팅 금지 — 플래시 소거 동안 모터 펄스 큐 보충이 멈춰 덜컹일 수 있음. 웹 API 409 + NVS 쓰기 지연.
- 노이즈 가드 — 인코더/버튼 노이즈 버스트 → 패널 입력 3초 차단, 60초 내 3회 → 모터 정지(레시피는 일시정지). 연속 비정상 리셋 3회 → 모터 잠금(전원 재투입 해제).
- 감속 정지 — 일시정지·단계 이동·건너뛰기는 S-커브 감속 후 동작. 비상정지만 즉시.
- 정전·리셋 복구 — 레시피 진행 상태(단계 목록·현재 단계·경과·상태)를 NVS
rec에 저장(모터가 멈춘 순간에만: 단계 시작 직전·확인대기·일시정지 완료·방향전환 휴지 중 10초마다 → 정전 시 경과는 휴지 주기 이내 근사). 재부팅 후 모터 자동 구동 없음 — 웹 배너/TFTRESUME?에서 이어서·단계 처음부터·다음 단계·취소 선택. 비상정지·완료 시 기록 삭제. - 하드웨어 감시 (
HwSafety) —PinMap.h에diagPin/vmPin을 배선·등록하면 동작: DIAG HIGH 60ms → 코일 즉시 차단, 구동 중 VM<10.5V 0.5초 → 정지, VM>28V → 즉시 차단. 레시피는 일시정지로 보존(복구 가능), 모터 잠금 후 원인 해소 시 웹에서 해제. 권장 배선: DIAG→GPIO + 10kΩ 풀다운, VM—100kΩ—ADC1 핀(1~10)—10kΩ—GND(분압비 11) + 100nF. - 전원 — 20V 라인에 퓨즈(1.5
2A), 드라이버 VM 근처 100470µF, 5V 레귤레이터 출력에 쇼트키 다이오드(USB 5V 역류 방지) 권장. 전원 켠 채 모터 커넥터 탈착 금지.
개인 프로젝트. 펌웨어는 Arduino-ESP32 + PlatformIO 환경에서 빌드합니다.
필요 라이브러리는 platformio.ini의 lib_deps에 명시되어 자동 설치됩니다.