NovAtel GNSS受信機のCRCコード

category: gnss

はじめに

私は、測位衛星の信号観測に、NovAtel(ノバテル)の受信機OEM729を利用しています。この受信機は、イーサネットインターフェースを備え、測位結果だけなでなく、生データ(衛星が放送するそのままのデータ)もTCP/IPにて出力できます。生データを読み出すときに必要な、CRC(cyclic redundancy check)による誤り検出のためのC++とPythonのコードを考えてみました。

C++版CRC32コード

NovAtelのPDF DocumentページのOEM7 Receiver Cardsをクリックすると、Commands and Logs Reference Manualを入手できます。C++で書かれたサンプルコードとテストデータは、このマニュアルにあります。

ここで用いられるCRC32は、一般的なもののようです。サンプルコードがあるので油断していました。しかし、このコードは、動作するものの、難読化されているようです。CRCには、いろいろな表現方法や初期値設定があるようで、Python標準モジュールbinasciiCRC32コードや一般的なCRCコードでは、このテストデータを処理できませんでした。ネット検索してみると、みなさん苦労しているようです。

そこで、このCommands and Logs Reference ManualにあるC++コードの変数名やキャストを整理して、簡潔に書き直しました。

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");
}

このコードをc++ -o novcrc novcrc.ccでコンパイルして、./novcrcにて実行します。

little-endian 484cdc42
big-endian    42dc4c48
Expected:     42dc4c48

このコードならば、簡単にPythonコードに変換できます。

Python版CRC32コード

上述のC++コードをPythonコードに書き直しました。

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

このコードを実行すると42dc4c48と表示されます。QZS L6 Toolnov2has.pyに反映させておきました。