Table of Contents
Nano Ardule MIDI Controller: 단계별 코딩 로드맵
목표: 하드웨어를 모두 연결한 뒤, 기능을 작은 단위로 쪼개어 안정적으로 구현·검증한다. 각 단계는 독립 스케치로 끝내고, 통합은 맨 마지막에 진행한다. 이 문서는 2025년 8월 21일에 작성된 개정판이다.
최종 수정일: — Haeyoung Jeong 2025/08/26 08:19
전제(Assumptions)
- 보드: Arduino Nano(ATmega328P)
- 표시장치: 1602 I2C LCD
- 입력: 로터리 인코더 1개(버튼 포함), 푸시버튼 6개
- 출력: MIDI OUT, LED(Part A/B/Drums, Activity)
- 저장: EEPROM(설정), microSD(Type 0 MIDI, User Program/Combi)
- 채널 규칙(권장): A=CH1, B=CH2, Drums=CH10 (Global에서 변경 가능)
전체 원칙
- 한 단계 = 하나의 기능만 탑재한 최소 스케치 → 성공 시 `/tests/step_##_name.ino`로 고정 저장
- LCD는 “미니 디버그 콘솔”처럼 사용(상태·에러 간단 표기)
- 문제 발생 시 직전 단계 스케치와 비교하여 원인 분리
- 통신/입출력은 비동기·상태머신 우선(블로킹 지양)
추천 디렉토리 구조
/NanoArdule /src config.h ui.h / ui.cpp // LCD UI, 화면 템플릿, 상태머신 midi_io.h / midi_io.cpp // TX/RX, Running Status, 유틸 routing.h / routing.cpp // Layer/Split, 채널 리매핑 storage.h / storage.cpp // EEPROM (User Program/Combi/Global) player.h / player.cpp // SD & Type-0 MIDI 재생 main.ino /tests step_00_base.ino step_01_lcd_power.ino step_02_inputs.ino step_03_midi_tx.ino step_04_midi_rx_pass.ino step_05_routing_layer_split.ino step_06_ui_modes.ino step_07_eeprom.ino step_08_sd_player.ino
단계 0. 베이스 골격-passed
목표: 공통 헤더·유틸과 전역 상태 정의, 부팅 배너
해야 할 일
- `config.h`에 핀/채널/타임아웃 등 상수 모음
- 전역 상태(`struct AppState`) : 모드(SINGLE/EDIT/PLAY/SETUP), 활성 파트(A/B/A+B/DRUMS), 각 파트의 PRG/BANK/VOL/PAN/RVB/CHR
- 부팅 배너: `Nano Ardule vX.Y` 1초 → `READY`
통과 기준
- 리셋/전원 인가 시 LCD에 배너 후 `READY` 표시
산출물: step_00_base.zip test/, src/ 구조는 아직 만들지 않고 단순하게 시작한다.
주의사항
- 업로드 시에는 D0(RX) 및 D1(TX) 핀에 연결된 사용자 제작 회로의 연결을 해제한다.
단계 1. 전원·LCD·LED 스모크 테스트_passed
목표: 전원 품질과 LCD, LED 배선 이상 유무 확인
해야 할 일
- LCD 초기화(`0x27, 16×2`) → “Hello, Ardule” 1.5초 → “READY”
- Activity/Part LED 한 번씩 시퀀스 점등
- (선택) `Serial.begin(115200)`로 부팅 로그 1~2줄
통과 기준
- 깜빡임 최소, 글자 깨짐 없음, LED 과다광도/발열 없음
산출물: step_01_lcd_power.zip 필요한 상수를 전부 코드 안에서 #define 문으로 정의했으므로 여기에서는 config.h 파일을 포함하지 않음
단계 2. 로터리 인코더 & 버튼 입력_passed
목표: 인코더 증감과 버튼 단·장 클릭을 정확히 읽어들임
해야 할 일
- 인코더 카운터 표시: `ENC: 123`
- 버튼(각각) 단클릭/장클릭(>700ms) 구분 → LCD 표시
- 소프트 디바운스 또는 라이브러리, 풀업/접지 확인
- 특히 PART SELECT 버튼에 연결된 A6에는 외부 풀업저항(10K)을 연결하였음
- RX 및 TX에서 외부 회로로 나가는 중간에 점퍼를 넣어 프로그램 업로드 시 이를 끊을 수 있게 하였음-TX와 그라운드 레일 사이에 절연 불량이라서 패드 사이를 잘 긁어서 문제를 해결함
- 로터리 인코더의 CLK/DT 단자와 GND 사이에 103 세라믹 커패시터를 삽입하여 불안정한 동작을 개선하였음(별도의 풀업 저항을 넣으라는 권유도 있었으나 이것까지는 하지 않음)
통과 기준
- 빠른 회전에도 카운트 누락·역방향 오검출 없음
- 모든 버튼 단·장 클릭이 일관되게 표시
산출물:
- step_02_final_inputs.zip ← 모든 입력장치 점검. 버튼 상태는 작동 상태를 LCD에 표시한 다음 잠시 뒤에 'BTN: Ready'로 바뀜.
단계 3. MIDI TX 기본_passed
목표: 실제 GM 음원으로 Note/Program/Channel 전송 검증
해야 할 일
- 버튼 단클릭: Note 60 On ↔ Off
- 인코더: Program Change 증감(001…128) — BANK은 후속 단계
- LCD 상단: `CH:01 PRG:001`, 하단: `NOTE: C4 ON/OFF`
통과 기준
- 소리 발생·정지가 정확, PRG 변경 즉시 반영
산출물:
- step_03_midi_tx.zip C-E-G를 단순 반복 재생
단계 4. MIDI RX & 패스스루
목표: 외부 키보드 입력 파싱 및 기본 패스스루
해야 할 일
- LCD 하단: `IN Ch1 N60 V90`, PB/Mod 등도 간단 로그
- IN → OUT 그대로 전달(지연 최소)
- Running Status 처리 점검
통과 기준
- 고속 연주·페달에도 누락 없음, 체감 지연 미미
산출물:
- step_04_progchange_encoder.zip인코더로 Program Change 번호를 선택하고 전송하는 기본 버전
- step_04b_progchange_with_loop_v2.zip인코더 Program Change에 더해서 버튼을 길게 놀러 노트 루프 ON/OFF 토글 기능을 추가한 확장 버전
중간단계 정리
Step 04 완료 체크리스트 (Step 05 시작 조건)
하드웨어/전원
- ✅ Arduino Nano(ATmega328P), 1602 I2C LCD(0x27), 인코더(D2/D3, SW=D4), Activity LED=D9 배선 정상
- ✅ 인코더 라인에 10 nF(=103) RC(또는 등가) 적용, 필요 시 4.7 kΩ 풀업 추가 고려
- ✅ 업로드 시 TX 외부 회로 분리 → 업로드 후 재연결 (RX/TX 간섭 없음)
펌웨어 기본
- ✅ MIDI TX 보드레이트 31,250 bps (Serial.begin(31250))
- ✅ Activity LED(D9) 전송 시 짧게 점등
- ✅ LCD 출력은 고정폭(16자 패딩) 또는 라인 전체 지우고 재출력으로 잔상 없음
입력 안정화
- ✅ 인코더: 인터럽트 RISING + µs 가드(STEP_GUARD_US≈800 µs)로 역방향/튀는 값 없음
- ✅ 인코더 버튼: 디바운스(≥25 ms), 단/장(>700 ms) 구분 정상
- ✅ A6(사용 시) 풀업 10 kΩ 또는 등가 값으로 안정 (히스테리시스 적용 가능)
Step 04 기능 달성
- ✅ Program Change(0–127): 인코더 회전으로 번호 변경 →
- 자동 커밋: 0.2 s 무동작 시 PC 전송
- 즉시 전송: 버튼 짧게 눌러 PC 전송
- ☐ (선택) Note Loop 토글: 버튼 길게 눌러 C–E–G–C5 순환 Note On/Off (FSM, 비차단)
- ✅ LCD 1행: PRG: xxx 고정폭 표시 / 2행: 상태 메시지(1초 후 “Ready/Loop” 복귀)
트러블슈팅 기록 (요약)
- ✅ “Ready52/꼬리 문자” → 라인 전체 클리어·패딩 출력으로 해결
- ✅ 업로드 정지 → 보드 뒷면 TX–GND 미세 단락 제거로 해결
- ✅ 인코더 튐 → RC + µs 가드 + 고정폭 표시로 착시/노이즈 해소
ChatGPT 대화 목록이 너무 길어져서 다음 단계부터는 새 창으로 넘어가려고 한다. 아래를 새 채팅 첫 메시지로 붙여넣으면 바로 다음 단계로 넘어간다고 한다.
프로젝트: Nano Ardule MIDI Controller 현황(요약): - Step 04까지 완료. Program Change를 인코더로 조절/전송 OK. - LCD는 16자 패딩으로 잔상 제거, Activity LED 동작. - 인코더 RISING + STEP_GUARD_US≈800µs, 버튼 디바운스/장단클릭 정상. - (선택) Note Loop 토글도 동작함/안함: [여기에 O/X 적기] 요청: - Step 05: Routing (Layer/Split)과 채널 리매핑 설계 및 테스트 스케치 제시. - 우선 목표: PART A/B 단일/동시(A+B)와 DRUMS(CH10) 모드 전환, 키 스플릿 포인트 지정, 트랜스포즈 엔트리 진입 UI 골격.
단계 5. 라우팅: Layer & Split
목표: 입력 노트를 A/B 다중채널로 복제 또는 음역대로 분기
해야 할 일
- Layer: 같은 노트를 A와 B 채널로 동시 송신
- Split: 스플릿 포인트 기준 A(하), B(상)로 분기
- LCD 예시: `LAYER ON` / `SPLIT A:C3↓ | B:C#3↑`
통과 기준
- 스플릿 경계에서 노트 누락·중복 없음
- Layer 시 두 악기 겹침이 안정적
산출물:
- step_05_midi_in_thru_lcd_monitor_v12.zip 사전 테스트로서 MIDI IN으로 들어온 신호를 그대로 MIDI THRU로 내보냄. LCD 갱신 주기가 너무 빠르면 THRU에 지연이 발생하므로, 인코더 버튼을 누르면 LCD 표시를 아예 끄도록 한다.
단계 6. UI 모드·화면 템플릿·안전 탈출
목표: 버튼으로 모드 전환, 인코더로 파라미터 순환 편집
해야 할 일
- 모드: `SINGLE(기본)/EDIT/PLAY/SETUP(Global)`
- 인코더 버튼: 편집 대상 순환(Program → Bank LSB → Vol → Pan → Reverb → Chorus → Cutoff(CC74) → Reso(CC71))
- LCD 16×2 템플릿(예)
- L1: `Mode:SGL A PRG:001`
- L2: `VOL:64 RVB:32 CHR:32`
- 어디서든 `STOP/EXIT`로 한 단계씩 안전 복귀
통과 기준
- 모드 간 전환 시 화면 깜빡임 최소, 조작 혼동 없음
산출물: `/tests/step_06_ui_modes.ino`
단계 7. EEPROM: User Program & Combi 저장/로드
목표: User Program(단일 파트)과 Combi(A+B) 슬롯 저장
데이터 레이아웃(예시)
필드 | 크기 | 설명 |
---|---|---|
MAGIC | 4B | `“ARDU”` |
VER | 1B | 구조 변경 대비 |
SLOTN | … | CH/PRG/BANK/VOL/PAN/RVB/CHR/필요 CC |
CRC16 | 2B | 무결성 |
해야 할 일
- `saveUser(slot) / loadUser(slot)` / `saveCombi(slot) / loadCombi(slot)`
- 빈 슬롯/CRC 불일치 시 `LOAD FAILED` 표시
- 저장 시 현재 볼륨 등 연주 필수치도 함께 기록
통과 기준
- 전원 재투입 후 동일 상태 복원
- 50회 반복 저장에도 에러 없음
산출물: `/tests/step_07_eeprom.ino`
단계 8. microSD & Type-0 MIDI 플레이어
목표: SD에서 곡 스캔, 선택 재생/일시정지/정지
해야 할 일
- 파일 규칙 권장: `file00.mid` … `file99.mid` (최대 100)
- LCD 상태: `▶`/`⏸`/`■`, 곡 번호, 경과/총시간(선택)
- 공연 보호: 재생 중 위험 조작은 이중 확인(PLAY+STOP 등)
통과 기준
- 짧은/긴/템포 변화 큰 파일 모두 정상
- SD 없음/불량 시 명확 경고 & 안전 복귀
산출물: `/tests/step_08_sd_player.ino`
단계 9. Global Settings(SETUP)
목표: 기본 악기/채널/대기시간/연속재생 등 EEPROM에 저장
권장 항목(예)
- Part A/B 기본 Program
- Part A/B 기본 Channel, 대체 CH(연주 중 리매핑용)
- 인코더 적용 지연(예: 0.2s)
- MIDI 파일 정지 시 리와인드 여부
- 연속 재생 On/Off, 곡 간 대기시간(초)
- (선택) 설정 덤프 to SD
통과 기준
- 전원 재투입 후 전역 설정이 기대대로 반영
산출물: `/src/storage.(h|cpp)` 업데이트 + UI 반영
단계 10. 통합·최적화·예외 처리
목표: 모든 기능 동시 구동에서 안정성 확보
체크리스트
- 지연/버퍼: IN → (Layer/Split) → OUT 지연 최소
- 노이즈·전원: 인코더/SD 동작 시 LCD 떨림·리셋 여부
- 메모리: SRAM 여유(문자열 `F()`), 스택/힙 충돌 없음
- 에러 처리: SD 제거, 빈 슬롯, MIDI IN 없음… → 사용자 친화 메시지
- 안전 토글: Panic, GM/GS/XG Reset SysEx(선택)
- 회귀 테스트: `/tests` 전 단계 스케치 재검증
산출물: `/src/main.ino` 완성 빌드
최종 QA 체크리스트
- 부팅 배너/READY 정상
- 인코더 증감/버튼 단·장 클릭 인식
- MIDI TX: Note/Program/Channel 정확
- MIDI RX: 수신·패스스루 누락 없음
- Layer/Split 라우팅 정확(경계 노트 포함)
- UI 모드 전환 & 안전 탈출(Stop/Exit) 일관
- EEPROM Save/Load + CRC 무결성
- SD 스캔/재생/일시정지/정지 안정
- 통합 후 지연/노이즈/에러 처리 OK
트러블슈팅 힌트
- LCD 깜빡임: `lcd.clear()` 남발 금지 → 커서 이동으로 덮어쓰기, I2C 클럭(100k/400k) 조정
- 인코더 튐: 풀업/접지 재확인, 인터럽트 vs 폴링 비교, 디바운스 임계 재조정
- MIDI 누락: 송수신 버퍼 크기, Running Status·Pitch Bend 등 가변길이 처리 확인
- SD 불안정: CS 핀/배선 짧게, 전원 디커플링(100nF+수십μF), 라이브러리 버전 고정
- EEPROM 내구: 매직/버전/CRC 사용, 덮어쓰기 최소화(더블버퍼·저널링 전략)
작업 순서 요약
1) 베이스 골격 → 2) LCD·LED 스모크 → 3) 입력 → 4) MIDI TX → 5) MIDI RX/패스 → 6) 라우팅 → 7) UI 모드 → 8) EEPROM → 9) SD/플레이어 → 10) Global → 11) 통합/QA