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`
통과 기준
산출물: step_00_base.zip test/, src/ 구조는 아직 만들지 않고 단순하게 시작한다.
주의사항
단계 1. 전원·LCD·LED 스모크 테스트_passed
목표: 전원 품질과 LCD, LED 배선 이상 유무 확인
해야 할 일
LCD 초기화(`0x27, 16×2`) → “Hello, Ardule” 1.5초 → “READY”
Activity/Part LED 한 번씩 시퀀스 점등
(선택) `Serial.begin(115200)`로 부팅 로그 1~2줄
통과 기준
산출물: 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 세라믹 커패시터를 삽입하여 불안정한 동작을 개선하였음(별도의 풀업 저항을 넣으라는 권유도 있었으나 이것까지는 하지 않음)
통과 기준
산출물:
단계 3. MIDI TX 기본_passed
목표: 실제 GM 음원으로 Note/Program/Channel 전송 검증
해야 할 일
통과 기준
산출물:
단계 4. MIDI RX & 패스스루_passed
목표: 외부 키보드 입력 파싱 및 기본 패스스루
해야 할 일
통과 기준
산출물:
-
-
step_05_midi_in_thru_lcd_monitor_v12.zip MIDI IN으로 들어온 신호를 그대로 MIDI THRU로 내보냄. LCD 갱신 주기가 너무 빠르면 THRU에 지연이 발생하므로, 인코더 버튼을 누르면 LCD 표시를 아예 끄도록 한다. 이 버전은 PC에서 보내는 MIDI 파일 재생 신호를 받아서 그대로 보내는 용도이다. 화면 표시는 0.2초 간격으로 갱신한다. 너무 자주 갱신하면 MIDI 신호를 내보내는데 지연이 발생할 수 있다.
중간단계 정리
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 다중채널로 복제 또는 음역대로 분기. 당초 단계 목표는 아주 간단하게 설정하였으나, SAM9703 도터보드에 장착된 GMS963200-B(4 MB ROM)의 모든 소리에 접근하기 위한 GS 사운드 브라우저 (Sound Browser) 기능이 들어가면서 다소 복잡해졌다. 사운드 브라우저에 진입하려면 인코더 버튼을 길게 눌러야 한다.
이 단계에서는 Nano Ardule MIDI Controller의 핵심 기능을 구현하는 것이므로 매우 주의깊게 진행하여 실패가 없도록 해야 한다. 8월 마지막 주말에 꽤 공을 들여서 코딩을 구현하였다. 애초에 구상하였던 작동 방식도 조금씩 합리적으로 변하고 있다.
(인코더 회전 step 변경) 프로그램이나 뱅크 변경은 현 방식대로 하되 나머지 수치 변경은 step = 3으로 변경(6으로 해 보았으나 한번에 너무 큰 값이 바뀜)
Reverb/Chorus의 각 채널별 초기 센드 설정: step_05_routing_layer_split_v1.6b_cycle_pc_rvb_cho_plusminus.ino
Single channel mode(A, B, Drums)와 multi-channel mode(A+B for layering, A/B for splitting)의 완벽한 분리(2025년 9월 4일). 따라서 split라는 용어는 코드에만 남게 되며, 장기적으로는 multi로 바꿀 것이다.
해야 할 일
Layer: 같은 노트를 A와 B 채널로 동시 송신
Split: 스플릿 포인트 기준 A(하), B(상)로 분기
LCD 예시: `LAYER ON` / `SPLIT A:C3↓ | B:C#3↑`
통과 기준
스플릿 경계에서 노트 누락·중복 없음
Layer 시 두 악기 겹침이 안정적
Split 세부적으로 들여다보기
생각보다 고려할 것이 많다!
산출물
단계 6. UI 모드·화면 템플릿·안전 탈출
목표: 버튼으로 모드 전환, 인코더로 파라미터 순환 편집. 사운드 브라우저(인코더 버튼 길게 눌러 진입)을 여기에서 구현한다.
해야 할 일
모드: `SINGLE(기본)/EDIT/PLAY/SETUP(Global)`
인코더 버튼: 편집 대상 순환(Program → Bank LSB → Vol → Pan → Reverb → Chorus → Cutoff(CC74) → Reso(CC71))
LCD 16×2 템플릿(예)
어디서든 `STOP/EXIT`로 한 단계씩 안전 복귀
통과 기준
사실 단계 5~6의 경계가 모호해졌다. 단일채널(A or B part 하나만 활성) 및 멀티채널(레이어: A+B, 스플릿:A/B) 기능 및 프로그램 편집 기능까지는 하나의 스케치 파일에서 해결하였는데, SD카드에 담긴 MIDI 파일(드럼 패턴 포함)까지 재생하는 기능을 넣기 시작하니 메모리가 꽉 차서 업로드가 되지 않고 있다. 이에 대해서는 블로그의 메모리 한계에 부딪친 Nano Ardule 컨트롤러라는 글에서 대략적인 해결 방안을 기록해 두었다. 여기에서 제안한 바를 이용하여 최적화를 먼저 실시한 뒤, 스케치 파일을 나누어서 나머지 개발을 진행해야 할 것이다. — Haeyoung Jeong 2025/09/09 13:10
산출물: `/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 플레이어
메모리가 매우 부족해져서 microSD 카드에 담긴 MIDI 파일 재생 기능은 완전히 독립시키기로 한다. SD 카드를 읽다가 read error가 나는 문제로 정말 고생을 많이 했다.
목표: 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