Treść zadania
Napisać program nadajnika i odbiornika, który będzie wysyłał plik za pomocą protokołu znakowego i za pomocą portu RS232.
Sposób rozwiązania
Ze względu na to, że dostępne były trzy komputery, podzieliliśmy rozwiązanie zadania na trzy części. Pierwszą z nich było przygotowanie fizycznej transmisji, do której zaliczyliśmy napisanie procedur obsługi portu RS232, kodowanie i dekodowanie znaków. Na drugim komputerze pisany był program nadajnik, natomiast na trzecim program odbiornik.
Uznaliśmy, że program, który był do napisania musiał składać się kilku faz: nawiązanie komunikacji, przesłanie nagłówka oraz przesłanie danych. Uzgodniliśmy, za przesyłane bloki będą miały po 128 znaków (7-bitowych), oraz że nagłówek zmieści się w pierwszym bloku i będzie zawierał nazwę kopiowanego pliku, a danymi będzie zawartość pliku. Kolejnym założeniem (program nadajnik) było to, że dane będą odczytywane z pliku i przesyłane po zakodowaniu do bufora, a po jego zapełnieniu nastąpi automatyczne „wysłanie” paczki danych wraz ze wszystkimi znakami sterującymi. Program odbiornik, wstępnie ma oczekiwać (odczytywać) port RS232 i po otrzymaniu informacji o rozpoczęciu transmisji odbierać znaki i zapamiętywać je w buforze. Tam z kolei będą one analizowane i w przypadku otrzymania znaków sterujących wykonywane odpowiednie akcje:
znak SOH - w buforze powinien znajdować się nagłówek do znaku STX,
znak ETB (lub ETX) - nastąpił koniec bloku, należy zdekodować zawartość bufora i po
sprawdzeniu z sumą kontrolną zapisać do pliku,
znak ETX - oznacza, że należy zamknąć plik, zakończyć transmisję i zakończyć działanie
programu.
Poniżej przedstawiony został kod źródłowy nadajnika:
program Nadajnik;uses Crt, Dos;const nr_Portu = 0; {stała oznaczająca port 1}
SOH=$01; {stałe znaków sterujących protokółu} STX=$02; ETX=$03; EOT=$04; ENQ=$05; ACK=$06; DLE=$10; NAK=$15; SYN=$16;
ETB=$17;
BlockLen = 126; {długość bloku}
var
Bufor: array [0..127] of Byte;
BufLen: integer;
LastH: Byte;
procedure InitData; {procedura ustawia zmienne pomocnicze}
begin
BufLen:=-1;
end;
procedure InitRS; {procedura inicjalizacji portu szeregowego}
Var
r : Registers;
Begin
r.ah:=0;
r.dx:=nr_portu;
r.al:=3+128+64+32; {ustawienia konfiguracyjne portu}
Intr($14,r);
End;
procedure SendB(zn : Byte); {procedura wysyłająca znak za pomocą portu szeregowego}
Var
r : Registers;
Begin
Delay(10);
repeat
r.ah:=3;
r.dx:=nr_portu;
Intr($14,r); {sprawdzanie statusu portu}
Until ((r.ah And 32) <> 0); {oczekiwanie na gotowość}
r.ah:=1;
r.dx:=nr_portu;
r.al:=zn;
Intr($14,r); {wysłanie znaku}
End;
function GetB(Var zn : Byte) : Boolean; {procedura odebrania znaku z portu szeregowego}
Var
r : Registers;
Begin
r.ah:=2;
r.dx:=nr_portu;
Intr($14,r);
zn:=r.al;
GetB:=(r.ah And 128 = 0);
End;
procedure Koduj(s : Byte;var res1,res2:Byte); {procedura kodująca znak s na dwa znaki 7 bitowe różne od kodów sterujących}
begin
res1:=(((s And $F0) SHR 4) +32);
res2:=((s And $0F) +32);
end;
function OdbierzPotw:Boolean; {sprawdza czy odczytano potwierdzenie transmisji}
var
ret: Boolean;
B: Byte;
begin
ret:=GetB(B);{odczyt znaku z portu}
ret:=(B=ACK);{czy jest to potwierdzenie transmisji}
OdbierzPotw:=ret;
end;
procedure Zakoncz(Err: String); {Wystąpił błąd transmisji}
begin
writeln(Err);
halt;
end;
procedure Rozlacz(Err: String); {rozłącznie połączenia}
begin
SendB(DLE);
SendB(EOT);
Zakoncz(Err);
end;
procedure SendBlock(EndOfData: Boolean); {wysłanie bloku danych}
var
B1, B2, LRC: Byte;
i, ile: integer;
OK: Boolean;
begin
ile:=0; {Ilość prób transmisji}
repeat
LRC:=0;
for i:=0 to BufLen do begin
LRC:=LRC xor Bufor[i]; {obliczanie sumy kontrolnej}
SendB(Bufor[i]); {wysyłanie znaku}
end;
{Koniec Danych}
if EndOfData then begin {gdy wysyłany jest ostatni pakiet danych}
LRC:=LRC xor ETX;
SendB(ETX); {wysłanie znaku końca danych}
end else begin {pozostały jeszcze dane do wysłania}
LRC:=LRC xor ETB;
SendB(ETB); {wysłanie znaku końca bloku}
end;
Koduj(LRC, B1, B2); {zakodowanie sumy kontrolnej}
SendB(B1); {wysłanie sumy kontrolnej na dwoch znakach}
SendB(B2);
ile:=ile+1;
OK := OdbierzPotw; {odebranie potwierdzenia przyjęcia danych}
until (OK) or (ile=3); {maksymalnie 3 próby transmisja}
if i=3 then Rozlacz('Blad podczas transmisji'); {wystąpł 3-krotnie błąd transmisji}
BufLen:=0;
Bufor[0]:=LastH;
end;
procedure PutB(B: Byte); {procedura wpisująca znaki do bufora}
begin if B=SOH then LastH:=SOH else if B=STX then LastH:=STX; BufLen:=BufLen+1; {zwiększenie ilości znaków w buforze}
Bufor[BufLen]:=B; {wprowadzenie kolejnego znaku do bufora}
if (BufLen=BlockLen-1) and (Bufor[0]=SOH) then SendBlock(false) else
if BufLen=BlockLen then SendBlock(false);
{wysłanie bufora gdy pełny lub gdy wysyłamy nagłówek i bufor już prawie pełny}
end;
procedure PackB(B: Byte); {wprowadzenie znaku do bufora}
var
b1, b2: Byte;
begin
Koduj(B, b1, b2); {kodowanie znaku na dwa 7 bitowe}
PutB(b1); {wprowadzenie do bufora}
PutB(b2); {wprowadzenie do bufora}
end;
function WyslijPoczatek:Boolean; {funkcja nawiązująca transmisje}
begin
Writeln(`Proba nawiązania transmisji');
SendB(EOT); {nawiązanie transmisji}
SendB(64); {adres odbiorcy}
SendB(35);
SendB(ENQ);
WyslijPoczatek:=OdbierzPotw; {sprawdzenie poprawności}
end;
procedure Polacz; {procedura nawiązująca połączenie}
var
naw: Boolean;
i: integer;
begin
i:=0;
repeat
naw:=WyslijPoczatek; {nawiązanie transmisji true-> zakończona sukcesem}
i:=i+1;
until (i=3) or (naw); {3 krotna próba nawiązania połączenia}
if naw=false then Zakoncz('Polaczenie nie zostalo nawiazane')
else Writeln(`Nawiązano połączenie');
end;
procedure WyslijPlik(path, name: string); {procedura wysyłająca plik}
var
f: File;
i, j: integer;
b: Byte;
begin
PutB(SOH); {rozpoczęcie transmisji „danych”}
for i:=1 to Length(name) do {wysłanie nazwy pliku przesyłanego}
PackB(ord(name[i]));
PutB(STX); {następny znak to „właściwe dane”}
assign(f, path+name); {otwarcie pliku}
reset(f, 1);
while not(eof(f)) do begin
BlockRead(f, b, 1, i); {odczytanie bloku danych}
PackB(b); {wysłanie do bufora 1 bajtu danych}
end;
close(f); {zamknięcie pliku}
SendBlock(true); {wysłanie danych w buforze}
end;
procedure przeslanie; {procedura realizująca transmisję danych}
begin
InitData; {inicjalizacja danych}
Polacz; {inicjalizacja połączenia}
WyslijPlik('','nazwa.txt'); {wysłanie pliku}
SendB(EOT); {wysłanie znaku końca transmisji}
Rozlacz('Plik przeslany'); {rozłączenie transmisji}
end;
begin
clrscr;
InitRS; {konfiguracja RS232}
przeslanie; {transmisja}
end.
Poniżej został przedstawiony kod źródłowy odbiornika:
Program Odbiornik;uses Crt, Dos;const nr_Portu = 1; {stała określająca numer portu RS232}
SOH=$01; {kody sterujące transmisją znakową}
STX=$02;
ETX=$03;
EOT=$04;
ENQ=$05;
ACK=$06;
DLE=$10;
NAK=$15;
SYN=$16;
ETB=$17;
BlockLen = 126; {długość danych}
var
Bufor: array [0..255] of Byte;
BufLen: integer;
LastH: Byte;
procedure InitData; {inicjalizacja daty}
begin
BufLen:=-1;
end;
procedure InitRS; {procedura inicjalizująca port RS232}
Var
r : Registers;
Begin
r.ah:=0;
r.dx:=nr_portu;
r.al:=3+128+64+32; {ustawienia transmisji}
Intr($14,r);
End;
procedure SendB(zn : Byte); {procedura wysyłająca znak na port RS232}
Var
r : Registers;
Begin
delay(10);
r.ah:=1;
r.dx:=nr_portu;
r.al:=zn; {wysłanie znaku}
Intr($14,r);
End;
procedure GetB(Var zn : Byte); {procedura odbierająca znak z portu RS232}
Var
r : Registers;
Begin
r.ah:=2;
r.dx:=nr_portu;
Intr($14,r);
zn:=r.al;
End;
Procedure Dekoduj(Var zn : byte; res1,res2 : byte); {Procedura zwracająca znak zdekodowany z dwóch znaków odebranych}
Begin
zn:=((((res1) -32) SHL 4) + ((res2) -32));
End;
procedure Odbieraj; {procedura odbierająca przesyłany plik i zapisująca go na dysk}
var
plik: file;
napis: string;
i, j: integer;
B1, B2, LRC, N_LRC, B, zn: byte;
begin
GetB(B); {odebranie znaku z portu RS232}
if (B<>ETX) and (B<>ETB) then begin {odebrano znak różny od końca transmisji lub bloku}
BufLen:=BufLen+1;
Bufor[BufLen]:=B; {zapamiętanie znaku w buforu}
end else begin {wystąpił znak końca transmisji lub bloku}
{************** obliczanie sumy kontrolnej **************}
LRC:=0; {zerowanie sumy kontrolnej}
for i:=0 to BufLen do LRC:=LRC xor Bufor[i]; {obliczenie sumy kontrolnej}
LRC:=LRC xor B; {obliczenie sumy kontrolnej dla odczytanego znaku}
GetB(B1); {odebranie dwóch znaków sumy kontrolnej}
GetB(B2);
Dekoduj(N_LRC, B1, B2); {dekodowanie sumy kontrolnej na znak 8 bitowy}
if LRC<>N_LRC then writeln('ZLE LRC'); {wystąpił błąd sumy kontrolnej}
if (N_LRC=LRC) then begin {prawidłowa suma kontrolna} SendB(ACK); {transmisja OK., wysłanie potwierdzenia poprawnej transmisji} Writeln(`Odebrano poprawnie przesyłane dane');
i:=1;
{ ***************** odczyt nahgłówka ****************************}
if Bufor[0]=SOH then begin {odczytanie nagłówka w bloku} napis:=''; while Bufor[i]<>STX do begin {nagłówek zakończony jest znakiem STX} Dekoduj(zn, Bufor[i], Bufor[i+1]); {dekodowanie znaku}
napis:=napis+chr(zn); {zamiana na nazwę pliku przesyłanego}
i:=i+2;
end; {endwhile - dopóki nie natrafi na znak STX - końca nagłówka }
assign(plik, napis); {otwarcie pliku}
rewrite(plik, 1);
i:=i+1;
end; { endif - odczytanie nagłówka w bloku }
{******************* odczyt danych ******************************} while (i<BufLen) do begin {jeżeli nie osiągnięto końca bufora} Dekoduj(zn, Bufor[i], Bufor[i+1]); {dekodowanie znaku z bufora}
i:=i+2;
BlockWrite(plik, zn, 1, j); {zapis zdekodowanego znaku do pliku}
write(chr(zn)); {zapis zdekodowanego znaku na ekran}
end; {endwhile osiągnięto koniec bufora}
if B=ETX then begin {otrzymano znak końca transmisji}
Writeln(`Zakończono prawidłowo transmisję danych');
Close(plik); {zamknięcie pliku}
halt;
end; {endif otrzymano znak końca transmisji}
end else {endif - poprawna suma kontrolna}
SendB(NAK); {wysłanie żądania powtórnego wysłania danych nieprawidłowe CRC} BufLen:=-1; end; {endif - wystąpił znak końca transmisji lub bloku}}
end;
var B:Byte;begin InitRS; {inicjacja portu szeregowego} repeat GetB(B); {oczekiwanie na nawiązanie transmisji} until B=ENQ;
Writeln(`Nawiązano transmisje...'); SendB(ACK); {wysłanie potwierdzenia nawiązania transmisji}
BufLen:=-1;
repeat
Odbieraj; {odbieranie danych}
until keypressed;
end.
Wnioski
Pierwszą rzeczą na jaką zwróciliśmy uwagę, było kodowanie znaków. Należy zapewnić takie kodowanie aby nie były używane znaki sterujące transmisją do kodowania znaków przesyłanych. Tak więc algorytm kodujący nie tylko musi być jednoznaczny (aby było możliwe dekodowanie), ale również nie może używać znaków specjalnych (sterujących).
Kolejną sprawą był transfer danych przez port RS232. Ze względu, iż dane byłe szybko obliczane należało (szczególnie przy transmisji) sprawdzać gotowość portu do wysłania kolejnego znaku. Było to głównym (i chyba jedynym) powodem dla jakiego nie udało nam się przy pierwszym uruchomieniu programu poprawnie odebrać dane. Kolejną ważną rzeczą jest ścisłe przestrzeganie protokołu, szczególnie zaś wysyłania potwierdzeń transmisji, kontroli ilości prób. Wypisywanie komunikatów o błędach podczas nawiązywania i samej transmisji jest znacznym ułatwieniem w usuwaniu problemów. Uruchomienie odbiornika, zaczęliśmy od programu wypisującego znaki, które były „wysyłane” i szukania przyczyn błędów. Umożliwiło to wykrycie błędów zarówno w nadajniku jak i odbiorniku
W rezultacie uzyskaliśmy podczas laboratorium poprawny i działający program realizujący przesył zawartości pliku między komputerami.