2025년 9월 17일 수요일

코딩하기 좋은 비 내리는 날

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.TXTASCII 또는 BOM 없는 순수 UTF-8로 가정하고 읽을 때, 파일 첫 줄 맨 앞의 BOM 3바이트가 내용으로 들어와 파싱 오류를 유발합니다. 그 결과 첫 파일명이 누락되거나, 비교 구문(strcmp, strncmp)이 실패하고, LCD/로그에 깨진 문자가 표시될 수 있습니다.

3) 대표적인 증상

  • 첫 번째 파일이 목록에 나타나지 않음 (예: "no mid found").
  • 첫 줄 파일명이 공백/깨짐으로 보임(앞에 보이지 않는 문자가 섞임).
  • 문자열 비교/파싱이 실패해 재생·선택 로직이 비정상 동작.

4) 해결 방법

(A) 파일 저장 단계에서 BOM 제거

  • Windows 메모장: "다른 이름으로 저장" → 인코딩에서 UTF-8(서명 없음, no BOM) 선택.
  • VS Code: 상태바의 인코딩 표시 클릭 → Save with EncodingUTF-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개의 작고 둥근 패드 모임에 연결을 해야 한다는 것. 핀 헤더도 아니고 이걸 도대체 어떻게 쓰라는 거야...

2025년 9월 10일 수요일

불평등이 건강과 수명에 남기는 흔적

처음 도서관에서 이 책을 접했을 때, 나는 국가간 불평등에 대해 이야기하는 것으로 생각했었다. 책등에 쓰인 제목만 보고 즉흥적으로 선택을 했다가 몰입해서 순식간에 읽은 뒤 정말 좋은 책을 골랐다는 만족감을 느낄 때도 있고, 인터넷 등에서 미리 책 소개를 보고 빌렸다가 정작 재미가 별로 없어서 읽다 만 책도 있다. 이 책은 전자에 해당한다.



알린 T. 제로니머스의 「불평등은 어떻게 몸을 갉아먹는가」(영문 원제: Weathering: The Extraordinary Stress of Ordinary Life in an Unjust Society)는 바로 미국의 실정을 이야기하고 있었다. 자유와 평등을 건국 이념으로 삼는 미국에서 왜 백인과 흑인의 평균 수명에 현격한 격차가 존재하는가? 10대보다 20대에 아이를 낳은 흑인이 왜 더 빨리 사망하는가? 철이 없고 절제를 하지 못해서 10대에 임신을 하는 것이 아니다. 20대에 아이를 낳으면, 아이가 충분히 자랄 때까지 엄마가 건강한 상태를 유지하기 어렵다는 것을 잘 알고 있기 때문이다! 병원 응급실에 실려 갔을 때 의료 인력이나 환자 중에 흑인이 얼마나 있는지를 먼저 살펴보고 과연 나의 다급한 목소리가 무시되지 않고 잘 전달될지 고민해야 하는 입장을 생각해보라. 

미국의 백인 청년 대다수는 자신이 장애 없이 살아서 50세 생일을 맞이할 것이라고 기대할 수 있을 뿐 아니라 그 이후로도 수십 년간 건강하게 살 것이다. 우리가 연구를 진행한 재정 지원이 끊긴 극빈곤지역 흑인 및 백인 집단에서는 죽지 않고 살아남은 사람들도 생존 기간 중 최대 30퍼센트를 장애인으로 살았다. (17쪽)

웨더링(weathering)이란 차별과 편견, 배제 등에 의하여 일어나는 반복적이고 만성적인 스트레스가 신체에 점진적으로 영향을 끼쳐서 건강과 수명에 나쁜 영향을 미치는 것을 의미한다. 19쪽의 설명에서는 '인종, 민족, 종교, 계급 차별에 의해 공격당하는 소외된 지역사회에서 살아가는 사람들이 겪는 생리학적 작용을 포괄하는 과정'이라고 하였다. 우리가 흔히 생각하듯 불평등한 사회에서 겪는 스트레스는 심리적인 영향으로 끝나는 것이 결코 아니다. '스트레스 받지 마. 다 마음먹기에 달린 거야'는 하등 도움이 되지 않는다. 스트레스는 스트레스이고, 우리의 몸에 직접적인 영향을 미친다. 

이 책에 의하면 건강과 수명을 개인의 책임으로 돌리는 비난 또는 개인적 책임 내러티브는 결코 바람직하지 않으며, 문제의 해결 방식도 되지 못한다. 이러한 불평등을 해소하기 위한 노력의 한 사례로서 저자는 뉴욕시에서 추진하는 바이탈 브루클린(Vital Brooklyn) 프로그램을 소개하였다. 이 사업에는 재생에너지 사회로의 전환을 꾀하면서 경제 및 건강 형평성을 도모하고자 한다. 바이탈 브루클린의 웨더링을 고려한 공평한 사회 정책 지침은 다음과 같다.

  • 전체론적, 생태학적으로 생각하라
  • 억압받는 이해관계자를 지우지 마라. "우리 없이 우리에 대해 아무것도 하지 말라"
  • 노동연령 및 생식연령 성인의 필요에 관심을 가져라
  • 우리 모두의 운명이 연결되어 있음을 인정하라

