====== Nano Ardule MIDI Controller: 단계별 코딩 로드맵 ====== //목표: 하드웨어를 모두 연결한 뒤, 기능을 **작은 단위로 쪼개어** 안정적으로 구현·검증한다. 각 단계는 **독립 스케치**로 끝내고, 통합은 맨 마지막에 진행한다. 이 문서는 2025년 8월 21일에 작성된 개정판이다.// 최종 수정일: --- //[[jeong0449@gmail.com|Haeyoung Jeong]] 2025/08/26 08:19// {{:pictures:flower_and_circuit.png?250|꽃과 PCB}} ---- ===== 전제(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` 표시 **산출물:** {{ :nano_ardule_midi_controller:step_00_base.zip |}} test/, src/ 구조는 아직 만들지 않고 단순하게 시작한다. ** 주의사항 ** * 업로드 시에는 D0(RX) 및 D1(TX) 핀에 연결된 사용자 제작 회로의 연결을 해제한다. ---- ===== 단계 1. 전원·LCD·LED 스모크 테스트_passed ===== **목표:** 전원 품질과 LCD, LED 배선 이상 유무 확인 **해야 할 일** * LCD 초기화(`0x27, 16x2`) → “Hello, Ardule” 1.5초 → “READY” * Activity/Part LED 한 번씩 시퀀스 점등 * (선택) `Serial.begin(115200)`로 부팅 로그 1~2줄 **통과 기준** * 깜빡임 최소, 글자 깨짐 없음, LED 과다광도/발열 없음 **산출물:** {{ :nano_ardule_midi_controller: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 세라믹 커패시터를 삽입하여 불안정한 동작을 개선하였음(별도의 풀업 저항을 넣으라는 권유도 있었으나 이것까지는 하지 않음) **통과 기준** * 빠른 회전에도 카운트 누락·역방향 오검출 없음 * 모든 버튼 단·장 클릭이 일관되게 표시 **산출물:** * {{ :nano_ardule_midi_controller:step_02_inputs.zip |}} * {{ :nano_ardule_midi_controller: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 변경 즉시 반영 **산출물:** * {{:nano_ardule_midi_controller:step_03_midi_tx.zip|}} C-E-G를 단순 반복 재생 ---- ===== 단계 4. MIDI RX & 패스스루_passed ===== **목표:** 외부 키보드 입력 파싱 및 기본 패스스루 **해야 할 일** * LCD 하단: `IN Ch1 N60 V90`, PB/Mod 등도 간단 로그 * IN → OUT 그대로 전달(지연 최소) * Running Status 처리 점검 **통과 기준** * 고속 연주·페달에도 누락 없음, 체감 지연 미미 **산출물:** * {{:nano_ardule_midi_controller:step_04_progchange_encoder.zip|}}인코더로 Program Change 번호를 선택하고 전송하는 기본 버전 * {{:nano_ardule_midi_controller:step_04b_progchange_with_loop_v2.zip|}}인코더 Program Change에 더해서 버튼을 길게 놀러 노트 루프 ON/OFF 토글 기능을 추가한 확장 버전 * {{:nano_ardule_midi_controller: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)의 모든 소리에 접근하기 위한 **[[sound_browser|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 세부적으로 들여다보기 ** 생각보다 고려할 것이 많다! * MVP(권장 최소 세트) — 가볍고 체감효과 큼 - 하드 스플릿(엔트포인트) - 듀얼 볼륨 + ratio 모드(A↑면 B↓) - 옥타브/세미톤 트랜스포즈(CPU 부담 거의 없고 코드 짧음) * PLUS(여유 있으면) - 오버랩 ON/OFF (경계 ±N노트) <- 겹치는 구간은 양쪽에 NoteOn/Off를 중복 전송만 하면 됨(연산량 낮음) - 페달/모드 휠 등 존별 라우팅 ** 산출물 ** * {{:nano_ardule_midi_controller:nano_ardule_browserpreview_userguide_ko_20250907.docx|}} ---- ===== 단계 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`로 한 단계씩 안전 복귀 **통과 기준** * 모드 간 전환 시 화면 깜빡임 최소, 조작 혼동 없음 사실 단계 5~6의 경계가 모호해졌다. 단일채널(A or B part 하나만 활성) 및 멀티채널(레이어: A+B, 스플릿:A/B) 기능 및 프로그램 편집 기능까지는 하나의 스케치 파일에서 해결하였는데, SD카드에 담긴 MIDI 파일(드럼 패턴 포함)까지 재생하는 기능을 넣기 시작하니 메모리가 꽉 차서 업로드가 되지 않고 있다. 이에 대해서는 블로그의 [[https://blog.genoglobe.com/2025/09/nano-ardule.html|메모리 한계에 부딪친 Nano Ardule 컨트롤러]]라는 글에서 대략적인 해결 방안을 기록해 두었다. 여기에서 제안한 바를 이용하여 최적화를 먼저 실시한 뒤, 스케치 파일을 나누어서 나머지 개발을 진행해야 할 것이다. --- //[[jeong0449@gmail.com|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