mardi 11 février 2014

MODBUS Calcul du CRC sur 16 bits en C#

Voici un exemple simple d'application WinForm en C# de Calcul du CRC sur 16 bits d'une trame en hexadécimal. Il y a beaucoup de littérature sur ce sujet mais pas d'exemple simple d'implémentation de l'algorithme de calcul du CRC véritablement en C#.



http://portelatine.chez-alice.fr/electronique/ecc_cce/ecc_crc.html
Code Correcteur d'Erreur

Je me suis donc lancé dans l'exercice. Voici donc l'interface de notre application WinForm :

WinForm C# de Calcul du CRC d'une trame (Requête)
Requête : C'est une trame en hexadécimal dont je vais calculer le CRC MSB (most significant byte ou poid fort) et LSB (poid faible) en cliquant sur le bouton "Calculer CRC".

Au delà d'implémenter et de valider l'algorithme de calcul du CRC sur 16 bits d'une trame par exemple de protocole ModBus, cet exemple est intéressant à plusieurs titres :
  • manipulation des strings en chars et conversion en byte[] et réciproquement
  • filtrage des caractères dans une TextBox pour n'obtenir que les caractères hexadécimaux
Tout se passe dans la TextBox, on va filtrer les caractères pour ne laisser "passer" que les caractères hexadécimaux et les contrôles. Sur le bouton on implémentera l'algorithme de calcul d'un CRC sur 16 bits.

Petit rappel rapide sur le calcul hexadécimal

En binaire, sur 4 bits, on peut compter jusqu'à (1111) binaire = (15) décimal = (F) hexadécimal
Sur un octet (8 bits = 1 Byte = 2 fois 4 bits) (1111 1111) binaire = (255) décimal = (FF) hexadécimal
Notation : binaire/hexadécimale :
1 = 1
11 = 3
111 = 7
1000 = 8
1010 = A
1011 = B
1100 = C
1101 = D
1110 = E
1111 = F
Sur un octet : FF que l'on notera (0xFF) hexadécimal = (255) décimal

Donc dans notre TextBox, on ne pourra entrer qu'un nombre pair de caractères correspondant à un nombre d'octets de la trame (ou Requête) en notation hexadécimal.

Algorithme du CRC 16 en C#

En C#, c'est à dire très proche du C ou du C++, j'écris l'algorithme de la façon suivante :

int CalculCRC16(byte[] octets)
{
   int crc = 0xFFFF;
   int i, done = 0;
   byte todo;
   int nb = octets.Length;

   if (nb >= 0)
   {
    do 
    {
     todo = octets[done];
     crc ^= todo;
     for (i = 0; i < 8; i++)
     {
      if (crc % 2 != 0)
      {
       crc = (crc >> 1) ^ 0xA001;
      }
      else
      {
       crc = crc >> 1;
      }
     }
     done++;
    } while (done < nb);
   }

return crc;
}

Afin d'utiliser les opérateurs de l'algèbre de bool :
^ : XOR ou OU exclusif
>> 1 : décalage à droite de 1 bit
l'algorithme prend en entré un tableau de byte : byte[] et retourne le CRC en int sur 16 bits.

Conversion d'une string en byte[]

Mon TextBox possède une propriété Text que je transforme en byte[] grâce à la fonction :

static public byte[] StringHexaToByteArray(string str)
{
char[] s = str.ToCharArray();
byte[] b = new byte[s.Length/2];

int oct = 0;
for (int i = 0; i < s.Length/2; i++)
{
string octet = string.Concat(s[oct], s[oct + 1]);
oct = oct + 2;
int hexa = int.Parse(octet, System.Globalization.NumberStyles.HexNumber);
b[i] = (byte)hexa;
}

return b;
}

On voit ici apparaître le faite qu'un byte est une paire de caractères de la chaîne str passée en paramètre c'est à dire la string TextBox.Text.

On note au passage une instruction bien sympa qui permet d'afficher un int en hexa :

string hexa = crc.ToString("X");

Simple, non ?

Filtrage des caractères non hexadécimaux

En ce qui concerne les caractères tapés au clavier le filtrage se fait dans la fonction :

private void textBoxRequete_KeyPress(object sender, KeyPressEventArgs e)
{
char [] _hexa = {'A','B','C','D','E','F'};
List<char> hexa = new List<char>(_hexa);

if (char.IsLower(e.KeyChar))
{
e.KeyChar = char.ToUpper(e.KeyChar);


if (char.IsLetterOrDigit(e.KeyChar) == false && char.IsControl(e.KeyChar) == false)
{
e.Handled = true;
}

if (char.IsLetter(e.KeyChar) == true && hexa.Contains(e.KeyChar) == false)
{
e.Handled = true;
}
}

La bibliothèque de fonctions static de char ne possède pas de fonction IsHexa, on est donc obligé d'y palier en ajoutant un petit traitement de List<T>.

Cette fonction est câblée sur le KeyPressEventHandler mais cela ne suffit pas car l'utilisateur peut encore copier/coller une chaîne de texte dans la TextBox. On cablera donc sur EventHandler la fonction :

private void textBoxRequete_TextChanged(object sender, EventArgs e)
{
char[] _hexa = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
List<char> hexa = new List<char>(_hexa);

string filter = string.Empty;
char f;
foreach (char c in textBoxRequete.Text)
{
f = char.ToUpper(c);
if (hexa.Contains(f))
{
filter += f;
}
}
textBoxRequete.Text = filter;
}

Pour les explications du code s'est fini ! Et pour le reste c'est de la tambouille de programmeur.

Quelques exemples de validation :
CRC16(0x00) = 0x40BF

Calcul du CRC de la trame 0x00
Sur un site que personnellement, je trouve fantastique : Sitelec Calcul du CRC16 on trouvera la trame :

CRC16(0x02062329000D) = 0x7092

Donc pour vérifier l'implémentation (valider) :

Calcul du CRC de la trame 0x02062329000D
On peut penser que l'implémentation de notre algorithme est correcte.

Bibliographie CRC


Pour optimiser cet algo il faut précalculer des tables dont certaine exeplications se trouvent ici :

Et sur ce site bien plus que le calcul de CRC :

Download source code in C#

Requirements :
  • Visual Studio 2010 
  • Langage C#