웨더링에 의한 만성 스트레스는 텔로미어의 길이를 줄이고 이는 세포 노화와 매우 연관이 깊다. 노화된 세포는 염증성 사이토카인을 분비하여 전신 건강에 영향을 미칠 수 있다. 예를 들어 만성 염증은 췌장 세포에 이상을 일으켜 당뇨병을 유발하거나 자가면역질환으로 이어질 수 있다. 이는 소외집단의 구성원에게서 발생할 가능성이 높은 일인 것이다. 

또 다른 연구에서는 사회경제적으로 취약한 청소년이 시간이 지남에 따라 SLC6A4 유전자(세로토닌 수송체)의 프로모터 부위에 메틸화, 즉 후생유전학적 표지를 더 많이 축적하는 경향이 있고, 이는 뇌의 편도체 반응성을 높이고 공포 표정에 더 민감하게 반응하며, 향후 우울 증세의 증가와 연계된다고 한다.

Poverty marks a gene, predicting depression Duke Today 2016년 5월 24일

스트레스가 일상인 한국 사회는 얼마나 건강한가? 최근 Nature Medicine에 실린 다음 논문에서는 BBAG(Biobehavioral Age Gap), 즉 보호 요인(건강, 인지, 기능, 교육)과 위험 요인(심혈관질환, 시각·청각장애 등)을 활용해 예측된 나이실제 나이 의 차이를 계산한 지표를 써서 각 국가의 가속 노화 정도를 산출하였다. 이에 의하면 유럽은 가장 건강하게 노화하고 있고 이집트와 남아프리카공화국은 가장 빠르게, 그리고 아시아 및 라틴아메리카는 그 중간에 해당하였다. 이 연구에는 한국인도 포함되어 있다. 거의 선진국 수준에 다다른 한국은 높은 경제 수준에도 불구하고 환경적 요인, 사회 불평등, 대기 오염, 정치·사회 구조 등의 exposome 요인이 복합적으로 작용하여 노화를 가속화하는 경향이 확인되었다. 

The exposome of healthy and accelerated aging across 40 countries Nature Medicine 2025년 7월

여기에서 새로운 개념이 또 하나 등장한다. 그것은 바로 엑스포좀(exposome). 이는 개인이 삶 전반에 걸쳐서 겪는 물리적·사회적 환경 요인의 총합을 의미한다. UK Biobank의 약 50만 명의 데이터를 이용하여 exposome과 유전적 요인(다유전자 위험 점수 , PSR)이 사망 및 노화에 미치는 상대적 기여를 비유한 다음의 논문에서는 exposome에 의한 설명력이 훨씬 컸다고 한다(5.5-49.4% vs. 10.3-26.2%). 그나마 다행스러운 것은, 변경 가능한(modifiable) 환경 요인—흡연 여부, 신체활동, 사회·경제적 상태—에 대한 개입을 통해 이를 개선할 수 있다는 점.

Integrating the environmental and genetic architectures of aging and mortality Nature Medicine 2025년 2월 

이상의 자료 분석을 통해 내가 내린 결론은 이러하다. 스트레스는 근본적으로 마음을 고쳐먹음으로써 극복할 수 있는 것이 아니다. 특히 빈곤이나 소외에 의한 스트레스는 그 자체가 세포를 늙고 병들게 만든다. 사회 구조적인 변혁을 통해 빈곤과 소외를 퇴치하는 것이 급선무이며, 그 과정에서 반드시 당사자가 참여해야 한다. 그리고... 국가통합바이오빅데이터구축사업을 맡고 있는 사람으로서 드러내놓고 말하기는 조심스럽지만, 거대한 예산을 투입해 유전체를 해독하고 신약 개발의 실마리를 찾는 일도 중요하다. 그러나 개인 차원에서 건강한 삶을 추구한다면, 조금 덜 먹고 좋은 음식을 고르며 꾸준히 몸을 움직이는 것이 훨씬 더 구체적이고 실천 가능한 길이며, 비용 대비 효과도 크다고 믿는다.

빠르게 진도를 나간다고 해서 꼭 좋은 것만은 아니다 - AI가 가져온 착각?

사진 속 물건은 Arduino Nano를 이용하여 제작 중인 MIDI controller.

