Logo250

Struktura protokolu.

Komunikace protokolem EZSP probíhá v datových blocích označovaných jako rámce (frame). Popis těchto rámců najdete v dokumentu ug101-uart-gateway-protocol-reference. I tento dokument jsem si uložil do svého archivu. Každý rámec se skládá z těchto položek:

  • Control byte, určuje typ rámce: data, potvrzení příjmu, reset, chybu, RSTACK
  • Pole dat, obsahující přenášenou instrukci
  • CRC, kontrolní součet, který je dvoubajtový
  • Flag byte, vždy 0x7E

Rámec se ale v tomto tvaru přes seriový port nevysílá. Nejprve se provede úprava připravených dat.  V praxi vypadá celý postup odvysílání rámce takto: 

  1. Připraví se požadovaná data rámce bez Control Byte, CRC a Flagu
  2. Provede se pseudorandomizace dat 
  3. Vypočítá se CRC a přidá se na konec dat
  4. Provede se proces stuffing
  5. Na začátek dat se přidá Control Byte, na konec dat se připojí znak 0x7E
  6. Vzniklý binární řetězec se odešle seriovým portem.

 Příjem rámce se provede přesně opačným postupem:

  1. Seriovým portem se načítají binární data tak dlouho, dokud nepříjde znak Flag = 0x7E
  2. Provede se proces unstuffing
  3. Zkontroluje se CRC 
  4. Provede se pseudorandomizace dat bez Control Byte, CRC a Flagu.

Takto zpracovaná data jsou přijatým rámcem.

Pseudorandomizace dat

Pseudorandomizaci dat můžeme považovat za jistou formu šifrování. Každý bajt se totiž změní operací XOR na jinou hodnotu. Bajty použité k operaci XOR ale nejsou úplně náhodné, proto pseudorandomizace. První bajt dat se zpracuje vždy s bajtem 0x42. Každý následující bajt pro pseudorandomizaci vzniká přesně definovaným postupem. Tento postup dokumentuje následující kód v pascalu pro data dlouhá 6 bajtů:

// PseudoRandomize
myRnd:=$42;
for I := 1 to 6 do
begin
  localbuff[I]:= localbuff[I] XOR myRnd;
  // vypočítej nový pseudorandom bajt
  if (myRnd MOD 2) = 1 then myRnd:= (myRnd SHR 1) XOR $B8
  else myRnd:= myRnd SHR 1;
end;

Cyklus obvykle začíná pozicí 1, protože před ním je umístěný Control Byte, který se do procesu pseudorandomize nezahrnuje. Takže například řetězec 6 bajtů 010203040506 po aplikaci pseudorandomizace bude obsahovat 4323AB502F13 a po opětovné pseudorandomizaci 010203040506. Pseudorandomizace je symetrický proces. Používá se ten stejný algorytmus pro odeslání i příjem dat. 

Výpočet CRC

Ani výpočet CRC není složitý. Důležité je, že se počítá jako hodnota Word, tedy dva bajty. Výchozí hodnota je vždy 0xFFFF. Vše popisuje následující ukázka kódu v pascalu.

function CRC16_HQX(const Data: array of Byte; Delka: Integer; InitialValue: Word): Word;
var
i: Integer;
CRC: Word;
begin
  CRC := InitialValue;
  for i := 0 to Delka - 1 do
  begin
    CRC := (CRC shl 8) xor CRCTable[((CRC shr 8) xor Ord(Data[i])) and $FF];
  end;
  Result := CRC;
end;

CRCTable má následující hodnotu:

