Showing Posts From

디버깅

센서 캘리브레이션, 왜 이렇게 시간이 걸리나

센서 캘리브레이션, 왜 이렇게 시간이 걸리나

센서 캘리브레이션, 왜 이렇게 시간이 걸리나 시작은 단순했다 프로젝트 킥오프 때 PM이 말했다. "온도 센서 하나 달고, 가속도 센서 하나 달면 되죠?" 그때 난 고개를 끄덕였다. 센서 데이터시트 보고 I2C 연결하면 끝이라고 생각했다. 2주면 충분하다고 보고했다. 지금 6주째다. 회의실에서 시연할 때였다. 온도를 보여주는데 25.3도가 나왔다. 실제 온도계는 23.1도. "2도 차이는 오차 범위 아닌가요?" PM이 물었다. 나는 대답했다. "데이터시트상 오차는 ±0.5도입니다." 그날부터 캘리브레이션 지옥이 시작됐다.데이터시트는 거짓말을 하지 않는다 데이터시트를 다시 읽었다. "Typical accuracy ±0.5°C"라고 적혀 있다. Typical이란 단어가 눈에 들어왔다. 각주를 봤다. "at 25°C, in ideal conditions". 이상적 조건. 우리 제품은 영하 10도에서 영상 50도까지 써야 한다. 보드 위에는 MCU가 있고, 전원 IC가 있고, 다 발열한다. 센서 옆에 파워 LED가 있다. 켜지면 0.3도가 올라간다. 이상적이지 않다. HW팀에 물었다. "센서 위치 옮길 수 있나요?" 답은 "레이아웃 다시 그려야 하는데요"였다. 양산 2달 남았다. 레이아웃 변경은 불가능하다. 결국 소프트웨어로 해결해야 한다. 캘리브레이션이다. 첫 번째 시도: 오프셋 보정 간단하게 생각했다. 실제값 - 측정값 = 오프셋. 이걸 빼주면 되지 않나. 항온 챔버를 빌렸다. 25도로 맞췄다. 센서는 27.2도를 가리킨다. 오프셋 -2.2도. 코드에 넣었다. float get_calibrated_temp(void) { float raw = read_temp_sensor(); return raw - 2.2; }다시 측정했다. 25.0도. 완벽하다. PM에게 보여줬다. "이제 됩니다." 다음 날 HW팀이 왔다. "0도에서 측정해봤는데 1.5도 차이 납니다." 챔버를 0도로 맞췄다. 센서는 1.2도. 보정하면 -1.0도. 실제는 0도. 1도 차이다. 오프셋이 온도마다 다르다.두 번째 시도: 선형 보정 고등학교 수학이 떠올랐다. 일차 함수. y = ax + b. 두 점을 측정했다. (0도, 1.2도), (25도, 27.2도). 기울기를 구했다. a = (27.2 - 1.2) / (25 - 0) = 1.04. 센서가 1.04배씩 틀린다. float get_calibrated_temp(void) { float raw = read_temp_sensor(); return (raw - 1.2) / 1.04; }0도에서 테스트. 0.0도. 25도에서 테스트. 25.1도. 완벽하다. "이제 정말 됩니다." PM에게 보고했다. 다음 주 HW팀이 또 왔다. "50도에서 측정해봤는데 2도 차이 납니다." 챔버를 50도로 맞췄다. 센서는 52.8도. 보정하면 49.6도. 실제는 50도. 0.4도 차이. 선형이 아니다. 세 번째 시도: 다항식 보정 이차 함수를 써야 한다. y = ax² + bx + c. 세 점이 필요하다. 0도, 25도, 50도. 각각 측정했다. 연립방정식을 풀었다. 대학교 선형대수 교재를 꺼냈다. 행렬 계산기를 돌렸다. 계수가 나왔다. a = 0.00012, b = 1.038, c = -1.15. float get_calibrated_temp(void) { float raw = read_temp_sensor(); float corrected = 0.00012 * raw * raw + 1.038 * raw - 1.15; return corrected; }전 구간을 측정했다. 오차가 ±0.3도 이내다. 데이터시트보다 좋다. 성공했다고 생각했다.문제는 개체차 양산 시제품이 왔다. 10개. 모두 측정했다. 1번: 0.2도 오차 2번: 0.8도 오차 3번: 1.3도 오차 ... 10번: 0.5도 오차 센서마다 특성이 다르다. 당연하다. 반도체 공정은 완벽하지 않다. 같은 웨이퍼에서 나와도 다르다. PM이 물었다. "양산할 때마다 캘리브레이션 하면 되지 않나요?" 양산 계획은 월 1만 개다. 하나당 3개 온도에서 10분씩 측정해야 한다. 30분 × 10,000개 = 5,000시간. 불가능하다. "자동화하면요?" PM이 또 물었다. 챔버가 100만원이다. 10대 사면 1억. 공간도 필요하다. 인건비도 필요하다. 제품 원가가 3만원인데. 불가능하다. 네 번째 시도: 원포인트 캘리브레이션 다른 방법을 생각했다. 한 점만 측정하면 어떨까. 25도에서만 측정한다. 그 오차를 저장한다. 부팅할 때 불러온다. 전체 구간에 적용한다. "25도는 어떻게 만드나요?" HW팀이 물었다. 에어컨 튼 사무실. 온도계 옆에 보드를 놓는다. 30분 기다린다. 25도 ±1도 정도는 된다. 정밀하진 않지만 대량 생산에선 현실적이다. void factory_calibration(void) { float raw = read_temp_sensor(); float offset = 25.0 - raw; // 25도 기준 write_flash(OFFSET_ADDR, &offset, sizeof(float)); }float get_calibrated_temp(void) { float offset; read_flash(OFFSET_ADDR, &offset, sizeof(float)); float raw = read_temp_sensor(); return raw + offset; }10개 시제품에 적용했다. 전 구간 오차가 ±1도 이내다. 데이터시트보다 나쁘지만 스펙은 만족한다. PM이 승인했다. 생산팀에 전달했다. 2주 후 생산팀에서 연락 왔다. "25도를 어떻게 확인하나요?" 가속도계는 더 복잡하다 온도 센서는 그나마 나았다. 가속도계는 차원이 다르다. 3축이다. X, Y, Z. 각각 오프셋이 있다. 각각 감도가 다르다. 축 간 크로스토크도 있다. 데이터시트를 봤다. "Factory calibrated". 공장에서 보정했다고 한다. 믿었다. 보드를 평평하게 놓았다. Z축은 1g를 가리켜야 한다. 중력이니까. 측정값: 0.97g. 보드를 세웠다. X축이 1g를 가리켜야 한다. 측정값: 1.03g. Factory calibrated라더니. 6면 캘리브레이션 방법을 찾았다. 보드를 6방향으로 놓는다. +X, -X, +Y, -Y, +Z, -Z. 각각 ±1g가 나와야 한다. 실제로 측정했다.+X: 1.03g -X: -0.98g +Y: 1.01g -Y: -0.99g +Z: 0.97g -Z: -1.02g평균을 구했다. 오프셋을 구했다. 스케일을 구했다. typedef struct { float offset_x, offset_y, offset_z; float scale_x, scale_y, scale_z; } accel_calib_t;void six_point_calibration(accel_calib_t *calib) { // 각 면에서 측정 // 오프셋 = (max + min) / 2 // 스케일 = 2.0 / (max - min) // 복잡한 계산... }한 개 보정하는데 10분 걸렸다. 각 면에서 안정화를 기다려야 한다. 정확히 수평을 맞춰야 한다. 양산은 불가능하다. 결국 타협 PM과 회의했다. "가속도계 정확도를 낮추면 안 될까요?" 스펙 문서를 다시 봤다. "가속도 정확도 ±5%". 현재 오차는 ±3%. 스펙은 만족한다. "그럼 캘리브레이션 안 해도 되는 거 아닌가요?" PM이 물었다. "지금은 그렇습니다. 근데 센서 로트 바뀌면 또 모릅니다." "그럼요?" "그때 다시 보겠습니다." 엔지니어링의 현실이다. 완벽한 해답은 없다. 시간과 비용과 정확도의 균형이다. 배운 것들 6주간 배운 걸 정리했다.데이터시트의 'Typical'은 '최선의 경우'다 센서마다 특성이 다르다 온도는 비선형이다 개체차는 피할 수 없다 양산성을 고려해야 한다 완벽한 캘리브레이션은 불가능하다 스펙을 만족하면 충분하다책상 서랍에 노트를 넣었다. 온도 보정 공식, 측정 데이터, 시행착오가 빼곡하다. 다음 프로젝트에서 또 쓸 것이다. 센서는 항상 틀린다. 그걸 보정하는 게 내 일이다. 완벽할 순 없다. 하지만 쓸 만하게는 만들 수 있다. 그게 펌웨어 엔지니어가 하는 일이다. 다음 프로젝트 오늘 새 프로젝트 킥오프가 있었다. PM이 말했다. "이번엔 압력 센서도 들어갑니다." 나는 데이터시트를 펼쳤다. "Typical accuracy ±2%"라고 적혀 있다. 각주를 봤다. "at 25°C, 1atm, in ideal conditions". 한숨이 나왔다. "캘리브레이션 일정 좀 넉넉히 잡아주세요." 내가 말했다. "얼마나요?" PM이 물었다. "4주요." "2주면 안 될까요?" 또 시작이다.센서는 거짓말쟁이다. 근데 우린 그걸 믿어야 한다. 보정하고, 측정하고, 또 보정한다. 결국 경험만 쌓인다.

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

중국산 칩의 데이터시트가 중국어다

중국산 칩의 데이터시트가 중국어다

중국어 데이터시트와의 전쟁시작은 가격이었다 회의실. 구매팀 과장이 엑셀을 띄웠다. "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개월 날렸다. 그래도 양산은 나간다.