(들어가는 말) 인공지능과 협업하는 프로그램 개발 작업은 과몰입 또는 과로를 유발하기 좋다. 많은 부분을 ChatGPT가 도와주고 있어서 과연 이 일을 누가 하고 있는지 철학적인 고민을 하는 것은 새로운 숙제이다. ChatGPT는 과거에는 하지 못하던 일을 가능하게 만들어 주지만, 사용자로 하여금 결과적으로 과로를 유발하는 것은 부정할 수 없다. 일손을 덜어주는 것이 아니라, '오늘이 가기 전에 조금만 더 하면 좋겠는데, 조금만 더 하면...'하면서 인간을 지나치게 드라이브하게 된다.

인공지능의 도움으로 Nano Ardule MIDI Controller를 구동할 코드를 작성하면서 오늘 쓴 글의 제목과 같은 교훈을 뼈저리게 느끼고 있다. ChatGPT에게 요구사항을 하나씩 제시하여 C++ 코드를 얻어내고, 이를 컴파일하여 아두이노 나노에 업로드한 뒤 오류를 수정하고 새로운 기능을 추가하는 방식으로 내 나름대로는 개발 목표에 빠르고 능률적으로 근접하고 있다고 착각을 하였었다. 코드가 어떻게 돌아가는지 근본 원리를 배우려는 생각은 별로 없이, 그저 기기를 돌아가게만 하면 된다는 생각으로.

왜 착각인가? Sound Module을 제어하는 기능 목표치의 60% 정도를 달성해 놓은 뒤 이어서 마이크로SD카드에 수록된 MIDI 파일을 재생하는 기능을 추가하려는 단계에서 한계에 도달하였다. 바로 메모리가 부족하게 된 것이다. 여기까지를 제1기 개발이라고 부르겠다.

메모리 한계에 부딪친 Nano Ardule 컨트롤러 (2025년 9월 9일 작성)

ChatGPT가 제시하는 최적화 방안을 적용하기 이전에, 악기의 목록 정의를 재정비하기로 하였다. SAM9703 + GSM963200-B(CleanWave32) synthesizer ROM 보드의 sound variation은 GS의 그것과 완벽하게 같지는 않기 때문이다. GSM963200-B와 같은 CleanWave32 계열의 ROM인 GMS973201B의 데이터시트를 입수하여 악기 목록을 다시 만들었다. 엑셀 파일로 악기 배치를 정리한 다음 ChatGPT에 업로드한 뒤 헤더 파일을 제대로 만들게 하였다. 그런데 표준이라고 알려진 Roland의 GS와 확장 사운드의 맵이 너무나 다름을 알게 되었다. Roland SoundCanvas SC-D70 매뉴얼의 34쪽 Playing the Internal Sound Generator를 살펴보자.

■ Using MIDI message to switch sounds from another device or sequencer software

By sending MIDI messages from sequencer software that is running on your computer, you can specify the sound (instrument) for each of the SC-D70’s parts.

Sounds are specified by variation number and instrument number (p. 33). However, you should be aware that the way in which numbers are displayed may differ depending on your software. On the SC-D70, variation numbers begin from 0, and instrument numbers begin from 1.

The variation number corresponds to the MIDI bank number, and the instrument number corresponds to the MIDI program number.

(Note) The MIDI bank number consists of a “Most Significant Byte” (MSB) and a “Least Significant Byte” (LSB). Since each can have a value of 0–127, a total of 128 x 128 = 16384 banks can be specified. The most significant byte of the bank number corresponds to the variation number of the SC-D70. The least significant byte of the bank number is used to switch between the SC-55 map/SC-88 map/SC-88Pro/SC8820 map. 

예를 들어 SC-D70의 파트 1(1번 채널)을 SC-8820 map의 005 Dist. E.Piano(variation 17)로 바꾸고 싶다면 다음의 순서로 명령을 전송하면 된다.

  1. MIDI CH = 01
  2. CC#00 017 ...select variation number 017
  3. CC#32 004 ...select the SC-8820 map
  4. PC# 004 ...select instrument number 005
SC-D70의 매뉴얼에서.
Dream의 CleanWave32 매뉴얼에서.

반면 Dream의 사운드 체계에서는 CC#32를 쓰지 않으며, 대부분의 variation sound는 CC#00=127에 몰려있다. 더군다나 variation sound와 주 악기 소리가 서로 연관성이 없는 것이 많다. 예를 들어 PC = 12(Vibraphone)의 variation sound는 Elec Organ이다. 이런 상황에서는 유사한 악기를 패밀리로 묶기도 곤란하다. 1기 개발은 이 데이터시트를 접하기 전이라서 패밀리 개념으로 악기를 묶어서 선택하는 계층형 사운드 브라우저를 만들고 있었다.

