CRC code in NovAtel GNSS receivers
Introduction
I use a NovAtel receiver OEM729 for signal observation of navigation satellites. This receiver is equipped with an Ethernet interface and can output not only positioning results but also raw data (data broadcasted by satellites) via TCP/IP. I have considered C++ and Python code for CRC (cyclic redundancy check) error detection required when reading raw data.
C++ version CRC32 code
We can get the Commands and Logs Reference Manual by clicking OEM7 Receiver Cards on NovAtel’s PDF Documents page. Sample code written in C++ and test data can be found in this manual.
The CRC32 used here seems to be a common one. This code works, but seems to be obfuscated. CRC codes have various expression methods and initial value settings, and CRC32 code or a generic CRC code could not handle this test data. Searching the related information on the Internet, I find everyone is struggling.
So I cleaned up the variable names and casts in the C++ code in this Commands and Logs Reference Manual and rewrote it concisely.
novcrc.cc
#include <stdio.h>
#include <stdint.h>
uint32_t crc32(uint_fast16_t data_len, uint8_t *data) {
const uint_fast32_t CRC32_POLYNOMIAL = 0xEDB88320L;
uint_fast32_t crc = 0;
for (uint_fast16_t i = 0; i < data_len; i++) {
uint_fast32_t tmp2 = (crc ^ data[i]) & 0xFF;
for (uint_fast8_t j = 0 ; j < 8; j++) {
if (tmp2 & 1)
tmp2 = (tmp2 >> 1) ^ CRC32_POLYNOMIAL;
else
tmp2 >>= 1;
}
uint_fast32_t tmp1 = (crc >> 8) & 0x00FFFFFFL;
crc = tmp1 ^ tmp2;
}
return crc;
}
int main() {
uint8_t data[] = {
0xAA, 0x44, 0x12, 0x1C, 0x2A, 0x00, 0x02, 0x20, 0x48, 0x00, 0x00, 0x00, 0x90, 0xB4, 0x93, 0x05, 0xB0, 0xAB, 0xB9, 0x12, 0x00, 0x00, 0x00, 0x00, 0x45, 0x61, 0xBC, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1B, 0x04, 0x50, 0xB3, 0xF2, 0x8E, 0x49, 0x40, 0x16, 0xFA, 0x6B, 0xBE, 0x7C, 0x82, 0x5C, 0xC0, 0x00, 0x60, 0x76, 0x9F, 0x44, 0x9F, 0x90, 0x40, 0xA6, 0x2A, 0x82, 0xC1, 0x3D, 0x00, 0x00, 0x00, 0x12, 0x5A, 0xCB, 0x3F, 0xCD, 0x9E, 0x98, 0x3F, 0xDB, 0x66, 0x40, 0x40, 0x00, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03
};
uint32_t CRCle = crc32(sizeof(data), data);
uint32_t CRCbe = __builtin_bswap32(CRCle);
printf("little-endian %x\n", CRCle);
printf("big-endian %x\n", CRCbe);
printf("Expected: 42dc4c48\n");
}
Compile this code with c++ -o novcrc novcrc.cc
and run it with ./novcrc
.
little-endian 484cdc42
big-endian 42dc4c48
Expected: 42dc4c48
This code can be easily converted to Python code.
Python CRC32 code
I have rewritten the above C++ code into Python code.
novcrc.py
#! /usr/bin/env python
data=b'\xaa\x44\x12\x1c\x2a\x00\x02\x20\x48\x00\x00\x00\x90\xb4\x93\x05\xb0\xab\xb9\x12\x00\x00\x00\x00\x45\x61\xbc\x0a\x00\x00\x00\x00\x10\x00\x00\x00\x1b\x04\x50\xb3\xf2\x8e\x49\x40\x16\xfa\x6b\xbe\x7c\x82\x5c\xc0\x00\x60\x76\x9f\x44\x9f\x90\x40\xa6\x2a\x82\xc1\x3d\x00\x00\x00\x12\x5a\xcb\x3f\xcd\x9e\x98\x3f\xdb\x66\x40\x40\x00\x30\x30\x30\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x0b\x00\x00\x00\x06\x00\x03'
def crc32(data):
polynomial = 0xedb88320
crc = 0
for byte in data:
tmp2 = (crc ^ byte) & 0xff
for _ in range(8):
if tmp2 & 1:
tmp2 = (tmp2 >> 1) ^ polynomial
else:
tmp2 = tmp2 >> 1
tmp1 = (crc >> 8) & 0x00ffffff
crc = tmp1 ^ tmp2
return crc.to_bytes(4,'little')
print(crc32(data).hex()) # expects 42dc4c48
Running this code will display 42dc4c48
. I reflected the knowledge in nov2has.py of QZS L6 Tool.