Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Typumwandlung hexwerte


daywalker0086 - Di 09.07.13 11:03
Titel: Typumwandlung hexwerte
Hallo Leute ich hab mal wieder ein Problem und komm nicht weiter.
Es geht um eine Checksummenberechnung.
Hier erstmal ein wenig code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
const
 // Table of CRC values for high–order byte */
 auchCRCHi : array [Byte] of Word =(
 $00$C1$81$40$01$C0$80$41$01$C0$80$41$00$C1$81,
 $40$01$C0$80$41$00$C1$81$40$00$C1$81$40$01$C0,
 $80$41$01$C0$80$41$00$C1$81$40$00$C1$81$40$01,
 $C0$80$41$00$C1$81$40$01$C0$80$41$01$C0$80$41,
 $00$C1$81$40$01$C0$80$41$00$C1$81$40$00$C1$81,
 $40$01$C0$80$41$00$C1$81$40$01$C0$80$41$01$C0,
 $80$41$00$C1$81$40$00$C1$81$40$01$C0$80$41$01,
 $C0$80$41$00$C1$81$40$01$C0$80$41$00$C1$81$40,
 $00$C1$81$40$01$C0$80$41$01$C0$80$41$00$C1$81,
 $40$00$C1$81$40$01$C0$80$41$00$C1$81$40$01$C0,
 $80$41$01$C0$80$41$00$C1$81$40$00$C1$81$40$01,
 $C0$80$41$01$C0$80$41$00$C1$81$40$01$C0$80$41,
 $00$C1$81$40$00$C1$81$40$01$C0$80$41$00$C1$81,
 $40$01$C0$80$41$01$C0$80$41$00$C1$81$40$01$C0,
 $80$41$00$C1$81$40$00$C1$81$40$01$C0$80$41$01,
 $C0$80$41$00$C1$81$40$00$C1$81$40$01$C0$80$41,
 $00$C1$81$40$01$C0$80$41$01$C0$80$41$00$C1$81,
 $40
 ) ;
 
// Table of CRC values for low–order byte */
 auchCRCLo: array [Byte] of Word =(
 $00$C0$C1$01$C3$03$02$C2$C6$06$07$C7$05$C5$C4,
 $04$CC$0C$0D$CD$0F$CF$CE$0E$0A$CA$CB$0B$C9$09,
 $08$C8$D8$18$19$D9$1B$DB$DA$1A$1E$DE$DF$1F$DD,
 $1D$1C$DC$14$D4$D5$15$D7$17$16$D6$D2$12$13$D3,
 $11$D1$D0$10$F0$30$31$F1$33$F3$F2$32$36$F6$F7,
 $37$F5$35$34$F4$3C$FC$FD$3D$FF$3F$3E$FE$FA$3A,
 $3B$FB$39$F9$F8$38$28$E8$E9$29$EB$2B$2A$EA$EE,
 $2E$2F$EF$2D$ED$EC$2C$E4$24$25$E5$27$E7$E6$26,
 $22$E2$E3$23$E1$21$20$E0$A0$60$61$A1$63$A3$A2,
 $62$66$A6$A7$67$A5$65$64$A4$6C$AC$AD$6D$AF$6F,
 $6E$AE$AA$6A$6B$AB$69$A9$A8$68$78$B8$B9$79$BB,
 $7B$7A$BA$BE$7E$7F$BF$7D$BD$BC$7C$B4$74$75$B5,
 $77$B7$B6$76$72$B2$B3$73$B1$71$70$B0$50$90$91,
 $51$93$53$52$92$96$56$57$97$55$95$94$54$9C$5C,
 $5D$9D$5F$9F$9E$5E$5A$9A$9B$5B$99$59$58$98$88,
 $48$49$89$4B$8B$8A$4A$4E$8E$8F$4F$8D$4D$4C$8C,
 $44$84$85$45$87$47$46$86$82$42$43$83$41$81$80,
 $40
 );

 var

 leistung1,leistung2,kaskade: integer;



function ModbusCRC16(const s: String): Word;
 var
     uchCRCHi: Byte;
     uchCRCLo: Byte;
     uIndex: Byte;
     I: Integer;
 begin
     uchCRCHi := $FF;
     uchCRCLo := $FF;
     for I := 1 to Length(s) do
     begin
         uIndex := uchCRCLo xor ord(s[i]);
         uchCRCLo := uchCRCHi xor auchCRCHi[uIndex];
         uchCRCHi := auchCRCLo[uIndex];
     end;
     Result:= uchCRCHi or uchCRCLo shl 8;
 end;

procedure TForm1.write_typ_c_btnClick(Sender: TObject);
var
sendstr, crc:string;
begin
sendstr:= ('01'+'10'+'05'+Inttohex(kaskade,2)+Inttohex(leistung1,4)+Inttohex(leistung2,4)) ;
serialp1.SendString(hextostr(sendstr));
memo1.Text:=sendstr;
//crc:= hextostr(inttohex(modbusCRC16(hexstrtostr(sendstring)),2));
memo1.Lines.Add(crc);