Ardule Controller 하나를 이용하여 내가 보유한 두 개의 사운드 모듈(Roland SoundCanvas SC-D70과 SAM9703 기반 제품)을 같이 제어하려던 당초의 목표는 빗나가고 말았다. 더 큰 문제는 Dream 제품 체계에 맞게 악기 목록을 재정의한 헤더 파일을 쓸 수 있도록 기존의 코드를 수정해 달라고 하였더니 꼬인 구조가 많아서 거의 처음부터 다시 만들어내는 것이 아닌가. 그것도 단계별로... 예를 들어 아두이노 나노의 A6 핀은 아날로그 전용이라서 이를 버튼 스위치에 연결하여 쓰려면 풀업저항이 필요하다. 이미 지난번 개발 단계에서 이를 감안하여 하드웨어적으로 수정을 다 해 놓았는데, ChatGPT는 원론적인 접근부터 다시 시작하고 있었다. 풀업저항을 연결하지 않은 상태라고 가정하고!

수 주에 걸쳐 나눈 대화의 맥락을 전부 기억하는 것이 아닌데다가, 결론적으로는 어차피 대수술이 필요한 상태였던 것이다. 모든 기능을 1500라인이 넘는 하나의 .ino 파일에 쑤셔넣은 1기의 코드를 분리하여 모듈화하는 것까지 포함하여 상당히 방대한 개조를 해야 한다. 이러한 대수술이 한 단계로 이루어지는 것은 불가능하다. 파트 선택 기능부터 하나씩 추가하여 성공하면 다음 단계의 것을 넣는 방법이 유일하다. 다음은 왜 이렇게 해야만 하는지에 대한 ChatGPT의 대답.

그렇다. 1기 개발에서는 '지름길'로 가고 있다고 착각을 했던 것이다. 어쩌겠는가. 다소 시간이 걸리더라도 처음부터 다시 시작한다고 생각하자. 

2025년 가을에 20여년 전 제조된 Legacy MIDI 장비를 (재)활용하기 위한 이런 노력을 들이는 것이 과연 가치가 있는 일인지는 잘 모르겠다. 하지만 그 과정에서 최신의 기술(인공지능)을 충분히 활용하고 있으며 취미 생활의 지평을 넓혀 가고 있으니 아주 무의미한 일은 아닐 것이다. 원래 본업이 아니라 취미에 빠져드는 일이 더 자연스럽고 또 즐겁기까지 하다. 다만 퇴근 후 개발에 몰두하느라 과로를 하지 않도록 주의해야 할 것이다. 

2025년 9월 9일 화요일

메모리 한계에 부딪친 Nano Ardule 컨트롤러

아두이노 나노. 직접 촬영하였음.

아두이노 나노를 이용한 MIDI 컨트롤러 'Nano Ardule'을 만들다가 매우 중대한 난관에 봉착하였다. 하드웨어 제작을 마친 뒤, 지금까지 많은 공을 들여서 키보드 입력을 두 채널로 복제하여 서로 다른 음색이 동시에 나오도록 레이어링하거나 키보드 영역을 위와 아래로 나누는 스플리팅하기, 그리고 음색 편집 등의 기본 기능이 가능하도록 프로그래밍을 해 왔다. 여기에다가 마이크로SD카드에 담긴 MIDI 파일을 재생하는 기능을 추가하려고 하니 드디어 메모리가 부족하여 로드를 하지 못하는 단계에 이르렀다. 에러 없이 컴파일은 되는데 저장장치에 업로드를 하다가 중단되는 모습을 보니 허탈함을 금할 수 없다.

아두이노 나노(ATmega328P)는 작은 마이크로컨트롤러라서 다음과 같은 3종류의 주요 메모리를 갖는다. SRAM은 휘발성이라 전원이 꺼지면 내용이 사라진다.

  • 플래시(32 KB): 코드와 상수 저장, PROGMEM 활용.
  • SRAM(2 KB): 런타임 변수/스택, 제일 부족하므로 절약 최우선.
  • EEPROM(1 KB): 전원 꺼져도 남는 데이터 저장.

실시간 데이터는 SRAM, 큰 테이블이나 문자열은 PROGMEM(플래시), 사용자 설정은 EEPROM에 두는 것이 바람직하다. 아직 이러한 원칙에 입각하여 코드를 모두 최적화하지는 않았다. 얄궂게도 EEPROM는 수명이 있어서 데이터를 약 10만회까지 쓸 수 있다. 읽는 것은 상관이 없다. 여기에는 사용자 설정이나 영구 데이터를 넣으면 적당하다.

아두이노 IDE에서 업로드한 코드는 플래시에 저장된다. const 변수나 문자열 리터럴도 기본은 플래시에 저장되지만 실행 시 SRAM으로 복사되는 경우가 많다. 2KB에 불과한 협소한 SRAM을 작업대로 여기고 많은 일거리를 펼쳐 놓으려니 아두이노 나노가 한계에 이르는 것은 당연하다. 그래서 PROGMEM이나 F() 매크로를 써서 복사하지 않고 플래시에서 직접 읽게 하면 SRAM을 절약할 수 있다. 마이크로SD카드에 담긴 모든 파일은 PC에서 미리 인덱스 파일을 만들어서 한 줄씩을 읽으면서 실제 파일에 접근하게 하는 방법도 고려해야 한다. MIDI 파일은 미리 type 0으로 바꾸어 놓아야 한다.

