Showing Posts From
디버깅
- 08 Dec, 2025
회사 '낡은' 컨벤션 vs 인터넷의 '신규' 트렌드
출근길에 본 글 지하철에서 Reddit 봤다. "Modern Embedded C Programming in 2024" 같은 제목. 클릭했다. 댓글에 누가 적어놨더라. "왜 아직도 헝가리안 표기법 쓰냐", "typedef 남발은 90년대나 하던 거". 웃겼다. 우리 회사 코드가 정확히 그거다. 회사 도착. 컴퓨터 켰다. 어제 푸시한 코드 리뷰가 떠 있다. "변수명 규칙 지켜주세요. g_u8DataBuffer 이런 식으로요." 헝가리안 표기법이다. 타입까지 변수명에 적는 거. C++이나 모던 C에서는 이미 안 쓴다는 게 정설인데.10년 전 코드베이스 우리 회사 메인 프로젝트는 2014년에 시작됐다. 벌써 11년째다. 그때 정한 코딩 컨벤션을 아직도 쓴다. 파일 하나 열면 이런 식이다. /****************************************************************************** * File Name : drv_uart.c * Description : UART Driver Module * Author : Kim XX * Date : 2014.03.15 * Version : v1.0 ******************************************************************************/typedef unsigned char UINT8; typedef unsigned short UINT16; typedef signed char INT8;static UINT8 g_u8RxBuffer[256]; static UINT16 g_u16RxIndex = 0;주석 박스. typedef로 타입 재정의. 전역변수에 g_ 접두사. 타입까지 변수명에. 2014년이면 이게 맞았다. 당시 임베디드 책들이 다 이렇게 가르쳤다. 문제는 2025년에도 이걸 쓴다는 거다. 신입이 작년에 들어왔다. 코드 보더니 물었다. "왜 stdint.h 안 쓰고 typedef로 다시 정의하나요?" 답은 간단했다. "원래 그렇게 해왔어요." 인터넷에서 본 트렌드 점심시간. Reddit r/embedded 들어갔다. 누가 올린 글. "Stop using global variables in embedded C". 내용 읽어봤다. 전역변수 대신 구조체로 컨텍스트 만들고, 함수에 포인터로 넘기라는 거다. 테스트하기도 쉽고 재사용성도 좋다고. 댓글도 다들 동의한다. "2024년에 전역변수는 레거시", "static 남발은 유지보수 지옥". 맞는 말이다. 우리 코드 전역변수 천지다. static uint8_t g_u8SensorData[10]; static bool g_bIsConnected = false; static uint32_t g_u32Timestamp = 0;파일 하나에 전역변수 30개 넘는 것도 있다. 어디서 바뀌는지 추적하려면 전체 검색 돌려야 한다. GitHub에서 최신 프로젝트들 봤다. Zephyr, FreeRTOS 예제 코드들. struct sensor_context { uint8_t data[10]; bool is_connected; uint32_t timestamp; };void sensor_read(struct sensor_context *ctx) { // ... }깔끔하다. 컨텍스트 구조체 하나로 상태 관리. 함수는 순수하게 로직만.개선 제안서 작성 저녁 먹고 돌아왔다. 생각했다. 제안서 써볼까. PPT 켰다. 제목 적었다. "코드 컨벤션 개선 제안". 슬라이드 3장 만들었다.현재 문제점: typedef 중복 정의, 전역변수 과다, 헝가리안 표기법 개선안: stdint.h 사용, 구조체 기반 컨텍스트, 모던 C 네이밍 기대효과: 가독성 향상, 유지보수성 개선, 신규 인력 적응 빠름레퍼런스도 달았다. NASA C Style Guide, Google C++ Style Guide, Linux Kernel Coding Style. 다음 날 팀장한테 보여줬다. 5분 봤다. 말했다. "취지는 좋은데요." 여기서 끝나면 좋겠는데. "지금은 여유가 없어요." 지금은 여유가 없다 팀장 말을 정리하면 이렇다. "지금 프로젝트 일정 빡빡합니다. 양산 2달 남았어요. 코드 스타일 바꾸다가 버그 생기면 어떻게 할 건데요. 제품 나간 다음에 생각해봅시다." 맞는 말이다. 양산 앞둔 시점에 대규모 리팩토링은 위험하다. 그런데. 2년 전에도 들었다. 그때도 "양산 끝나고". 1년 전에도 들었다. 그때는 "다음 프로젝트 시작할 때". 지금 또 들었다. "제품 나간 다음에". 결국 안 바뀐다. 영원히. 이유는 간단하다. 임베디드는 항상 바쁘다. 양산 끝나면 다음 제품. 다음 제품 끝나면 또 다음. 여유라는 게 없다. 선배한테 물었다. 7년차. "형, 우리 코드 스타일 언제부터 이랬어요?" "내가 입사했을 때도 이랬어. 그때도 바꾸자는 얘기 나왔는데." "안 바뀐 거네요." "응. 계속 바쁘다고."실무와 이상의 간극 퇴근길. 생각했다. 인터넷에서 보는 건 이상적이다. 깨끗한 코드, 모던한 기법, 최신 트렌드. 실제 회사는 다르다. 레거시 코드, 빡빡한 일정, 리스크 회피. 누구 잘못도 아니다. 구조적인 문제다. 웹 개발자들 부럽다. 걔네는 배포하고 A/B 테스트하고 롤백한다. 실험할 수 있다. 우리는 못한다. 한 번 양산 나가면 끝이다. 펌웨어 업데이트 구조 없으면 그냥 그대로 간다. 리스크가 크니까 보수적이 된다. 보수적이니까 안 바뀐다. 악순환이다. 신입이 물어봤다. 어제. "선배님, 학교에서 배운 거랑 너무 달라요. 전역변수 쓰지 말라고 배웠는데 여기는..." 할 말이 없었다. "그냥... 익숙해져." 작은 개선이라도 포기는 안 한다. 큰 개선은 안 돼도 작은 건 된다. 새로 작성하는 파일에는 stdint.h 썼다. uint8_t, uint32_t 이런 식으로. 리뷰에서 지적 안 들어왔다. 팀장도 그냥 넘어갔다. "기존 코드랑 다른데요." "새 모듈이라 따로 뺐습니다." "음... 뭐 돌아가면 되지." 승인 떨어졌다. 다음 파일도 그렇게 했다. 조금씩. 전역변수도 줄였다. 새 기능은 구조체로 만들었다. struct ble_context { uint8_t rx_buffer[256]; uint16_t rx_index; bool is_connected; };함수도 컨텍스트 받게 수정했다. 기존 코드는 못 고친다. 건드리면 테스트 다시 해야 한다. 시간 없다. 새로 짜는 것만이라도 깨끗하게. 이게 현실적이다. 10년 후엔 생각해봤다. 10년 후. 2035년. 나도 11년차가 돼 있다. 신입이 들어온다. 코드 보고 물어본다. "왜 이렇게 작성했어요?" 나는 뭐라고 답할까. "원래 그렇게 해왔어." 이러고 싶지는 않다. 그렇다고 지금 당장 전부 바꿀 수는 없다. 현실이 그렇다. 타협점을 찾아야 한다. 레거시는 유지. 신규는 개선. 천천히. 10년 걸려서 바뀔 수도 있다. 어쩌면 안 바뀔 수도 있다. 그래도 시도는 한다. 포기하면 영원히 안 바뀐다.회사 코드는 박물관이다. 신입은 적응한다. 나도 그랬다.
- 04 Dec, 2025
중국산 칩의 데이터시트가 중국어다
중국어 데이터시트와의 전쟁시작은 가격이었다 회의실. 구매팀 과장이 엑셀을 띄웠다. "STM32는 개당 8달러. 중국 칩은 2달러." 양산 수량 10만 개. 계산기 두드릴 필요도 없다. 6억 차이. "기능은 비슷합니까?" 하드웨어 팀장이 물었다. "거의 동일합니다. Cortex-M4 코어에 플래시 256KB..." 나는 듣고 있었다. 불안했다. "데이터시트 봤어요?" 내가 물었다. "있습니다. PDF로." "한글 버전요?" "중국어인데, 번역하면 되죠." 회의는 그렇게 끝났다. 결정됐다. 중국 칩. 이름도 처음 듣는 회사. 퇴근길에 검색했다. 자료가 없었다. 영문 리뷰도 없었다. 다음 날 샘플이 도착했다. 데이터시트 PDF도 함께. 820페이지. 전부 중국어. 번역기는 거짓말을 한다구글 번역에 넣었다. 1분 걸렸다. 결과물을 열었다. "GPIO configuration method: please configure pin function enable switch after power on sequence completes successfully." 무슨 소리지. 원문을 다시 봤다. 한자가 빽빽했다. 파파고도 돌렸다. 비슷했다. "핀 기능 활성화 스위치를 전원 순서 완료 후 설정하십시오." 핀 기능? 활성화 스위치? 전원 순서? 레지스터 맵을 봤다.地址 名称 说明0x4000 0000 GPIO_CFG 配置寄存器번역기: "Configuration register" 그래. 그건 알겠어. 비트 필드를 봤다.位 名称 说明[7:0] PIN_FUNC 引脚功能选择번역기: "Pin function selection" 도움이 안 됐다. 어떻게 선택하는데? 값은? 0x00이 뭐고 0x01이 뭔데? 표를 찾았다. 40페이지 뒤에 있었다.值 功能0x00 通用输入输出0x01 复用功能10x02 复用功能2번역기: "General input output", "Multiplexing function 1", "Multiplexing function 2" 복용 기능이 뭔데. 다시 검색했다. "alternate function"이었다. 하나하나 이랬다. 820페이지를. 예제 코드는 없다 보통 데이터시트 뒤에는 예제가 있다. STM32는 HAL 라이브러리에 수백 개 예제가 있다. 중국 칩은? SDK 다운로드 링크가 있었다. 중국 사이트. 회원가입 필요. 휴대폰 인증은 중국 번호만. 막혔다. 하드웨어 팀한테 물었다. "칩 회사 연락처 있어요?" "있긴 한데, 이메일 답장이 늦어요." 메일 보냈다. 영어로. "Could you provide SDK download link?" 3일 뒤 답장 왔다. 중국어. 번역기 돌렸다. "请访问我们的官方网站下载。" 공식 사이트 방문하래. 거기가 안 되는데. 다시 메일 보냈다. "Cannot access from Korea." 5일 뒤 답장. 중국어. "请使用VPN。" VPN 쓰래. 회사에서 VPN? 보안팀 허락받아야 한다. 보안팀: "업무용인가요?" "네." "중국 사이트요?" "칩 제조사 공식 사이트입니다." "검토 후 회신 드리겠습니다." 2주 걸렸다. 승인 안 났다. 결국 집에서 내 노트북으로 받았다. 규정 위반인지 모르겠다. SDK 압축 풀었다. README.txt 열었다. 중국어. 예제 폴더 열었다. 주석이 전부 중국어. // 初始化GPIO void gpio_init(void) { // 使能时钟 RCC->APB2ENR |= (1 << 3); // 配置为推挽输出 GPIOB->CRL &= ~(0x0F << 0); GPIOB->CRL |= (0x03 << 0); }"使能时钟". 번역기: "Enable clock". 이건 알겠다. "推挽输出". 번역기: "Push pull output". 이것도 알겠다. 하지만 레지스터 값이 맞는지는 모른다. 데이터시트랑 대조해야 한다. 중국어 데이터시트랑. 커뮤니티는 더 없다STM32 쓸 때는 몰랐다. 구글에 검색하면 답이 나온다는 걸. 스택오버플로우에 물어보면 30분 안에 답글 달린다는 걸. 중국 칩은? 검색해도 안 나온다. 영문 포럼 없다. 깃허브 이슈도 없다. 중국 포럼은 있다. CSDN이라는 사이트. 전부 중국어다. 회원가입했다. 질문 올렸다. 영어로. "How to configure UART interrupt priority?" 답글 없었다. 일주일 지나도. 중국어로 번역해서 다시 올렸다. "如何配置UART中断优先级?" 구글 번역 돌린 문장이라 자연스럽지 않았을 거다. 답글 하나 달렸다. 중국어. "请参考手册第245页。" 번역: "Please refer to page 245 of the manual." 245페이지 봤다. 이미 봤던 페이지. 도움 안 됐다. 다시 답글 달았다. "Still not clear." 답 없었다. 유튜브 찾아봤다. 튜토리얼 영상 있을까. 없었다. 중국 사이트 Bilibili에는 있었다. 중국어 음성. 자막 없음. 영상 보면서 따라했다. 레지스터 값 적었다. 주석 없이 코드만 따라 치는 느낌. 왜 이 값인지 모른다. 그냥 작동하니까. 실전은 더 지옥이다 타이머 인터럽트 설정하는데 3일 걸렸다. 데이터시트에는 이렇게 써 있었다. "定时器中断使能位于NVIC_ISER寄存器。" 번역: "Timer interrupt enable is located in NVIC_ISER register." NVIC_ISER는 ARM 표준이다. 이건 안다. 근데 인터럽트 번호가 뭔지 안 나와 있다. 표를 찾았다. 60페이지 뒤에.中断源 中断号TIM1 25TIM2 26TIM1이 25번. 코드 짰다. NVIC->ISER[0] |= (1 << 25);안 됐다. 인터럽트 안 들어온다. 하루 디버깅했다. 오실로스코프로 핀 확인. 파형 나온다. 인터럽트만 안 된다. 데이터시트 다시 봤다. 200페이지 더 읽었다. 작은 주석 발견. "注意:使用前需配置中断向量表偏移。" 번역: "Note: Need to configure interrupt vector table offset before use." 뭐? 벡터 테이블 오프셋? STM32는 자동인데. 코드 추가했다. SCB->VTOR = 0x08000000;됐다. 인터럽트 들어왔다. 3일 걸린 이유: 데이터시트 한 줄. 200페이지 뒤에 숨어 있음. 중국어로. 하드웨어 버그인가 펌웨어 버그인가 ADC 값이 이상했다. 3.3V 인가했는데 읽히는 값이 2.8V. 하드웨어팀: "회로는 문제없어요." 나: "그럼 캘리브레이션 문제?" 데이터시트 찾아봤다. ADC 챕터. 80페이지. 캘리브레이션 절차 있었다. 중국어. 번역기 돌렸다. "校准步骤:设置校准位 等待校准完成 读取校准值""Calibration steps:Set calibration bit Wait for calibration to complete Read calibration value"괜찮네. 따라했다. ADC->CR2 |= ADC_CR2_CAL; while(ADC->CR2 & ADC_CR2_CAL); uint16_t cal_val = ADC->DR;안 됐다. 여전히 2.8V. 에러타 찾았다. Errata sheet. 칩 버그 리스트. 중국어. 당연히. 번역 돌렸다. 30개 버그 중 하나 발견. "ADC校准值需乘以系数1.18。" "ADC calibration value needs to be multiplied by factor 1.18." 뭐? 곱하기 1.18? 왜? 이유 안 나와 있다. 일단 했다. float voltage = adc_value * 3.3 / 4096 * 1.18;됐다. 3.3V 나왔다. 이유는 모른다. 지금도. 양산이 다가온다 개발 3개월 걸렸다. STM32였으면 한 달. 디버깅 시간의 70%는 데이터시트 읽는 시간. 중국어 해석하고, 번역 의심하고, 예제 찾고, 안 되고. 팀장: "일정 괜찮아?" 나: "빡셉니다." 팀장: "다음엔 검증된 칩 쓰자." 나: "네." 다음에도 안 그럴 거다. 가격 때문에. 이제 양산 준비. 펌웨어 동작은 확인됐다. 근데 불안하다. 내가 놓친 중국어 주석이 있을까. 200페이지 뒤에 숨어있는 "注意" 또 있을까. 그냥 운에 맡긴다. 밤새 데이터시트 다시 읽는다. 중국어. 820페이지. 번역기는 여전히 거짓말한다. "请确保正确配置。" 번역: "Please ensure correct configuration." 뭘 어떻게 확인하라는 건데. 모른다. 내일 또 읽는다.6억 아끼려다 3개월 날렸다. 그래도 양산은 나간다.