serialp1.SendString(inttohex(modbusCRC16(sendstr),4));


Der erste Teil funktioniert, es sollen die direkten Hexweerte gesendet werden, das wird hier gemacht:

Delphi-Quelltext
1:
2:
sendstr:= ('01'+'10'+'05'+Inttohex(kaskade,2)+Inttohex(leistung1,4)+Inttohex(leistung2,4)) ;
serialp1.SendString(hextostr(sendstr));

und läuft ich bekomme auf der Schnittstelle 01 10 05 00 C3 50 00 00 gesendet.

JEtzt muss noch der CRC bestimmt werden:

Delphi-Quelltext
1:
serialp1.SendString(inttohex(modbusCRC16(sendstr),4));                    

Jetzt kommt das Problem mit der Typumwandlung:
Als Vergleich lasse ich den Wert auf folgender Seite berechnen:
http://www.lammertbies.nl/comm/info/crc-calculation.html
Wenn ich dort 01100500C3500000 eingebe C91F raus(er stellt erst Low und dann Highbyte dar)
Dies macht auch meine Routine, sie rechnet also prinzipiell richtig.
ABER: auf der seite muss "input type: HEX" angeklickt werden, damit ich den zugehörigen CRC bekomme da es ja Hexwerte sind.
Und dann kommt ein anderes Ergebnis raus. Aber dieses brauch ich eigentlich.
Also meine routine greift die Werte als ASCII auf und nicht als Hex, das ist falsch.
Hab schon wild umgewandelt aber aber kommt nie das raus was ich brache.
Kann mir da jemand helfen ?
Ich weis nichtmehr weiter.


WasWeißDennIch - Di 09.07.13 11:18

Ich bin nicht sicher, ob ich Dich richtig verstanden habe, aber wie ist es so?

Delphi-Quelltext
1:
uIndex := uchCRCLo xor IntToStr('$' + s[i]);                    


OlafSt - Di 09.07.13 12:04

Ich weiß, was das Problem ist. Der TE hat den Unterschied zwischen Bytes und einer Zeichenkette noch nicht realisiert.

Nehmen wir die Bytefolge "01 10 05 00 C3 50 00 00". Das sind zunächst einmal lauter Bytes, also $01, $10, $05, $00, $C3, $50, $00 und $00. Durch die Umwandlung in eine Zeichenkette (s:='01 10 05 00 C3 50 00 00') sind das aber keine Bytes mehr. Das erste Byte $01 wird nun in eine zweistellige Zeichenkette, bestehend aus den Zeichen "0" und "1" umgewandelt. Es ist nun kein Byte mehr.

Ergo wird eine Iteration über die einzelnen Zeichen der Zeichenkette nicht mehr die ehemaligen Bytes erwischen, sondern die einzelnen Zeichen der Zeichenkette. Ergo: Ord(s[1]) holt sich das Zeichen "0", und ermittelt dessen ASCII-Code, in diesem Falle $30. Dann kommt das nächste Zeichen "1", dessen ASCII-Code $31 ist.

Deine CRC-Berechnung läuft also nicht über $01, $10, $05 und so weiter. Sie läuft über $30, $31, $20 (Leerzeichen) $30, $35 usw.

Um das ganze korrekt zu machen, würde ich die zu sendenden Bytes als allererstes in ein Array of Byte stopfen. Über dieses Array (!) würde ich dann die CRC-Checksumme bilden und an das Array anhängen. Anschließend die Bytes des Arrays in eine Zeichenkette wandeln und rüber über die SIO damit.


Gammatester - Di 09.07.13 12:09

Du must halt Deinen String auch nach Hex wandeln (bzw. je nach Quelle nicht nach String wandeln). Also in Deinem Beispiel

Quelltext
1:
2:
01100500C3500000 ->  $01, $10, $05, $00, $C3, $50, $00, $00 
-> ansichar($01) + ansichar($10) ansichar($05) + ansichar($00) + ansichar($C3) + ansichar($50) + ansichar($00)+ ansichar($00)

Im übrigen ist es eine Unart, CRC- und Hash-Routinen mit Strings zu füttern, denn diese Algorithmen arbeiten auf Bit/Byte-Basis. Deshalb ist es auch wichtig (wenn es denn sein muß, Zeichen zu benutzen), mit ansichar/string an Stelle von char/string zu arbeiten, um möglich Unicodeproblemen vorzubeugen.


daywalker0086 - Di 09.07.13 12:47

