Showing Posts From

버전

펌웨어 버전 v1.2.4 릴리스, 하지만 버전 번호만 올렸다

펌웨어 버전 v1.2.4 릴리스, 하지만 버전 번호만 올렸다

펌웨어 버전 v1.2.4 릴리스, 하지만 버전 번호만 올렸다 오전 10시, 릴리스 노트 작성 중 모니터 앞에 앉았다. 빈 텍스트 파일이 나를 보고 있다. Release Notes - Firmware v1.2.4타이핑을 멈췄다. 뭘 쓰지. 사실 바뀐 게 없다. 코드는 v1.2.3이랑 똑같다. 단 한 줄도 안 건드렸다. 그런데 버전은 올려야 한다. 이유는 간단하다. 하드웨어가 바뀌었으니까.HW팀에서 온 메일 어제 오후 3시. HW팀 김대리가 메일을 보냈다. "펌웨어팀님, PCB 리비전 올라갑니다. Rev.B → Rev.C. 레귤레이터 교체했어요. 출력 전압은 동일하고요. 펌웨어 수정 필요 없습니다." 좋은 소식이다. 펌웨어 수정 없다니. 그런데 메일 끝에 한 줄이 더 있었다. "버전은 올려주세요. 나중에 AS 들어오면 구분해야 해서요." 아. 그렇다. 똑같은 코드인데 다른 하드웨어에서 돌아가면, 버전을 구분해야 한다. 나중에 문제 생기면 '이 제품은 Rev.B 보드였나 Rev.C 보드였나' 추적해야 하니까. 레귤레이터 교체. TPS73733에서 RT9013로. 둘 다 3.3V 출력. 성능은 비슷하다. 가격이 100원 쌌다고 들었다. 대량 양산하면 몇천만원 차이. 우리 팀은 상관없다. 전압만 안정적이면 된다. 코드 수정 없다. 그런데 버전은 올려야 한다. 시맨틱 버저닝의 딜레마 우리 팀은 시맨틱 버저닝을 쓴다. v메이저.마이너.패치 형식이다.메이저: API 바뀌거나 큰 기능 변경 마이너: 기능 추가 패치: 버그 수정그럼 이번엔 뭘 올려야 하나. 기능 안 바뀌었으니 메이저 아니다. 기능 추가 없으니 마이너도 아니다. 버그도 안 고쳤으니 패치도 아니다. 그런데 올려야 한다. 결국 패치 번호를 올렸다. v1.2.3 → v1.2.4. 제일 낮은 단위니까. 시맨틱 버저닝 원칙에는 안 맞는다. 하지만 현실에서는 맞다. 하드웨어 세계에서는.릴리스 노트에 뭘 쓸까 다시 빈 텍스트 파일이다. Release Notes - Firmware v1.2.4정직하게 쓸까. "코드 변경 없음. 하드웨어 호환성 때문에 버전만 올림." 아니다. 그러면 나중에 문서 보는 사람이 헷갈린다. "왜 버전을 올렸지? 의미 없는 거 아니야?" 좀 더 전문적으로 쓸까. Changes: - Compatible with PCB Rev.C - Hardware regulator changed (TPS73733 → RT9013) - No firmware modification required이것도 이상하다. 'No firmware modification'이라고 쓰면, '그럼 왜 버전을 올렸어?' 또 물어본다. 결국 이렇게 썼다. Release Notes - Firmware v1.2.4Changes: - Support for PCB Rev.C (hardware revision update) - Regulator component change: TPS73733 → RT9013 - Voltage output remains 3.3V, firmware logic unchanged - For traceability in production and after-serviceNote: This version is functionally identical to v1.2.3 but must be distinguished for hardware compatibility tracking.길다. 설명이 너무 많다. 하지만 나중에 내가 다시 봤을 때 이해할 수 있어야 한다. 3개월 뒤에 AS 문의 들어오면, "v1.2.4가 뭐였더라?" 찾아볼 거다. 그때 이 문서를 본다. 그때 내가 이해할 수 있으면 된다. Git 커밋 메시지 릴리스 노트 썼으니 이제 Git에 태그를 달아야 한다. git tag -a v1.2.4 -m "Release v1.2.4" git push origin v1.2.4잠깐. 커밋 메시지는 뭐라고 쓰지. 마지막 커밋은 일주일 전이다. "Fix UART timeout bug in low power mode". v1.2.3 릴리스 후 버그 하나 고쳤던 거다. 그 이후로 커밋이 없다. 그럼 v1.2.4 태그는 그 커밋에 다는 건가. 그런데 그 커밋은 v1.2.3용 버그 픽스였는데. 고민했다. 그냥 빈 커밋을 하나 만들까. git commit --allow-empty -m "Bump version to v1.2.4 for PCB Rev.C compatibility"--allow-empty. 파일 변경 없이 커밋만 만드는 옵션이다. 이런 경우에 쓴다. 커밋 메시지도 고민이다. 짧게 쓰면 정보가 부족하고, 길게 쓰면 나중에 로그 보기 불편하다. 결국 이렇게 썼다. Bump version to v1.2.4 for PCB Rev.C compatibility- Hardware: PCB Rev.B → Rev.C - Regulator: TPS73733 → RT9013 - Firmware code: no changes from v1.2.3 - Purpose: version tracking for production/AS커밋 본문에 상세하게. 첫 줄은 짧게. Push 했다. 이제 v1.2.4 태그가 원격 저장소에 올라갔다.양산팀에 전달 버전 올렸으니 양산팀한테 알려야 한다. 메일을 썼다. "양산팀님, 펌웨어 v1.2.4 릴리스되었습니다. PCB Rev.C용입니다. 바이너리 파일 첨부합니다." 바이너리 파일은 v1.2.3이랑 똑같다. SHA256 해시값도 같다. 그냥 파일 이름만 firmware_v1.2.4.bin으로 바꿨다. 양산팀에서 답장 왔다. "확인했습니다. 그런데 v1.2.3이랑 뭐가 다른가요?" 예상한 질문이다. "코드는 동일합니다. 하드웨어 버전 구분용입니다. Rev.C 보드에는 v1.2.4를 넣어주세요. 나중에 추적 필요할 수 있어서요." "알겠습니다." 간단한 대화. 하지만 이게 중요하다. 지금은 별거 아닌 것 같지만, 6개월 뒤 고객사에서 문제 리포트 들어오면 다르다. "제품 시리얼 12345에서 전원 이슈가 있습니다." 그럼 우리는 추적한다. 시리얼 번호로 생산 로그 찾는다. "아, 이거 Rev.C 보드네요." 펌웨어 버전 확인한다. "v1.2.4 맞네요." 그다음 HW팀이랑 회의한다. "Rev.C 보드 RT9013 레귤레이터 쓴 거 맞죠? 혹시 그쪽 문제 아닐까요?" 이런 식으로 범위를 좁혀간다. 만약 버전 구분 안 했으면, "이게 어떤 보드에 들어간 건지" 추적이 어렵다. 생산 날짜로 추정해야 한다. "8월 1일 이후면 아마 Rev.C일 거예요." 이렇게 '아마'로 시작하면 디버깅이 오래 걸린다. 문서 업데이트 릴리스 노트 썼고, Git 태그 달았고, 양산팀한테 전달했다. 이제 문서를 업데이트해야 한다. 우리 팀 위키에 "Firmware Version History" 페이지가 있다. 표 형식이다.Version Date PCB Rev Changesv1.2.3 2024-11-15 Rev.B UART timeout fixv1.2.4 2024-11-22 Rev.C Hardware compatibility간단하다. 나중에 누가 봐도 한눈에 들어온다. "아, v1.2.4부터 Rev.C구나." 이게 전부다. 복잡한 설명 필요 없다. 표 한 줄이면 된다. 근데 이걸 안 해두면, 나중에 누가 v1.2.4를 보고 "이게 뭐지?" 헤맨다. 코드 diff 보면 변경 없으니 더 혼란스럽다. 문서가 중요한 이유다. 사실 찜찜하다 솔직히 찜찜하다. 코드는 똑같은데 버전만 올린다. 시맨틱 버저닝 원칙에도 안 맞다. 이게 맞는 방법인가. 다른 방법도 생각해봤다.빌드 번호를 쓴다. v1.2.3+build.001, v1.2.3+build.002 이런 식으로. 하지만 우리 빌드 시스템은 빌드 번호를 지원 안 한다.메타데이터를 쓴다. v1.2.3+revC 이런 식으로. 근데 이것도 시맨틱 버저닝 표준이고, 우리 툴체인이 '+' 기호를 제대로 처리 못 한다.하드웨어 버전을 별도로 관리한다. 펌웨어 버전은 v1.2.3 고정, 하드웨어 버전 정보를 펌웨어 안에 define으로 넣는다. #define HW_REV_C. 이게 제일 깔끔한데, 이미 양산 중인 제품이라 지금 구조 바꾸기 어렵다.결국 패치 버전을 올리는 게 제일 현실적이다. 찝찝하지만, 이게 펌웨어 세계다. 웹 개발자들은 이런 거 안 겪는다. 프론트엔드 코드는 하드웨어랑 상관없다. 어떤 폰에서든 똑같은 코드가 돌아간다. 우리는 다르다. 똑같은 코드라도 보드가 다르면 추적해야 한다. 팀장님 리뷰 오후 2시. 팀장님이 내 자리로 왔다. "v1.2.4 릴리스 봤어요. 코드 변경 없는데 버전 올린 거?" "네. Rev.C 보드 대응입니다. HW팀에서 요청했어요." "흠. 릴리스 노트에 설명 잘 써놨네요. 나중에 추적 가능하겠어요." "네. 표도 업데이트했습니다." "좋아요. 근데 이런 거 너무 자주 하면 버전 번호 인플레이션 생겨요. 가능하면 묶어서 올리는 게 나을 텐데." 팀장님 말이 맞다. 하드웨어 리비전 올라갈 때마다 펌웨어 버전 올리면, 버전 번호가 너무 빨리 증가한다. v1.2.10, v1.2.15... 이렇게 되면 버전만 봐서는 큰 변화인지 작은 변화인지 구분이 안 된다. "다음부턴 HW팀이랑 미리 조율해볼게요. 리비전 올라가는 거 미리 알려달라고 하고, 펌웨어 업데이트랑 같이 묶을 수 있으면 묶고." "그래요. 그게 나을 거예요." 팀장님 돌아갔다. 사실 이번에도 묶으려고 했다. 다음 주에 BLE 통신 개선 작업이 있어서, 그거랑 같이 v1.3.0으로 올리려고 했다. 근데 HW팀이 급했다. 부품 수급 문제로 레귤레이터를 당장 교체해야 했다. 양산 라인이 멈출 뻔했다. 어쩔 수 없이 버전만 올렸다. 현실은 계획대로 안 된다. 6개월 뒤를 생각한다 지금은 귀찮다. 코드 안 바뀌었는데 버전 올리고, 문서 쓰고, 메일 보내고. 근데 6개월 뒤엔 고마울 거다. 문제 생겼을 때, 정리된 문서가 있으면 30분이면 원인 찾는다. 없으면 3일 걸린다. "이 펌웨어가 어떤 보드용이었지? 코드 diff 봐야 하나? Git 로그 뒤져야 하나? 양산팀한테 물어봐야 하나?" 이런 거 안 하려고 지금 문서 쓰는 거다. 펌웨어 개발은 코딩만 하는 게 아니다. 추적 가능성을 만드는 것도 일이다. 특히 양산 제품은 더 그렇다. 코드는 잘 돌아가는데, 3년 뒤 AS 들어왔을 때 뭐가 뭔지 모르면 소용없다. 그래서 버전 관리가 중요하다. 귀찮지만 해야 한다. 다른 팀은 어떻게 하나 점심시간. 옆 부서 이재훈이랑 밥 먹으면서 물어봤다. "너네도 이런 거 있어? 코드 안 바뀌었는데 버전 올리는 거?" "당연하지. 우리도 하드웨어 바뀔 때마다 올려. 근데 우리는 빌드 메타데이터로 관리해." "어떻게?" "버전은 그대로 두고, 빌드 ID를 다르게 줘. 펌웨어 안에 HW_VERSION define 있어서 거기다 Rev.C 같은 거 박아." "그럼 버전 번호는 안 올라가?" "응. v2.1.0에서 계속 쓰고, 내부 빌드 정보로 구분하지." 부럽다. 그게 더 깔끔하다. "너네는 왜 그렇게 했어?" "처음 설계할 때부터 그렇게 했어. 하드웨어 버전이랑 펌웨어 버전을 분리하는 게 맞다고 판단했대. 선배들이." 우리 팀은 그런 거 없이 시작했다. 처음엔 하드웨어 버전이 하나뿐이었으니까. Rev.A만 있었다. 그러다 Rev.B 나오고, Rev.C 나오고, 이제 와서 구조 바꾸기 애매하다. 기술 부채다. "다음 제품에선 그렇게 해볼까 생각 중이야. 지금 제품은 이미 늦었고." "ㅇㅇ 다음에는 처음부터 구조 잡고 시작해." 맞는 말이다. 매뉴얼도 업데이트 오후 4시. 고객사 매뉴얼도 업데이트해야 한다는 걸 깨달았다. 우리 제품은 B2B다. 고객사에서 우리 모듈을 사서 자기네 제품에 넣는다. 그래서 기술 매뉴얼이 있다. 매뉴얼 48페이지. "Firmware Version Information" 섹션이 있다. Supported Firmware Versions: - v1.2.3: Compatible with PCB Rev.B - v1.2.4: Compatible with PCB Rev.C이것만 추가하면 된다. 근데 매뉴얼은 PDF다. 워드 파일 열고, 수정하고, PDF 다시 뽑고, 서버에 업로드하고. 30분 걸렸다. 고객사한테도 메일 보냈다. "기술 매뉴얼 업데이트되었습니다. v1.2.4 정보 추가되었습니다." 답장 없다. 읽기만 했다. 그러면 된 거다. 결국 누구를 위한 버전인가 저녁 7시. 퇴근 준비하면서 생각했다. v1.2.4. 코드는 안 바뀌었지만 버전은 올라갔다. 이게 누구를 위한 건가. 개발자를 위한 거다. 내년에 나를, 3년 뒤 이 코드를 인계받을 후배를 위한 거다. 사용자는 모른다. 제품 쓸 때 펌웨어 버전 같은 거 안 본다. v1.2.3이든 v1.2.4든 똑같이 쓴다. 고객사도 별로 신경 안 쓴다. 잘 돌아가면 된다. AS팀이 본다. 문제 생기면. 그때 버전 추적한다. 결국 내부 사람들 위한 거다. 엔지니어들 위한 버전 번호다. 그래서 더 신경 써야 한다. 코드만큼 중요한 게 버전 관리다. 나중에 내가 이 코드를 다시 볼 때, "아, v1.2.4였구나" 하고 바로 이해할 수 있어야 한다. 그게 좋은 버전 관리다. 내일은 내일 출근하면 v1.2.4 바이너리를 최종 테스트한다. Rev.C 보드 10개에 올려보고, 기능 테스트 한 번 더 돌린다. 코드는 똑같지만, 하드웨어가 다르니 혹시 모른다. 레귤레이터가 달라지면서 전압 라이즈 타임이 미세하게 달라질 수 있다. 그러면 MCU 부팅 타이밍이 바뀔 수 있다. 가능성 낮지만, 확인해야 한다. 펌웨어 엔지니어는 의심쟁이여야 한다. "설마 이것도 영향 있겠어?" 하는 것들이 실제로 영향 있다. 테스트 통과하면, 양산 승인. v1.2.4 정식 릴리스. 그리고 다음 작업으로 넘어간다. BLE 통신 개선. 그건 v1.3.0이 될 거다.버전 번호는 숫자가 아니라 역사다. 코드의 타임라인이다. 귀찮아도 제대로 써야 한다.

