www.eprace.edu.pl » automatyka-basenowa » Opis oprogramowania mikrokontrolera » oprogramowanie komunikacyjne

oprogramowanie komunikacyjne

Procedura odpowiedzialna za komunikację z komputerem była dla mnie najbardziej pracochłonna. Zapoznałem się z kilkoma protokołami transmisji stosowanymi w sterownikach przemysłowych, jednak po ich analizie doszedłem do wniosku, że są zbyt rozbudowane i „pamięciożerne” jak na moje potrzeby. Postanowiłem samodzielnie opracować protokół transmisji, ustalić zasady adresacji urządzeń i wymiany danych. Początkowo wzorowałem się na protokole stworzonym przez autora „Klocków 485” („Elektronika Praktyczna”, cykl artykułów z 2003 roku) jednak szybko odkryłem w nim poważną wadę. Chciałem aby użytkownik mógł obsługiwać moje urządzenia zarówno z terminala jak i z dedykowanego programu. Autor „Klocków” do adresowania urządzeń użył kodów ASCII poszczególnych klawiszy. Doszedłem do wniosku, że w celu ułatwienia ewentualnego programowania urządzenia za pomocą terminala, użytkownik musi mieć możliwość wpisywania jego adresu jako liczby z zakresu 1-32. Pozostała część protokołu pozostała bez większych zmian.

Zaznaczyć należy, że nie posiadałem kodów źródłowych a jedynie opis ramki danych. Opracowany przeze mnie protokół powstał przy użyciu inżynierii wstecznej. Najpierw powstał graf przejść automatu odbiorczego, a później został on zapisany w języku C. Ramka danych wysyłana do sterownika wygląda następująco:


Nr bajtu Znaczenie:
1 Znak ESC (kod klawisza escape)
2, 3 Numer urządzenia, np. 01
4 Znak „r” (odczyt) lub „w” (zapis)
5 Kod danej do odczytu / zapisu
6, 7, 8 Dana do zapisu (tylko w przypadku zapisu), np. 250
6 (odczyt) lub 9 (zapis) Znak ENTER

Tabela 5 - opis ramki danych

W zależności czy dana jest odczytywana czy zapisywana, do urządzenia wysyłane jest 6 lub 9 bajtów. Nie jest to optymalnie stworzona procedura komunikacyjna, ale daje ona możliwość intuicyjnego programowania urządzenia przez terminal. W tabeli nr 6 przed-stawione są kody danych wykorzystywane w komunikacji z modułem.

Opracowany algorytm w obecnej postaci jest zbliżony do protokołu MODBUS ASCII, jednakże pozbawiony jest bajtów kontroli parzystości. Przy obsłudze przez terminal, nie byłyby one w ogóle przydatne. Przeprowadzone testy wykazały, że przy obsłudze przez terminal kwestię przekłamania danych można pominąć. Ma ona znaczenie dopiero przy obsłudze sterownika za pomocą programu „SPA – Soft”. Wtedy bowiem poszczególne bajty ramki nadawane są jeden po drugim z dużą prędkością (co nie ma miejsca przy obsłudze z terminala). Aby zapobiec błędom w transmisji wprowadzono po stronie oprogramowania „SPA – Soft” prosty mechanizm ich wykrywania. Polega on na odczytaniu zapisanej danej i porównaniu, czy dana ta nie uległa nieoczekiwanej zmianie w stosunku do tej która została wysłana do urządzenia. Jeżeli 10 razy z rzędu dana taka nie da się poprawnie zapisać, program przerywa komunikację z modułem „SPA –1”, informuje o tym użytkownika, a następnie dokonuje rozpoznania podłączonych do sieci urządzeń. Z testów jakie do tej pory przeprowadziłem, wynika iż taka procedura jest w stanie skutecznie zabezpieczyć poprawność komunikacji.


Znak: Znaczenie:
m minuty
s sekundy
c czułość
z szerokość
d długość
p poziom
a alarm
t temperatura aktualna
n temperatura min
x temperatura max
Spacja żądanie podania trybu pracy
1 komenda załączenia wyjścia
0 komenda wyłączenia wyjścia
r komenda wykonania restartu

Tabela 6 - komendy sterujące

