内部RS485バスで集中的な交換を実行するデバイスがあり、送信されるパケットの数は1秒あたり約数千で、各パケットは7バイト長で、そのうち2つはCMSバージョンにCRC16チェックサムを格納するように設計されています(多項式= 0x8005、開始値= 0xFFFF)。 受信はFIFOバッファで実行され、後続の各バイトの受信後に混雑してシフトアップされます。 実際のパケットを受信したことの指標は、そのチェックサムがパケット自体で送信された値と一致するという事実です。 ヘッダーや追加パラメーターはありません。
問題は次のとおりです。定期的に、データを送信するときに約5分に1回、パケットがスリップし、そのデータはチャネルの1つにデータ異常値を与え、ほとんどの場合、異常値は同じ値になりました。 最初は物理的な衝突の方向に目を向けましたが、データが収集されたバッファに時々異なる結果が出ました。前のパケットの終わりと次のパケットの始まりからなるパケットが出現し、そのような結合されたパケットのチェックサムは正しいことが判明しました。 つまり、チェックサムの衝突があります。パッケージは意味がありませんが、正しいチェックサムを提供します。
当然、パッケージにヘッダーがなかったため、エラーはすでにシステム設計レベルにありました。追加のバイトヘッダーの導入により、エラーの数が検出できないレベルに減少しましたが、これは十分ではないように思われました。 さまざまな種類の16ビットチェックサムが実際の状態でどのように異なるかを確認することにしました。 実際、これと記事について。
比較のために、さまざまな多項式、開始値、ビット到着メカニズムを備えた、最も一般的に使用される16ビットチェックサムを選択しました。 選択した金額を次の表にまとめます。
指定 | 多項式 | 初期化 | 精製 | Refout | Xorout |
CMS | 0x8005 | 0xFFFF | 偽 | 偽 | 0x0000 |
CCITT | 0x1021 | 0xFFFF | 偽 | 偽 | 0x0000 |
8月-CCITT | 0x1021 | 0x1D0F | 偽 | 偽 | 0x0000 |
バイパス | 0x8005 | 0x0000 | 偽 | 偽 | 0x0000 |
CDMA2000 | 0xC867 | 0xFFFF | 偽 | 偽 | 0x0000 |
DDS-110 | 0x8005 | 0x800D | 偽 | 偽 | 0x0000 |
DECT-X | 0x0589 | 0x0000 | 偽 | 偽 | 0x0000 |
EN-13757 | 0x3D65 | 0x0000 | 偽 | 偽 | 0xFFFF |
Modbus | 0x8005 | 0xFFFF | 本当 | 本当 | 0x0000 |
T10-DIF | 0x8BB7 | 0x0000 | 偽 | 偽 | 0x0000 |
TELEDISK | 0xA097 | 0x0000 | 偽 | 偽 | 0x0000 |
XMODEM | 0x1021 | 0x0000 | 偽 | 偽 | 0x0000 |
この場合:
- RefIn-データバッファからのビットの到着順序:false-最上位ビットから(MSBが最初)、true-LSBが最初。
- RefOut-出力でビットの順序を反転するフラグ:true-反転。
パケットの破損をエミュレートするとき、次のモデルを実装しました。
- シャッフル:パケット内のランダムなバイト数をランダムな値で埋める
- ビットシフト:パケット内のランダムなバイトを左にシフトします
- ロールパケット:左側のパケットのリングバイトシフト
- 右シフト:パケットを右に1バイトシフトし、左に0xFFを追加(送信はUART経由)
- 左シフト:パケットを1バイト左にシフトし 、0xFFを右に追加します
- ゼロを埋める:パケット内のランダムなバイト数を0x00バイトで埋めます(すべてゼロ)
- 1を埋める:パケットのランダムなバイト数を0xFFバイト(すべての単位)で埋めます
- バイト挿入:パケットにランダムな場所でランダムなバイトを挿入します。挿入された後のバイトはテール方向にシフトされます。
- シングルビット:シングルランダムビットの損傷
次に、プログラムはランダムに1億個のパッケージを生成し、それぞれが上記の操作を実行した後、元のパッケージと最新化されたパッケージのチェックサムを比較しました。 変換中に変更されなかったパケットは破棄されました。 チェックサムが同じ場合、エラーが記録されました。
その結果、エラー数とともに次の表が得られました。
指定 | シャッフル | ビットシフト | ロールパケット | 右シフト | 左シフト | ゼロを埋める | 記入する | バイトインジェクション | 合計 |
CMS | 5101 | 3874 | 2937 | 1439 | 1688 | 3970 | 4010 | 1080 | 24099 |
CCITT | 2012 | 1127 | 3320 | 1494 | 1486 | 1063 | 1096 | 1130 | 12728 |
8月-CCITT | 2012 | 1127 | 3320 | 1494 | 1486 | 1063 | 1096 | 1130 | 12728 |
バイパス | 5101 | 3874 | 2937 | 1439 | 1688 | 3970 | 4010 | 1080 | 24099 |
CDMA2000 | 1368 | 1025 | 1946 | 1462 | 1678 | 1043 | 1028 | 1112 | 10662 |
DDS-110 | 5101 | 3874 | 2937 | 1439 | 1688 | 3970 | 4010 | 1080 | 24099 |
DECT-X | 1432 | 1189 | 5915 | 1779 | 1580 | 1215 | 1209 | 1093 | 15412 |
EN-13757 | 1281 | 2209 | 3043 | 1520 | 1528 | 2193 | 2187 | 1039 | 15,000 |
Modbus | 5090 | 3888 | 3086 | 1282 | 1582 | 3947 | 3897 | 1073 | 23845 |
T10-DIF | 1390 | 922 | 1424 | 1421 | 1630 | 994 | 938 | 1093 | 9812 |
TELEDISK | 1394 | 1049 | 5398 | 1451 | 1512 | 1096 | 1066 | 1065 | 14031 |
XMODEM | 2012 | 1127 | 3320 | 1494 | 1486 | 1063 | 1096 | 1130 | 12728 |
明らかに、アルゴリズムの開始値は結果に何の影響も与えません。これは論理的です。開始値はチェックサムの異なる値のみを提供しますが、計算メカニズム自体は変更されません。 したがって、これらのチェックサムを今後の検討から除外しました。 同様に、単一ビットのエラーを考慮することは意味がありません。すべてのチェックサムはエラーなしでこれに対処しましたが、実際には作成中にエラーが必要でした。
さて、チェックサムの最終品質表は、すでに重複アルゴリズムを考慮せずに:
指定 | 衝突回数 | 場所 |
CMS | 24099 | 8 |
CCITT | 12728 | 3 |
CDMA2000 | 10662 | 2 |
DECT-X | 15412 | 6 |
EN-13757 | 15,000 | 5 |
Modbus | 23845 | 7 |
T10-DIF | 9812 | 1 |
TELEDISK | 14031 | 4 |
残りの結論は読者に任せます。 私は、チェックサム多項式の単位数が結果に一定の影響を与えることに自分自身から注意します。 しかし、これは私の個人的な主観的な意見です。 他の説明を聞いてうれしいです。
プログラムのソースコードを以下に示します。
ソースコード
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <time.h> #define PACKET_LEN (7) #define NUM_OF_CYCLES (100000) static unsigned char reverse_table[16] = { 0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF }; uint8_t reverse_bits(uint8_t byte) { // Reverse the top and bottom nibble then swap them. return (reverse_table[byte & 0b1111] << 4) | reverse_table[byte >> 4]; } uint16_t reverse_word(uint16_t word) { return ((reverse_bits(word & 0xFF) << 8) | reverse_bits(word >> 8)); } uint16_t crc16_common(uint8_t *data, uint8_t len, uint16_t poly, uint16_t init, uint16_t doXor, bool refIn, bool refOut) { uint8_t y; uint16_t crc; crc = init; while (len--) { if (refIn) crc = ((uint16_t)reverse_bits(*data++) << 8) ^ crc; else crc = ((uint16_t)*data++ << 8) ^ crc; for (y = 0; y < 8; y++) { if (crc & 0x8000) crc = (crc << 1) ^ poly; else crc = crc << 1; } } if (refOut) crc = reverse_word(crc); return (crc ^ doXor); } uint16_t crc16_ccitt(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0xFFFF, 0x0000, false, false); } uint16_t crc16_bypass(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0x0000, 0x0000, false, false); } uint16_t crc16_xmodem(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0x0000, 0x0000, false, false); } uint16_t crc16_teledisk(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0xA097, 0x0000, 0x0000, false, false); } uint16_t crc16_augccitt(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0x1d0f, 0x0000, false, false); } uint16_t crc16_cdma2000(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0xc867, 0xffff, 0x0000, false, false); } uint16_t crc16_dds110(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0x800d, 0x0000, false, false); } uint16_t crc16_dect(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x0589, 0x0000, 0x0000, false, false); } uint16_t crc16_en13757(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x3d65, 0x0000, 0xffff, false, false); } uint16_t crc16_t10dif(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8bb7, 0x0000, 0x0000, false, false); } uint16_t crc16_cms(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, false, false); } uint16_t crc16_modbus(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, true, true); } bool compare_buf(uint8_t *buf1, uint8_t *buf2) { uint8_t x; for (x = 0; x < PACKET_LEN; x++) { if (buf1[x] != buf2[x]) return true; } return false; } bool method_shuffle(uint8_t *buf) { uint8_t i, j; uint16_t rnd; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN; i++) { for (j = 0; j < 10; j++) { rnd = (uint16_t)rand(); if (rnd % 7 == 0) buf[i] ^= (1 << (rnd % 8)); } } return compare_buf(buf, copy); } bool method_bitshift(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] == 0) buf[i] = 0x01; else buf[i] <<= 1; } return compare_buf(buf, copy); } bool method_packetroll(uint8_t *buf) { uint8_t x, i, j; uint8_t temp; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % (PACKET_LEN - 1)) + 1; for (j = 0; j < x; j++) { temp = buf[0]; for (i = 0; i < PACKET_LEN - 1; i++) buf[i] = buf[i + 1]; buf[PACKET_LEN - 1] = temp; } return compare_buf(buf, copy); } bool method_shiftright(uint8_t *buf) { uint8_t i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN - 1; i++) buf[i + 1] = buf[i]; buf[0] = 0xff; return compare_buf(buf, copy); } bool method_shiftleft(uint8_t *buf) { uint8_t i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN - 1; i++) buf[i] = buf[i + 1]; buf[PACKET_LEN - 1] = 0xff; return compare_buf(buf, copy); } bool method_zero(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] != 0x00) buf[i] = 0x00; else buf[i] = 0xFF; } return compare_buf(buf, copy); } bool method_one(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] != 0xFF) buf[i] = 0xFF; else buf[i] = 0x00; } return compare_buf(buf, copy); } bool method_injection(uint8_t *buf) { uint8_t x, i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN); for (i = PACKET_LEN - 1; i > x; i--) { buf[i] = buf[i - 1]; } buf[x] = (uint8_t)rand(); return compare_buf(buf, copy); } bool method_single(uint8_t *buf) { uint8_t x; x = (uint8_t)(rand() % (PACKET_LEN * 8)); buf[(uint8_t)(x / 8)] ^= (1 << (x % 8)); return true; } typedef struct { uint16_t crc1; uint16_t crc2; uint32_t errors; uint16_t (*fn)(uint8_t *data, uint8_t len); char name[32]; } tCRC; typedef struct { bool (*fn)(uint8_t *buf); char name[32]; } tMethod; static tMethod methods[] = { {method_shuffle, "Shuffle"}, {method_bitshift, "Bit shift"}, {method_packetroll, "Roll packet"}, {method_shiftright, "Right shift"}, {method_shiftleft, "Left shift"}, {method_zero, "Fill zeros"}, {method_one, "Fill ones"}, {method_injection, "Byte injection"}, {method_single, "Single bit"} }; static tCRC crcs[] = { {0, 0, 0, crc16_cms, "CMS"}, {0, 0, 0, crc16_ccitt, "CCITT"}, {0, 0, 0, crc16_augccitt, "AUG-CCITT"}, {0, 0, 0, crc16_bypass, "BYPASS"}, {0, 0, 0, crc16_cdma2000, "CDMA2000"}, {0, 0, 0, crc16_dds110, "DDS-110"}, {0, 0, 0, crc16_dect, "DECT-X"}, {0, 0, 0, crc16_en13757, "EN-13757"}, {0, 0, 0, crc16_modbus, "Modbus"}, {0, 0, 0, crc16_t10dif, "T10-DIF"}, {0, 0, 0, crc16_teledisk, "TELEDISK"}, {0, 0, 0, crc16_xmodem, "XMODEM"} }; int main(int argc, char * argv[]) { uint32_t num_of_cycle; uint32_t num_of_sums; uint8_t packet[PACKET_LEN]; uint8_t i; uint8_t m; //uint8_t buf[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; srand(time(NULL)); printf("------------------------- CRC16 comparison -------------------------\n"); num_of_sums = sizeof(crcs) / sizeof(tCRC); for (m = 0; m < sizeof(methods) / sizeof(tMethod); m++) { printf("\r%s:\n", methods[m].name); for (i = 0; i < num_of_sums; i++) { crcs[i].errors = 0; } for (num_of_cycle = 0; num_of_cycle < NUM_OF_CYCLES; num_of_cycle++) { for (i = 0; i < PACKET_LEN; i++) packet[i] = (uint8_t)rand(); for (i = 0; i < num_of_sums; i++) crcs[i].crc1 = crcs[i].fn(packet, PACKET_LEN); if (!methods[m].fn(packet)) continue; for (i = 0; i < num_of_sums; i++) { crcs[i].crc2 = crcs[i].fn(packet, PACKET_LEN); if (crcs[i].crc1 == crcs[i].crc2) crcs[i].errors++; } if (num_of_cycle % 1000 == 0) printf("\r%.2f%%", (float)num_of_cycle / NUM_OF_CYCLES * 100); } for (i = 0; i < num_of_sums; i++) printf("\r %20s: %10d\n", crcs[i].name, crcs[i].errors); } return 0; }
その結果、製品の次のバージョンでは、内部バスにCCITTチェックサムが選択されました。これは、主に、使用されているマイクロコントローラーのハードウェアで実装が利用可能であったためです。