In einem Projekt ging es darum, über eine lange Zeit (mehrere Tage) Messdatenpakete mit einer hohen Rate über eine RS232-Schnitstelle zu versenden. Da war es zu erwarten, dass es gelegentlich zu Störungen kommt. Indem man die Pakete mit Prüfsummen versieht, hat man eine hohe Wahrscheinlichkeit, solche zu entdecken. Im Folgenden wird der Code zur zyklischen Redundanzprüfung (CRC8, CRC16, CRC32) in C++, C# , VB.NET und Java Varianten vorgestellt.
Wenn man im Netz sucht, findet man viele Varianten für die Prüfsummenberechnung. In diesem Projekt werden die Prüfsummen nach der CCITT-Norm berechnet. Die CCITT-Variante für die CRC32-Berechnung wird u.a. in den ZIP-Algorithmen verwendet und ist auch Grundlage der Java-Klasse java.util.zip.CRC32.
Detaillierte Erläuterungen für die CRC16 findet man bei SourceForge (http://srecord.sourceforge.net/crc16-ccitt.html). Einen CCITT-kompatiblen Online-Generator gibt es bei Sven Reifegerste (http://www.zorc.breitbandkatze.de/crc.html). Dort findet man viele weitere Links und einen universell anwendbaren Code.
Im ZIP-Archiv CCITT-CRC-Generator.zip sind Visual-Studio-2019-Projektdateien mit dem Quellcode für die C++-, C#- und VB.Net-Varianten und ein Visual-Studio-Code-Projekt für die Java-Variante als Maven-Projekt.
Zur Berechnung der CRC wird ein Startwert und ein Generatorpolynom (eine Zahl) benötigt (die Bitfolge der Daten wird durch ein vorher festzulegendes Generatorpolynom (das CRC-Polynom) Modulo 2 geteilt). Man findet CRC-Berechnungen mit unterschiedlichen Angaben für diese Werte. Hier werden folgende Werte benutzt:
Startwert | Polynom | |
---|---|---|
CRC8 | 0xFF | 0x31 |
CRC1CRC16 | 0x1D0F | 0x1021 |
CRC32 | 0xFFFFFFFF | 0xEDB88320 |
Prinzipiell gibt es zwei Arten der Berechnung, die unterschiedlichen Anforderungen genügen.
Für geschwindigkeitsoptimierte Anwendungen wird mit Look-Up-Tabellen gearbeitet. Die Methoden werden sehr schnell ausgeführt, es wird jedoch Speicherplatz für die Tabellen benötigt. Diese Tabellen erfordern 256 Einträge in dem zur CRC-Variante passenden Datentyp. Dies sind 256 * 1 Byte (= 256 Byte) für CRC8, 256 * 2 Byte (= 512 Byte) für CRC16 und 256 * 4 Byte (= 1024 Byte) für CRC32.
Für speicheroptimierte Anwendungen wird die Prüfsumme ohne eine Look-Up-Tabelle berechnet. Der Code ist aufwändiger und benötigt zur Berechnung deutlich mehr Zeit. Die Werte die ansonsten aus der Tabelle entnommen werden, müssen praktisch on-the-fly berechnet werden.
Für die C#-, VB.Net- und Java-Varianten wurde nur die Funktionen implementiert, die eine Look-Up-Tabelle benutzen. Diese Sprachen werden fast ausschließlich auf PCs verwendet. Bei denen spielen einige Bytes Speicherplatz i.d.R. keine Rolle. Bei Java wurde auf die Bibliotheksklasse java.util.zip.CRC32 zurück gegriffen.
Je nach Projekt oder verwendeter Übertragungsschnittstelle, werden im Prinzip Pakete versendet (z.B. UDP, MQTT) oder es wird ein Datenstrom erzeugt (z.B. TCP, RS232).
Die Methoden generateCRC() liefern die Prüfsummen für ein Datenpaket.
Für die Stream-Variante werden drei Methoden benötigt.
Die Verwendung von eines Funktionszeigers (executor) zur Weitergabe der Daten an den Stream ist nicht performant. In Projekten mit hohem Performance-Bedarf sollten die Methoden zur Beschickung des Streams direkt eingesetzt werden.
Die Versionen für die verschiedenen Sprachen sind möglichst gleich gehalten.
C# und VB.Net kennen keine Funktionszeiger. Hier muss mit Delegate-Typen und Instanzen gearbeitet werden. In Java muss hier eine Interface-Klasse verwendet werden.
Java kennt keine unsigned-Datentypen. Deshalb muss mit den größeren Datentypen gearbeitet werden. Das bedeutet, dass intern immer wieder nachjustiert werden muss, weil z.B. bei einer << (left shift) Operation nicht nach links herausgeschoben wurde. Hier müssen die überzähligen Bits per & 0xFF... wieder entfernt werden. Ebenso müssen die Rückgabe-Typen beachtet werden. Die sind int oder long.
Für jede der Sprachen ist ein Testprogramm enthalten, dass für vier verschiedene Byte-Felder die Prüfsummen für alle implementierten Methoden enthält. Hier in C++-Notation:
const uint8_t testData[] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 }; //"123456789";
const uint8_t testData1[] = { 0xE4, 0x6D, 0x46, 0xF8, 0xFB, 0x16, 0xAB, 0x52, 0xED, 0x64, 0x50, 0x23, 0x63, 0x0D, 0x80, 0xBC, 0x98, 0xD8, 0x16, 0xFF, 0xEF, 0x3C, 0x4E, 0xD9, 0xBD, 0xA1, 0x85, 0x95, 0x13 };
const uint8_t testData2[] = { 0x42, 0xDF, 0x54, 0x99, 0x99, 0x20, 0x98, 0xCF, 0xF1, 0xFC, 0xF5, 0x9E, 0xED, 0x36, 0x61, 0x4A, 0xA2, 0x31, 0xF3, 0x3E, 0x02, 0xB1, 0x8A, 0x95, 0xE5, 0xF7, 0x88 };
const uint8_t testData3[] = { 0x5B, 0xB5, 0x06, 0x04, 0x52, 0xDE, 0xD8, 0xEC, 0x13, 0x35, 0x6B, 0x59, 0xC8, 0x66, 0xA4, 0xF6, 0xC9, 0xA8, 0x63, 0x03, 0xB9, 0xA8, 0xFD, 0xD9, 0x57 };
Das erste Byte-Feld ist die üblicherweise zum Test benutzte Zahlenfolge "123456789" in der ASCII-Darstellung.
Um die Streaming-Varianten zu testen, wird eine kleine Funktion verwendet, die das Test-Feld byte-weise an die zu testende Funktion übergibt und zum Schluss das Ergebnis abruft. Hier als Beispiel für die CRC8-Prüfsumme in C++-Notation:
uint32_t stream8TestData(const uint8_t* data, size_t len) {
while (len--)
crc8Generator.streamCRC(*data++);
return crc8Generator.streamFin();
}
Der typische Output des Testprogramme ist:
C++ CRC-Generator Test
==========================
CRC8
==========================
genCRC: 244
stream: 244
genFast: 244
streamF: 244
--------------------------
1 genCRC: 133
stream: 133
genFast: 133
streamF: 133
--------------------------
2 genCRC: 233
stream: 233
genFast: 233
streamF: 233
--------------------------
3 genCRC: 229
stream: 229
genFast: 229
streamF: 229
==========================
CRC16
==========================
genCRC: 58828
stream: 58828
genFast: 58828
streamF: 58828
--------------------------
1 genCRC: 39760
stream: 39760
genFast: 39760
streamF: 39760
--------------------------
2 genCRC: 59077
stream: 59077
genFast: 59077
streamF: 59077
--------------------------
3 genCRC: 32278
stream: 32278
genFast: 32278
streamF: 32278
==========================
CRC32
==========================
genCRC: 3421780262
stream: 3421780262
genFast: 3421780262
streamF: 3421780262
--------------------------
1 genCRC: 1628163719
stream: 1628163719
genFast: 1628163719
streamF: 1628163719
--------------------------
2 genCRC: 2942970280
stream: 2942970280
genFast: 2942970280
streamF: 2942970280
--------------------------
3 genCRC: 948803831
stream: 948803831
genFast: 948803831
streamF: 948803831