Z końcem 2010 roku na polskim rynku pokazała się wydana
przez firmę Atnel książka autorstwa Mirosława Kardasia pt:
\"Mikrokontrolery AVR - Język C - Podstawy Programowania\".
Postanowiliśmy przybliżyć czytelnikom tę interesującą pozycję
poprzez opublikowanie jej fragmentu poświęconego tworzeniu
serwera http na mikrokontrolerach AVR. Przykłady prezentowane
w książce mogą być z łatwością wykonane z użyciem zestawów
deweloperskich firmy Atnel.
?));
plen=fill_tcp_data_p(buf,plen,PSTR(?
Witaj !\n?));
plen=fill_tcp_data_p(buf,plen,PSTR(?
twój serwer www działa znakomicie\n\n?));
plen=fill_tcp_data_p(buf,plen,PSTR(?
?));
plen=fill_tcp_data_p(buf,plen,PSTR(?
www.atnel.pl?));
plen=fill_tcp_data_p(buf,plen,PSTR(?\n?));
return(plen);
}
// główna funkcja programu
int main(void){
uint16_t dat_p;
// CLKPR=(1<
200 OK?));
goto SENDTCP;
}
// just one web page in the ?root directory? of the web server
if (strncmp(?/ ?,(char *)&(buf[dat_p+4]),2)==0){
dat_p=print_webpage(buf);
goto SENDTCP;
}else{
dat_p=fill_tcp_data_p(buf,0,PSTR(?HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\
n401 Unauthorized
?));
goto SENDTCP;
}
SENDTCP:
www_server_reply(buf,dat_p); // wysłanie strony http
// tcp koniec obsługi portu 80
}
return (0);
}
Na listingu 3. Przedstawiono cały kod
programu serwera HTTP uruchomiony na
mikrokontrolerze ATmega32. Na początku
typowe jest dołączanie plików nagłówko-
wych zarówno tych systemowych, jak i po-
trzebnych bibliotek. Warto zauważyć, że
oznaczyłem jako komentarz linię dołączającą
plik timeout.h, który wcześniej usunęliśmy.
MAC, IP i port
Następnie mamy do czynienia z bardzo
istotną rzeczą, jaką jest przydzielenie odpo-
wiedniego adresu MAC dla naszego urzą-
dzenia, oraz adresu IP. Trzeba pamiętać, że
oba muszą być unikalne, tzn. że nie mogą
przybierać takiej samej wartości jak w in-
nym urządzeniu pracującym w twojej sieci
lokalnej. Musisz, zatem sprawdzić w syste-
mie, (np. Windows), jaki adres IP ma twój
komputer. Można tego dokonać za pomocą
polecenia ?ipconfig /all? wydanego z linii
poleceń w konsoli typu DOS. Zakładam
oczywiście, że czytelnik ma dostęp do lokal-
nej sieci LAN, w której pracuje jakiś router,
a ten wyznacza dostępną klasę adresów IP
85ELEKTRONIKA PRAKTYCZNA 3/2011
Mikrokontrolery AVR
Co sugeruje, iż można bez cienia wątpli-
wości wprowadzić własny adres np.:
static uint8_t mymac[6] =
{?A?,?L?,?A?,0x10,0x00,0x24};
Niestety w ten sposób przypadkowo
utworzony adres MAC może powodować
przedziwne zachowania się przykładowych
programów w sieci lokalnej lub rozległej.
Przedziwne z powodu braku znajomości pra-
widłowych sposobów tworzenia MAC adre-
sów. Często właśnie przypadkowa zmiana
MAC adresu w przykładowych programach
z tej strony powoduje, że nie działają one
zgodnie z oczekiwaniem i przez to wielu lu-
dzi się zniechęca do dalszych prób ze stosem
TCP. Początkujący nie zwraca uwagi na to, że
dokonał zmiany adresu MAC, tylko na to, że
kod jest identyczny jak w przykładach, a po-
mimo to informacje raz się pojawiają, a in-
nym czasem nie docierają do albo z kompu-
tera. Zasada tworzenia adresów MAC została
zilustrowana na rysunku 2.
Za niepowodzenia początkujących
w ustalaniu adresu MAC odpowiadają dwa
pierwsze najmłodsze bity z 1. oktetu. Być
może autorzy tuxgraphics.org wybrali trzy
pierwsze litery świadomie, szczególnie
pierwszą literę ?T?. Kod litery T wynosi
0x54. (binarnie: 0b0101 0100), zatem dwa
najmłodsze bity 1. oktetu ustawione są na
0 i taki adres MAC praktycznie w każdych
Dokładnie tak samo wprowadzamy 6
cyfr, które są bezpośrednio kodami ASCII
podanych w apostrofach liter. Uwagę może
wzbudzić pierwszy bajt o wartości 0. Niestety
autorzy z tuxgraphics.org popełnili błąd tłu-
macząc w kodzie sposób, jak można uzyskać
własny adres MAC. W ich przypadku jest to:
static uint8_t mymac[6] =
{?T?,?U?,?X?,0x10,0x00,0x24};
W rzeczywistości trzy pierwsze litery
TUX zostały zaprezentowane w postaci szes-
nastkowej w ich przykładach:
static uint8_t mymac[6] = {0x54,0x55,0x58
,0x10,0x00,0x24};
Jednocześnie tłumaczą, jak uzyskać wła-
sny MAC adres, cytuję:
?// how did I get the mac addr? Translate the
first 3 numbers into ascii is: TUX?
w sieci. Np. jeśli komputer będzie miał ad-
res 192.168.0.100, niemal na pewno można
urządzeniu przypisać adres IP z zakresu
od: 192.168.0.1 do 192.168.0.254 pomijając
adres IP, który jest przypisany komputero-
wi PC oraz adres tzw. bramy domyślnej.
Adres tej bramy także zostanie wyświetlo-
ny w konsoli, przy czym zwykle w tej kla-
sie domyślnie wynosi on: 192.168.0.1 ale
w teorii może być on różny, w zależności
od routera. Warto sprawdzić, w jakiej części
klasy router przydziela adresy automatycz-
nie, a którą jej część pozostawia dla adresów
przydzielanych statycznie (przez użytkow-
nika). Tu wszystko zależy od konfiguracji
routera. Na komputerze autora router za po-
mocą DHCP przydziela automatycznie adre-
sy w zakresie 192.168.0.2 do 192.168.0.99,
dlatego wybrałem dowolny adres z zakresu:
192.168.0.100 do 192.168.0.254, konkret-
nie: 192.168.0.110. Nieco inaczej i prościej
jest z dobraniem własnego adresu MAC
dla naszego urządzenia. W zasadzie można
wpisać 6 dowolnych przypadkowych liczb
do tablicy mymac[6]. Można także zasto-
sować nieco bardziej przyjazny zapis, np.
w takiej postaci:
// definicja własnego MAC adresu
urządzenia
static uint8_t mymac[6] =
{0,?M?,?I?,?R?,?E?,?K?};
Rys. 2. Zasada tworzenia adresów MAC.
R E K L A M A
86 ELEKTRONIKA PRAKTYCZNA 3/2011
NOTATNIK KONSTRUKTORA
warunkach nie będzie powodował proble-
mów z przykładami prezentowanymi przez
autorów. Nie będę dalej szczegółowo opisy-
wał, co oznacza ?unicast?, ?multicast? czy
?locally administrated?, ponieważ temat
nieco odbiega od naszego głównego wąt-
ku. Podpowiadam tylko, że warto, aby do
takich podstawowych prób, testów obydwa
bity miały wartość równą 0. Uniemożliwia
to korzystanie ze wszystkich liter, gdyż
w części z nich, ich kody ASCII będą miały
ustawione obydwa te bity lub któryś z nich
na wartość 1. Dlatego podany wyżej przy-
kład z trzema pierwszymi literami ALA nie
będzie działał w każdym przypadku zgod-
nie z oczekiwaniami. Reasumując, z po-
wodów opisanych wyżej, najbezpieczniej
będzie, jeśli zawsze pierwszy oktet MAC
adresu będzie miał wartość 0, wtedy unik-
niemy przykrych niespodzianek i długiego
poszukiwania błędów we własnym progra-
mie.
Przy prawidłowo dobranych numerach
MAC adresu wystąpi raczej bardzo nikłe
prawdopodobieństwo, że jakieś urządzenie
w sieci LAN będzie miało już wybrany przez
nas adres. W dalszej kolejności ustawiamy
port, na którym będzie nasłuchiwał nasz ser-
wer WWW. Typowo odbywa się to na porcie
80. Dzięki temu, we własnej przeglądarce
internetowej wystarczy wpisać w miejsce
nazwy strony adres IP (w moim przypadku
192.168.0.110) oraz nacisnąć klawisz EN-
TER. Jeśli jednak zmienimy port np. na war-
tość 8090, to wywołanie w przeglądarce bę-
dzie już wymagało wyspecyfikowania tegoż
portu: 192.168.0.110:8090.
Definicja bufora
Kolejna kwestia dotyczy definicji bufora
do obsługi komunikacji TCP. Obsługuje on
zarówno dane przychodzące, jak i wycho-
dzące. Jego wielkość zwiększyłem do 850
bajtów. Można ją jeszcze zwiększać, ale trze-
ba pamiętać o obserwacji zajętości pamięci
RAM po kompilacji. Nie może zostać jej zbyt
mało, gdyż dojdzie do problemów z działa-
niem stosu. W dalszej kolejności mamy de-
finicję dwóch funkcji narzędziowych. Pierw-
sza z nich służy tylko do przygotowania od-
powiedzi z serwera w formacie HTTP o tym,
że zapytanie do serwera było poprawne,
druga natomiast, print_webpage(), odpowie-
dzialna jest bezpośrednio za wygląd strony
WWW, jaką podaje nasz serwer. Funkcja ta
napełnia tylko bufor ramki TCP, natomiast
samo wysyłanie odbywa się za pomocą in-
nych funkcji w dalszej części programu.
Na jej wyjściu otrzymujemy długość strony
w bajtach.
Główna część programu
Następnie rozpoczyna się główna funk-
cja programu, main(). Na początku dekla-
rowane są potrzebne zmienne, oraz do-
konywana inicjalizacja sprzętowej części
karty sieciowej, ustawianie adresów MAC
oraz IP itd. Później rozpoczyna się pętla
nieskończona, w której wciąż na początku
dokonywany jest odczyt danych z układu
ENC28J60, jeśli takie w ogóle pojawiły się na
jego wejściu. Posługujemy się tutaj zmienną
dat_p. Jej wartość po odczycie odpowiada
ilości odczytanych bajtów. Jeśli jest równa
0 to oznacza, że nie było żadnego żądania
i pętla jest kontynuowana bez wykonywania
pozostałych poleceń, które są umieszczone
w dalszej części programu. Trzeba pamię-
tać o tej konstrukcji, bo jeśli np. umieścimy
jakiś własny dodatkowy kod także poniżej
sprawdzania ilości odczytanych bajtów, to
nie zostanie on nigdy wykonany, dopóki nie
nadejdzie żądanie do serwera. Aby wybrnąć
z tej sytuacji, np. gdy chcemy umieścić dla
celów testowych naszą wyżej omawianą
funkcję SuperDebounce(), która w zależno-
ści od stanu klawisza zapalałaby bądź gasi-
ła diodę LED, trzeba umieścić ją w tej pętli
albo przed odczytem czy sprawdzaniem
zmiennej dat_p, albo nieco inaczej napisać
kod całej pętli biorąc pod uwagę to, że ko-
lejne polecenia powinny być wykonywane
tylko i wyłącznie, gdy mamy jasną sytuację,
że nadeszły jakieś dane.
Dalsza część programu
W dalszej części programu, jeśli na-
dejdzie już jakieś zapytanie z zewnątrz do
naszej karty sieciowej, to po odczytaniu
w buforze zostanie umieszczona ramka TCP
wraz z danymi, które nas interesują. Dlatego
w pierwszym warunku obsługujemy troszkę
po macoszemu zapytania typu GET, nato-
miast w kolejnym sprawdzamy w uprosz-
czony sposób, czy nastąpiło odwołanie do
domyślnej strony w głównym folderze ?/ ?.
Występująca tutaj spacja oznacza, że pod-
czas wywołania naszego adresu w prze-
glądarce nie określono żadnej konkretnej
nazwy pliku do odczytu. W przeciwnym
wypadku moglibyśmy na tym właśnie po-
ziomie rozpoznawać dostępne dla zapytań
pliki, np.:
if (strncmp(?/index.html?,(char
*)&(buf[dat_p+4]), 11)==0)
To spowodowałoby, że nasz serwer
wyświetli stronę WWW tylko dla zapytań
wpisanych w przeglądarce w ten sposób:
?192.168.0.110/index.html?. Można przy-
gotować kilka różnych stron HTML w pa-
mięci FLASH mikrokontrolera albo też na
karcie pamięci SD i wysyłać je w zależno-
ści od nadchodzących żądań z zewnątrz.
Ten prosty przykład nie pokazuje wpraw-
dzie wprost możliwości sterowania mikro-
kontrolerem poprzez wyświetlanie strony
www, ale istnieje wiele innych przykładów
na stronie tuxgraphics.org, dzięki którym
można bez problemu sterować zdalnie do-
wolnymi procesami.
Niestety jest jednak pewien problem,
którego nie można pominąć. Otóż biblioteki
stosu tcp, o których wspomniałem wyżej,
są wciąż rozwijane, ale także dosyć mocno
modyfikowane, co powoduje, że praktycz-
nie w każdym pobranym przykładowym
projekcie z tuxgraphics.org spotkamy się
z mniej lub więcej zmodyfikowanymi funk-
cjami w plikach ?ip_arp_udp_tcp.*?. Są
one czasem wprost dostosowane do kon-
kretnego projektu, przez co niestety nie
można powiedzieć, że to biblioteki w pełni
uniwersalne. Nie wchodząc zatem nieco
głębiej w tajniki działania stosu TCP czy-
telnik może mieć problemy, aby płynnie
modyfikować opisane tam projekty, jeśli
chodzi o wprowadzanie większych zmian
w ich funkcjonowaniu. Proponuję jednak
przećwiczyć i przetestować chociaż kilka
projektów dostosowując je tak, jak pokaza-
łem w tym rozdziale, do własnego mikro-
kontrolera. Łatwo wtedy dostrzec pewne
powtarzające się zależności, z których ja-
sno wyłoni się sposób podstawowej obsługi
stosu TCP. W dużym skrócie, jego obsługa
sprowadza się tak, jak w tym przykładowym
omawianym tu programie prostego serwera
HTTP, do ciągłego sprawdzania, czy układ
enc28j60 otrzymał jakieś dane. W dalszej
kolejności zwykle sprawdzamy, czy zapy-
tanie, które dotarło, nie jest zapytaniem
czysto informacyjnym związanym ustala-
niem adresów, obsługi ARP, czy też obsługi
zewnętrznych poleceń typu ping. W innych
projektach znaleźć można sprawdzanie,
czy ramka, którą odczytał układ ENC28J60,
jest przeznaczona właśnie dla nas, i dopie-
ro po tym następują procedury parsowania
(sprawdzania) danych przesłanych w ram-
ce, które mogą być przydatne do sterowania
procesami. Obsługa stosu już w kodzie pro-
gramu, mając do dyspozycji chociaż podsta-
wowe funkcje, nie jest wcale taka trudna.
Dodam że chociaż na schematach przykła-
dowych urządzeń z tuxgraphics.org prawie
wszędzie podłączony jest sygnał INT z kar-
ty sieciowej do wejścia INTx mikrokontro-
lera, to jednak w kodzie jest ono nie uży-
wane. A szkoda. Można sobie wprowadzić
taką obsługę, wystarczy zainicjalizować to
przerwanie, w naszym zestawie będzie to
wejście INT2 i napisać najprostszą obsługę
przerwania w postaci ustawiania zwykłej
flagi, która będzie następnie sprawdzana
i zerowana w pętli głównej. Oznaczać ona
będzie zdarzenie odbioru danych z karty
sieciowej. Można dzięki temu nieco upro-
ścić oprogramowanie w pętli głównej pro-
gramu, zamiast wciąż badać, czy bufor jest
pusty. Wprawdzie w tak prostym przykła-
dzie nie robi to wielkiej różnicy, jednak daje
pewne możliwości dla przyszłej wygodniej-
szej rozbudowy całego programu.
Mirosław Kardaś
Atnel