CRCTable: array[0..255] of Word = (
$0000, $1021, $2042, $3063, $4084, $50A5, $60C6, $70E7,
$8108, $9129, $A14A, $B16B, $C18C, $D1AD, $E1CE, $F1EF,
$1231, $0210, $3273, $2252, $52B5, $4294, $72F7, $62D6,
$9339, $8318, $B37B, $A35A, $D3BD, $C39C, $F3FF, $E3DE,
$2462, $3443, $0420, $1401, $64E6, $74C7, $44A4, $5485,
$A56A, $B54B, $8528, $9509, $E5EE, $F5CF, $C5AC, $D58D,
$3653, $2672, $1611, $0630, $76D7, $66F6, $5695, $46B4,
$B75B, $A77A, $9719, $8738, $F7DF, $E7FE, $D79D, $C7BC,
$48C4, $58E5, $6886, $78A7, $0840, $1861, $2802, $3823,
$C9CC, $D9ED, $E98E, $F9AF, $8948, $9969, $A90A, $B92B,
$5AF5, $4AD4, $7AB7, $6A96, $1A71, $0A50, $3A33, $2A12,
$DBFD, $CBDC, $FBBF, $EB9E, $9B79, $8B58, $BB3B, $AB1A,
$6CA6, $7C87, $4CE4, $5CC5, $2C22, $3C03, $0C60, $1C41,
$EDAE, $FD8F, $CDEC, $DDCD, $AD2A, $BD0B, $8D68, $9D49,
$7E97, $6EB6, $5ED5, $4EF4, $3E13, $2E32, $1E51, $0E70,
$FF9F, $EFBE, $DFDD, $CFFC, $BF1B, $AF3A, $9F59, $8F78,
$9188, $81A9, $B1CA, $A1EB, $D10C, $C12D, $F14E, $E16F,
$1080, $00A1, $30C2, $20E3, $5004, $4025, $7046, $6067,
$83B9, $9398, $A3FB, $B3DA, $C33D, $D31C, $E37F, $F35E,
$02B1, $1290, $22F3, $32D2, $4235, $5214, $6277, $7256,
$B5EA, $A5CB, $95A8, $8589, $F56E, $E54F, $D52C, $C50D,
$34E2, $24C3, $14A0, $0481, $7466, $6447, $5424, $4405,
$A7DB, $B7FA, $8799, $97B8, $E75F, $F77E, $C71D, $D73C,
$26D3, $36F2, $0691, $16B0, $6657, $7676, $4615, $5634,
$D94C, $C96D, $F90E, $E92F, $99C8, $89E9, $B98A, $A9AB,
$5844, $4865, $7806, $6827, $18C0, $08E1, $3882, $28A3,
$CB7D, $DB5C, $EB3F, $FB1E, $8BF9, $9BD8, $ABBB, $BB9A,
$4A75, $5A54, $6A37, $7A16, $0AF1, $1AD0, $2AB3, $3A92,
$FD2E, $ED0F, $DD6C, $CD4D, $BDAA, $AD8B, $9DE8, $8DC9,
$7C26, $6C07, $5C64, $4C45, $3CA2, $2C83, $1CE0, $0CC1,
$EF1F, $FF3E, $CF5D, $DF7C, $AF9B, $BFBA, $8FD9, $9FF8,
$6E17, $7E36, $4E55, $5E74, $2E93, $3EB2, $0ED1, $1EF0
);

 

Takže například řetězec 6 bajtů 010203040506 bude mít CRC = 0xD71C.

Stuffing / Unstuffing

Tohle je zajímavý úkon, o kterém jsem dřív nikdy neslyšel. Jde o to, že v protokolu EZSP existují zakázané bajty, které se nesmí vyskytovat ve vysílaném řetězci. Jsou to 0x11, 0x13, 0x18, 0x1A, 0x7E a 0x7D. Pokud je potřeba takovýto bajt odvysílat, nahradí se dvojicí bajtů

  • 7D
  • zakázaný bajt XOR 0x20

Tím se vysílaný řetězec prodlouží o 1 bajt na každý zakázaný znak. Při příjmu se postupuje tak, že se prochází všechny přijaté bajty. Pokud má některý hodnotu 0x7D, vyloučí se z přijatých dat a s následujícím bajtem se provede operace XOR 0x20. Tím se data vrátí na původní hodnotu.

Pro data dlouhá 9 bajtů by algorytmus stuffing mohl vypadat takto.

// Stuffing
ZeroMemory(@BuffSend,sizeof(BuffSend));
myStf:=0;
for I := 0 to 8 do
begin
  case localbuff[I] of
  $11, $13, $18, $1A, $7E, $7D:
  begin
    BuffSend[myStf + I]:= $7D;
    inc(myStf);
    BuffSend[myStf + I]:= localbuff[I] XOR $20;
  end;
  else BuffSend[myStf + I]:= localbuff[I];
  end;
end;

Opačný algorytmus by mohl vypadat takto.

// Unstuffing
escaped:= False;
myStf:=0;
Count:=0;
ZeroMemory(@localbuff,sizeof(localbuff));
for I := 0 to aLen - 2 do
begin
  myByte:=aData[i];
  if myByte = $7D then escaped:= True
  else
  begin
    if escaped then
    begin
      localbuff[I-myStf]:=aData[I] XOR $20;
      Inc(myStf);
      escaped:=False;
    end
    else localbuff[I-myStf]:=aData[I];
    Inc(Count);
    end;
  end;
ZeroMemory(@aData,sizeof(aData));
for I := 1 to Count-1 do aData[I]:= localbuff[I];

Příklad celého procesu ukážu na jednom přijatém rámci, který obsahuje data 664F21A9062A7D338ED97E

  • Nejprve odstraníme Flag byte: 664F21A9062A7D338ED9
  • Pak zjistíme, jestli data obsahují bajt 0x7d: 664F21A9062A7D338ED9
  • Pokud ano, tento bajt smažeme a s následujícím provedeme operaci XOR 0x20: 664F21A9062A138ED9
  • Pak odstraníme úvodní Control Byte a závěrečný CRC: 4F21A9062A13 
  • Na tato data uplatníme proces Pseudorandomize: 0D0001520006

Tím získáme datový řetězec rámce. Všechny tři algorytmy je možné testovat v programu ManEZSP bez potřeby připojovat se k seriovému portu. Procedura Calculate XOR Random (Pseudorandomize) v programu nepočítá s prvním bajtem vstupního pole, kterým by měl být Control bajt. Na tom, jaký bajt přidáte na začátek, v tomto případě nezáleží.

 

 

Žádné komentáře

Komentáře jsou uzavřeny

Komentáře k tomuto obsahu jsou uzavřeny.