Obsługa nadchodzących z komputera danych była kolejnym wyzwaniem. Najprostszym rozwiązaniem jest zastosowanie bufora odbiorczego do którego zapisywane są przychodzące dane, a następnie odczytanie jego zawartości i jej interpretacja. Jednak znaczna liczba użytych w programie przerwań, trybów pracy i opóźnień czasowych wymusiły inne podejście do problemu. Zadałem sobie pytanie: co się stanie, jeżeli do bufora trafi zbyt krótka lub zbyt długa ramka danych? Jak wtedy zareaguje program? Zabieg z buforem odbiorczym, jeżeli dane trafiające do niego nie są zabezpieczone sumą kontrolną, może być bardzo ryzykowny i nieść nieprzewidziane skutki. Poza tym byłby bardziej podatny na błędy. Postanowiłem do odbioru i interpretacji danych wykorzystać automat sekwencyjny.

Automat przetwarzający dane trafiające do mikrokontrolera dokonuje ich analizy „w locie”. Oznacza to tyle, że bajt jaki został aktualnie odebrany powoduje przejście automatu do kolejnego stanu. Jeżeli bajt ten nie był poprawną spodziewaną przez nas daną, automat powraca do stanu początkowego i pozostaje w nim tak długo, aż nie wykryje pojawienia się kodu klawisza ESCAPE (bajt początku ramki danych). Zabieg ten, w połączeniu ze wspomnianym już zabezpieczeniem transmisji po stronie programu ”SPA – Soft” powoduje, że użytkownik ma pełną wiedzę o wszelkich zaistniałych problemach z komunikacją. Rysunek nr 55 przedstawia graf przejść automatu. Graf narysowany jest w wersji skróconej, ilustrującej jedynie zasadę działania automatu. Pełny graf zawierałby znacznie więcej stanów, co znacząco zaciemniłoby rysunek.

Rysunek 55 - uproszczony graf przejść automatu odbiorczego

Na listingu poniżej zaprezentowana jest pełna procedura obsługi przerwania od USART, wraz z dwiema pomocniczymi funkcjami. Całość poniższego kodu stanowi gotowy moduł obsługi transmisji.