So kurze Zwischenfrage:
habe in Array of Byte erstellt und wollte diesem jetzt zum testen feste Werte zuweisen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
procedure TForm1.write_typ_c_btnClick(Sender: TObject);
var
sendstr, crc:string;
daten: array[0..7of Byte;
begin
sendstr:= ('01'+'10'+'05'+Inttohex(kaskade,2)+Inttohex(leistung1,4)+Inttohex(leistung2,4)) ;
serialp1.SendString(hextostr(sendstr));
memo1.Text:=sendstr;
//crc:= hextostr(inttohex(modbusCRC16(hexstrtostr(sendstring)),2));

daten[0]:=01;
daten[1]:=10;
daten[2]:=05;
daten[3]:=00;
daten[4]:=195//C3
daten[5]:=50;
daten[6]:=00;
daten[7]:=00;

mach ich das jetzt so richtig? Wenn ja muss ich wohl noch die CRC Routine umstricken damit die Byte frisst oder?
Habe diese nur kopiert weil ich mir dachte warum das Rad neu erfinden und augenscheinlich funktionierte sie ja erstmal.


Gammatester - Di 09.07.13 13:06

user profile icondaywalker0086 hat folgendes geschrieben Zum zitierten Posting springen:

mach ich das jetzt so richtig? Wenn ja muss ich wohl noch die CRC Routine umstricken damit die Byte frisst oder?
Habe diese nur kopiert weil ich mir dachte warum das Rad neu erfinden und augenscheinlich funktionierte sie ja erstmal.
Nein, jetzt verwechselst Du teilweise (bis auf die 193) hex und dec, am besten die Hex-Werte (wie schon oben beschrieben) mit $ zuweisen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
daten[0]:=$01;
daten[1]:=$10;
daten[2]:=$05;
daten[3]:=$00;
daten[4]:=$C3;
daten[5]:=$50;
daten[6]:=$00;
daten[7]:=$00;

Hier mal ein halb-automatischer Code aus meinen CRC/Hashroutinen [http://www.wolfgang-ehrhardt.de/crchash_de.html]:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
{---------------------------------------------------------------------------}
function CRC16_MODBUS(data: pointer; len: word): word;
const
  rpoly = $A001{Reflected $8005}
type
  tbyteptr = ^byte;
var
  j: integer;
  crc: word;
  ptr: tbyteptr;
begin
  crc := $FFFF;
  ptr := tbyteptr(data);
  while len>0 do begin
    crc := crc xor ptr^;
    for j:=1 to 8 do begin
      if odd(crc) then  crc := (crc shr 1xor rpoly
      else crc := crc shr 1;
    end;
    inc(ptr);
    dec(len);
  end;
  CRC16_MODBUS := crc;
end;

const
  tv: array[0..7of byte = ($01$10$05$00$C3$50$00$00);
begin
  writeln('CRC16_MODBUS Test daywalker0086:', CRC16_MODBUS(@tv,sizeof(tv))=$6ac);
end.

Die Ausgabe ist (via True/False, weil ich IntToHex nicht einbinden wollte):

Quelltext
1:
CRC16_MODBUS Test daywalker0086:TRUE                    

Oder mit array of byte:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
{---------------------------------------------------------------------------}
function CRC16_MODBUS(const b: array of byte): word;
const
  rpoly = $A001{Reflected $8005}
var
  i,j: integer;
  crc: word;
begin
  crc := $FFFF;
  for i:=low(b) to high(b) do begin
    crc := crc xor b[i];
    for j:=1 to 8 do begin
      if odd(crc) then  crc := (crc shr 1xor rpoly
      else crc := crc shr 1;
    end;
  end;
  CRC16_MODBUS := crc;
end;

const
  tv: array[0..7of byte = ($01$10$05$00$C3$50$00$00);
begin
  writeln('CRC16_MODBUS Test daywalker0086:', CRC16_MODBUS(tv)=$6ac);
end.

Edit: Ein paar Korrekturen :wink:


daywalker0086 - Di 09.07.13 13:56

Danke für die Hilfe!
Hab das mit deiner Routine jetzt so gelöst:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
daten[0]:=$01;
daten[1]:=$10;
daten[2]:=$05;
daten[3]:=$00;
daten[4]:=$C3//C3
daten[5]:=$50;
daten[6]:=$00;
daten[7]:=$00;
daten[8]:=Lo(CRC16_MODBUS(@daten,8));
daten[9]:=Hi(CRC16_MODBUS(@daten,8));
for i:=0 to 9 do
begin
  sendstr:=sendstr+inttohex(daten[i],2);
end;
serialp1.SendString(hextostr(sendstr));


Ist zwar auch wieder ein wenig hextostr und inttohex aber funktioniert bis jetzt tadellos. oder kann man es noch stark verbessern?
Jetzt muss ich nur noch sehen das ich die festen Werte richtig mit meinen Eingaben ersetze...


Gammatester - Di 09.07.13 14:08

user profile icondaywalker0086 hat folgendes geschrieben Zum zitierten Posting springen:
Danke für die Hilfe!
Hab das mit deiner Routine jetzt so gelöst:

Delphi-Quelltext
1:
2:
3:
//..
daten[8]:=Lo(CRC16_MODBUS(@daten,8));
daten[9]:=Hi(CRC16_MODBUS(@daten,8));

Ist zwar auch wieder ein wenig hextostr und inttohex aber funktioniert bis jetzt tadellos. oder kann man es noch stark verbessern?
Etwas kann man noch ein wenig verbessern: Du berechnest den CRC-Wert zweimal. Bei so wenigen Daten ist das nur unschön, bei Megabytes geht's auch in die Zeit: einfach eine Word-Variable zum Zwischenspeichern nehmen.