Modbus RTU - Calcul du CRC 16 [Résolu]

Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
- 11 mai 2009 à 13:56 - Dernière réponse :
Messages postés
1
Date d'inscription
mardi 12 mai 2009
Dernière intervention
27 mai 2009
- 27 mai 2009 à 13:56
Bonjour a vous tous,

Je suis entrain de développer une petite appli Delphi qui permet d'aller écrire dans un registre d'un régulateur West 6100+ la valeur d'une alarme.
J'utilise les composants de Tcomport pour la liaison série puis un convertisseur RS232/RS485 pour faire la liaison avec le regulateur.

Le regulateur utilise le protocole modbus soit RTU soit ASCII, j'ai donc choisi de communiquer en RTU. Mais le soucis vient du polynome imposé par le regulateur pour le calcul du CRC16, qui est le suivant: 216+215+22+1=98309

Voici ce que j'ai codé pour l'envoi de la trame:



procedure TForm1.ButEnvoiClick(Sender: TObject);
var
  CRC:string;
begin
{ouverture du port COM}
comport1.Connected:=true;
ButEnvoi.Enabled:=false;
{Conversion de la valeur de l'alarme en Hexa}
ConcatenerValAlarme:=PanChiffre1.caption+PanChiffre2.caption+PanChiffre3.caption;
ValeurAlarme:=StrToInt(ConcatenerValAlarme);
Data:=inttohex(ValeurAlarme,4);
Label1.Caption:=Data;
{Construction de l'entete de la trame}
Esclave:=IntToHex(StrToInt(CbxEsclave.Text),2);
CodeFonction:=IntToHex(StrToInt(EditCdFct.Text),2);
NumPara6100:=IntToHex(StrToInt(EditNumPara6100.Text),4);
{Assemblage de la trame sans CRC16}
TrameModbus:=Esclave+CodeFonction+NumPara6100+Data;
Label2.Caption:=TrameModbus;
{Calcul de CRC16}
CRC:=CalcCRC16(TrameModbus);
Label3.Caption:=CRC;
TrameModbusCRC16:=TrameModbus+CRC;
Label4.Caption:=TrameModbusCRC16;
{écriture de la trame sur le port COM}
comport1.WriteStr(TrameModbusCRC16);
{{Reception trame reponse (Facultatif, si 1 seul esclave, envoie de la trame
 à l'adresse esclave 0 en broadcast car pas de reponse de l'esclave dans ce cas)}
Comport1.ClearBuffer(True,False); // Supression de ce qui traine dans le buffer d'entrée
long:=Comport1.ReadStr(TrameRecue,LongueurTrame);// Lecture bloquante de la longueur demandée}
Label5.Caption:=TrameRecue;
{fermeture de port COM}
comport1.Connected:=false;
ButEnvoi.Enabled:=true;
end;





et voici ma procedure de calcul du CRC




Function CalcCRC16(trame:string):string;
var
  octet:char;
  ajout:boolean;
  CRC16:WORD;
  i,j:integer;
begin
  CRC16:=$FFFF;
  for i:=0 to length(trame)-1 do
    begin
      octet:=trame[i];
      CRC16:=(CRC16) xor ord(octet);
      for j:=1 to 8 do
        begin
          if odd(CRC16) then
              ajout:=true
          else
              begin
              ajout:=false;
              CRC16:=CRC16 div 2;
              end;
          if ajout=true then
            begin
              CRC16:=CRC16+98309;
              result:= IntToHex(CRC16,4);
            end;
        end;
    end;
end;




Est ce que mon code de calcul du CRC vous semble correct, car lorsque je test mon appli, je visualise mon CRC dans un label, mais si je change ma valeur de 3 ou 4 unité, ma trame change bien mais le CRC non.
par exemple, je met ma valeur d'alarme a 185 ma trame sera: 01 06 000D 00B9 A8DA  // A8DA etant mon CRC.
Pour 189 ma trame sere 01 06 000D 00B9 A8DA // le CRC est toujours A8DA.
Il faut que je monte a 192 pour voir mon CRC changer, pour cette valeur il est a : E9B0.

cela me parait bizarre car la trame changeant, le CRC devrait aussi changer, non?

En vous remerciant par avance.

Cordialement,

Jonathan

PS: Au besoin je peux vous zipper et vous envoyer l'appli complete.
Afficher la suite 

Votre réponse

14 réponses

Meilleure réponse
Messages postés
759
Date d'inscription
vendredi 21 mars 2003
Dernière intervention
1 octobre 2009
12 mai 2009 à 15:16
3
Merci
Salut,

Et salut à toi JulioDelphi !

Exact pour ajout ...
Ce qui donne au propre :


begin
  CRC16 := $FFFF;
  for i := 1 to length(trame) do








  begin
    octet := trame[i];
    CRC16 := (CRC16) xor ord(octet);
    for j := 1 to 8 do
    begin



     


Ajout :=



odd
(CRC16)

;


      CRC16 := CRC16 div 2;


      if ajout then


        CRC16 := CRC16 xor $A001;   






    end;

  end;

  result := IntToHex(CRC16, 4);


end;




Pour le polynôme générateur

:

Each message is followed by CRC (Cyclic Redundancy Check) with two byte. The CRC identify the
incongruity situations of the message, in this case the receiver ignore the message.
The CRC is calculated in accordance with a formula that imply a recursive division of the data by a
polynomial.
The polynomial divisor is:
216 + 215 + 22 + 1(Hex 18005)
but is modified in two ways:
• Since the bits order are reversed, then the binary pattern is also reversed, and the most significant
bit (MSB) is the right-most bit.
• Since interest only the remainder, the right-most bit could be discarded.
Therefore, the polynomial divisor has value: Hex A001

Ken@vo

Code, Code, Codec !

Merci cs_Kenavo 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 92 internautes ce mois-ci

Commenter la réponse de cs_Kenavo
Messages postés
759
Date d'inscription
vendredi 21 mars 2003
Dernière intervention
1 octobre 2009
11 mai 2009 à 17:30
0
Merci
Salut,






begin
  CRC16 := $FFFF;
  for i := 1 to length(trame) do  


    // caractères : trame[i] à trame[length(trame)]




  begin
    octet := trame[i];
    CRC16 := (CRC16) xor ord(octet);
    for j := 1 to 8 do
    begin
      if odd(CRC16) then               // ou : if  Ajout :=

odd
(CRC16)

{plus
élégant}




        ajout : = true
      <strike>else</strike>
      <strike>begin</strike>
       <strike>ajout := false;</strike>
        CRC16 := CRC16 div 2;         


// quelque soit le résultat de Odd(CRC16)




 


     <strike>end;</strike>
      if ajout<strike> = true</strike> then


             // pas beau !!!!




      <strike>begin
</strike>        CRC16 := CRC16 xor $A001;     


// il me semble

....
       <strike>result := IntToHex(CRC16, 4);</strike>   // le résultat n'est pas mis à jour                                          // à chaque boucle : à déplacer à la fin                                               // de la fonction
      <strike>end</strike>;                                
    end;
  end;
  result : = IntToHex(CRC16, 4);
end;

Ken@vo

Code, Code, Codec !
Commenter la réponse de cs_Kenavo
Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
11 mai 2009 à 17:47
0
Merci
Merci bien,

J'essayerai dans la soirée.

Apres pour la structure proprement dite, je l'ai reprise de mon cours de "bus de terrain que j'ai eu a l'IUT, commme quoi...

En tout cas encore merci.

Si, est tu sure pour le for i:=1 to length(Trame) car une chaine de caractere est un tableau a 1 dimension commencent a l'indice 0, non, enfin c'est ce que je croyais jusqu'a aujourd'hui 
Commenter la réponse de GingermaN
Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
11 mai 2009 à 17:58
0
Merci
Ah oui, désolé, pour le "flood" j'ai pas trouvé pour éditer mon post.

Pour la valeur  $A001, d'apres mon cours, cela correspond au polynome x15+x13+x0 et le polynome utilisé dans le regulateur pour calculer le crc est: 216+215+22+1.

cordialement,

Jonathan
Commenter la réponse de GingermaN
Messages postés
1651
Date d'inscription
samedi 10 juillet 2004
Dernière intervention
25 juillet 2014
11 mai 2009 à 19:18
0
Merci
Les chaines sont des tableaux particuliers elle comment à l'indice 1 l'indice 0 est réservé à la longueur pour les chaines courtes <255 caractères pour les chaines longues ce sont les indices 0 à -3 qui contiennent la longueur
 c'est en C que les chaines commencent à l'indice 0 et se terminent par un 0

@+
jlen
Commenter la réponse de jlen100
Messages postés
759
Date d'inscription
vendredi 21 mars 2003
Dernière intervention
1 octobre 2009
11 mai 2009 à 19:36
0
Merci
Salut,

Merci à jlen100 pour cette précision !

Je ne sais pas d'où vient ce polynome que tu utilises, mais je confirme !

Voir ici, page 112
(http://www.modbus.org/docs/PI_MBUS_300.pdf)

Ken@vo








Code, Code, Codec !
Commenter la réponse de cs_Kenavo
Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
11 mai 2009 à 20:04
0
Merci
Merci jlen, pour la précision.

Kenavo, mon polynome sort d'ici: http://docs-europe.electrocomponents.com/webdocs/02c0/0900766b802c0643.pdf   à la page 67.

Je test ta solution au boulot demain avec le régulateur, et je te tiens au courant.

Merci a vous deux!
Commenter la réponse de GingermaN
Messages postés
2354
Date d'inscription
dimanche 5 octobre 2003
Statut
Modérateur
Dernière intervention
18 novembre 2010
12 mai 2009 à 09:14
0
Merci
hors sujet : ho Kenavo ! le retour ? bon, bah ... Kenavo ! :]
Commenter la réponse de JulioDelphi
Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
12 mai 2009 à 09:19
0
Merci
Bonjour Kenavo,

Je viens de tester ta version de ma fonction, et il y a un truc qui va pas, pour n'importe quelle valeur mon CRC= $C001 .

Mais je pense savoir d'ou ca vient, il suffit que la variable ajout soit mise a true des la premeire boucle, et elle reste a true tout le long du calcul, ce qui par conséquent a chaque fois, on fait CRC16 xor $A001 qu'il y ai parité ou non.

begin
  CRC16 := $FFFF;
  for i := 1 to length(trame) do       // caractères : trame[i] à trame[length(trame)]
  begin
    octet : = trame[i];
    CRC16 := (CRC16) xor ord(octet);
    for j := 1 to 8 do
    begin
      if odd(CRC16) then               // ou : if  Ajout := odd(CRC16) {plus élégant}
        ajout : = true
      <strike>else</strike>
      <strike>begin</strike>
       <strike>ajout := false;</strike>
        CRC16 := CRC16 div 2;          // quelque soit le résultat de Odd(CRC16)  
     <strike>end;</strike>
      if ajout<strike> = true</strike> then             // pas beau !!!!
      begin<strike>
</strike>        CRC16 := CRC16 xor $A001;     // il me semble ....
       Ajout : = false;
      end;                                
    end;
  end;
  result := IntToHex(CRC16, 4);
end;

Parcontre je n'ai pas encore pu tester sur le régulateur car nous en pleine production.

Encore merci en tout cas!
Commenter la réponse de GingermaN
Messages postés
759
Date d'inscription
vendredi 21 mars 2003
Dernière intervention
1 octobre 2009
12 mai 2009 à 15:22
0
Merci
Ça, c'est celle que j'utilise depuis .... Turbo Pascal 4, je crois !



function CRC16(s: string): word;
var
  i, j: integer;
  w, x: word;
begin
  w := $FFFF;
  for i : = 1 to length(s) do
  begin
    x := ord(s[i]);
    w := w xor x;
    for j : = 1 to 8 do
    begin
      if W and $0001 = $0001 then
      begin
        w : = w shr 1;
        w := w xor $A001;
      end
      else
        w : = w shr 1;
    end;
  end;
  CRC16 := w;
end ;


Ken@vo

<hr size ="2" width="100%" />

Code, Code, Codec !
Commenter la réponse de cs_Kenavo
Messages postés
8
Date d'inscription
mardi 20 novembre 2007
Dernière intervention
12 mai 2009
12 mai 2009 à 16:01
0
Merci
Héhé, merci Ken@vo!

Je comprend mieux maintenant.

Donc en gros ça veux dire que si j'envoie mon CRC sous la forme Pf/PF, le polynôme devient $A001.

Donc par exemple, si ma fonction me retourne un CRC de $8C7B, il faut que dans ma trame j'envoie 7B8C.

En tout cas, merci pour ton aide, cela fait une semaine que je tourne en rond la-dessus .
Commenter la réponse de GingermaN
Messages postés
4304
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
9 mars 2018
12 mai 2009 à 19:22
0
Merci
version plus rapide : 75c vs 411c pour '123456789' = 0x4B37
Init $FFFF : CRC-16 ModBus, check '123456789' 0x4B37Init $0000 : CRC-16 Standard, check '123456789' 0xBB3D
function CRC16(const s: AnsiString; const Init: word $FFFF; const Reversed: boolean false; const Inverted: boolean = false): word;
var
  pB : ^byte;
  L  : integer;
begin
  pB := @S[1];
  L  := Length(S);

  result := Init;
 
  repeat
    result := result xor pB^;
    inc(pB);
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    if (result and $1) = $1 then result := (result shr 1) xor $A001
                            else result := result shr 1;
    dec(L);
  until L = 0;

  if Reversed then
    result := byte(result shr 8) or (byte(result) shl 8);

  if Inverted then
    result := not result;
end;

Commenter la réponse de f0xi
Messages postés
759
Date d'inscription
vendredi 21 mars 2003
Dernière intervention
1 octobre 2009
14 mai 2009 à 11:53
0
Merci
Salut f0xi ! Toujours pressé à ce que je vois ...

Pour être le plus rapide possible dans le calcul du CRC, il faut oublier l'algorithme et utiliser une table de constantes

const
  TableCRC: array[0..255] of word =
   ($0000, $C0C1, $C181, $0140, $C301, $03C0, $0280, $C241,
    $C601, $06C0, $0780, $C741, $0500, $C5C1, $C481, $0440,
    $CC01, $0CC0, $0D80, $CD41, $0F00, $CFC1, $CE81, $0E40,
    $0A00, $CAC1, $CB81, $0B40, $C901, $09C0, $0880, $C841,
    $D801, $18C0, $1980, $D941, $1B00, $DBC1, $DA81, $1A40,
    $1E00, $DEC1, $DF81, $1F40, $DD01, $1DC0, $1C80, $DC41,
    $1400, $D4C1, $D581, $1540, $D701, $17C0, $1680, $D641,
    $D201, $12C0, $1380, $D341, $1100, $D1C1, $D081, $1040,
    $F001, $30C0, $3180, $F141, $3300, $F3C1, $F281, $3240,
    $3600, $F6C1, $F781, $3740, $F501, $35C0, $3480, $F441,
    $3C00, $FCC1, $FD81, $3D40, $FF01, $3FC0, $3E80, $FE41,
    $FA01, $3AC0, $3B80, $FB41, $3900, $F9C1, $F881, $3840,
    $2800, $E8C1, $E981, $2940, $EB01, $2BC0, $2A80, $EA41,
    $EE01, $2EC0, $2F80, $EF41, $2D00, $EDC1, $EC81, $2C40,
    $E401, $24C0, $2580, $E541, $2700, $E7C1, $E681, $2640,
    $2200, $E2C1, $E381, $2340, $E101, $21C0, $2080, $E041,
    $A001, $60C0, $6180, $A141, $6300, $A3C1, $A281, $6240,
    $6600, $A6C1, $A781, $6740, $A501, $65C0, $6480, $A441,
    $6C00, $ACC1, $AD81, $6D40, $AF01, $6FC0, $6E80, $AE41,
    $AA01, $6AC0, $6B80, $AB41, $6900, $A9C1, $A881, $6840,
    $7800, $B8C1, $B981, $7940, $BB01, $7BC0, $7A80, $BA41,
    $BE01, $7EC0, $7F80, $BF41, $7D00, $BDC1, $BC81, $7C40,
    $B401, $74C0, $7580, $B541, $7700, $B7C1, $B681, $7640,
    $7200, $B2C1, $B381, $7340, $B101, $71C0, $7080, $B041,
    $5000, $90C1, $9181, $5140, $9301, $53C0, $5280, $9241,
    $9601, $56C0, $5780, $9741, $5500, $95C1, $9481, $5440,
    $9C01, $5CC0, $5D80, $9D41, $5F00, $9FC1, $9E81, $5E40,
    $5A00, $9AC1, $9B81, $5B40, $9901, $59C0, $5880, $9841,
    $8801, $48C0, $4980, $8941, $4B00, $8BC1, $8A81, $4A40,
    $4E00, $8EC1, $8F81, $4F40, $8D01, $4DC0, $4C80, $8C41,
    $4400, $84C1, $8581, $4540, $8701, $47C0, $4680, $8641,
    $8201, $42C0, $4380, $8341, $4100, $81C1, $8081, $4040);

function CRC16(s: string): word;
var
  i: integer;
  w: word;
  b, x: byte;
begin
  w : = $FFFF;
  for i := 1 to length(s) do
  begin
    x : = ord(s[i]);
    b := w xor x;
    w : = (w shr 8) xor TableCRC[b];
  end;
  CRC16 := w;
end ;

... et je sais que tu peux l'optimiser !

Ken@vo



<hr size ="2" width="100%" />



Code, Code, Codec !
Commenter la réponse de cs_Kenavo
Messages postés
1
Date d'inscription
mardi 12 mai 2009
Dernière intervention
27 mai 2009
27 mai 2009 à 13:56
0
Merci
slt, est ce ke tu peut m'enoyer ton programme complet et merci d'avance voici mon msn :nipo-95@hotmail.com
Commenter la réponse de houirieaa

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.