Showing Posts From

11시

목요일 밤 11시, 명일 릴리스를 위해 코드 리뷰 중

목요일 밤 11시, 명일 릴리스를 위해 코드 리뷰 중

목요일 밤 11시, 명일 릴리스를 위해 코드 리뷰 중 11시 3분. 팀원 코드 열었다. 내일 오전 10시 릴리스. 지금 발견하면 안 되는데.처음엔 괜찮아 보였다 커밋 메시지: "타이머 인터럽트 우선순위 조정 완료" 좋아. 이 부분 문제였는데. 코드 열어봤다. 한 200줄 정도 수정됐다. 처음 50줄은 깔끔했다. 우선순위 설정 부분, 문제없다. 다음 100줄도 괜찮았다. 그런데. void TIMER_IRQHandler(void) { GPIO_ToggleBit(LED_PORT, LED_PIN); // 다른 처리들... delay_us(100); // <- 이거 }인터럽트 핸들러 안에서 delay를 쓴다. 11시 15분. 슬랙 열었다. "민수씨, 혹시 아직 안 주무셨나요?" 읽음 표시 떴다. 3초 후 답장. "네 형 아직이요" 다행이다. 전화했다.Low 레벨에서 보면 "민수씨, 67번 줄 delay_us 있는데요" "아 그거요? LED 깜빡임 보려고 넣었어요" "네... 근데 여기 인터럽트 핸들러잖아요" 침묵. "인터럽트 타이밍이 문제가 될 수 있을 것 같은데요" "아..." 설명했다. 100us 딜레이 중에 다른 인터럽트 못 받는다. UART 인터럽트 우선순위가 같으면 데이터 놓친다. 시리얼 통신 깨진다. "죄송합니다 형. 제가 생각을 못 했네요" "괜찮아요. 지금 발견해서 다행이에요" 다행이긴 뭐가 다행이야. 내일 아침인데. 11시 28분. 수정 방법 논의했다.딜레이 빼기 → 가장 간단 플래그로 메인에서 처리 → 더 안전 타이머 우선순위 더 낮추기 → 근본 해결 아님2번으로 결론. 민수가 지금 수정한대. "30분이면 될 것 같아요" 좋아. 12시까지 커밋해달라고 했다.테스트는 내가 해야 한다 민수 코드 수정하는 동안. 나는 테스트 시나리오 짰다. 확인해야 할 것:LED 정상 동작 UART 통신 안정성 인터럽트 중첩 상황 엣지 케이스보드 3개 꺼냈다. 오실로스코프 채널 4개 다 쓸 거다. 11시 50분. 커밋 알림 왔다. "수정 완료했습니다" 코드 확인했다. 깔끔하다. volatile uint8_t led_toggle_flag = 0;void TIMER_IRQHandler(void) { led_toggle_flag = 1; // 다른 처리들... }// 메인 루프에서 if (led_toggle_flag) { GPIO_ToggleBit(LED_PORT, LED_PIN); led_toggle_flag = 0; }좋아. 이제 올려보자. 12시 5분. 빌드 시작. 경고 2개. 무시 가능한 거다. 플래시 굽기 시작. "Programming... OK" 전원 켰다. LED 깜빡인다. 정상. 시리얼 터미널 열었다. 데이터 들어온다. 정상. 이제 스트레스 테스트. 새벽까지 테스트 12시 30분. 본격 시작. 테스트 1: UART 연속 수신 → 10만 바이트 전송했다. → 에러 0개. 통과. 테스트 2: 타이머 + UART 동시 → 양쪽 다 인터럽트 발생시켰다. → 파형 깨끗하다. 통과. 테스트 3: 최악의 상황 → 모든 인터럽트 동시 발생. → SPI, I2C, UART, 타이머 전부. → 오실로스코프가 크리스마스트리 같다. → 근데 데이터는 정상. 통과. 1시 15분. 엣지 케이스 몇 개 더 돌렸다. 전부 통과. 슬랙에 썼다. "테스트 완료했습니다. 문제없어요" 민수: "감사합니다 형ㅠㅠ" "고생하셨어요. 내일 봐요" 1시 30분. 문서 작업 시작했다. 수정 사항 정리.변경된 파일 목록 테스트 결과 알려진 이슈 (없음)릴리스 노트 업데이트. 버전 넘버 확인. v2.4.3 2시. 팀장님한테 보고 메일 보냈다. "내일 오전 릴리스 준비 완료" 답장 바로 왔다. "고생 많으셨습니다" 팀장님도 안 주무신 거다. 다들 긴장한다. 양산 첫 펌웨어라. 릴리스 전날은 다 이렇다 2시 20분. 정리했다. 보드 3개 전원 끄고. 프로브 정리하고. 테스트 로그 폴더에 저장하고. 가방 쌌다. 내일은... 아니 오늘은 9시 출근. 7시간 후다. 불 끄기 전에. 코드 한 번 더 봤다. 괜찮다. 이번엔 문제없을 거다. 사무실 나왔다. 새벽 공기가 차갑다. 걸어가면서 생각했다. 만약 내일... 아니 오늘 아침에 발견했으면? 릴리스 못 했다. 일정 미뤄졌다. 민수 혼났을 거다. 코드 리뷰를 밤에 한 게. 귀찮았지만. 잘한 거다. 잠들기 전 2시 40분. 집 도착. 씻지도 않고 침대에 누웠다. 내일... 오늘 할 일.9시 출근 10시 릴리스 오후 모니터링 이슈 없으면 퇴근이슈 없기를. 눈 감았다. 근데 잠이 안 온다. 혹시 놓친 게 있나? 타이머 설정 다시 확인했나? 인터럽트 우선순위 표 맞나? 일어나서 노트북 켰다. 코드 다시 봤다. 괜찮다. 진짜 괜찮다. 3시 10분. 다시 누웠다.내일 릴리스 잘 되면, 민수한테 점심 사줘야지.

밤 11시, 여전히 'printf' 디버깅 중이다

밤 11시, 여전히 'printf' 디버깅 중이다

밤 11시, 여전히 'printf' 디버깅 중이다 시작은 단순했다 오후 3시쯤이었다. UART 통신이 간헐적으로 끊긴다는 보고가 올라왔다. "간단한 거 아니야?" 싶었다. 보드레이트 확인하고, 패리티 비트 체크하면 될 줄 알았다. printf("UART Start\n"); 일단 찍어봤다. 잘 나온다. printf("TX: %d\n", tx_count); 이것도 나온다. "문제없는데?" 생각한 게 5시간 전이다.printf의 함정 문제는 printf를 많이 찍기 시작하면서 생겼다. 센서 데이터를 실시간으로 보고 싶어서 루프 안에 넣었다. while(1) { sensor_data = read_sensor(); printf("Data: %d\n", sensor_data); HAL_Delay(100); }그런데 이상했다. 센서 값이 튀기 시작했다. 아니, printf 넣기 전에는 정상이었는데? 로그를 더 자세히 봤다. 타이밍이 이상하다. 100ms마다 찍혀야 하는데 가끔 200ms, 300ms씩 벌어진다. "printf가 블로킹이라 그런가?" UART 전송이 끝날 때까지 CPU가 기다린다. 당연히 느리다. 그래서 인터럽트 타이밍이 밀린 거다. 밤 7시. 저녁 먹으러 가자는 팀장님 말씀에 "조금만요" 했다.로그 메시지가 만든 버그 printf를 줄여봤다. 루프에서 빼고, 중요한 곳에만 넣었다. 그랬더니 또 다른 문제가 보였다. printf("Timer Start\n"); start_timer(); printf("Timer Running\n");타이머 시작하고 바로 다음 printf 찍는데, 타이머 인터럽트가 안 들어온다. 아니, 들어오긴 하는데 타이밍이 늦다. printf 하나에 몇 ms씩 걸리니까, 타이머 정밀도가 다 깨진 거다. "이래서 printf 디버깅 하지 말라는 거구나." 알면서도 할 수밖에 없다. JTAG 디버거는 느리고, 오실로스코프는 채널이 부족하고. 결국 printf가 제일 편하다. 밤 9시. 사무실에 나랑 옆자리 김 대리만 남았다. "형, 먼저 가세요." "너도 일찍 가라. 내일 또 있어." 말은 그렇게 하고 둘 다 안 간다.DMA로 바꿔보자 printf가 블로킹이라면 DMA를 쓰면 되지 않을까? UART DMA 설정을 켰다. 코드를 수정했다. char log_buffer[256]; sprintf(log_buffer, "Data: %d\n", sensor_data); HAL_UART_Transmit_DMA(&huart2, (uint8_t*)log_buffer, strlen(log_buffer));돌려봤다. 좋아졌다. ...라고 생각한 게 10시였다. 30분 돌리니까 또 문제다. 로그가 중간에 깨진다. "DatDaData: 123" 버퍼가 덮어써진 거다. DMA 전송이 끝나기 전에 다음 sprintf가 들어간 거다. "플래그 체크를 해야 하나?" if(uart_dma_ready) { sprintf(log_buffer, ...); HAL_UART_Transmit_DMA(...); uart_dma_ready = 0; }이것도 완벽하지 않다. 로그가 누락된다. 링 버퍼를 만들어야 하나? 지금 11시인데? 또 다른 버그의 발견 DMA 설정하면서 메모리 맵을 보다가 이상한 걸 발견했다. .bss 영역이 생각보다 크다. 40KB. 전역 변수를 확인해봤다. uint8_t rx_buffer[32768];누가 이렇게 큰 버퍼를 전역으로 잡았지? 커밋 히스토리를 뒤졌다. 3개월 전, 나였다. "당시엔 급했으니까..." 근데 이게 문제다. 이 버퍼 때문에 SRAM이 부족해서 힙 할당이 실패하고 있었다. 그래서 가끔 센서 초기화가 안 됐던 거다. printf 찍다가 발견한 완전히 다른 버그. 이거 고쳐야 한다. 지금. 밤 11시 반. 커피를 내렸다. 네 번째. 스택 오버플로우 검색 "uart dma buffer overwrite" 검색했다. 스택 오버플로우에 똑같은 질문이 있다. 2016년. 답변: "Don't use sprintf with DMA. Use a queue." 큐를 만들라고? 지금? 다른 답변: "Just use ITM/SWO for debugging." SWO? 그게 뭐지? 검색했다. ST-Link로 디버그 메시지를 보내는 기능이래. printf처럼 쓸 수 있는데 UART를 안 쓴다. "진작 알았으면..." 설정 방법을 찾아봤다. CubeMX에서 SYS 설정을 바꾸고, 스크립트를 추가하고... 30분 걸렸다. 안 된다. "지금 SWO 배우고 있을 시간이 아니지." 그냥 printf로 돌아갔다. 최소화해서 쓰자. 로그 레벨 구현 정신을 차렸다. 제대로 해야 한다. 간단한 로그 시스템을 만들었다. typedef enum { LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG } log_level_t;log_level_t current_log_level = LOG_WARN;void log_print(log_level_t level, const char* fmt, ...) { if(level > current_log_level) return; char buffer[128]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); printf("[%d] %s", HAL_GetTick(), buffer); }이제 LOG_DEBUG는 릴리스 빌드에서 안 찍힌다. 중요한 것만 LOG_ERROR로 남긴다. 30분 돌려봤다. 괜찮다. 1시간 돌렸다. 문제없다. "이제 됐나?" 새벽 1시. 긴 테스트를 돌려놓고 화장실 갔다 왔다. 로그를 봤다. [ERROR] Sensor timeout "...또야?" 타이밍 문제의 본질 로그를 자세히 봤다. 패턴이 있다. 센서 타임아웃이 발생하는 시점마다 UART 로그가 몰려 있다. 아, printf가 많이 찍힐 때 센서 폴링이 늦어진 거다. 근본적인 해결책은 하나다. RTOS 태스크 분리. 센서 읽기는 우선순위 높은 태스크. 로그는 우선순위 낮은 태스크. "지금 RTOS 도입할 거야?" 양산까지 2주 남았다. 일단은 printf를 더 줄이자. 센서 읽는 부분에선 아예 안 찍자. 에러만 간단히. if(sensor_error) { error_count++; // printf 안 찍음 }나중에 error_count 값만 주기적으로 찍는다. // 10초마다 한 번 if(HAL_GetTick() - last_log_time > 10000) { log_print(LOG_INFO, "Errors: %d\n", error_count); last_log_time = HAL_GetTick(); }돌려봤다. 타임아웃이 안 난다. 새벽 1시 반. 드디어 해결된 것 같다. 문서화 코드에 주석을 달았다. // WARNING: printf는 블로킹. 센서 루프에서 사용 금지 // 로그 필요시 log_print() 사용 // 긴급 디버깅 시에만 printf 직접 사용그리고 위키에 정리했다. 펌웨어 디버깅 가이드printf는 최소화할 것 타이밍 크리티컬한 코드에서 로그 금지 DMA 사용 시 버퍼 오버라이트 주의 긴 테스트는 로그 레벨 낮춰서내일 팀원들한테 공유해야지. 아니, 오늘이네. 벌써 새벽 2시. "집 가자." 보드 전원을 껐다. 가방을 챙겼다. 모니터를 끄려는데 슬랙 알림이 왔다. 김 대리: "형 아직 있어요?" 나: "지금 퇴근" 김 대리: "저도요 ㅋㅋ 수고하셨습니다" 계단을 내려갔다. 경비실 아저씨가 놀란 표정으로 보신다. "아직도 일해요?" "네, 마무리하고 가는 길이에요." "고생이 많네. 조심히 가요." 밖은 추웠다. 11월 새벽 공기. 원룸까지 걸어가면서 생각했다. printf 하나 제대로 못 쓰는 게 펌웨어 개발이다. 웹 개발자들은 console.log 마음껏 찍잖아. 브라우저 콘솔에 다 나오잖아. 우린 printf 하나에 타이밍이 깨지고, DMA 설정하고, 버퍼 관리하고. "그래도 재밌긴 해." 집에 도착했다. 2시 10분. 샤워하고 침대에 누웠다. 내일... 아니 오늘 출근은 10시에 해야지. 눈을 감았다. 머릿속에 printf 로그가 스쳐 지나간다. [INFO] Sleep mode enteredprintf 디버깅. 간단한 것 같지만 절대 간단하지 않다. 내일도 또 찍겠지.