Showing Posts From

회사

토요일에도 회사, 양산 준비 열기 1주일 남음

토요일에도 회사, 양산 준비 열기 1주일 남음

토요일에도 회사, 양산 준비 열기 1주일 남음 오전 10시, 토요일 출근 알람 없이 일어났다. 7시 반. 토요일인데 몸이 알아서 깬다. 평소 같으면 "토요일은 무슨"이라고 했을 거다. 이번은 다르다. 양산 D-7. 팀장이 어제 말했다. "내일 나올 수 있는 사람?" 아무도 대답 안 했다. 5초쯤 지나서. "전 나오겠습니다." 내가 먼저 말했다. 그러니까 다들 나온다고 했다. 5명 전원. 씻고 나왔다. 회사 도착 9시 50분. 주차장이 텅 비었다. 우리 팀 차만 5대.주차장에서 코드 리뷰 사무실 들어가기 전에 차에 앉았다. 노트북 켰다. 어젯밤 커밋한 코드. DMA 버퍼 오버플로우 체크 로직. 주차장에서 다시 봤다. if(dma_buffer_idx >= DMA_BUFFER_SIZE) { // 여기서 뭘 해야 하지 }주석이 이렇게 되어 있다. 어젯밤 새벽 2시에 쓴 거다. 뭘 생각했는지 기억이 안 난다. 10분 동안 봤다. 아, 인터럽트 끄고 리셋해야지. 노트북에 메모했다. 사무실 가서 고치자. 차에서 내렸다. 팀장 차도 와 있다. 회의실에 모인 5명 다들 와 있었다. 회의실에 노트북 펼쳐놓고. "커피 타올게요." 막내가 말했다. "나도." "저도요." 결국 5잔. 팀장이 화이트보드에 썼다.UART 타임아웃 이슈 DMA 버퍼 관리 저전력 모드 전류 초과 OTA 검증 미완4개. 1주일에 4개. "하나씩 가자. UART부터." 시작했다.UART 타임아웃, 그놈의 9600bps UART 통신이 가끔 멈춘다. 재현이 안 된다. 100번 중 1번꼴. "타임아웃 값이 문제 아닐까요?" "이미 늘렸어요. 1초로." "그럼 보레이트?" "9600으로 고정이에요. 센서 스펙." 센서 스펙. 저가 센서라 9600bps 고정이다. 바꿀 수가 없다. 오실로스코ープ 가져왔다. 실제 파형 봤다. Start 비트는 정상. Data 비트도 정상. Stop 비트에서 가끔 글리치. "하드웨어 문제 아닐까요?" 내가 물었다. "PCB는 검증 끝났어요." HW 팀장 답변. 그럼 뭐지. 점심때까지 봤다. 결론: 일단 리트라이 로직 강화. 근본 원인은 모른다. 치킨과 디버깅 점심은 치킨. 토요일이니까 치킨. 회사 앞 치킨집. "양산 나가면 괜찮을까요?" 막내가 물었다. "괜찮아야지." 팀장이 웃었다. 웃긴데 안 웃겼다. 치킨 먹으면서도 코드 봤다. 노트북 옆에 치킨. 키보드에 기름 묻었다. 휴지로 닦았다. "DMA 버퍼는 제가 오늘 고칠게요." 내가 말했다. "저는 저전력 모드 볼게요." 선배가 말했다. 역할 분담 끝. 치킨 다 먹었다. 오후 2시.DMA 버퍼 수정, 인터럽트 지옥 사무실로 돌아왔다. 에어컨 빵빵하다. 토요일이라 전기 아깝지 않은가 봐. DMA 버퍼 코드 열었다. 아침에 본 그거. void DMA_IRQHandler(void) { if(dma_buffer_idx >= DMA_BUFFER_SIZE) { __disable_irq(); dma_buffer_idx = 0; memset(dma_buffer, 0, DMA_BUFFER_SIZE); __enable_irq(); error_count++; } }이렇게 고쳤다. 빌드. 보드에 올렸다. 테스트 시작. 1분. 정상. 5분. 정상. 10분. 정상. 좋아. 30분째. 에러 로그 떴다. error_count: 1 뭐지. 다시 코드 봤다. 인터럽트 안에서 memset? 이게 문제인가? 수정. volatile uint8_t buffer_reset_flag = 0;void DMA_IRQHandler(void) { if(dma_buffer_idx >= DMA_BUFFER_SIZE) { buffer_reset_flag = 1; error_count++; } }// 메인 루프에서 if(buffer_reset_flag) { __disable_irq(); dma_buffer_idx = 0; memset(dma_buffer, 0, DMA_BUFFER_SIZE); buffer_reset_flag = 0; __enable_irq(); }다시 빌드. 다시 올렸다. 오후 4시. 저전력 모드, 18mA의 비극 선배가 소리쳤다. "이거 봐요!" 멀티미터 들고 있다. "18mA요." 스펙은 10mA 이하. 거의 두 배. "어디서 새는 거예요?" "모르겠어요. LED 다 껐는데." 회로도 펼쳤다. A4 용지 10장. 하나씩 봤다.MCU 슬립 모드: OK 센서 파워다운: OK 통신 모듈 오프: OK그럼? "풀업 저항 아닐까요?" 내가 말했다. "다 확인했어요." 30분 더 봤다. 발견했다. ADC 클럭. 슬립 모드에서도 안 꺼져 있다. 레지스터 하나 놓쳤다. RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;이 한 줄. 안 들어가 있었다. 추가하고 측정. 9.2mA. 성공. 오후 6시. 저녁 7시, 아직 2개 남음 저녁은 편의점. 컵라면이랑 삼각김밥. 회의실에서 먹었다. 화이트보드 봤다.UART 타임아웃 이슈 (임시 해결) DMA 버퍼 관리 (완료) 저전력 모드 전류 초과 (완료) OTA 검증 미완하나 남았다. 제일 큰 거. OTA. Over The Air 펌웨어 업데이트. 양산 나가면 필드 업데이트 필요하다. 근데 검증이 덜 됐다. "부트로더 점프 부분 확인해 봤어요?" 팀장이 물었다. "네, 그건 되는데요." 막내가 답했다. "뭐가 문제?" "CRC 체크가 가끔 실패해요." CRC. 펌웨어 무결성 검사. 이게 실패하면 벽돌. "가끔?" "네. 100번 중 3번 정도." 100번 중 3번. 3%면 적은 건가? 아니다. 양산 1만 개면 300개 벽돌. "오늘 못 고치면?" "월요일에 해야죠." 팀장 얼굴이 어둡다. 다들 어둡다. OTA, 끝나지 않는 밤 CRC 실패 원인 찾기. 로그 뽑았다. Flash Write: OK Flash Read: OK CRC Calculate: 0xA3B5C2D1 CRC Expected: 0xA3B5C2D1 Result: PASS이렇게 나올 때도 있고. Flash Write: OK Flash Read: OK CRC Calculate: 0xA3B5C2D1 CRC Expected: 0xA3B5C2D8 Result: FAIL이렇게 나올 때도 있다. Expected 값이 달라진다. "플래시에 쓸 때 문제인가?" "근데 Flash Read는 OK잖아요." 코드 다시 봤다. 플래시 쓰기 부분. for(int i = 0; i < fw_size; i += 4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_APP_ADDR + i, *(uint32_t*)(fw_buffer + i)); }뭔가 이상하다. 플래시 쓰기 후 검증이 없다. 추가했다. uint32_t written_data = *(uint32_t*)(FLASH_APP_ADDR + i); uint32_t expected_data = *(uint32_t*)(fw_buffer + i); if(written_data != expected_data) { // 에러 처리 }다시 돌렸다. 100번. 실패 0번. 됐다. 오후 9시 반. 퇴근, 토요일 밤 "오늘 고생했습니다." 팀장이 말했다. "수고하셨어요." 다들 인사. 짐 챙겼다. 노트북, 충전기, 텀블러. 주차장 나왔다. 여전히 우리 차만 5대. 시동 걸었다. 라디오 켰다. 토요일 밤 음악 프로그램. 집 가는 길. 10분. 편하다. 양산까지 7일. 월요일부터 또 전쟁. 근데 오늘은 괜찮았다. 문제 3개 해결했다. 팀원들이랑 같이. 토요일에 회사 나온 게 아깝지 않다. 이상하게. 집 도착. 현관문 열고. 침대에 누웠다. 내일은 쉰다. 진짜 쉰다.월요일이 두렵지만, 오늘은 잘했다.

회사 '낡은' 컨벤션 vs 인터넷의 '신규' 트렌드

회사 '낡은' 컨벤션 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년 걸려서 바뀔 수도 있다. 어쩌면 안 바뀔 수도 있다. 그래도 시도는 한다. 포기하면 영원히 안 바뀐다.회사 코드는 박물관이다. 신입은 적응한다. 나도 그랬다.