버전 관리, 펌웨어는 다르다

버전 관리, 펌웨어는 다르다

버전 관리, 펌웨어는 다르다 웹은 v1.2.3으로 끝난다 웹 개발자 친구가 물었다. "너네도 Git 쓰지?" 쓴다. 당연히 쓴다. "그럼 버전 관리 똑같지 않아?" 아니다. 완전히 다르다. 웹은 간단하다. v1.2.3 찍고 배포. 안 되면 롤백. 5분이면 끝. 펌웨어는? 펌웨어 버전 v1.2.3 하드웨어 리비전 Rev2.1 메인 칩 STM32F4 Rev Y 센서 칩 BME280 Rev 1.8 이게 하나의 '버전'이다. 문서에는 이렇게 적는다. "FW v1.2.3 / HW Rev2.1 / MCU RevY / Sensor Rev1.8" 이 조합이 양산에 나갔다. 필드에서 문제 생겼다. 어느 버전이 나갔더라? 추적이 시작된다.하드웨어 리비전부터가 문제다 회로가 바뀌면 리비전이 올라간다. Rev1.0 → Rev1.1 → Rev2.0 문제는 이거다. Rev1.1에서 돌던 펌웨어가 Rev2.0에서 안 돌 수 있다. 저항 값 하나 바뀌어도 타이밍이 달라진다. 커패시터 용량 달라지면 부팅 시퀀스가 바뀐다. "하드웨어 약간만 수정했어요" 약간이 없다. 전부 다시 테스트다. 그래서 펌웨어에 하드웨어 리비전을 박는다. #define HW_REVISION_MIN 0x0200 // Rev2.0 #define HW_REVISION_MAX 0x0210 // Rev2.1if (hw_rev < HW_REVISION_MIN) { error_incompatible_hardware(); }이거 안 넣으면? 고객이 구형 보드에 신형 펌웨어 올린다. 보드 망가진다. 반품 들어온다. HW팀이 "Rev 올렸어요" 말 안 하고 넘어간 적 있다. 양산 나가고 일주일 뒤에 알았다. 밤새서 추적했다. 어느 시리얼 넘버부터 Rev2.1인지. 엑셀 파일이 세 개 버전으로 나뉘어 있었다. 최종본이 뭔지 몰랐다. 칩 리비전은 더 골치다 같은 STM32F407이어도 내부 리비전이 있다. Rev A, Rev Y, Rev Z 실리콘 버그 수정한 거다. Rev A에는 I2C 버그가 있다. 특정 조건에서 ACK를 못 받는다. 우리 코드에서 그 조건이 걸렸다. 해결책? Rev A는 워크어라운드 코드 추가. Rev Y부터는 정상 코드. uint32_t mcu_rev = read_mcu_revision(); if (mcu_rev == MCU_REV_A) { i2c_init_with_workaround(); } else { i2c_init_normal(); }문제는 양산 중에 칩이 바뀐다는 거다. 반도체 회사가 예고 없이 Rev 올린다. 우리한테 통보 안 한다. 어느 날 갑자기 보드가 안 돌았다. 칩 확인했다. Rev Z로 바뀌어 있었다. 데이터시트에 없는 리비전이었다. ST 게시판 뒤져서 찾았다. "Rev Z has modified flash timing" 플래시 타이밍이 바뀌었다. 우리 부트로더가 타이밍 하드코딩이었다. 코드 수정. 테스트. 3일 걸렸다. 그동안 생산 라인 멈췄다.센서 칩도 버전이 있다 BME280 온습도 센서 쓴다. 이것도 리비전이 있다. 칩 ID로 구분한다. 초기 버전은 습도 센서에 오프셋이 있다. +3% 정도 높게 나온다. 데이터시트 읽다가 발견했다. "Known issue in early samples" 그래서 펌웨어에서 보정한다. if (bme280_chip_id == 0x58) { // Early revision humidity -= 3.0; }문제는 부품사가 바뀔 때다. 중국 부품사로 변경했다. 같은 BME280인데 칩 ID가 다르다. 0x60이다. 보정 안 들어간다. 값이 이상하다. 필드 테스트에서 걸렸다. 다시 코드 추가. if (bme280_chip_id == 0x58) { humidity -= 3.0; } else if (bme280_chip_id == 0x60) { humidity -= 1.5; // 중국 버전은 1.5% }이제 우리 펌웨어는 센서 칩 버전도 체크한다. 처음부터 알았으면 설계를 다르게 했을 텐데. 양산 추적이 악몽이다 고객사에서 전화 왔다. "제품이 계속 재부팅돼요." 시리얼 넘버 받았다. S/N: 2024010500123 이 제품이 어떤 조합인지 찾아야 한다. 생산 기록 엑셀 열었다. 2024년 1월 5일 생산분. 500번대 시리얼. 펌웨어 버전: v2.3.1 하드웨어: Rev2.0 MCU: STM32F407 Rev Y 센서: BME280 ID 0x60 이더넷 칩: W5500 Rev 1.1 근데 또 문제다. 이날 생산분 중에 일부는 Rev1.9 보드를 썼다. 보드 재고가 섞여 있었다. 시리얼 500번대가 Rev1.9인지 Rev2.0인지 모른다. 생산 팀에 전화했다. "기록 남겼죠?" "네... 어디 적었는지 찾아볼게요." 한 시간 뒤에 연락 왔다. "500~600번은 Rev1.9 보드예요." 아, 그럼 Rev1.9 + v2.3.1 조합이네. 우리 테스트 매트릭스에 없는 조합이다. 이 조합으로 테스트 안 했다. v2.3.1은 Rev2.0 기준으로 개발했다. Rev1.9는 GPIO 핀 배치가 약간 다르다. 재부팅 원인 찾았다. 패치 펌웨어 만들었다. v2.3.1a Rev1.9 전용. 고객에게 보냈다. "이 펌웨어로 업데이트하세요." "어떻게 업데이트하나요?" USB 케이블 연결하고, 프로그램 실행하고... 전화로 20분 설명했다.매트릭스가 폭발한다 테스트 매트릭스가 기하급수다. 펌웨어 버전: 5개 (메이저 버전만) 하드웨어 리비전: 3개 MCU 리비전: 3개 센서 칩: 2개 5 × 3 × 3 × 2 = 90가지 90가지를 다 테스트할 수 없다. 보드도 없고 시간도 없다. 그래서 "지원 조합"을 정한다. 공식 문서에 적는다.FW Ver HW Rev MCU Rev Sensorv2.3.x Rev2.0 Y, Z 0x60v2.2.x Rev1.9 Y 0x58이 조합만 보증한다. 다른 조합은 "미검증"이라고 적는다. 그래도 필드에서는 온갖 조합이 나온다. 부품 수급 때문에 칩이 바뀐다. 보드 재고 때문에 리비전이 섞인다. 어쩔 수 없다. 문제 생기면 그때 대응한다. 추적 시스템을 만들었다 더 이상 못 참겠다. 엑셀로는 한계다. 사내 DB 만들었다. 생산 시 자동으로 기록되게. 시리얼 넘버 입력하면펌웨어 버전 하드웨어 리비전 사용된 모든 칩 리비전 생산 일시 작업자다 나온다. 만드는 데 2주 걸렸다. 생산 팀 설득하는 데 1주 걸렸다. "왜 이렇게 복잡하게 해요?" "나중에 추적 못 하면 더 복잡합니다." 지금은 잘 쓴다. 고객 문의 오면 10분이면 찾는다. 문제는 과거 제품이다. DB 만들기 전 제품은 여전히 엑셀이다. 2년 치 생산 기록. 정리 안 돼 있다. 언젠가 정리해야 하는데. 할 시간이 없다. 웹은 부럽다 웹 개발자는 이런 거 신경 안 쓴다. 서버 버전 하나. 클라이언트 버전 하나. 끝. 브라우저 버전? 그건 유저 문제다. "최신 브라우저로 업데이트하세요." 우리는? "최신 하드웨어로 교체하세요?" 불가능하다. 필드에 나간 제품은 10년 간다. 그 10년 동안 펌웨어 업데이트가 나간다. 하드웨어는 그대로다. Rev1.0 보드가 2015년에 나갔다. 2025년 지금도 돌고 있다. 펌웨어는 v8.2.1까지 올라갔다. v8.2.1이 Rev1.0을 지원해야 한다. 10년 전 하드웨어를. 레거시 코드가 산더미다. // Rev1.0 workaround (DO NOT REMOVE!) #ifdef HW_REV_1_0 delay_ms(100); // Wait for old capacitor #endif주석에 "DO NOT REMOVE" 적어놨다. 누가 지우면 Rev1.0 보드가 죽는다. 근데 신입이 "왜 이런 코드가?" 하면서 지웠다. 다행히 리뷰에서 걸렸다. "이거 못 지운다. Rev1.0 때문이다." "Rev1.0이 뭔데요?" "10년 전 보드다." "아직도 써요?" "쓴다." 버전 넘버링 규칙도 복잡하다 웹은 시맨틱 버저닝이다. Major.Minor.Patch 명확하다. 우리도 그렇게 하려 했다. 안 됐다. v2.3.1인데 어느 하드웨어용인지 알 수가 없다. 그래서 규칙을 바꿨다. v{HW}{Major}{Minor}_{Patch} 예: v2_03_01_05HW Rev2.x 전용 메이저 3 마이너 1 패치 5근데 이러니까 숫자가 길다. 고객이 헷갈려한다. "버전이 v2_03_01_05인가요? v20_30_10_5인가요?" 다시 규칙 바꿨다. FW-R2-v3.1.5FW: 펌웨어 R2: Rev2.x용 v3.1.5: 버전좀 나아졌다. 근데 또 문제다. Rev2.0과 Rev2.1이 호환 안 될 때가 있다. FW-R2.0-v3.1.5 FW-R2.1-v3.1.6 이렇게 해야 한다. 버전 스트링이 점점 길어진다. 펌웨어에 박힌 문자열도 길어진다. 플래시 용량 아깝다. 256KB 플래시인데 버전 정보만 1KB 먹는다. 효율적으로 바꿨다. 바이너리 인코딩. typedef struct { uint8_t hw_major; uint8_t hw_minor; uint8_t fw_major; uint8_t fw_minor; uint16_t fw_patch; uint32_t build_date; // Unix timestamp } version_info_t;24바이트로 줄었다. 근데 이러니까 사람이 못 읽는다. 디버깅할 때 메모리 덤프 보면 "02 00 03 01 00 05..." 뭔지 모른다. 문자열도 같이 넣었다. 24 + 64 = 88바이트. 플래시 용량 항상 부족하다. 빌드 정보도 넣어야 한다 버전만으로 부족하다. 같은 v3.1.5인데 빌드가 다를 수 있다. Git 커밋 해시 넣었다. #define FW_VERSION "v3.1.5" #define GIT_HASH "a3f2c91" #define BUILD_DATE "2025-01-15" #define BUILD_TIME "14:23:01"이것도 자동으로 생성되게 했다. 빌드 스크립트에서 넣는다. #!/bin/bash GIT_HASH=$(git rev-parse --short HEAD) BUILD_DATE=$(date +%Y-%m-%d) BUILD_TIME=$(date +%H:%M:%S)echo "#define GIT_HASH \"$GIT_HASH\"" > version.h echo "#define BUILD_DATE \"$BUILD_DATE\"" >> version.h echo "#define BUILD_TIME \"$BUILD_TIME\"" >> version.h이제 펌웨어 버전이 이렇게 나온다. "FW-R2.0-v3.1.5-a3f2c91 (2025-01-15 14:23:01)" 길다. 근데 필요하다. 고객이 문제 보고하면 정확히 어느 빌드인지 알아야 한다. "v3.1.5 쓰는데 문제 있어요." "커밋 해시가 뭐죠?" "어떻게 알아요?" "시리얼 터미널 열고 'version' 입력하세요." 고객 대부분이 못 한다. 원격 지원으로 알려줘야 한다. 양산 브랜치 관리 개발은 develop 브랜치에서 한다. 양산은 release 브랜치. 문제는 여러 양산 버전이 동시에 유지된다는 거다.release/v2.3 (Rev1.9용, 구형 제품) release/v3.1 (Rev2.0용, 현재 제품) release/v4.0 (Rev2.1용, 신제품)세 브랜치 다 유지보수한다. 버그 픽스가 나오면? develop에서 고친다. 세 브랜치에 다 체리픽한다. git checkout release/v2.3 git cherry-pick abc123git checkout release/v3.1 git cherry-pick abc123git checkout release/v4.0 git cherry-pick abc123충돌 난다. 하드웨어 차이 때문에 코드가 다르다. 수동으로 머지한다. 하루 걸린다. 웹 개발자는 "master 브랜치 하나면 되잖아?" 한다. 안 된다. 양산 나간 버전은 못 바꾼다. OTA 업데이트는 더 복잡하다 OTA (Over-The-Air) 업데이트 지원한다. 인터넷으로 펌웨어 업데이트. 편하긴 한데 복잡하다. 서버에 펌웨어 파일이 여러 개다.FW-R1.9-v2.3.5.bin FW-R2.0-v3.1.8.bin FW-R2.1-v4.0.2.bin제품이 서버에 접속한다. "나 Rev2.0이고 v3.1.5 쓰는데 업데이트 있어?" 서버가 확인한다. "Rev2.0용 최신은 v3.1.8이야. 다운받아." 다운로드 시작. 256KB 파일. 2G 모뎀으로 받는다. 5분 걸린다. 중간에 끊길 수 있다. 재개 기능 필요하다. 청크로 나눠서 받는다. 다 받았다. 체크섬 확인. OK. 플래시에 쓴다. 리부팅. 새 펌웨어 실행. 자기 검증. "나 v3.1.8 맞아?" 맞다. 서버에 보고. "업데이트 성공." 여기까지 10분. 문제는 실패 케이스다. 다운로드 중 끊겼다? 다시 받아야 한다. 근데 몇 번까지 재시도? 3번? 5번? 체크섬 안 맞는다? 파일 깨졌다. 다시 받는다. 플래시 쓰기 실패? 하드웨어 문제다. 롤백해야 한다. 새 펌웨어 부팅 안 된다? 백업 펌웨어로 되돌린다. 백업 펌웨어도 안 된다? 제품 벽돌됐다. 서비스센터 가야 한다. 이런 케이스 다 처리해야 한다. typedef enum { OTA_IDLE, OTA_CHECKING, OTA_DOWNLOADING, OTA_VERIFYING, OTA_FLASHING, OTA_REBOOTING, OTA_SUCCESS, OTA_FAILED, OTA_ROLLBACK } ota_state_t;상태 머신 복잡하다. 웹은? 새 버전 배포하면 바로 적용된다. 유저는 새로고침만 하면 된다. 실패하면? 서버가 알아서 롤백한다. 우리는 제품이 필드에 있다. 롤백이 자동으로 안 된다. 제품 안에 코드로 다 처리해야 한다. 호환성 지옥 OTA로 v2.3.5에서 v3.1.8로 업데이트한다. 메이저 버전이 올라갔다. 데이터 포맷이 바뀌었다. 구버전 설정 파일을 신버전이 읽을 수 있나? 마이그레이션 코드 필요하다. if (config_version == 0x0203) { // v2.3 // Migrate to v3.1 format new_config.mode = old_config.legacy_mode; new_config.timeout = old_config.legacy_timeout * 1000; // ms로 변환 }문제는 버전이 여러 개 건너뛸 수 있다는 거다. v2.1 → v3.1 업데이트? v2.2, v2.3을 건너뛴다. 마이그레이션을 순차로 해야 한다. if (config_version == 0x0201) { migrate_v21_to_v22(); migrate_v22_to_v23(); migrate_v23_to_v31(); }마이그레이션 코드가 쌓인다. 10년 치 마이그레이션. 코드가 비대해진다. "오래된 버전 지원 중단하면 안 돼요?" 안 된다. 필드에 v1.0이 아직 있다. v1.0에서 v4.0으로 업데이트 가능해야 한다. 마이그레이션 경로를 유지해야 한다. 웹은? DB 마이그레이션 한 번 돌리면 끝. 유저는 신경 안 쓴다. 우리는 제품마다 마이그레이션이 일어난다. 100만 대 출하했으면 100만 번 마이그레이션이 필드에서 실행된다. 한 번이라도 실패하면? 고객 불만. 제품 반품. 테스트를 엄청 많이 한다. 필드 테스트의 고통 실험실에서는 다 된다. 필드에 나가면 문제가 생긴다. v3.1.5로 업데이트했다. 고객 보고: "가끔 멈춰요." 재현이 안 된다. 우리 보드로는 정상이다. 고객 보드를 받았다. 시리얼 넘버 확인. 2023년 생산품. Rev1.9 + 초기 센서 칩 조합. 우리 테스트는 Rev2.0 + 최신 칩으로 했다. 고객 조합으로 테스트. 재현됐다. 센서 폴링 타이밍 문제였다. v3.1.5a 패치. 고객에게 보냈다. 일주일 뒤 또 연락. "다른 제품도 같은 문제 있어요." 시리얼 넘버 받았다. 2024년 생산품. Rev2.0 + 중국 칩. 또 다른 조합이다. 패치가 안 먹혔다. 중국 칩 타이밍이 달랐다. v3.1.5b 패치. 또 보냈다. 이제 세 버전이 있다.v3.1.5 (오리지널) v3.1.5a (Rev1.9용 패치) v3.1.5b (중국 칩용 패치)관리가 안 된다. v3.1.6 릴리스하면서 다 합쳤다. 모든 조합 지원하게. 테스트 기간이 두 배 늘었다. 문서화는 필수다 이 모든 걸 문서로 남긴다. 버전별 릴리스 노트. 지원 하드웨어 조합. 알려진 이슈. 마이그레이션 가이드. 문서 쓰는 데 개발 시간만큼 걸린다. # FW v3.1.5 Release Note## Supported Hardware - Rev2.0 + STM32F407 RevY/Z + BME280 ID 0x60## Changes - Fixed sensor polling timing issue (#234) - Added OTA retry mechanism (#245) - Improved error reporting (#251)## Known Issues - Incompatible with Rev1.9 + early sensor chip (Use v3.1.5a instead)## Migration - v2.x to v3.1: Auto migration supported - v1.x to v3.1: Manual config reset required이렇게 적는다. 고객은 안 읽는다. "문서 어디 있어요?" 링크 보내준다. "너무 길어요. 요약해주세요." 전화로 설명한다. 기술 지원팀이 따로 있으면 좋으련만. 우리가 다 한다. 개발하고, 테스트하고, 문서 쓰고, 고객 지원하고. 웹은 기술 지원이 FAQ면 된다. 우리는 하드웨어 조합별로 답이 다르다. FAQ가 100페이지다. 내부 버전과 외부 버전 마케팅팀이 버전을 바꾸고 싶어 한다. "v3.1.5는 너무 복잡해요. v3으로 하면 안