Motivation

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.

Quellen

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.

Download

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.

Verwendung

Startwert und Polynom

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

Look-Up-Tabelle vs. Bit-by-Bit-Berechnung

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.

Paket vs. Stream

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).

Paket

Die Methoden generateCRC() liefern die Prüfsummen für ein Datenpaket.

Stream

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.

Sprachspezifika

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.

Test

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