아두이노 나노 에브리(ATmega4890)로 업그레이드하면 코드를 거의 바꾸지 않고 원활한 작업이 가능할 것이다. 플래시는 48KB, SRAM은 6KB, EEPROM은 약간 적어서 256B(플래시에서 에뮬레이션)이다.

아두이노 나노 에브리. 사진 출처: 쿠팡


2만원 조금 넘는 비용을 들여서 아두이노 나노 에브리를 살 것인가? 또는 최적화 기법을 총동원하여 아두이노 나노에 모든 기능을 욱여넣을 것인가? 9월 한달 동안은 아두이노 나노를 최대한 쥐어짜서 하는 데까지 해 보려고 한다. 다음은 챗GPT가 제안한 최적화 방안이다.


아두이노 나노(ATmega328P)로 프로그램/콤비 저장MIDI 파일 재생, 기본 UI를 동시에 구현하기 위한 최적화 방법을 우선순위대로 정리했습니다.

1) SRAM(2 KB) 먼저 살리기 — 최우선

  • 모든 상수 문자열은 플래시에서 직접 출력: F() 매크로 사용
    lcd.print(F("Init SD..."));
  • 테이블/이름 전부 PROGMEM (GM/GS/드럼 이름, UI 라벨)
    const char sPlay[] PROGMEM = "Play";
    char buf[17];
    strcpy_P(buf, sPlay);
    lcd.print(buf);
  • String 금지char[] 고정 버퍼(예: 32~64B) 공용 사용
  • 큰 로컬 배열 금지 (스택 폭발 방지) → 전역의 작은 공용 버퍼 재사용
  • 시리얼 버퍼 축소/끄기, 디버그는 필요한 구간만
  • SD 브라우저 최소화
    • 8.3 파일명 only (LFN 비활성)
    • /SONGS/INDEX.TXT, /DRUMS/INDEX.TXT 한 줄 한 파일 순차 읽기
    • 현재 선택 항목만 char line[32]로 보관

2) 코드/플래시(32 KB) 절약

  • 기능 스위치: #define FEATURE_BROWSER 0/1, #define FEATURE_EDIT 0/1
  • 중복 문자열 제거 (공통 접두/접미어 조합), inline 남발 금지
  • 가벼운 LCD 라이브러리/필요 함수만 사용, 미사용 라이브러리 제거
  • 컴파일 옵션: LTO, -Os(기본) 유지

3) MIDI 재생부 설계(작고 단단하게)

  • Type-0 전용 스트리밍 (PC에서 Type-1 → 0 변환)
  • 템포(Meta 0x51)만 처리, 나머지 Meta는 스킵
  • Running Status 유지, 이벤트 즉시 전송 (대형 큐 금지)
  • 버퍼 1개(≤512B)read → parse → send 파이프라인

4) 파일시스템 선택

  • SdFat (설정 최소화, LFN off)로 충분하면 그대로
  • 더 빡빡하면 Petit FatFs (읽기 전용, 초저메모리) 검토

5) EEPROM(1 KB) 사용 요령

  • 콤비/설정은 고정 구조체(≈32B)로 저장 → 20~30개 가능
  • 저장은 버튼으로만 (수시 자동 저장 금지)
  • 간단 wear-leveling: 다음 슬롯에 순환 저장 + CRC로 무결성 체크

6) 빌드 프로파일 두 가지 운용(권장)

  • A안: 편집/브라우저 중심 (재생 최소)
  • B안: 재생 중심 (브라우저 축소, INDEX.TXT 의존)
  • → 스위치 1–2개로 코드 크기·RAM 밸런스 즉시 전환
* 여기서 말하는 '스위치'란 컴파일 타임 스위치(전처리 매크로 #define)를 말합니다. 즉, 빌드할 때 켜고 끄는 플래그로 코드/데이터(특히 PROGMEM 테이블, 브라우저 로직, 플레이어 로직)를 아예 포함하거나 제외해서 플래시·SRAM 점유를 즉시 바꾸는 방식이에요.

7) 진단/관찰 루틴(문제 예방)

  • 부팅 시 freeRAM() (남은 SRAM 추정) 한 번 출력
  • SD 읽기/파싱 오류는 짧은 메시지로만 표시
  • LCD 갱신 주기 완만하게 (예: 150–200 ms) → MIDI 막힘 예방