SIGNAL(SIG_UART_TRANS)
{
rs485_receive;
}
void rxstring(char adress)
{
if ((znak!=13)&&(znak>=48)&&(znak<=57))
{
rxtmp[rx_tmp]=znak;
rx_tmp++;
}
else if (znak==13)
{
if(adress ==0x68)
{
eeprom_write_byte (adress, (atoi(rxtmp)+4));
}
else
{
eeprom_write_byte (adress, atoi(rxtmp));
rs=0;
rx_tmp=0;
}
}
else
{
rs=0;
rx_tmp=0;
}
}
void zap_temp(char adra, char adrb)
{if ((znak!=13)&&(znak>=48)&&(znak<=57))
{
rxtmp[rx_tmp]=znak;
rx_tmp++;
}
else if (znak==13)
{
eeprom_write_byte (adra, atoi(rxtmp)/255);
eeprom_write_byte (adrb, atoi(rxtmp)%255);
rs=0;
rx_tmp=0;
}
else
{
rs=0;
rx_tmp=0;
}
}
SIGNAL(SIG_UART_RECV)
{
znak = UDR;
switch (rs)
{
//poczatek transmisji - czy podano ESC??
case 0:
if(znak == 27)
{ SET_GREEN;
rs=1;
UDR=0;
rx_tmp=0;
rx_tmpb=0;
}
else
{rs=0;CLR_GREEN;}
break;
//jeżeli był ESC to czy adres poprawny??
case 1:
if ((znak>=48) && (znak<=57))
{
rxtmpb[rx_tmpb]=znak;
rx_tmpb++;
if(rx_tmpb==2)
{
if(atoi(rxtmpb) == adres)
{
rs=2;
}
else
{
rs=0;
}
}
}
else
{
rs=0;
rx_tmpb=0;
rx_tmp=0;
}
break;
//jezeli adres ok to odczyt czy zapis pamieci??
case 2:
if(znak == 'r') //read
{
rs=3;
}
else if(znak == 'w') //write
{
rs=4;
}
else
{
rs=0; //jezeli komenda zla to powroc do oczekiwania na ESC
}
break;
//określenie co ma byc odczytane
case 3: //odczyt
if(znak == 's')
{
txtmp = eeprom_read_byte(0x65);
rs=30;
}
else if(znak == 'm')
{
txtmp = eeprom_read_byte(0x66);
rs=30;
}
else if(znak == 'c')
{
txtmp=czulosc-4;
rs=30;
}
else if(znak == 'z')
{
txtmp=szer;
rs=30;
}
else if(znak == 'd')
{
txtmp=dl;
rs=30;
}
else if(znak == 'p')
{
txtmp=poziom;
rs=30;
}
else if(znak == 'a')
{
txtmp=alarm;
rs=30;
}
else if(znak == 't')
{
txtmp = temperatura;
rs=30;
}
else if(znak == 'n')
{
while(!EERIE){;}
txtmp = tmin;
rs=30;
}
else if(znak == 'x')
{
txtmp = tmax;
rs=30;
}
else if(znak == 32)
{
txtmp = eeprom_read_byte(0x76);
rs=30;
}
else
{
rs=0; //jezeli komenda zla to powroc do oczekiwania na ESC
}
break;
case 4: //zapis
if(znak == 's')
{
rs = 20;
}
else if(znak == 'm')
{
rs=21;
}
else if(znak == 'c')
{
rs=22;
}
else if(znak == 'z')
{
rs=23;
}
else if(znak == 'd')
{
rs=24;
}
else if(znak == 'p')
{
rs=25;
}
else if(znak == 'a')
{
rs=26;
}
else if(znak == 't')
{
rs=27;
}
else if(znak =='n')
{
rs=29;
}
else if (znak== 'x')
{
rs=31;
}
else if(znak == 'r')
{rs=28; }
else if(znak== '1')
{
rs=18;
}
else if(znak== '0')
{rs=19;}
else
{rs=0;}
break;
case 18:
if (znak==13)
{
if (ID_SPA==1)
{
TIMSK=0b00000100;
ustaw=3;
}
else if (ID_SPA==2||ID_SPA==3)
{zewRS=1;}
}
rs=0;
break;
case 19:
if (znak==13)
{
if (ID_SPA==1)
{
TIMSK=0b00000000;
sekunda = eeprom_read_byte(0x65);
minuta = eeprom_read_byte(0x66);
ustaw=0;
}
else if (ID_SPA==2||ID_SPA==3)
{zewRS=0;}
}
rs=0;
break;
case 20:
rxstring(0x65);
break;
case 21:
rxstring(0x66);
break;
case 22:
rxstring(0x68);
break;
case 23:
rxstring(0x70);
break;
case 24:
rxstring(0x71);
break;
case 25:
rxstring(0x72);
break;
case 26:
rxstring(0x73);
break;
case 27:
rxstring(0x74);
break;
case 29:
zap_temp(0x77,0x78);
break;
case 31:
zap_temp(0x79,0x80);
break;
case 28:
if (znak=13)
{
SET_GREEN;
LCDcls();
LCDxy(1,0);
LCDcls();
LCDtext(" RESTART");
waitsek(1);
for (char yy=0;yy<16;yy++)
{
LCDxy(yy,1);
LCDtext("*");
waitms(200);
}
waitsek(1);
wdt_enable(WDTO_15MS);
waitms(200);
}
else
{rs=0;}
break;
//zazadano wyslanie zawartosci pamieci
//jezeli ENTER to wyslij zawartosc wskazanej komorki
//jezeli inny znak to wroc do oczekiwania na ESC
case 30:
if(znak==13)
{
rs485_send;
rs_text(itoa(txtmp, resulta, 10));
rs=0;
}
else
{rs=0;}
break;
}
znak=0;
CLR_GREEN;
return;
}

Powyższy kod realizuje wszystkie zadania zapisu / odczytu danych oraz zdalnego sterowania urządzeniem. Automat sekwencyjny daje się w łatwy sposób rozbudowywać o nowe funkcjonalności. Kod ten był już z powodzeniem używany w innych urządzeniach.



komentarze

Copyright © 2008-2010 EPrace oraz autorzy prac.