Chciałbym wam przedstawić bardzo ciekawy sposób komunikacji z naszymi mikrokontrolerami, a mianowicie sterowanie za pomocą własnych komunikatów. Na wstępie informuję iż przedstawioną bibliotekę pisałem na zajęciach z programowania ARM w C wg specyfikacji laboratorium. Służyła ona do sterowania serwomechanizmem poprzez interfejs RS232, jednak jej zastosowanie może być zgoła inne. Jako że piszę obecnie pracę mgr na temat bezprzewodowego systemu pomiarowego w oparciu o moduły RFM12B i mikrokontrolery ATmega88, pomyślałem że fajnie byłoby wysyłać komendy sterujące do modułu nadrzędnego przez UART. Przerobiłem bibliotekę na rzekomy AVR i chciałbym się tym z wami podzielić, aby każdy mógł z łatwością dogadywać się z mikrokontrolerem za pomocą własnych komend ASCII. Należy jeszcze tutaj wspomnieć o obsłudze interfejsu UART. Wszelkie dane wysłane przez UART są w postaci łańcuchów znakowych. Stworzone są dwa oddzielne bufory: dla nadajnika i odbiornika. Znaki odbierane są do momentu napotkania znaku terminatora, czyli Enter '\r', na końcu dodawany jest NULL. Całość oparta na przerwaniach. A więc do dzieła
Komunikaty(komendy):Komunikaty mają postać łańcuchów znakowych zakończonych znakiem NULL (ang. Null terminated string) i składają się z jednego bądź więcej tokenów. Tokeny to sekwencje znaków oddzielone jednym lub wieloma delimiterami. Funkcję delimitera pełnić będzie znak spacji ‘\s’ (20h, 32).
Przykład komunikatu pokazano poniżej:
„Ola ma jeża”.
Składa się on z trzech tokenów rozdzielonych dwoma delimiterami.
Dekodowanie:Dekodowanie komunikatu polega na policzeniu ilości wykrytych tokenów, oraz ich zdekodowaniu. Zdekodowanie tokenu to określenie jego typu i wartości a następnie zapamiętanie ich w odpowiedniej strukturze danych.
Typ tokenu: Będziemy rozróżniali trzy typy tokenów: KEYWORD, NUMBER i STRING.
Token zostanie rozpoznany jako KEYWORD jeżeli będzie należał do listy słów kluczowych. W takim przypadku wartość tokenu stanowić będzie typ wyliczeniowy enum.
Przykład: Załóżmy, że zadeklarowano następującą listę słów kluczowych:
Keyword (enum)----------odpowiadający jej łańcuch znakowy (char[])
LD-----------------------“load”
ST ----------------------“store”
RST----------------------“reset”
Wtedy tokeny “store” i „reset” zostaną zdekodowane w sposób następujący
token -------------- typ-----------wartość (enum)
„reset” -------------KEYWORD------RST
“store”--------------KEYWORD------ST
Token zostanie rozpoznany jako NUMBER jeżeli będzie spełniał format liczby zapisanej w kodzie heksadecymalnie. W takim przypadku wartość tokenu stanowić będzie wartość liczby.
Przykład:
token----------typ-----------wartość (unsigned int )
„0x10”--------- NUMBER------ 16 (decymalnie)
„0x0A”---------NUMBER ------10 (decymalnie)
Token zostanie rozpoznany jako STRING jeżeli nie zostanie rozpoznany jako KEYWORD ani jako NUMBER. Wartość tokenu typu STRING stanowi wskaźnik na ten token.
Przykład:
token---------typ----------wartość (char *)
„add”---------STRING------Wskaźnik na „add”
“subtract”----STRING------ Wskaźnik na “subtract”
Typ tokenu jest przechowywany w zmiennej wyliczeniowej zdefiniowanej jak poniżej:
Kod:
typedef enum TokenType {KEYWORD, NUMBER, STRING};
Wartość tokenu:Ponieważ typ tokenu może być różny (typ wyliczeniowy, liczba, łaocuch znakowy) dlatego jego wartość nie może być przechowywana w zmiennej jednego typu. Z tego względu do przechowywania wartości tokenu została wykorzystana unia zmiennych zdefiniowana jak poniżej:
Kod:
typedef enum KeywordCode { LD, ST, RST};
typedef union TokenValue
{
enum KeywordCode eKeyword; // jezeli KEYWORD
unsigned int uiNumber; // jezeli NUMBER
char * pcString; // jezeli STRING
};
Tablica tokenów:Typ i wartośc tokenu są przechowywane w jednej strukturze zdefiniowanej jak poniżej:
Kod:
typedef enum TokenType { KEYWORD, NUMBER, STRING};
typedef struct Token
{
enum TokenType eType; // KEYWORD, NUMBER, STRING
union TokenValue uValue; // enum, unsigned int, char*
}
Ponieważ w pojedynczym komunikacie mamy najczęściej do czynienia więcej niż z jednym tokenem wiec wynik dekodowania jest przechowywany w tablicy tokenów zdefiniowanej jak poniżej:
Kod:
#define MAX_TOKEN_NR 3 //maksymalna dopuszczalna ilość tokenów
struct Token asToken[MAX_TOKEN_NR]
Ilośc tokenów:Oprócz wypełnienia tablicy tokenów wynikiem dekodowania będzie również liczba odebranych tokenów zapamiętana w zmiennej:
Kod:
unsigned char ucTokenNr;
Dekodowanie komunikatu polega na wypełnieniu tablicy sToken na podstawie odebranego łaocucha znakowego.
Przykład: Wynik rozkodowania komunikatu: „load 0x20 immediately”.
token Type Value
“load” ----------KEYWORD------ LD
„0x20”----------NUMBER-------- 32 (decymalnie)
„immediately”--- STRING-------- wskaznik na „immediately”
Lista słów kluczowych:Do sprawdzenia czy token jest typu KEYWORD musi istnieć lista łańcuchów znakowych rozpoznawanych jako słowo kluczowe. Elementem listy jest struktura składająca się z dwóch elementów – typu słowa kluczowego oraz związanego z nim łańcucha znakowego.
Kod:
#define MAX_KEYWORD_STRING_LTH 10 // mksymalna dlugosc komendy
typedef enum KeywordCode { LD, ST, RST};
typedef struct Keyword
{
enum KeywordCode eCode;
char cString[MAX_KEYWORD_STRING_LTH + 1];
};
Deklaracja listy poprawnych słów kluczowych wygląda następująco, to właśnie w tym miejscu definiujemy własne słowa kluczowe(oraz w samym enum KeywordCode):
Kod:
#define MAX_KEYWORD_NR 3
struct Keyword asKeywordList[MAX_KEYWORD_NR]=
{
{RST,"reset"},
{LD, "load" },
{ST, "store"}
};
Zmienne globalne: Kod:
asKeywordList[] // uzywana przez bStringToCommand
asToken[] // wypelniana przez DecodeMsg
ucTokenNr // liczba tokenów w zdekodowanym komunikacie
Funkcje:Do dekodowania potrzebne są następujące funkcje:
ucFindTokensInString (char *String)Argumentem funkcji jest wskaźnik na początek dekodowanego komunikatu, funkcja zwraca ilość znalezionych tokenów w komunikacie. Zadaniem funkcji jest wypełnianie pola uValue tablicy asToken wskaźnikami na początki znalezionych tokenów w łańcuchu String.
Implementacja funkcji ma postać automatu, ponadto funkcja jest odporna na:
- pusty łańcuch, tj. Łańcuch składający się z samych delimiterów
- delimiter przed pierwszym tokenem
- więcej niż jeden delimiter miedzy dwoma tokenami
eSringToKeyword (char cStr[],enum eKeywordType *peKeyword)Zadaniem funkcji jest zamienić łańcuch znakowy na komendę na podstawie listy komend.
W przypadku powodzenia funkcja zwraca wartość OK.
W przypadku niepowodzenia funkcja zwraca wartości ERROR.
DecodeTokens(void)Zadaniem funkcji jest zdekodować wszystkie tokeny tj. dla każdego tokenu ustalić jego typ i wartość i wpisać je do tablicy asToken.
Funkcja korzysta ze wskaźników początków tokenów znajdujacych się w asToken[0..ucTokenNr].uValue.pcString (patrz funkcja ucFindTokensInString).
DecodeMsg(char *String)Na podstawie String-a i asCommandList funkcja wypełnia tablice sToken i ustawia zmienna ucTokenNr.
W tym celu:
- indeksuje początki tokenów
- zamienia wszystkie delmitery na nulle
- dekoduje poszczególne tokeny.
Ponadto do dekodowania wykorzystywane są jeszcze inne funkcje służące do pracy na łańcuchach:
void ReplaceCharactersInString(char cString[], char cOldChar, char cNewChar);Funkcja służy do podmiany znaku cOldChar na znak cNewChar w łańcuch cString.
enum CompResult eCompareString(char cStr1[], char cStr2[]);Funnkcja służy do porównywania dwóch stringów, jeżeli śa takie same funkcja zwraca EQUAL jeżeli nie są takie same zwraca NOTEQUAL
void AppendUIntToString (unsigned int uiValue, char cDestinationStr[]);Funkcja służy do dodania liczby uiValue do końca łańcucha cDestinationStr, liczba zamieniana jest na string w postaci hexadecymalnej.
Result eHexStringToUInt(char cStr[], unsigned int *puiValue);Funkcja zamienia string w postaci liczby hexadecymalnej cStr na jej wartość i umieszcza jak pod wskaźniekiem puiValue.
Chwili wyjaśnienia wymaga program główny w pętli while. Otóż w pętli sprawdzany jest status odbiornika UART, jeśli pojawił się jakiś komunikat status odbiornika zmienia się na READY. Następnie komunikat jest dekodowany i w zależności od wysłanej komendy ustawiane są flagi, jest to tzw. „producent”. Jeżeli „producent” ustawi którąś z flag, oraz nadajnik jest w stanie bezczynności (FREE) to UART zajmie się „konsumpcją” „wyprodukowanej” flagi.
Po wgraniu programu do AVR i wpisaniu w polu terminala np. komunikatu „calc 0x0002„
Mikrokontroler natychmiast odeśle nam odpowiedź w postaci wartości odebranej pomnożonej przez 2 , czyli „calc 0x0004„.
W taki prosty, nieblokujący sposób można rozkazywać naszemu prockowi.
Załączam kompletną bibliotekę pod ATmege88,