8) “정 안 되면” 최후의 수단

  • UI 라벨 더 줄이기, GM/GS 풀네임 대신 축약명
  • 드럼 브라우저는 숫자+간단명
  • 그래도 모자라면 Nano Every(6 KB SRAM) 고려
    (단, Every는 EEPROM 256B → 콤비는 SD 저장 권장)

바로 적용 체크리스트

  • [ ] F()/PROGMEM 전면 적용, String 전면 금지
  • [ ] 공용 워크버퍼 1개(64–128B)로 파서/표시 공유
  • [ ] 8.3 이름 + INDEX.TXT 순차 브라우즈
  • [ ] Type-0 스트리밍 + 템포만 처리
  • [ ] EEPROM: 버튼 저장 + CRC + 간단 wear-leveling
  • [ ] 기능 스위치로 “편집↔재생” 빌드 분리

참고 코드 조각

(1) PROGMEM 문자열 출력

const char sLoading[] PROGMEM = "Loading";
char line[17];

void printP(const char* p) {
  strcpy_P(line, p);
  lcd.print(line);
}

(2) 간단한 Combi 저장(EEPROM, CRC 포함 예시 골격)

#include <EEPROM.h>

struct Combi {
  uint8_t ver, mode;
  uint8_t chA, chB, progA, progB;
  uint8_t bankA_msb, bankA_lsb, bankB_msb, bankB_lsb;
  uint8_t volA, volB, panA, panB;
  uint8_t splitPoint, octaveA, octaveB;
  uint16_t crc;
};

uint16_t crc16(const uint8_t* d, size_t n); // 구현 필요

bool loadCombi(uint8_t idx, Combi& c) {
  int addr = idx * sizeof(Combi);
  EEPROM.get(addr, c);
  uint16_t got = c.crc;
  c.crc = 0;
  return (crc16((uint8_t*)&c, sizeof(Combi)) == got);
}

bool saveCombi(uint8_t idx, Combi& c) {
  c.ver = 1; c.crc = 0;
  c.crc = crc16((uint8_t*)&c, sizeof(Combi));
  int addr = idx * sizeof(Combi);
  EEPROM.put(addr, c);
  return true;
}

(3) INDEX.TXT 한 줄씩 읽기(개념)

// 사용 중인 FS 라이브러리에 맞게 File 타입/호출 교체
bool readIndexLine(File& f, char* out, size_t n) {
  size_t i = 0; int ch;
  while (i < n - 1 && (ch = f.read()) >= 0) {
    if (ch == '\n' || ch == '\r') { if (i == 0) continue; break; }
    out[i++] = (char)ch;
  }
  out[i] = 0;
  return (i > 0);
}

2025년 9월 8일 월요일

가을의 문턱에서 여름 철새 후투티를 만나다

대전시립미술관 주차장에서 만난 후투티(영문: Eurasian hoopoe, 학명: Upupa epops). 이미지 이어붙이기는 여기(bbom.org)를 이용하였다. 화려한 모관을 자랑하는 후투티를 휴대폰으로 찍었다. 모자 혹은 댕기로 표현하는 사람들도 있다. 관모는 씨앗에 붙어 바람에 잘 날아가게 만든 솜털과 같은 구조물을 말한다.



뽕나무 숲에 잘 나타나서 오디새라고도 부른다고 한다. 후투티는 순 우리말로서, '훗 훗'하고 우니까 서울대학교 동물학교실에서 1950년에 발간된 한국조류명휘(韓國鳥類名彙, A Hand-List of the Korean Birds)에서 이런 이름을 제안한 것이 그대로 정착했다고 한다. 彙('휘')는 '무리, 고슴도치라는 뜻을 갖는다. 잘 쓰지 않는 한자라고 생각하기 쉽지만, '어휘(語彙)'라는 용례가 있다.

국내 최초의 조류 종목록은 1948년 남태경 박사에 의한 「한국조류명휘」로 431종이 기재되었습니다.. (국가생물종목록 발간사 링크)

1948년과 1950년, 어느 것이 맞는지는 잘 모르겠지만(첫 번째 링크에서 보인 영문 책자에는 1950년), 해방 후 전란이 일어날 때까지 혼란스러운 시기에 이런 역작을 만들어냈다는 것은 놀랍다.

후투티는 한국 중부 이북 어디서나 볼 수 있는 여름 철새이나 흔치는 않다고 한다. 지구 온난화로 인해 한반도로 점점 내려와서 텃새가 되고 있다니, 기후 변동은 지구상에 사는 모든 생명체에게 영향을 미침을 알 수 있다. 

겨울 대구서 발견된 여름 철새 후투티…텃새가 됐나! 연합뉴스 2021년 12월 5일

