MIDI controller와 MIDI file player를 하나의 아두이노 나노에서 구현하기에는 너무 벅차다고 생각해서 아두이노 나노(ATMega328)보다 메모리가 조금 더 큰 아두이노 나노 에브리(ATMega4809)를 구입하였다.
![]() |
쿠팡에서 구입한 '정품' 아두이노 나노 에브리(플래시 메모리 48KB, SRAM 6KB, EEPROM 256byte). 핀에는 마킹이 되어 있지 않지만 전반적으로 매우 고급스럽다. USB 단자도 C-type이다. |
이번에 구입한 아두이노 나노 에브리는 호환품이 아니라 정품이라서 가격은 조금 나가는 편이다. MIDI 파일 플레이어를 별도로 구현하면서 오늘도 수십 차례의 컴파일과 업로드 및 테스트를 진행해 보았는데, 이것만으로도 상당한 수준의 메모리를 쓰고 있다. 플레이어 기능은 매우 단순하므로(착각이었음) 따로 떼어내면 아주 수월하게 개발이 되고 또 메모리도 적게 쓰리라 기대했는데 그렇지만도 않다.
Sketch uses 22346 bytes (72%) of program storage space. Maximum is 30720 bytes.
Global variables use 1544 bytes (75%) of dynamic memory, leaving 504 bytes for local variables. Maximum is 2048 bytes.
두 기능을 합쳐 놓으면 아두이노 나노 에브리의 48KB 메모리에 채워 넣기 위해서 상당한 수준의 '다이어트'를 해야 할 것이다. 곡예 수준의 다이어트를 하지 않으면 불가능할지도 모른다. 이런 현실을 모르고 아두이노 나도 단독으로 개발하려 했으니... 불가능함이 판명되면 두 기능을 각각 별개의 아두이노 나노에 업로드한 뒤 필요에 따라서 소켓에 아두이노를 갈아 끼우는 그야말로 미련한 방법으로 운용해야 한다.
오늘도 수십 차례의 디버깅이 이루어 지고 있다. 챗GPT를 이용하여 MIDI 파일을 점검 및 처리용 파이썬 스크립트 mid_check.py를 뚝딱 만들었다. 옵션을 사용하는 방법이나 도움말 기능 등에 부족함이 없다. 이걸 그대로 GitHub에 올려도 문제가 없을 것이다.
ChatGPT가 도운 코드를 GitHub에 올리는 것은 도덕적으로 문제가 없으며, 오히려 오픈소스 공유 정신에 부합합니다. 필요하다면 라이선스와 크레딧 표기 정도만 신경 쓰시면 됩니다.
누구나 mid_check.py 정도의 코드는 이제 챗GPT로 만들어 낼 수 있다. 그러니 내가 이를 인터넷에 공개하는 것이 무슨 의미가 있느냐고 되물을 수도 있다. 그러나 챗GPT가 처음부터 실수를 전혀 하지 않는 파이썬 스크립트를 만들어 내지는 않았다. 사람에 의한 검증을 거쳤다는 의미를 부여할 수 있다. 이 작업을 하면서 Windows PowerShell에서 파이썬 인터프리터를 설치하여 쓸 수 있다는 것도 알았다. 간단한 프로그래밍을 할 수 있는 아나콘다/미니콘다 외의 대안으로서 나쁘지 않다.
Nano Ardule + 컴퓨터 + MIDILIFE 2.0은 반주기의 모습을 점점 닮아간다. SD카드에 담긴 format 0 MID file(제목은 8.3 포맷에 맞추어야 함)은 INDEX.TXT 파일을 통해 접근한 뒤 원하는 BPM이나 드럼킷으로 재생하고, 이펙트(리버브)를 조절할 수 있다. 앞서 언급한 mid_check.py는 포맷을 1에서 0으로 전환하고 긴 파일명을 8.3 포맷에 맞춘 뒤 INDEX.TXT 파일을 만드는 것까지 가능하다. 단순 MIDI file player라면 트랙이 별도로 나뉜 포맷 1을 굳이 필요하지 않다. 추가할 기능은 박자에 맞추어 LED를 점멸하게 하는 것과 loop play이다. 그러면 아주 고급스런 메트로놈 대용도 할 수 있다.
어제 SD카드에 담긴 INDEX.TXT 파일을 제대로 처리하지 못해서 생겼던 BOM(Byte Order Mark) 문제를 간단히 설명해 보고자 한다. 나는 코드에서 BOM 3바이트를 건너뛰는 방식을 택하였다. 부득이하게 편집기를 이용하여 INDEX.TXT을 만들 때 BOM으로 시작하지 않도록 매번 설정을 건드리는 것이 너무나 불편하기 때문이다.
1) BOM(Byte Order Mark)이란?
BOM은 텍스트 파일의 맨 앞에 붙는 특별한 바이트 시퀀스로, 파일의 문자 인코딩을 알리기 위해 사용됩니다.
가장 흔한 것은 UTF-8 BOM이며 3바이트 0xEF 0xBB 0xBF
로 시작합니다.
Windows 메모장 등 일부 에디터는 기본 설정에서 BOM을 덧붙여 저장할 수 있습니다.
인코딩 | BOM 바이트 | 비고 |
---|---|---|
UTF-8 | EF BB BF |
가장 흔함 |
UTF-16 LE | FF FE |
2바이트 BOM |
UTF-16 BE | FE FF |
2바이트 BOM |
2) INDEX.TXT에서 왜 문제가 되나?
아두이노 등 임베디드 환경에서 INDEX.TXT
를 ASCII 또는 BOM 없는 순수 UTF-8로 가정하고 읽을 때,
파일 첫 줄 맨 앞의 BOM 3바이트가 내용으로 들어와 파싱 오류를 유발합니다.
그 결과 첫 파일명이 누락되거나, 비교 구문(strcmp
, strncmp
)이 실패하고,
LCD/로그에 깨진 문자가 표시될 수 있습니다.
3) 대표적인 증상
- 첫 번째 파일이 목록에 나타나지 않음 (예: "no mid found").
- 첫 줄 파일명이 공백/깨짐으로 보임(앞에 보이지 않는 문자가 섞임).
- 문자열 비교/파싱이 실패해 재생·선택 로직이 비정상 동작.
4) 해결 방법
(A) 파일 저장 단계에서 BOM 제거
- Windows 메모장: "다른 이름으로 저장" → 인코딩에서 UTF-8(서명 없음, no BOM) 선택.
- VS Code: 상태바의 인코딩 표시 클릭 → Save with Encoding → UTF-8 선택( with BOM 아님 ).
(B) 코드에서 BOM 감지 후 건너뛰기
파일을 열고 첫 3바이트가 EF BB BF
인지 검사한 뒤, 맞으면 스킵합니다.
// 간단한 BOM 스킵 예시(C/C++)
// 파일에서 처음 3바이트를 미리 읽어 검사
uint8_t bom[3];
size_t n = file.read(bom, 3);
bool hasBOM = (n == 3 && bom[0]==0xEF && bom[1]==0xBB && bom[2]==0xBF);
if (!hasBOM) {
// BOM이 아니면, 읽은 3바이트를 다시 처리해야 함
// 구현에 따라 버퍼 복원 또는 파일 포인터 되감기 필요
file.seek(0);
}
// 이후 라인 단위 파싱 진행
팁: 줄을 읽을 때 isprint()
또는 범위 검사로 비가시 제어문자를 걸러주는 보조 방어 로직을 추가하면 안정성이 올라갑니다.
5) 요약
- BOM(특히 UTF-8 BOM: EF BB BF)은 텍스트 선두의 보이지 않는 3바이트입니다.
- INDEX.TXT를 파싱할 때 첫 줄이 깨지거나 누락되는 주원인이 될 수 있습니다.
- 가장 확실한 해결은 BOM 없이 저장하거나, 코드에서 BOM을 감지해 스킵하는 것입니다.
2025년 9월 18일 업데이트
아두이노 나노와 아두이노 나노 에브리는 SD카드 모듈을 연결하는 법이 다르다고 한다. 아두이노에서는 카드와 통신하기 위한 ICSP(In-Circuit Serial Programming)핀이 D11-D13에도 그대로 연결되어 있는데, 아두이노 나노에서 이들 디지털 입출력 단자는 일반 GIOP일 뿐이다. 보드 뒷면에 6개의 작고 둥근 패드 모임에 연결을 해야 한다는 것. 핀 헤더도 아니고 이걸 도대체 어떻게 쓰라는 거야...