2016年9月30日金曜日

チェックサムあれこれ

タイトル的にいろんなチェックサムを網羅的に解説してるような感じですが、あれ(GPS)と、これ(XBee)だけです。

XBeeはAPIモードのチェックサムを、GPSはNMEA-0183の最後についてるやつを扱います。



市販のGPSモジュールは大体がNMEA-0183というフォーマットに則って出力されます。このフォーマットはNMEA(National Marine Electronics Association)が策定しており、電子版のドキュメントは400USD(4万円)くらいするようです。もっともググれば大体のフォーマットなどは得られますから、それを利用することにします。

NMEA0183では文字化け対策にチェックサムがついていますが、基本的に任意で使うことになっており、無視しても問題はありません。ただしRMC(GPRMC)等、一部は必須になっています。といっても電子工作で使う程度なら無視しても問題ないわけですが。

下記のNmeaCheckxorはNMEA0183の最低限のフォーマット(ドル記号始まり、アスタリスク終わり、HEX2文字)を確認し、それらに問題があればそれぞれのステータスコードを返すようになっています。一応手元で動作確認はしていますが、あんまり詳しくテストしてないので間違ってたらごめんなさい。

簡単に計算方法ですが、ドル記号とアスタリスクの間(ドル記号とアスタリスクは含まない)をXORしていきます。チェックサムと言いながらSum(加算)ではなくXorなので注意してください。

蛇足ですが、仲間内でデータフォーマットを作る場合、最後にチェックサムがついていて「これってどうやって計算するの?」って聞いたら「チェックサムなんだから加算だろ」とか怒られることが有ります。無用なトラブルを産まないためにも、データフォーマットを作る場合はチェックディジットの計算方法を明記するようにしてください。

/*
ステータスコード(戻り値)
0   チェックサムGood
1   チェックサムNot Good
5   Null Pointer
10  スタートキャラエラー($記号)
11  エンドキャラエラー(*記号)
12  チェックサム文字エラー
255 未定義エラー
*/

uint8_t NmeaCheckxor(const char *data) {
    if (!data) {
        return(5);
    }

    if (data[0] != '$') {
        return(10);
    }

    uint8_t xor = 0;

    for (data++; *data != '*'; xor ^= *data++) {
        if (!*data) {
            return(11);
        }
    }

    char h1 = data[1];
    char h2 = data[2];

    if (!((h1 >= '0'&&h1 <= '9') || (h1 >= 'A'&&h1 <= 'F') || (h1 >= 'a'&&h1 <= 'f')) ||
        !((h2 >= '0'&&h2 <= '9') || (h2 >= 'A'&&h2 <= 'F') || (h2 >= 'a'&&h2 <= 'f'))) {
        return(12);
    }

    if (h1 >= 'a') { h1 &= 0xDF; }
    if (h2 >= 'a') { h2 &= 0xDF; }

    if (h1 >= 'A') { h1 -= 7; }
    if (h2 >= 'A') { h2 -= 7; }

    h1 -= '0';
    h2 -= '0';

    uint8_t sum = h1 << 4 | h2;

    return(xor == sum ? 0 : 1);
}

チェックサムの計算自体は比較的簡単ですが、最後の16進ASCIIで書かれたチェックサムを読み込むのが結構面倒です。もちろんsscanfを始めとした16進文字列を整数に変換できるライブラリを使えば簡単になります。関数呼び出し深さが限定されない環境ではそのほうが楽です。

***

次にXBeeです。XBeeでメッシュネットワークを使いたい場合等、APIモードを使うことも多いと思います。その場合チェックディジットは必須になります。ただ計算は非常に簡単です。
XBeeでAPIモードを使う場合、ひとつの転送はひとつのパケットに収めます。パケットは先頭にスタートバイト、フレームレングス(2バイト)、フレーム本体(可変長)、チェックサムとなります。チェックサムはフレーム本体に対して計算し、スタートバイトやフレームレングスは含みません。
パケットにはエンドバイト等は無く、パケットの終了地点はフレームレングスにのみ依存します。つまりフレームレングスが化けると都合がわるいことになるわけですが、あんまり気にしていないようです。

XBeeのチェックサムの計算の場合、「加算して0xFFで割ったあまりを0xFFから引いて~」と解説される場合がありますが、面倒なので8bit内で加算してキャリーは捨てていきます。

XBeeのフレームレングスは2バイトなので65536バイトまで収めることができますが、素直にすべて加算すると32bit変数が必要になります。最近の32bitマイコンを搭載したボードであれば32bit変数でも問題ありませんが、メッシュネットでばらまきたいからとにかく安いマイコンを…といった選定をした場合は32bitの計算はバカになりません。ということで最初から8bitで計算しておきましょう。
また0xFFから引いた差をパケットに入れることになりますが、これもせっかくなのでビット演算で実装しておきます。

static uint8_t CalcChecksum(const uint8_t *Frame, int FrameSize) {
    uint8_t sum;
    
    for (sum = 0; FrameSize > 0; sum += *Frame++, FrameSize--);

    return(~sum);
}

0 件のコメント:

コメントを投稿