벌써 9월 초순이니 예년 같으면 조석으로 제법 쌀쌀한 기운이 느껴지고 여름 철새도 남쪽으로 슬슬 이동을 해야 할 터인데 여전히 기온이 높으니 한반도에 남아서 겨울을 나기로 한 모양이다. 원래는 4월 말에 한국을 찾아 새끼를 키운 뒤 6~7월에는 남쪽으로 날아가야 한다. 방향을 잃지 않고 수천 km를 날아가야 하는 큰 일을 당장 하지 않고 한 곳에 머물게 된다면야 철새에게는 큰 이득이겠지만, 세상에 공짜가 어디 있으랴. 이에 따라서 멀리 날아가는 능력을 잃게 될지도 모른다. 인류도 인공지능 때문에 깊게 사고하고 판단하는 능력을 잃어버리고 있는지도 모른다. 


2025년 9월 3일 수요일

빠르지 않았지만 매우 균일한 페이스를 보여준 어제의 달리기

9월에 접어들면서 비가 뿌리더니 제법 아침 저녁의 기온이 많이 내려갔다. 덕분에 어제 저녁의 달리기는 꽤 수월하였다. 준비운동 삼아서 아령을 이용한 상체 운동과 스쿼트 및 스트레칭을 한 때문이었을까, 어제의 '정량화된 자아(Quantified Self)'는 꽤 바람직한 기록을 보여주었다. 빨리 달렸다는 것이 아니라 8km를 달리는 동안 매우 균일한 페이스를 유지했다는 뜻이다. 항상 완만한 '우하향'의 직선 형태를 보이던 페이스가 어제는 거의 수평선이었다.



랩 통계에서도 매 km 구간에 따른 편차가 많지 않았다. 내 기억으로도 이렇게 고른 페이스를 유지하면서 달린 것은 어제가 처음인 것 같다. 



달리면서 혹시 처지고 있는 것은 아닌지 늘 의식을 하는 것, 그리고 글로 옮기기는 어렵지만 발바닥을 쓰는 방법에 신경을 쓴 것도 도움이 되었다. 나는 가급적 '미드풋'으로 뛰려고 애를 쓰는데, 여기에도 달리는 사람에 따라서 많은 변주가 가능할 것 같다,

아령 운동은 2년 전 다쳤던 오른쪽 상완골과 어깨 주변을 강화하기 위함이다. 작년 10월부터 녹슨 5kg 아령으로 상체 운동을 시작하였는데, 그렇게 자주 하지는 못한다.

뜻하지 않은 부상(상완골 근위부 골절) 후 꼭 1년이 지나다 - 상체 근력 운동의 필요성

내가 운동 매니아가 될 가능성은 거의 없다. 하지만 습관이 된 달리기, 아령 운동 + 보충 운동(플랭크, 스쿼트 등)으로 중년 건강을 지켜 나가야 되겠다.

2025년 9월 2일 화요일

아두이노 자작품을 만들면서 얻은 교훈 - 코딩은 기다림이다!

소프트웨어 개발 절차는 보통 다음의 6가지 절차로 이루어진다고 한다.

  1. 요구사항 분석(Requirement Analysis)
  2. 시스템 명세
  3. 설계(Design)
  4. 프로그래밍(Programming)
  5. 테스트(Testing)
  6. 유지보수(Maintenance)

현재는 데이터 센터와 유사한 성격의 곳에서 일을 하고 있노라니 1번 단계, 즉 개발자가 사용자를 만나서 인터뷰를 수행하는 모습을 곁에서 많이 접하게 된다. 프로젝트의 성격이 단순하다면 이러한 6가지의 단계를 다 거칠 필요는 없고, 몇 가지 단계가 한꺼번에 뭉쳐서 진행되기도 한다.

아두이노 나도 응용 DIY 제품을 구동할 용도로 프로그램을 만드는 것도 비교적 단순한 소프크웨어 개발 절차라고 할 수 있다. 프로그램 코드를 저장하고기 위해 마련된 플래시 메모리의 공간이 32KB에 불과한 아두이노 나노에 채워 넣어야 하는 코드가 아무리 방대하고 복잡한들 그게 얼마나 되겠는가? 

C/C++ 문법도 잘 모르는 내가 아두이노 나노로 구동되는 MIDI sound module 제어기('Nano Ardule MIDI Controller')용 코드를 짜고 있다니 생각해 보면 정말 말도 안되는 일이다. 제어 대상은 본 블로그에서 여러 차례 소개했던 SAM9703 기반의 반주기 개조품(유튜브 링크).

그런데 그게 정말 현실에서 일어나고 있다. 필요한 기능을 챗GPT 대화창에 입력해 넣으면, 이를 실행하게 해 주는 .ino 파일을 생성하여 통째로 다운로드할 수 있게 만들어 주기 때문이다.

