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:
- Připraví se požadovaná data rámce bez Control Byte, CRC a Flagu
- Provede se pseudorandomizace dat
- Vypočítá se CRC a přidá se na konec dat
- Provede se proces stuffing
- Na začátek dat se přidá Control Byte, na konec dat se připojí znak 0x7E
- Vzniklý binární řetězec se odešle seriovým portem.
Příjem rámce se provede přesně opačným postupem:
- Seriovým portem se načítají binární data tak dlouho, dokud nepříjde znak Flag = 0x7E
- Provede se proces unstuffing
- Zkontroluje se CRC
- 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.
// Unstuffingescaped:= 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ží.