CRC code in NovAtel GNSS receivers

category: gnss

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.