기다림은 바로 이 과정에서 필요하다. 챗GPT가 생각을 하고 코드를 짜 주는 동안 기다려야 한다. 문제는 이렇게 하여 제공한 코드가 항상 잘 컴파일이 되지는 않는다는 것이다. 중괄호를 빼먹는 것으로부터 선언하지 않은 변수나 함수가 쓰인다든지... C/C++을 잘 이해하는 사람이라면 아두이노 IDE에서 뱉어내는 에러 메시지만 보고서 금방 소스 코드를 수정할 터인데, 나와 같은 코딩맹(盲)은 에러 메시지를 그대로 복사하여 챗GPT에 붙여 넣은 뒤 문제를 파악하고 개선한 코드 파일을 다시 제공할 때까지 기다려야 한다. 

그런데 내가 봐도 똑같아 보이는 실수를 계속 반복하는 모습을 보고 있노라면 부글부글 속에서 뭔가 치밀어 오르는 답답함을 느낀다. 그러면 또 컴파일 중에 나오는 오류 메시지를 복사해서 제출하고, 기다리고... 그래서 코딩은 기다림이다. 지루한 반복 작업을 견디다 못한 나는 가장 마지막에 컴파일 및 작동에 성공한 코드 파일을 업로드한 뒤 여기에 추가 기능을 단계적으로 더해 나가는 방식으로 전환하였다. 너무 많은 양의 요구사항을 들이대면 실수를 많이 하는 것 같다. '잘 되고 있는 것은 건드리지 말아라'라는 식의 너무나 당연하고도 구체적인 요구를 때로는 해야 한다.

챗GPT는 마치 머리는 꽤 좋은데 가끔 딴생각을 하고 주위가 산만한 천재를 대하는 것 같다. 가끔 고집도 피운다. 그러나 조금만 마음을 가다듬고 생각해 보면 챗GPT가 없이 과연 내가 이런 취미 프로젝트를 단 한 발자국도 실현해 나갈 수 없었을 것이다. 그러니 다소 오류가 포함된 소스 코드를 반복적으로 토해 내더라도 무한한 인내심을 발휘해야 한다. 예를 들어 버튼을 한번 클릭할 때와 길게 눌렀을 때 서로 다른 동작을 하게 만드는 코드를 내 창의력과 검색만으로 짠다고 생각해 보라. 가당키나 한 일이겠는가. 1602 LCD, 4개의 LED, 5개의 버튼, 로터리 인코더... 시프트 레지스터 칩를 쓰지 않은 상태에서 아두이노 나노의 모든 입출력 핀을 전부 사용해야 하는 데다가, 메뉴 구성도 결코 단순하지 않기 때문이다.

끊이지 않는 버그, 머리를 쥐어뜯고 싶다!


테스트를 하는 과정에서 비합리적이거나 무리한 요구사항을 재정의하여 개선하는 일도 흔히 벌어진다. 모든 부품을 기판 위에 전부 올려서 배선을 한 날, 이것이 마지막 납땜이라고 호기롭게 자신했지만 그 뒤로 몇 번이나 회로 수정을 했는지 모른다. 코딩 역시 마찬가지다. 정의해 놓은 기능을 단계적으로 구현해 나가는, 즉 절대로 우회나 후진은 하지 않으면서 프로젝트가 진행될 것이라고 생각했으나 그것은 헛된 기대였다. 다시 중간 지점으로 돌아가서 기능을 다시 정의하는 일이 허다하게 벌어지고 있다.

현재 소스 코드의 분량은 700줄 정도이다. 마이크로SD카드에 데이터를 쓰고 읽는 기능까지는 아직 구현하지 못한 상태다. 바로 어제까지 작업하여 MIDI IN으로 들어오는 단일채널 키보드 신호를 MIDI 사운드 모듈의 2개 채널로 복제하여 보내는 것에 성공하였다. 채널은 서로 다른 악기(program)으로 할당해 둔 뒤, 버튼을 눌러서 어느 하나만 연주하거나 layer 또는 split이 가능하게 만들었다. Korg의 방식으로 말하자면 두 개의 프로그램으로 이루어진 심플한 'combi' 음색을 만들 수 있는 것이다. 보람이 느껴지지만 몸은 고단하다.

돌이켜보니 연기를 피우며 납땜을 할 때, 즉 하드웨어를 제작하던 단계가 훨씬 덜 고되었던 것 같다. 정신건강까지 고려한다면 하드웨어 제작단계가 건강에 미치는 영향은 코딩 단계보다 더 적은 것이 아닐까...

'몰입'은 즐거운 일이다. 그러나 몸과 마음을 너무 갉아먹을 정도라면 이야기는 달라진다. AI가 사람의 수로고움을 대신한다고? 어떤 측면에서는 옳다. 그러나 결과적으로 일을 더 하게 된다. 아무래도 다음번 글의 주제는 'AI가 촉발한 과몰입(또는 과노동)'이 될 것 같다.