Showing Posts From

4시간을

CRC 검사로 4시간을 날렸다

CRC 검사로 4시간을 날렸다

CRC 하나로 4시간 출근했다. 오늘은 통신 프로토콜 검증. UART로 센서 데이터 받는 건데, 가끔 값이 이상하다. 데이터 오류인지 파싱 문제인지 모르겠어서 CRC 붙이기로 했다. CRC 자체는 어렵지 않다. 체크섬 계산해서 끝에 붙이고, 수신측에서 다시 계산해서 맞는지 확인. 이론은 간단하다. 문제는 구현이다.레퍼런스 코드를 찾았다 구글에 'CRC-16 CCITT C code' 검색. 스택오버플로우에 코드 있다. 복사 붙여넣기. 컴파일. 보드에 올렸다. uint16_t crc16_ccitt(uint8_t *data, uint16_t length) { uint16_t crc = 0xFFFF; for (uint16_t i = 0; i < length; i++) { crc ^= data[i] << 8; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc = crc << 1; } } return crc; }송신측에서 CRC 계산해서 붙였다. 수신측에서 받아서 다시 계산. 비교했다. 안 맞는다. 처음엔 내가 잘못 썼나 싶었다. 데이터 길이 확인. 맞다. 바이트 순서 확인. 빅엔디안 리틀엔디안 바꿔봤다. 안 맞는다. 알고리즘 문제일까 CRC-16에도 종류가 많다. CCITT, MODBUS, USB, XMODEM... 초기값이 다르고, 다항식이 다르고, 최종 XOR 값이 다르다. 스펙 문서 찾았다. 센서 제조사 문서에 'CRC-16 CCITT' 쓴다고 나와 있다. 그럼 내 코드가 맞는 거 아닌가. 혹시 몰라서 온라인 CRC 계산기 찾았다. 테스트 데이터 넣어봤다. "01 02 03 04 05" 이런 거. 계산기 결과랑 내 코드 결과 비교. 다르다. 뭐가 문제지.다항식부터 다시 확인 CRC-16 CCITT 다항식은 0x1021이다. 내 코드에 있다. 맞다. 초기값은 0xFFFF. 코드에 있다. 맞다. 최종 XOR? 어... 코드에 없다. 그냥 crc 리턴한다. 혹시 이게 문제인가 싶어서 return crc ^ 0xFFFF; 해봤다. 여전히 안 맞는다. 시간은 오후 3시. 벌써 2시간 지났다. 팀장한테 물어볼까 했는데, 이런 거 물어보면 '그것도 모르나' 소리 들을 것 같아서 참았다. HW팀한테 물어봤자 '펌웨어 문제 아니에요?' 할 거고. 코드 한 줄씩 뜯어봤다 디버거 붙여서 스텝 실행. CRC 계산 과정을 한 바이트씩 확인했다. data[0] = 0x01 crc ^= 0x01 << 8 → crc = 0xFFFF ^ 0x0100 = 0xFEFF 비트 시프트 8번 돌면서 다항식 XOR...손으로 계산해봤다. 종이에 이진수 써가면서. 내 코드 결과랑 같다. 그럼 코드는 맞는 거다. 온라인 계산기가 틀렸나? 여러 개 찾아봤다. 다 같은 값 나온다. 내 값이랑 다르다. 뭐지. 커뮤니티 검색 포기하고 커뮤니티 뒤졌다. 'CRC-16 CCITT wrong result' 검색. 임베디드 포럼에 똑같은 질문 있다. 2015년 게시글. 답변 보니까 누가 이렇게 썼다. "CRC-16 CCITT는 초기값이 두 가지예요. 0xFFFF 쓰는 버전이랑 0x0000 쓰는 버전. 그리고 입력 데이터 반사 여부도 다릅니다. 정확히는 CRC-16/CCITT-FALSE랑 CRC-16/XMODEM이 다른 겁니다." 뭐라고?표준이 여러 개 찾아보니까 CRC-16 'CCITT'라고 부르는 게 실제로는 여러 변종이 있다.CRC-16/CCITT-FALSE: 초기값 0xFFFF, 입력/출력 반사 없음 CRC-16/XMODEM: 초기값 0x0000, 입력/출력 반사 없음 CRC-16/KERMIT: 초기값 0x0000, 입력/출력 반사 있음내가 쓴 코드는 CCITT-FALSE였다. 온라인 계산기는 XMODEM 기준이었다. 센서 스펙 문서는 그냥 'CCITT'라고만 써놨다. 초기값 0x0000으로 바꿨다. 테스트했다. 맞는다. 시간은 저녁 7시. 4시간 날렸다. 왜 표준이 이렇게 많나 CRC 알고리즘 자체는 1961년에 나왔다. 그 이후로 여러 회사가 각자 입맛대로 변형해서 썼다. 초기값 다르게, 다항식 다르게, 반사 여부 다르게. 나중에 표준화하려고 했는데 이미 쓰는 곳이 많아서 그냥 다 표준으로 인정했다. 그래서 CRC-16만 해도 변종이 수십 개다. 문제는 다들 이름을 똑같이 'CRC-16 CCITT'라고 부른다는 거다. 정확한 변종 이름을 안 쓴다. 스펙 문서에 초기값, 다항식, 반사 여부 명시 안 하면 알 수가 없다. 결국 테스트 데이터로 센서 제조사 문서에 샘플 데이터 있었다. "이 데이터의 CRC는 0x1234입니다" 이런 거. 그걸로 역산했다. 초기값 0x0000 쓰면 맞고, 0xFFFF 쓰면 안 맞는다. XMODEM 방식이다. 코드 수정했다. 테스트 통과. 양산 코드에 들어갔다. 4시간의 교훈 CRC 구현할 때는 다음을 확인해야 한다.다항식 (polynomial) 초기값 (initial value) 입력 반사 (reflect input) 출력 반사 (reflect output) 최종 XOR (final XOR)이 5개가 하나라도 다르면 결과가 다르다. 스펙 문서에 'CRC-16'이라고만 쓰여 있으면 제조사한테 물어봐야 한다. 아니면 샘플 데이터로 역산. 스택오버플로우 코드 복붙은 위험하다. 질문자가 어떤 변종 쓰는지 모른다. 답변자도 그냥 일반적인 거 올린다. 온라인 계산기도 조심해야 한다. 어떤 변종 쓰는지 확인. CRC-16 선택지가 10개 넘게 나오는 계산기가 정확하다.CRC 하나에 4시간. 알고리즘은 맞았는데 표준이 틀렸다. 임베디드는 이런 거에 시간 간다.