STM32 I i s i @ o Ethernet Marcin Peczarski w p v z y k Z a à a c k
Spis treści 3 Przedmowa...7 1. Podstawy...11 1.1. M ikrokontroler S T M 32F107...12 1.2. Struktura przykładów... 13 1.3. Przykład I a - miganie diodam i św iecącym i...15 1.3.1. Plik ex_ied.c...16 1.3.2. Pliki util_delay.h i util_delay.c...17 1.3.3. Pliki board_led.it i board_led.c...17 1.3.4. Pliki stni32fi0x_gpio.lt i slin32fl0x_rcc.lt... 18 1.3.5. Pliki stni32fl0x.lt, system_stm32fi0x.h i core_an3.h...19 1.3.6. Plik Iibstiii32fi0x.a... 19 1.3.7. Plik stm32f KLconf.h... 20 1.3.8. Pliki board_def.lt i board_defs.lt... 21 1.3.9. Plik stdiiit.h... 22 1.3.10. Plik startup_stm32_cld.c... 22 1.4. Kom pilowanie przykładów... 23 1.5. Wejścia i w yjścia binarne...26 1.6. W yśw ietlacz ciekłokrystaliczny...30 1.7. Przykład lb - test w yświetlacza ciekłokrystalicznego...33 1.7.1. Pliki board_lcd.lt i board_lcd_kso108.c...34 1.7.2. Pliki fotil5x8.lt \font5x8.c...37 1.7.3. Pliki ntil_lcd.lt i a liljc d.c...37 1.7.4. Plik ex_lcd.c... 38 1.8. Organizacja pam ięci program u...38 1.8.1. Sekcje...38 1.8.2. Procedura startowa...42 1.8.3. Skrypt konsolidatora...45 1.9. Styl pisania i kom entowania tekstu źródłow ego...49 2. Intersieci...51 2.1. M odel w arstw ow y...52 2.2. E thernet...54 2.3. IP - protokół intersieci... 60 2.4. A RP - tłum aczenie adresów sieciow ych na adresy sprzętow e... 66 2.5. Sieć testow a... 67 2.6. Przykład 2 - m onitor sieci...69 2.6.1. Pliki board_init.lt i boardjnit.c...70 2.6.2. Pliki util_led.h i util_led.c... 79 2.6.3. Plik ex_eth.c...80
Spis treści 5 3. Stos TCP/IP...83 3.1. Przegląd im plem entacji... 84 3.2. Biblioteka Iw IP... 86 3.2.1. Dopasowanie do architektury mikrokontrolera - plik conex-in3.li...86 3.2.2. Parametry konfiguracyjne - plik Iwipopts.h...93 3.2.3. Kompilowanie - plik liblwipd.ci... I^0 3.2.4. Kody błędów... I01 3.2.5. Struktura p b u f... 101 3.2.6. Struktura n e t i f...104 3.3. D M A... 1 6 3.4. Przykład 3a - pierw sza w ersja sterow nika E thernetu... 109 3.4.1. Pliki util_liine.li i utu_time.c...109 3.4.2. Pliki util_eth.li i util_eth.c - inicjowanie interfejsu sieciowego... 112 3.4.3. Plik ulil_eth.c - wysyłanie ramek ethernetowych...117 3.4.4. Plik utu_eth.c - odbieranie ramek ethernetowych...118 3.4.5. Pliki utiijw ip.h i utiljw ip.c - inicjowanie interfejsu sieciowego... 120 3.4.6. Pliki util_lwip.h i utiljw ip.c - budziki...122 3.4.7. Pliki board_conf.li i boardjconf.c... 124 3.4.8. Pliki util_lcd_ex.h i util_lcd_ex.c... 125 3.4.9. Plik ex_ip.c...125 3.5. Przykład 3b - sterow nik Ethernetu bez kopiow ania... 127 3.5.1. Plik util_eth_nc.c - inicjowanie interfejsu sieciowego... 127 3.5.2. Plik util_eth_nc.c - wysyłanie ramek ethernetowych...130 3.5.3. Plik util_eth_nc.c - odbieranie ramek ethernetowych...130 3.6. Przykład 3c - eksperym entalny sterow nik Ethernetu bez kopiow ania...132 3.6.1. Plik util_eth_zc.c - inicjowanie interfejsu sieciowego...133 3.6.2. Plik util_etli_zc.c - wysyłanie ramek ethernetowych... 134 3.7. Testy sterow ników... 136 3.8. 1CMP - komunikaty kontrolne i kom unikaty o błędach... 137 3.8.1. Sprawdzanie osiągalności odbiorcy...138 3.8.2. Powiadamianie o nieosiągalności odbiorcy...139 3.8.3. Kontrola przepływu...139 3.8.4. Przekroczenie czasu...140 3.8.5. Problem z parametrem... 140 3.9. D HCP - konfigurow anie ustawień sieciowych w ęzła... 141 4. Programowanie w modelu klient-serwer 143 4.1. N um er portu... 145 4.2. T C P... 146 4.2.1. Podstawowe własności... 146 4.2.2. Nagłówek... 147 4.2.3. Otwieranie połączenia...149 4.2.4. Przesyłanie danych...152 4.2.5. Zamykanie połączenia...153 4.2.6. Funkcje biblioteczne...154 4.2.7. Funkcje zwrotne...160 4.3. U D P...162 4.3.1. Podstawowe własności...162 4.3.2. Nagłówek...162 4.3.3. Inicjowanie klienta i serwera...163 4.3.4. Przesyłanie danych... 163 4.3.5. Funkcje biblioteczne...164 4.3.6. Funkcja zwrotna... 166 4.4. Uwagi końcow e...166 5. Serwer TCP...169 5.1. Protokół warstwy aplikacji... 170 5.1.1. Projekt protokołu...170 5.1.2. Projekt implementacji protokołu... 172 5.2. Przykład 5a - pierwsza w ersja serw era T C P...178 5.2.1. Pliki tcp_server.h i tcp_server.c... 178 5.2.2. Plik ex_tcpd.c...185 5.2.3. Testowanie przykładu... 186 5.3. Przykład 5b - serw er TC P z nadzorcą... 188 5.3.1. Pliki util_wdg.h i util_wdg.c... 189 5.3.2. Plik ex_tcpd_wdg.c... 190 5.3.3. Testowanie przykładu... 191 6. Klient TCP... 193 6.1. Projekt protokołu... 194 6.2. Przykład 6a - pierwsza w ersja klienta T C P...197 6.2.1. Tryby o obniżonym poborze mocy...198 6.2.2. Pliki util_rtc.h i ulil_rtc.c...198 6.2.3. Pliki tcp_client.h i tcp_client.c... 202 6.2.4. Plik util_error.h...210 6.2.5. Plik ex_tcp_client.c...211 6.2.6. Testowanie przykładu... 212 6.3. Przykład 6b - klient TCP z obsługą rejestrów zapasow ych...214 6.3.1. Pliki util_bkp.h i util_bkp.c...2 14 6.3.2. Plik lcp_client_bkp.c...216 6.3.3. Plik ex_tcp_clnt_bkp.c...217 6.3.4. Testowanie przykładu... 217
6 Spis treści 7. Serwer li DI»...219 7.1. Projekt protokołu... 220 7.2. Przykład 7 - prosty serw er U D P...221 7.2.1. Pliki udp_server.h i udp_server.c... 221 7.2.2. Plik ex_udpd.c...224 7.2.3. Testowanie przykładu... 225 8. Klient UDP...227 8.1. D N S... 228 8.2. S N T P... 229 8.3. Przykład 8 - autom atyczna synchronizacja zegara czasu rzeczyw istego 232 8.3.1. Pliki sntpjciient.h i sntp_client.c...232 8.3.2. Plik ex_sntp.c... 238 8.3.3. Testowanie przykładu...240 8.3.4. Uwagi końcowe... 241 9. Rozgłaszanie UDP... 243 9.1. Przykład 9a - rozgłaszanie dalagram ów U D P...244 9.1.1. Plik ex_send_bcast.c...244 9.1.2. Testowanie przykładu...247 9.2. Przykład 9b - odbieranie datagram ów U D P... 247 9.2.1. Plik ex_recv_bcast.c... 248 9.2.2. Testowanie przykładu...249 10. Serwis W W W...251 ło.i. Komunikacja m iędzy klientem a serwerem W W W...252 10.1.1. URI... 252 10.1.2. HTTP...253 10.1.3. HTML...256 10.2. Przykład 10 - prosty serwis W W W...259 10.2.1. Pliki httpjmrser.h i http_jmrser.c... 259 10.2.2. Pliki http_server.h, littp.lt i http_applicatian.h... 265 10.2.3. Plik http_server.c - obsługa HTTP... 266 10.2.4. Plik lutp_server.c - obsługa połączenia TCP... 271 10.2.5. Plik littp_(ipplication.c... 273 10.2.6. Plik stm32_logo.h... 277 10.2.7. Plik exjittpd.c...278 10.2.8. Testowanie przykładu... 278 Dodatek. Narzędzia GNU... 281 Literatura... 286 Przedmowa Proponując Czytelnikow i kolejną na naszym rynku książkę o m ikrokontrolerach, pow inienem na wstępie udzielić odpow iedzi na dwa istotne pytania. O czym jest ta książka? Do kogo jest skierow ana? Na pierwsze pytanie najkrócej m ógłbym odpow iedzieć, że książka traktuje o im plem entowaniu protokołów i tworzeniu aplikacji sieciow ych na mikrokontrolery, które są wyposażone w interfejs Ethernet. W ydajność obliczeniow a mikrokontrolerów stale rośnie (przy zachow aniu poboru mocy w granicach rozsądnych dla lego rodzaju układów. Umożliwia to zaim plem entow anie stosu protokołów sieciowych TCP/IP, dotychczas dostępnego tylko na dużych m aszynach i komputerach osobistych. M oim zdaniem spow oduje to rew olucję zastosow ań mikrokontrolerów, podobną do tej, jaką spow odowało kilkadziesiąt lat temu łączenie kom puterów osobistych w sieci lokalne (ang. local area networks i intersieci (ang. internets. U rządzenia sterow ane m ikrokontrolerami mogą stać się częs'cią Internetu, co stwarza zupełnie nowe możliwości. Specyfika urządzeń sterow anych mikrokontroleram i polega między innymi na konieczności reagow ania na wiele zdarzeń zew nętrznych i sterowania wielom a peryferiam i. Czasy reakcji powinny być na tyle krótkie, aby sprawiać wrażenie, że w szystko dzieje się niejako równolegle. Stosowane są tu dwa podejścia: - cala aplikacja napisana jest w postaci jednego programu, m ającego dostęp do całości zasobów sprzętow ych, bezpośrednio obsługującego układy peryferyjne i w ykorzystującego intensyw nie system przerwań - działaniem m ikrokontrolera steruje system operacyjny, ukrywający obsługę sprzętu i przerwań w sterow nikach urządzeń, a aplikacja jest podzielona na zadania, procesy lub wątki w ykonywane w spółbieżnie pod kontrolą tego systemu operacyjnego. W niniejszej książce prezentuję pierw sze podejście, które sprzyja zmniejszaniu rozm iaru pamięci zajm owanej przez aplikację, co bywa nadal istotne w systemach wbudowanych. Książka ta ma w założeniu być podręcznikiem i przew odnikiem. Chcąc zrealizować zasadę, że najlepiej uczyć się na przykładach, stanąłem przed koniecznością wyboru platform y sprzętowej i środowiska program istycznego. Prezentowane w książce zagadnienia ilustruję przykładami napisanym i na m ikrokontroler STM 32F107 z rdzeniem ARM Cortex-M 3 firmy STM icroeleclronics. Do ich przetestowania użyłem zestawu ZL29ARM z modułem ethernetow ym ZL3ETH oraz wyświetlaczem ciekłokrystalicznym W G 12864A. Przykłady napisane są w języku C, w którym obecnie najczęściej program uje się mikrokontrolery. Kompilowałem je za pomocą GCC w wersji 4.4.3, z wykorzystaniem GNU Binutils w wersji 2.20.1 i Newiib w wersji 1.18. Użyłem w nich publicznie dostępnej im plem entacji stosu TCP/IP - biblioteki lwip w wersji 1.3.2. Skorzystałem też z bibliotek dostarczanych przez STM icroeleclronics: CM SIS (Cortex M icrocontroller Software Interface Standard w wersji 1.30, STM 32F10x Standard Peripherals Library w wersji 3.3.0 i STM 32 ETH Firm w are Library w wersji 1.1.0.
8 Przedmowa Przedmowa 9 Świadom ie zrezygnowałem z pisania przykładów w języku C++, który zyskuje ostatnio coraz większą popularność w s'wiecie mikrokontrolerów. C++ ujawnia swoje zalety przy dużych programach, zw łaszcza gdy intensywnie korzysta się ze standardowej biblioteki wzorców STL (ang. Standard Template Library. Nie ma co ukrywać, że C++ jest bardzo trudnym językiem. Chcę, aby książka była w pełni dostępna dla Czytelnika nieznającego C++. Używając pełnej siły wyrazu C++, ryzykowałbym niezrozum ienie przykładów, a nie ma tu miejsca na szczegółowe objaśnianie zaawansowanych konstrukcji tego języka. N atom iast pisanie przykładów w jakiejś' okrojonej wersji C++ byłoby tylko niepotrzebnym dodawaniem lukru obiektowości. Czytelnik znający C++ bez problemu przełoży na ten język przykłady napisane w C. Przykłady starałem się pisać w sposób ogólny, aby mogły być łatwo przeniesione na inne mikrokontrolery. Jedynym rzeczyw iście specyficznym i mocno zależnym od sprzętu fragmentem kodu jest sterow nik (ang. driver pośredniczący między biblioteką lwip a m ikrokontrolerem. Szczegółowo w yjaśniam, jak napisać taki sterow nik i jak dopasow ać bibliotekę lw ip do architektury konkretnego m ikrokontrolera. Pozostała część prezentowanego przeze mnie oprogram ow ania wcale lub tylko w bardzo niewielkim stopniu zależy od architektury sprzętu i daje się łatwo zaadaptować na dowolny mikrokontroler. Postaram się teraz odpowiedzieć na drugie z postawionych na początku pytań. Książka przeznaczona jest dla osób, które ju ż mają jakieś doświadczenie w program owaniu mikrokontrolerów. Zakładam, że Czytelnik umie programować w C. Chcącym poszerzyć sw oją wiedzę o tym języku polecam klasyczną książkę Język ANSI C, napisaną przez jego twórców, Kernighana i Ritchiego [2J. Nie jest potrzebna znajom ość Asemblera ARM. Oczekuję co najmniej podstawowej wiedzy z zakresu elektroniki, tak aby na podstawie opisów i schem atów zam ieszczonych w książce oraz danych katalogowych umieć sam odzielnie zmontować układ prototypowy lub w ykonać niezbędne połączenia w jakim ś zestawie uruchom ieniowym, których wiele jest dostępnych na rynku. Nie oczekuję natom iast żadnej wiedzy o sieciach kom puterowych czy protokołach sieciowych - staram się wyjaśniać wszystko od podstaw. Zakładam też, że Czytelnik ma wystarczające obycie informatyczne, aby sam odzielnie zainstalować środowisko program istyczne (edytor, kom pilator itd. i zapoznać się ze szczegółami jego obsługi. M am nadzieję, że książka będzie ciekawą lekturą zarówno dla profesjonalisty, zaw odowo projektującego układy z mikrokontrolerami, jak i hobbysty zajm ującego się tym tylko dla własnej satysfakcji. M oże też zainteresować osoby, które nigdy dotąd nie programowały mikrokontrolerów, a chciałyby się dowiedzieć, jak implem entuje się protokoły sieciowe na taki komputer. Wydaje mi się, że dodatkowym atutem książki jest zaprezentowanie Czytelnikowi przedstawiciela nowoczesnej rodziny mikrokontrolerów STM32. Jest to niewątpliwie rodzina układów, które mają szansę odnieść rynkowy sukces w najbliższej przyszłości. Wybrany do napisania przykładów mikrokontroler STM 32F107 należy do linii zorientowanej na komunikację (ang. connectivity line. Oprócz zwykle spotykanych w mikrokontrolerach interfejsów I2C, SPI i US ART, wyposażony jest w CAN, Ethernet, I2S i USB. Interfejs Ethernet może pracować z przepływnością 10 Mb/s lub 100 Mb/s w trybie dwukierunkowym naprzemiennym (ang. half-duplex lub dwukierunkowym jednoczesnym (ang. full-duplex. Interfejs USB może pracować z przepływnością 12 Mb/s (ang. fu ll speed jako urządzenie (ang. device oraz z przepływnością 1,5 Mb/s (ang. low speed lub 12 Mb/s jako host. Ponadto STM32F107 zawiera dość typowe układy licznikowe oraz przetworniki analogowo-cyfrowe i cyfrowo-analogowe. Skonstruowany jest w oparciu o 32-bitowy rdzeń ARM Cortex- -M3, który można taktować maksymalnie z częstotliwością 72 MHz. Rdzeń ten zaprojektowano specjalnie do sterowania systemami głęboko wbudowanymi (ang. deeply embedded. Oferuje rozbudowany system przerwań z krótkim i przewidywalnym czasem rozpoczęcia obsługi oraz typowe dla tego segmentu układów tryby oszczędzania energii. Rdzeń obsługuje tylko zestaw instrukcji Thumb-2, rozszerzający zestaw instrukcji Thumb o kodach 16-bitowych o instrukcje o kodach 32-bitowych. Thumb-2 jest reklamowany jako osiągający wydajność tylko nieznacznie mniejszą od zestawu instrukcji ARM o kodach 32-bitowych przy zmniejszeniu o około 25% zapotrzebowania na pamięć programu, co jest porównywalne z gęstością kodu osiąganą przy zastosowaniu okrojonego zestawu instrukcji Thumb, który jednak cechuje się istotnie mniejszą wydajnością od pełnego zestawu instrukcji ARM. Układ książki jest następujący. Rozdział 1 pełni funkcję rozbudowanego wstępu. Poruszam w nim zagadnienia niezwiązane bezpośrednio z tytułem książki, ale potrzebne w dalszej jej części. Opisuję obsługę wejść i wyjść ogólnego przeznaczenia (ang. general purpose input-output na przykładzie diod świecących i wyświetlacza ciekłokrystalicznego. W yjaśniam strukturę archiwum z przykładami i parametry kompilacji. Omawiam, też organizację pamięci programu oraz działanie procedury startowej (ang. startup code i skryptu konsolidatora (ang. linker script. Rozdział 2 zaczynam od przedstawienia modelu warstwowego intersieci. Następnie opisuję technologię Ethernet, ale skupiam się tylko na jej wariantach i trybach pracy obsługiwanych przez mikrokontroler. Przedstawiam budowę ramki i rodzaje adresów ethernetowych. Następnie wyjaśniam, jak ramki podstawowego protokołu intersieci, czyli IP (ang. Internet Protocol, są przesyłane w ramkach Ethernet. Opisują też działanie ARP (ang. Address Resolution Protocol, czyli protokołu tłumaczącego adresy IP na adresy ethernelowe. Rozdział ten kończę przykładem prostego monitora sieci. W rozdziałach 3 i 4 omawiam bibliotekę lw ip implementującą stos protokołów TCP/IP. W rozdziale 3 zajm uję się współpracą tej biblioteki ze sprzętem - pokazuję trzy wersje sterownika Ethernetu. Poszczególne sterowniki różnią się trybem pracy DMA (ang. Direct M em ory Access oraz sposobem obsługi buforów odbiorczych i nadawczych. Pokazuję wersję z kopiowaniem zawartości buforów i bez kopiowania ich zawartości (ang. zero copy. Na końcu tego rozdziału przedstawiam nieco informacji o protokołach ICM P (ang. Internet Control Message Protocol i DHCP (ang. D ynam ie H ost Configuration Protocol. W rozdziale 4 zajm uję się interfejsem programistycznym API (ang. application programming interface między biblioteką lw ip a aplikacją. Opisuję podstawowy interfejs udostępniany przez tę bibliotekę, czyli interfejs surowy (ang. raw. Pokazuję, jak wykorzystać ten interfejs do tworzenia aplikacji w modelu klient-serwer. W rozdziale tym przedstawiam też niezbędne informacje o protokołach TCP (ang. Transmission Control Protocol i UDP (ang. User Datagram Protocol. Rozdział 4 jest jedynym w książce niezakończonym żadnym przykładem, gdyż właściwie kolejne rozdziały do końca książki zawierają przykłady korzystające z wiadomości przedstawionych w tym rozdziale. W rozdziale 5 opisuję dwa przykłady serwerów TCP, czyli podstawowego strumieniowego pro-
10 Przedmowa tokołu warstwy transportowej intersieci. W drugim z tych serwerów uruchamiam układ nadzorcy (ang. watchdog w celu zopobieżenia trwałemu zawieszeniu się programu. W rozdziale 6 omawiam dw a przykłady klientów TCP. Korzystają one z wbudowanego w m ikrokontroler zegara czasu rzeczywistego RTC (ang. Real Time Clock, w celu urucham iania się w zadanych odstępach czasu. Ponadto korzystają leż z trybu czuwania (ang. standby, aby zm niejszyć pobór prądu zasilania między kolejnymi uruchomieniami. Drugi z klientów dodatkowo używa rejestrów zapasowych (ang. backup registers m ikrokontrolera STM 32F107, aby między kolejnymi uruchomieniami przechować w nich dane aplikacji. W rozdziale 7 zamieszczani przykład prostego serwera UDP, czyli podstawow ego pakietowego protokołu warstwy transportowej intersieci. Serw er ten pozw ala zdalnie sterować portami wejścia-wyjścia mikrokontrolera. W rozdziale 8 opisuję przykład klientów dwóch protokołów bazujących na UDP, a mianowicie DNS (ang. Domain Name System i SN TP (ang. Simple Network Time Protocol. SN TP służy do synchronizacji czasu w sieci i umożliwia zsynchronizowanie zegara czasu rzeczywistego mikrokontrolera z jakim ś dostępnym w Internecie atom owym wzorcem czasu. W rozdziale 9 zajm uję się rozgłaszaniem (ang. broadcast w UDP. W rozdziale 10 pokazuję, jak skom unikować się z mikrokontrolerem za pom ocą przeglądarki internetowej i opisuję im plem entację prostego serwisu W W W (ang. World Wide Web. Listę książek i dokumentów, z którymi warto się zapoznać, zam ieszczam w spisie literatury. W dodatku opisuję narzędzia GNU, których użyłem do skom pilowania przykładów. Duża część książki, począw szy od rozdziału 4, zaw iera szczegółow e opisy, jak im plem entuje się własne protokoły i tworzy aplikacje korzystające bezpośrednio z protokołów transportowych T C P i UDP. W szystkie przykłady używają interfejsu surowego, który pozw ala na lepszą integrację aplikacji ze stosem TCP/IP. Interfejs surowy jest preferowany w system ach wbudowanych, gdyż ma male wym agania pam ięciow e i nie potrzebuje dużej mocy obliczeniowej - nie ma w ątków ani procesów, nie trzeba zm ieniać kontekstu. Przykładow e programy są sterow ane zdarzeniami (ang. event based za pom ocą funkcji zwrotnych (ang. callback functions. Źródłem zdarzeń są przerwania. Przykłady konstruuję przyrostow o - kolejne korzystają z modułów zaprogram ow anych na potrzeby poprzednich przykładów. W szystkie pliki źródłowe przykładów, łącznie ze źródłami biblioteki lwip oraz źródłami specyficznych dla m ikrokontrolerów STM 32 bibliotek dostarczanych przez STM icroelectronics, dostępne są w postaci skom presow anego archiwum, które można ściągnąć ze strony W ydaw nictwa BTC http://www.btc.pl lub strony dom o wej A utora http://w w w.m iinuw.edu.pl/~nw rpe/book. Chcę wyraźnie zaznaczyć, że książka nie jest instrukcją obsługi narzędzi program istycznych ani nie stanowi w yczerpującej dokum entacji sprzętu użytego do przetestowania zam ieszczonych przykładów. Czytelnik z łatwością znajdzie potrzebną dokum entację na stronach internetowych odpow iednich producentów. Staram się prom ować polską terminologię. Dla wygody Czytelnika, zwykle zm u szonego korzystać z dokum entacji w języku angielskim, przy pierwszym, a czasem i przy kolejnym, użyciu terminu, który nie jest pow szechnie znany, um ieszczam w nawiasach jego angielski odpow iednik. M arcin Peczarski, W arszawa 2011 Podstawy
12 I. Podstawy 1.2. Struktura przykładów 13 W tym rozdziale poruszam zagadnienia niezwiązane bezpośrednio z tytułem książki. Zapoznanie się z w iększością tego m ateriału uważam jednak za potrzebne do zrozum ienia przykładów zam ieszczonych w następnych rozdziałacłi. Ponadto niektóre z tych zagadnień nie są, moim zdaniem, dostatecznie obszernie opisane w literaturze poświęconej m ikrokontrolerom, a wydają mi się ważne i ciekawe. Staram się tu przedstaw ić tylko niezbędne podstawy, ujęte w takiej kolejności, aby Czytelnik mógł jak najszybciej zacząć urucham iać programy. Czytelnik czujący, że przedstawione w tym rozdziale tem aty są mu dobrze znane, może go szybko przekartkować lub przejść od razu do następnego rozdziału, czyli do zasadniczej części książki. 1.1. Mikrokontroler STM32F107 W szystkie prezentowane w lej książce przykłady napisałem w języku C na mikrokontroler STM 32F107, który wyposażony jest w 32-bitowy rdzeń ARM Cortex- M3. Ponadto m ikrokontroler ten ma wiele układów peryferyjnych, m.in. interfejs Ethernet, który um ożliwia podłączenie go do sieci lokalnej, a za jej pośrednictwem do Internetu. Uktad STM 32F107 jest dostępny w kilku wersjach różniących się w ielkościam i pamięci i liczbą w yprowadzeń. Zestaw iono je w tabeli 1.1. Skrót KiB oznacza niedawno w prow adzoną do informatyki nową jednostkę rozmiaru pamięci - kibibajt (kilobinarybajt, czyli dokładnie 1024 bajty, w odróżnieniu od kilobajta (skrót kb, który formalnie pow inien mieć 1000 bajtów. Obsługa Ethernetu i stosu T C P /IP jest bardzo pam ięciożerna, mimo to wszystkie zam ieszczone w tej książce przykłady można uruchom ić na najskrom niejszym modelu. W tabeli 1.2 przestaw iono podstaw ow e typy C obsługiw ane sprzętow o przez rdzeń Cortex-M 3. W celu w ykonania operacji arytm etycznych na innych typach, w szczególności na 64-bitow ym typie całkow itym ło n g lo n g i typach zm iennoprzecinkowych, wywoływane są funkcje biblioteczne. D latego ze w zględów wydajnościo- Tab. 1.1. Dostępne wersje mikrokontrolera STM32F107 Model Flash SRAM Obudowa STM 32F107RB 128 KiB 48 KiB LQFP64 STM 32F107RC 256 KiB 64 KiB LQFP64 STM32F107VB 128 KiB 48 KiB LQFP100 lub LFBGA100 STM32F107VC 256 KiB 64 KiB LQFP100 lub LFBGA100 Tab. 1.2. Podstawowe typy języka C i ich rozmiary w architekturze Cortex-M3 Typ char signed char unsigned char short unsigned short int unsigned long unsigned long a tires, void*, char* itd. Rozm iar 8 bitów 16 bitów 32 bity 32 bity 32 bity wych, jeśli jest to możliwe, należy unikać stosow ania typu long long, a przede wszystkim typów zm iennoprzecinkowych f l o a t, double i long double. W mikrokontrolerach STM 32 rdzeń Cortex-M 3 jest skonfigurowany na stale w trybie cienkokońcówkow ym (ang. little-enclian. Zatem zm ienna wielobajtowa przechow yw ana jest w pamięci od najm niej do najbardziej znaczącego bajtu. Ilustruje to poniższy fragment programu w języku C. union { unsigned short s; unsigned char c [2; } a; a.s = 0xla2b; if (a.c[0 == Qx2b 66 a.cli] == Qxla /* Jestem cienkokońcówkowy (ang. little-endian. */ else if (a.cfo] == Gxla 66 a.c[l] == 0x2b /* Jestem grubokońcówkowy (ang. big-endian. */ Cortex-M 3 ma typowy dla architektury ARM zestaw rejestrów: - RO do R12 - rejestry ogólnego przeznaczenia (ang. general purpose registers, - SP (R13 - w skaźnik stosu (ang. stack pointer, - LR (R 14- adresu powrotu (ang. link register, - PC (R15 - licznik programu (ang. program counter, - PSR - rejestr znaczników (ang. program status register, - PRIMASK, FAULTMAyJK, BASEPRI - rejestry m askujące przerwania (ang. exception m ask registers - w dokum entacji przerwania nazywane są wyjątkami, - CONTROL - rejestr sterujący trybami pracy rdzenia. O bszerniejszy opis architektury mikrokontrolera STM 32F107 znajduje się w kolejnych rozdziałach, gdzie będzie potrzebny w prezentowanych przykładach. Czytelnik zainteresowany szczegółami może też zaw sze sięgnąć do dokumentacji dostępnej w Internecie. Ogólny przegląd architektury i param etry elektryczne zamieszczone są w [9]. N iezbędne dla program istów inform acje o rdzeniu można znaleźć w 7], a wszystkie układy peryferyjne są opisane szczegółow o w [8]. Struktura przykładów Integralną częścią książki są teksty źródłow e przykładów. Skom presow ane archiwum zaw ierające aktualną ich w ersję m ożna ściągnąć ze strony W ydawnictwa BTC http://www.btc.pl lub ze strony dom ow ej A utora htip://www.minutw.echt. pt/~m arpe/book. Spora część tekstów źródłow ych ma charakter dość standardowy, bardzo techniczny i ich dokładne poznanie jest zbędne do zrozum ienia działania program u. Ponadto przeglądanie długich wydruków programów bywa nużące. Natom iast przestudiow anie całości teksów źródłowych staje się oczywiście konieczne, gdy chcem y je zm odyfikow ać. D latego w książce zam ieszczam tylko kluczow e fragm enty tekstów źródłowych, a chcących poznać całość odsyłam do archiw um w wersji elektronicznej. A rchiw um to zaw iera strukturę katalogów pokazaną na rysunku 1.1. Żeby zachow ać przejrzystość, niektóre podkatalogi - nieistotne z punktu w idzenia zrozum ienia treści książki - na tym rysunku pominięto. Ścieżki w tym archiw um podaję w konwencji uniksowej z ukośnikiem w prawą
14 I. Podstawy 1.3. Przykład la - miganie diodami świecącymi 15 stronę. Jeśli nazwa ścieżki rozpoczyna się od kropki, to jest to ścieżka względem katalogu, w którym rozpakow ano archiw um z przykładam i. Jestem przekonany, że taka konw encja pozwoli C zytelnikow i łatwo odnajdow ać potrzebne katalogi i pliki w system ie, którego używa. K atalog./exam ples zaw iera teksty źródłow e w szystkich przykładów opisanych w tej książce. Podkatalog./exam ples/src zaw iera pliki z im plem entacją. Podkatalog./exam ples/include zaw iera pliki nagłów kow e. Jego podkatalogi butterfly, prototype, z,l29ann+z.l3eth zaw ierają pliki nagłów kow e definiujące konfigurację sprzętu. Podkatalog./exam ples/scripts zaw iera skrypty konsolidatora (ang. linker scripts. W nazwach plików stosuję następującą konw encję. Pliki, których nazw a zaczyna się od przedrostka board, zaw ierają tekst źródłowy zależny od użytego zestawu uruchom ieniowego, czyli od schem atu elektrycznego konstruow anego urządzenia. Pliki, których nazwa zaczyna się od przedrostka util, zaw ierają funkcje użyteczne w wielu przykładach i niezależne od schem atu elektrycznego, ale być może nadal zależne od architektury m ikrokontrolera. Nazwy głów nych plików poszczególnych przykładów, zaw ierających funkcję main, zaczynają się od przedrostka ex. Pozostałe pliki nie zależą w cale lub zależą w niew ielkim stopniu od architektury m ikrokontrolera, a tym bardziej nie zależą od schem atu elektrycznego. Nazwa pliku, który im plem entuje protokół sieciowy, zaw iera skrót nazwy tego protokołu (np. tcp, udp, snip, http oraz pełnioną w tym protokole funkcję (np. client, server. Poszczególne pliki źródłowe, um ieszczone w katalogu./exam ples, opisuję sukcesyw nie w rozdziałach dotyczących odpow iednich przykładów, w których są wykorzystywane. i W katalogu./lwip-1.3.2 znajdują się źródła biblioteki iwip im plem entującej stos protokołów TCP/IP. Do napisania przykładów prezentowanych w tej książce wykorzystałem jej wersję 1.3.2. Katalog./excimples/include/arcli zawiera pliki nagłów kowe specyficzne dla architektury Cortex-M 3 i potrzebne do skom pilowania tej biblioteki. Ponadto w katalogu./exam ples/include znajduje się plik nagłówkowy lwipopts.li, za pom ocą którego konfiguruje się opcje stosu TCP/IP. Bibliotekę IwIP i wszystkie te pliki nagłówkowe opisuję szczegółowo w rozdziale 3. W katalogu Jst/Libraries um ieszczone są źródła bibliotek dostarczanych przez STM icroelectronics. Są to CM SIS (Cortex M icrocontroller Software Interface Standard w wersji 1.30, STM 32FIOx Standard Peripherals Library w wersji 3.3.0 [6] i STM 32 ETH Firm ware Library, przez producenta określana też jak o STM 32 ETH Driver, w wersji 1.1.0 patrz rów nież [3], W szystkie razem nazywam dalej skrótowo biblioteką STM 32. Sposób skom pilowania biblioteki STM 32 opisuję w rozdziale 1.4. U dostępniane przez nią struktury i funkcje, które wykorzystuję, omawiam przy odpow iednich przykładach. W katalogu,/st znajdują się też dw a pliki w formacie chm. Jeden zaw iera dokum entację STM 32F10x Standard Peripherals Library, a drugi - opis STM 32F107 Lw lp Dem onstration Package wraz z dokumentacją ETH Firmware Library. W katalogu./st/libraries/cm SIS/Docum entation znajduje się dokum entacja CM SIS w formacie html. Katalog./make zawiera skrypty dla programu make. Opisuję je w dodatku. Katalog ten zawiera również programy pom ocne przy urucham ianiu niektórych przykładów. Przedstawiam je w odpowiednich miejscach, gdzie są wykorzystywane. examples - - lw lp-1.3.2- - s t Libraries- make -s rc - Include scripts -src - arch - butterfly - prototype - zl29arm+zl3eth - netif - include I -Ipv4 ipv4 -STM 32F1 Ox_StdPeriph_Driver- -S T M 32 ETH D river--------------- n src inc Rys. 1.1. Struktura katalogów archiwum z przyktadami - CMSIS - - CM3 i CoreSupport ' D evicesupport- - S T - -STM32F10X 1.3. Przykład 1a - miganie diodami świecącymi W wielu książkach uczących program ow ania pierwszym prezentowanym przykładem jest zw ykle program typu Witaj Świecie. W św iecie m ikrokontrolerów oznacza to zam iganie diodą świecącą. Nie jestem oryginalny i leż w pierwszym przykładzie migam diodam i. Posłuży mi to jednak do czegoś więcej, do opisania kolejnych składników architektury m ikrokontrolera, zależności między plikami nagłówkowymi, sposobu konfigurow ania zestawu uruchom ieniow ego oraz kompilowania całości. Przykłady, które prezentuję w kolejnych rozdziałach, są coraz bardziej skom plikowane, składają się czasem naw et z kilkudziesięciu plików źródłowych, ale ich struktura jest analogiczna do struktury pierw szego przykładu. Poznanie tej struktury na prostym przykładzie um ożliwi później skupienie się na merytorycznej zawartości przykładów, bez potrzeby opisyw ania za każdym razem, gdzie w archiwum znajdują się potrzebne pliki, jak dopasow ać przykład do innego schem atu elektrycznego, jak go skom pilować itd. W zestawie ZL29ARM do mikrokontrolera podłączone są dwie diody świecące. Załóżmy, że jedna jest czerwona, a druga zielona. W pierwszym przykładzie m igają one w nieskończonej sekwencji: świeci czerw ona, świecą obie, świeci zielona, obie nie świecą. W tabeli 1.3 zestaw iono nazwy plików źródłowych i bibliotecznych, które trzeba w yspecyfikować kom pilatorow i, aby skom pilować ten przykład. Tab. 1.3. Pliki przykładu 1a e x je d.c startup _stm32_cld. c Źródłowe I biblioteczne boardj e d.c util_delay.c Iibstm32/10x.a Nagłówkowe board_def.h board_defs.li board led.h uth_delay.h Stm32t10x_conf,h
16 1. Podstawy 1.3. Przykład la - miganie diodami świecącymi 17 Rys. 1.2. Pliki nagłówkowe przykładu 1a Ponadto zaw iera ona nazwy plików nagłów kow ych wykorzystywanych w tym przykładzie i znajdujących się w podkatalogu./exam ples/include. Pliki nagłów kowe bibliotek nie są w ym ienione. Na rysunku 1.2 zobrazow ano graf zależności włączania plików nagłówkowych z uw zględnieniem plików nagłów kow ych bibliotek. W kolejnych podrozdziałach opisuję szczegółowo zawartość poszczególnych plików. O opcjach kompilacji piszę w rozdziale 1.4. U stawienia zworek zestawu ZL29ARM w tym przykładzie nie m ają istotnego znaczenia. Należy tylko wybrać sposób zasilania płytki zw orką PW R SET (USB lub EXT - zewnętrzny zasilacz i zworkami BOOTO i BOOT1 miejsce, skąd startuje program. Ja ustawiam BOOTO w pozycji 0, aby program startował z pamięci Flash. W tym przypadku ustawienie BOOT1 nie ma znaczenia. 1.3.1. Plik e x je d.c Plik e x je d.c znajduje się w katalogu./exam ples/src i zawiera funkcję main przykładu. W funkcji main najpierw wywołujem y funkcję LEDconfigure konfigurującą wyprowadzenia, do których podłączone są diody świecące, a następnie w nieskończonej pętli w ywołujem y funkcje włączające lub w yłączające odpow iednią diodę świecącą, poprzedzielane w yw ołaniem funkcji opóźniającej Delay. Funkcje te opisuję w następnych podrozdziałach. int main(void static const unsigned delay_time = 2000000; LEDconfigure(; for (;; { RedLEDonO; Delay(deiay_time; GreenLEDonO ; Delay (delay_tiine; RedLEDoff(; Delay(delay_time; GreenLEDoff(; Delay(delay_time; 1.3.2. Pliki util_delay.h i util_delay.c Plik util_delay.h znajduje się w katalogu./exam ples/include, a plik util_delay.c w katalogu./examples/src. Pierwszy z tych plików deklaruje sygnaturę funkcji opóźniającej, a drugi zaw iera jej im plem entację. Funkcja ta jest bardzo prosta: void Delay(volatile unsigned count ( while(count ; I U zyskanie wym aganego opóźnienia w ym aga eksperym entalnego skalibrowania wartości argumentu, gdyż czas opóźnienia zależy od częstotliwości taktowania i ustawienia opcji kompilatora. Ponadto w szelkie przerwania obsługiw ane w trakcie wykonywania tej funkcji wydłużają opóźnienie. Precyzyjne odmierzanie czasu można uzyskać, w ykorzystując układy licznikowe, co jednak wymaga znacznie więcej pracy programistycznej. Jednak w wielu zastosowaniach taka prosta funkcja jest wystarczająca i będziem y z niej korzystać w kolejnych przykładach. 1.3.3. Pliki boardjed.h i boardjed.c Plik boardj e d.li znajduje się w katalogu./exam ples/include, a plik b o a rd jed.c w katalogu./examples/src. W pierwszym z tych plików deklarujem y sygnatury funkcji obsługujących diody świecące, a w drugim umieszczam y ich implementacje. N ajpierw uzależniamy tekst źródłowy programu od połączeń w zestawie uruchomieniowym. Deklarujemy, że diody św iecące podłączone są do portu E (GPIOE, ang. general purposeńnput-output E, czerw ona dioda do w yprowadzenia 15, a zielona - do wyprowadzenia 14. Do w yprowadzeń podłączone są katody diod, zatem następnie definiujemy, że dioda świeci, gdy na w yprowadzeniu jest stan niski (stała Bit_RESET, a nie świeci w stanie wysokim (stała Bit_SET. Odpowiedni fragment programu wygląda następująco: ((include <board_def,h> lifndef BOARD_TYPE lerror BOARD_TYPE undefined (fendif łif BOARD_TYPE ==* PROTOTYPE i \ BOARD_TYPE == BUTTERFLY!! \ BOARD_TYPE == ZL29ARM ( define GPIO_LED ( define REDDLED fdefine GREEN_LED idefine RCC_APB2Periph_GPIO_LED (define LED_ON Idefine LED_OFF ( else terror Undefined BOARD_TYPE fendif GPIOE GPIO_Pin_15 GPIO_Pin_14 RCC_APB2Periph_GPIOE W t - U. J. BitJtESET Bit SET Stała BOARD_TYPE musi być zdefiniow ana w pliku board_def.li. Wartości, do których porów nywana jest ta stała, zdefiniow ane są w pliku board_defs.ii. Pliki board_def.lt i board_defs.h opisuję w jednym z następnych podrozdziałów. W mikrokontrolerach STM 32 przed użyciem jakiegokolw iek układu peryferyjnego musimy włączyć taktowanie tego układu. D omyślnie, po wyzerowaniu mikrokontrolera, w szystkie sygnały taktujące peryferie są w yłączone. Jest to rozsądne,
18 I. Podstawy 1.3. Przykład la - miganie diodami świecącymi 19 gdyż oszczędza energię - nieużyw ane układy nie pobierają niepotrzebnie prądu. Zatem w funkcji konfigurującej diody św iecące najpierw musimy uaktyw nić taktowanie portu, do którego są one podłączone. Następnie, przed w łaściwym skonfigurowaniem, ustawiamy tak port wyjściowy, aby diody były początkowo w yłączone. W yprowadzenia konfigurujem y w trybie wyjściowym przeciwsobnym (ang. push-pull output. Ponieważ sterow anie diodam i świecącym i nie wym aga stromych zboczy sygnału, ustawiamy najm niejszą (z trzech możliwych: 2 M Hz, 10 M Hz lub 50 MHz szybkość zmian sygnału na tych wyprowadzeniach. Cała funkcja konfigurująca diody świecące w ygląda tak: void LEDconfigure(void { GPICJInitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_LED, ENABLE; GPIOJiriteBit(GPIO_LED, RSD_LED GREEN_LED, LEDJ3FF; GPIO~StructInit{&GPIO_InitStruct}; GPIO_InitStruct.GPI0_Pin * GREEN_LED j RED_LED; GPI(f InitStruct.GPIOJfode = GPIO_Mode_Out_PP; GPIO_InitSŁruct.GPIO_Speed = GPI0_Speed_2MHz; GPX0 Init (GPI0_LED, &GPIO_InitStruct:; } Dla każdej z diod św iecących definiujem y trzy funkcje: włączającą, w yłączającą oraz zw racającą stan diody, czyli 1, gdy dioda świeci, a 0 w przeciw nym przypadku. Funkcje te obudow ują w ywołania funkcji z biblioteki STM 32. Dla czerwonej diody wyglądają one następująco: void RedLEDon(void ( GPIOJłriteBit(GPIO_LED, RED_L D, LED ON; j } ' void RedLEDoff (void { GPIO WriteBit(GPICJLED, RED_LED, LED OFF ; int RedLEDstate(void ( return GPIOJleadOutputDataBit(GPI0_LED, RED_LED == LED_0M; 1 Funkcje obsługujące zieloną diodę są analogiczne. 1.3.4. Pliki s tm 32f10xjjp io.h i stm 32f10x_rcc.h Pliki stm 32fl0x_gpio.il i stm 32fI0x_rcc.li znajdują się w katalogu Jst/Libraries/ STM 32F10x_StdPeripli_Driver/inc. Pliki te są częścią biblioteki STM 32. W pliku stm 32f 0x_gpio.lt zdefiniow ane są struktury, stałe i funkcje obsługujące porty w ejścia-wyjścia, a w pliku stm 32fl0x_rcc.h - obsługujące zerow anie układów m ikrokontrolera i dystrybucję sygnałów zegarowych do układów peryferyjnych. Identyfikatory (funkcji, stałych, typów zdefiniow ane w pliku stm 32fl 0x_gpio.lt mają prefiks GPIO (ang. G eneral Purpose Input-Output, a te w pliku stm 32fl0x_ rec.h - prefiks RCC (ang. Reset and d o c k Control. W katalogu Jst/Libraries/ STM 32F10x_StdPeriph_Driver/src znajdują się odpow iednie pliki stm 32fW x_gpio. c i stm32j'10x_rcc.c z im plem entacją. D o każdego układu peryferyjnego m ikrokontrolera biblioteka STM 32 dostarcza odpow iednią parę plików, np. dla układów licznikowych są to pliki stm 32fl0x_ tim.h i stm32fj0x_tim.c. 3.5. Pliki stm 32f10x.h, system_stm32f10x.h i core_cm3.h Pliki stm 32fl0x.h i system _stm 32fl0x.h znajdują się w katalogu Jst/Libraries/ C M SIS/CM 3/D evicesupport/st/stm 32F]0x, a plik core_cm3.h w katalogu J st/ Libraries/CM SlS/CM 3/CoreSupport. Są one częścią biblioteki STM 32. W plikach tych zdefiniow ane są adresy rejestrów w przestrzeni w ejścia-wyjścia, makra obudow ujące instrukcje A sem blera, numery przerwań i przeróżne stale potrzebne, aby program ow anie było wygodniejsze. Przykładowo, użyty wyżej port E i rejestry, za pom ocą których funkcje biblioteczne odw ołują się do niego, są zdefiniowane następująco: ffdefine 10 volatile typedef struct f _ I 0 uint32_t CRŁ; 10 uint32_t CRH; 10 uint32_t IDR; 10 uint32_t ODR; _ X 0 uint32 t BSRR; 10 uint32_t BRR; _ I 0 uint32 t LCKR; GPIOJTypeDef; ffdefine PERIPH_BASE {(uint32_t 0x40000000 ifdefine APB2PERIPHJ3ASE (PERIPHJBASE + 0x10000 ffdefine GPIOE_BASE (APB2PERIPH_BASE + 0x1800} ffdefine GPIOE ((GPIOJTypeDef * GPIOE_BASE Pow yższe definicje sprawiają, że np. do rejestru w yjściowego ODR (ang. output data register portu E możemy się odwołać, pisząc: GPIOE->ODR Taki bezpośredni sposób odw oływ ania się do rejestrów w ejścia-w yjścia będziemy jeszcze wykorzystywać w dalszej części lego rozdziału. Innym przykładem są, zdefiniowane w pliku core_cm3.h, funkcje w łączające i wyłączające przerwania: static inline void enable_irq( ( asm volatile ("cpsie i"; static inline void disable_irq( { asm volatile ("cpsid i"; Te i inne podobne makra pojawią się w rozdziale 3. 3.6. Plik Iibstm 32f10x.a Do każdego przykładu musimy dołączyć właściwe pliki biblioteczne - ten przykład wym aga plików stm 32fl0x_gpio.c i stm 32fl0x_rcc.c. Zastanawianie się, które pliki są niezbędne w konkretnym program ie i dołączanie do każdego projektu innego zestawu plików bibliotecznych jest czasochłonne i nużące. Lepszym rozwiązaniem jest skom pilowanie raz w szystkich dostarczonych plików źródłowych biblioteki STM 32 i zebranie ich w jednym binarnym pliku bibliotecznym, który potem tylko konsolidujem y z plikami przykładu. D odatkow ym zyskiem jest skrócenie czasu rekom pilacji, jeśli modyfikujemy przykład - pliki biblioteczne nie są rekom pilowane. Plik Ubsim32f]0x.a zaw iera w łaśnie skom pilowaną bibliotekę STM 32, którą możem y konsolidować z plikami przykładów. Plik U bstm 32fl0x.a m usim y utw o rzyć sami - powstaje on przez skom pilowanie w szystkich plików z rozszerzeniem c z następujących katalogów:
20 I. Podstawy 1.3. Przykład Ja - miganie diodami iwiecqcymi 21 -./'st/u braries/stm 32FI0x_StdPeriph_D river/src, -./st/libraries/stm32_etj-j_driver/src, - Jst/Libraries/C M SlS/C M 3/CoreSupport, - Jst/Libraries/C M SIS/C M 3/D evicesttpport/st/stm 32F 1 Ox. Pliki nagłówkowe biblioteki znajdują się w katalogach: - Jst/Libraries/SI'M 32F JOx_StdPeriph_Driver/inc, -./.st/libntries/stm 32_ETJJ_D river/inc, -./.sl/libraries/c M SIS/C M 3/CoreSupport, - Jst/Libraries/C M SIS/C M 3/D evicesupport/sizstm 32F 10x. Ponadto potrzebny jest jeszcze plik nagłów kow y stm32flox_conf.li, który jest w katalogu./examples/include. Przydatne opcje kom pilatora opisuję szczegółowo w rozdziale 1.4. 1.3.7. Plik stm32f10_conf.h Plik stm 32f]0_coiifh znajduje się w katalogu./exam ples/include. W zamyśle tw órców biblioteki STM 32 ma on być centralnym m iejscem konfigurow ania opcji projektu. Zaleca się, aby w nim um ieszczać dyrektyw y #in c lu d e w szystkich używanych plików stm32j'10x_*.h oraz definicje zw iązane z konfiguracją sprzętu, zestawu uruchom ieniow ego itp., czyli definicje zależne od schem atu elektrycznego. Niestety takie rozw iązanie się nie sprawdza. Jak widać na rysunku 1.2, każdy z plików stm 32flO x_*.h w łącza plik stm 32fI0_conf.h - pośrednio poprzez plik stm32j'i0x.h. Zatem w łączanie plików stm 32fI0x_*.h w pliku stm 32fl O_conf. h pow oduje pow stanie cykli w łączeń plików nagłów kow ych. K om pilator sobie z nimi radzi. Jednak przy tw orzeniu w iększych projektów, w celu skrócenia czasu rekom pilacji, stosujem y zw ykle narzędzia, które w ykrywają zależności m iędzy plikami. Dzięki temu po edycji plików źródłowych w ykonyw ana jest rekom pilacja przyrostow a - kom pilow ane są tylko zm ienione pliki i pliki od nich zależne. W ystępowanie cyklu w łączeń plików nagłów kow ych jest błędem, gdyż w prowadza zbędne zależności m iędzy plikami i m oże pow odow ać niepotrzebne kom pilow anie wielu plików. Ponadto zależność plików nagłów kow ych biblioteki STM 32 od pliku sttn32fjo_conf.lt sprawia, że jakakolw iek zm iana w nim w ym aga przekom pilow ania tej biblioteki, co jest nieuzasadnione - biblioteka nie pow inna zależeć od plików nagłów kow ych projektu. Błędem jest też w łączanie w szędzie w szystkich m ożliw ych plików nagłów kow ych, gdyż niepotrzebnie w ydłuża czas kom pilow a nia projektu. Z wyżej om ówionych powodów zrezygnow ałem w tej książce z wykorzystywania pliku stin32fj0_conf.h. Stosuję tradycyjne podejście i w każdej jednostce translacji (dla języka C jest to zw ykle pojedynczy plik źródłowy z rozszerzeniem c włączam tylko te pliki, które są tam niezbędne. N iestety pliku stm 32f 10_conf.lt nie można się pozbyć całkowicie i trzeba go dostarczyć, gdyż zaw iera definicję makra a s s e r t_ param, które jest używane przez bibliotekę STM 32. M akro to służy do sprawdzania poprawności argum entów w funkcjach bibliotecznych. M oże to być w ykorzystane na etapie urucham iania programu, ale w wersji ostatecznej takie sprawdzanie raczej należy wyłączyć, gdyż zw iększa rozm iar kodu w ynikowego i wydłuża czas wykonyw ania programu. Na potrzeby tej książki plik stm32fj O_conf.il zawiera tylko puste makro: idefine assert_param(expr ((void0 1.3.8. Pliki board_def.li i board_defs.h Pliki bounl_def.h i boayd_defs.h są moją propozycją rozwiązania problemu konfigurow ania elem entów projektu zależnych od schem atu elektrycznego. Plik bottrd_ defs.li znajduje się w katalogu./exam ples/include i zawiera definicje stałych dla poszczególnych wariantów sprzętu: idefine BUTTERFLY 10700 idefine PROTOTYPE 10701 idefine ZL29ARM 10729 Idefine STE100P 100 idefine ZL3ETH 300 Plik baaixl_defs.liyml włączany przez plik bocird_def.li, który definiuje używany w projekcie wariant sprzętu. Przykładowo w katalogu./exaniples/incliide/zl29ann+zl3elli jest plik boanl_defh określający, że chcem y użyć zestawu ZL29ARM z modułem ethernetowym ZL3ETH. Zawiera on definicje: Idefine BOARDTYPE ZL29ARM Idefine ETI1 B0ARD ZL3ETH Plik board_ defh jest.włączany każdorazowo, gdy w którym ś pliku źródłowym trzeba uzależnić jakieś definicje od wariantu sprzętu, jak to pokazałem w rozdziale 1.3.3. Mamy dw ie możliwości użycia tego pliku. Po pierwsze, możemy podać kom pilatorowi, żeby poszukiwał plików nagłów kow ych m.in. w katalogu./exam ples/ inclttde/zl29ann+zl3eth. Dla każdego projektu możemy indywidualnie ustawić tę ścieżkę. Dla każdego wariantu sprzętu tworzym y osobny podkatalog z inną wersją pliku board_def.h. Po drugie, możem y po prostu um ieszczać w bieżącym katalogu projektu właściwy plik board_defli. Trzeba wtedy pamiętać, żeby przy włączaniu ujm ować jego nazwę w cudzysłowy, co w ym usza poszukiwanie pliku nagłówkowego najpierw w katalogu bieżącym. W łaściwa dyrektywa preprocesora powinna zatem wyglądać lak: iinclude "board_def.h" K olejnym zadaniem pliku board_def.hj e s t weryfikacja spójności definicji wymaganych przez bibliotekę STM32. Użycie tej biblioteki wymaga zdefiniowania trzech stałych preprocesora języka C. Pierwsza z nich to stała USE_STDPERIPH_DRIVER oznaczająca właśnie, że chcem y korzystać ze Standard Peripherals Library, która jest częścią biblioteki STM 32. Druga stała definiuje model mikrokontrolera. STM 32F107 należy do linii zorientowanej na kom unikację (ang. connectivity line i odpow iednia stała to STM32F10X_CL - dw ie ostanie litery nazwy są skrótem angielskiej nazwy tej linii mikrokontrolerów. Trzecia stała HSE_VALUE definiuje częstotliwość w hercach kwarcu, którym taktujem y mikrokontroler. Definicje tych trzech stałych muszą być w idoczne we w szystkich plikach nagłówkowych i źródłowych biblioteki STM 32 oraz w plikach źródłowych projektu, w których włączane są pliki nagłów kow e tej biblioteki. Ponadto definicje te muszą być wszędzie jednakow e. Najw łaściwszym rozwiązaniem byłoby zdefiniow anie tych stałych w jakim ś pli
22 I. Podstawy 1.4. Kompilowanie przykładów 23 ku nagłówkowym. Z oczyw istego pow odu nie może to być board_def.h, bo nie jest on włączany przez pliki biblioteki STM 32. M ógłby to być plik stm 32fl(_conf h, ale niestety jego w łączenie jest uzależnione od wcześniejszego zdefiniow ania stałej USE_STDPERIPH_DRIVER, a ponadto pozostałe dwie stale są używane w pliku slm 32fl0x.h jeszcze przed w łączeniem w nim pliku stm 3 2 fl0 _ co n fh. Jedynym rozwiązaniem jest zdefiniow anie tych trzech stałych jako argum entów kompilatora, jak opisuję to w rozdziale 1.4. Żeby jednak uniknąć niespójności w ich definicji, co może powodować błędne działanie program u, dobrze jest zw eryfikować te definicje w pliku board_def.lv. łifndef USE_STDPERIPH_DRIVER łerror USE_STDPERIPH_DRIVER not defined ffendif iifndef STM32F10X_CL fierror STM32F10X_CL not defined ffendif Iifndef HSE_VALUE fierror HSE_VALUE not defined ffendif ffif HSE_VALUE!= 10000000 terror ZL29ARM board uses 10 MHz external quarz, tendif Na koniec należy zwrócić uwagę, że w wersjach Standard Peripherals Library starszych niż 3.2.0 stała HSE_VALUE nazyw ała się HSE_Value. Trzeba wtedy za pom ocą argum entu kom pilatora zdefiniow ać wartość stałej HSE_Value, a przed pierwszym użyciem HSE_VALUE dodać definicję: łdefine HSE_VALUE HSE_Value! 1.3.9. Plik s tdinth Plik stdint.h jest częścią standardow ej biblioteki języka C i znajduje się w katalogu ze standardowymi plikami nagłów kow ym i. Standardowa biblioteka jest zwykle dostarczana razem z zestawem narzędzi (ang. toolchain lub środowiskiem programistycznym (IDE, ang. integrated developm ent environment. W tej książce używam biblioteki Newlib - popularnej im plem entacji standardowej biblioteki C na mikrokontrolery. Plik stdint.h definiuje m.in. typy calkow itoliczbowe uniezależniające im plem entację od architektury procesora, np. typy: in t8 _ t, u in t8 _ t, in t l6 _ t, u in tl6 _ t, in t3 2 _ t, u in t3 2 _ t. D efiniuje też makra do obsługi tych typów. Potrzeba takich definicji wynika z tego, że język C pozostawia pewną sw obodę jego implementatorom co do rozm iarów poszczególnych typów. Przykładowo typ i n t w architekturach 16-bitowych ma zwykle 16 bitów, a w architekturach 32- i 64-bitowych - 32 bity. Z kolei typ long w architekturach 16- i 32-bitowych ma zwykle 32 bity, a w architekturach 64-bitow ych - 64 bity. 1.3.10. Plik startup_stm32_cld.c Plik startup_stm 32_cld.c znajduje się w katalogu./exam ples/src i zawiera definicję wektora przerwań oraz przykładową procedurę startową, w ykonywaną przed w y wołaniem funkcji main programu. Najpraw dopodobniej najlepszym rozwiązaniem jest dołączenie do projektu pliku startow ego dostarczonego wraz z używ anym zestawem narzędzi. Plik ten ma zw ykle nazw ę podobną do tej w tytule podrozdziału. Przykłady plików startow ych dla różnych kompilatorów, napisane w Asemblerze, znajdują się w archiwum w katalogu Jst/Libraries/C M SlS/C M 3/D evicesupport/st/ STM 32F10x/startup. Ważne jest, aby użyć pliku właściwego do zastosow anego m i krokontrolera. Pliki startow e dla STM 32F107 m ają w końców ce nazwy litery cl (ang. connectivity line. Poznanie zawartości pliku z procedurą startową nie jest konieczne do zrozum ienia zawartości książki. Tym bardziej nie jest wymagana też um iejętność pisania procedury startowej. Dla zainteresowanych opisuję te zagadnienia w rozdziale 1.8. 1.4. Kompilowanie przykładów Zakładam, że Czytelnik ma ju ż jakieś dośw iadczenie w program owaniu mikrokontrolerów ARM w języku C, ma zainstalowane jakieś ulubione środowisko program istyczne i umie w nim skonfigurow ać projekt. D latego nie będę rozwodził się na tem at instalowania narzędzi program istycznych i ograniczę się do wyjaśnienia ustawień kom pilatora i konsolidatora. W szystkie darm owe zestawy narzędzi i wiele kom ercyjnych wykorzystuje oprogram ow anie stworzone w ramach projektu GNU, czyli kom pilator C/C++ z pakietu GCC (ang. GNU Compiler Collection oraz pakiet GNU Binary Utilities, w skrócie nazyw any Binutils, zawierający konsolidalor Id oraz inne pom ocnicze programy do m anipulow ania plikami binarnymi, jak np. ar, objcopy, objdum p itd. Do kom pilow ania opisywanych w tej książce przykładów używ ałem właśnie tych narzędzi i dlatego ograniczam się tylko do opisu ich opcji. Inne narzędzia mają bardzo podobny zestaw opcji i C zytelnik z łatwością dobierze odpow iednie ustawienia. Dla zw olenników programu make i kompilow ania z linii poleceń zam ieszczam w archiwum w katalogu./m ake zestaw plików m akefile - zawartość tego katalogu opisuję w dodatku. W środowisku program istycznym trzeba ustawić katalog./exam ples/src jako miejsce, gdzie kom pilator ma szukać plików źródłowych. Przypom inam, że jest to ścieżka względem miejsca, gdzie zostało rozpakow ane archiwum z przykładami. Tę i w szystkie kolejne ścieżki trzeba dopasow ać do konkretnego systemu. Podstawow e opcje kom pilatora zam ieszczone są w tabeli 1.4. Każdy plik źródłowy języka C (jednostka translacji kom pilujem y do binarnego pliku pośredniego, który ma tę samą nazwę, co odpow iedni plik źródłowy, a tylko rozszerzenie zmienione z c na o. Z pewnością warto włączyć w ypisyw anie przez kom pilator wszystkich ostrzeżeń, no i oczyw iście należy czytać te ostrzeżenia. Sam fakt, że program się skom pilował, nie daje żadnej gwarancji jego popraw ności. Uważna analiza każdego ostrzeżenia i zastanowienie się nad przyczyną, dla której kom pilator je wypisał, może uchronić przed wielogodzinnym poszukiwaniem błędu w programie. Dobry styl program ow ania to między innymi dbanie o to, aby w raporcie kompilacji nie Tab. 1.4. Podstawowe opcje kompilatora -c -Wa 1.1 Opcja Znaczenie Tylko kompilacja do pliku pośredniego (z rozszerzeniem o Włączenie generowania wszystkich ostrzeżeń -g Włączenie informacji dla debuggera -mcpu=cortex-m3 -mthumb Wybór architektury mikrokontrolera, rdzeń Cortex-M3 Zestaw instrukcji Thumb lub Thumb-2
24 1. Podstawy 1.4. Kompilowanie przykładów 25 Tab. 1.5. Opcje kompilatora sterujące optymalizacją -oo -Os o o o i i i Opcja -ffunction-sections -fdata-sections Wyłączenie wszelkich optymalizacji Znaczenie Minimalizacja rozmiaru kodu wykonywalnego Maksymalizacja szybkości wykonywania kodu, większe wartości oznaczają bardziej agresywną optymalizację Każda funkcja w osobnej sekcji Każda zmienna globalna i statyczna w osobnej sekcji pojawiały się żadne ostrzeżenia, oczyw iście nie przez wyłączenie ich wypisywania. Każdy program da się tak napisać, aby kom pilator nie zgłaszał żadnych ostrzeżeń. Co do pozostałych opcji, to z kodu wynikow ego nie musimy usuwać informacji dla debuggera, bo nie są one zapisywane w pamięci program u m ikrokontrolera - debugger jest urucham iany na kom puterze, z którego w ykonuje się program owanie mikrokontrolera. Niezbędne jest w ybranie włas!ciwej architektury m ikrokontrolera i obsługiw anego zestawu instrukcji. M ikrokontrolery z rdzeniem Cortex-M 3 wykonują tyiko zestaw instrukcji Thumb-2. Bardzo w ażne jest wybranie pożądanego poziom u optym alizacji kodu wynikowego. Kom pilator ma bardzo dużo szczegółow ych opcji sterujących procesem optym alizacji. Zwykle wystarczy jednak wybrać między minim alizacją rozm iaru kodu wykonyw alnego a szybkością jego w ykonyw ania za pom ocą opcji z tabeli 1.5. Panuje dość powszechne przekonanie, że program ując mikrokontrolery, należy preferować minim alizację rozm iaru kodu wynikowego. Trzeba jednak pamiętać, że poszczególne warianty optym alizacji nie są całkiem niezależne. M niejszy kod na ogól w ykonuje się szybciej, dlatego zastosow anie opcji -Os wpływa też na przyspieszenie wykonywania programu. Z drugiej strony, ustawienie jednej z opcji -01, -02 lub -03 w łącza również te optym alizacje, które zm niejszają rozm iar kodu wynikowego i nie spow alniają wykonywania program u, zatem może rów nież przyczynić się do zm niejszenia rozm iaru kodu w ynikow ego. Choć oczyw iście niektóre optym alizacje, takie jak umieszczanie ciała funkcji w miejscu jej wywołania (ang. inline czy rozwijanie pętli, istotnie zw iększają rozm iar kodu wynikowego. Uważam, że dopóki rozm iar dostępnej pamięci na to pozw ala, powinniśm y optym alizować szybkość w ykonywania programu. M inim alizowanie rozm iaru kodu jest uzasadnione, jeśli pozwoli zastosować tańszy model m ikrokontrolera - z m niejszą pam ięcią lub jeśli bez takiej minim alizacji program nie mieści się w największej dostępnej w danej rodzinie m ikrokontrolerów pamięci. Zapew ne jednak najwięcej pamięci m ożna zaoszczędzić przez właściwy dobór struktur danych i algorytmów. Istotną redukcję rozmiaru kodu wynikow ego można też stosunkow o prosto osiągnąć, nie rezygnując z optym alizacji szybkości wykonywania. Okazuje się, że sporo miejsca w pamięci program u zajm uje kod dołączanych bibliotek. Każda biblioteka zaw iera bardzo wiele użytecznych funkcji i zmiennych globalnych, z których nasz program zwykle w ykorzystuje tylko niew ielką część. Sami piszemy też w łasne biblioteki i nie zaw sze wywołujemy wszystkie zaim plem entow ane funkcje w każdym projekcie. Aby pozbyć się tego nieużyw anego kodu, możem y ustawić dw ie ostatnie opcje w ym ienione w tabeli 1.5, a następnie w fazie konsolidow ania programu ustawić opcję odśm iecania (ang. garbage collector - patrz tabela 1.9. U stawienia Tab. 1.6. Miejsca poszukiwania plików nagłówkowych Param elry kompilatora -I./examples/include -I./examples/include/zl29arm+zl3eth -I./st/Libraries/STM32F10x_StdPeriph_Driver/inc -I./st/Librari.es/STM32_ETH Driver/inc -I./st/Libraries/CMSIS/CM3/CoreSupport -I./st/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x -I./lwip-1.3,2/src/.tnclude -I./lwip-1.3.2/src/include/ipv'l Tab. 1.7. Stale konfigurujące bibliotekę STM32 Parametr kompilatora -DUSE STDPERIPH DRIVER -DSTM32F10X CL -DIISE VALUE=10000000 Użyj Standard Peripherals Library Znaczenie Kompiluj dla STM32F105 lub STM32F107 (ang. connectivity line device Zainstalowany jest kwarc 10 MHz te sprawiają, że kom pilator umieści każdą funkcję i zmienną globalną lub statyczną w osobnej sekcji, a konsolidator sprawdzi, do których funkcji i zmiennych są odw ywołania w tekście źródłowym i pozostawi w kodzie wynikowym tylko te funkcje i zmienne, które są rzeczyw iście w ykorzystywane. Należy zaznaczyć, że uzyskanie zadow alającego rezultatu wymaga, aby wszystkie pliki źródłowe poddać tej optymalizacji. Dotyczy to rów nież bibliotek. Są to, oprócz biblioteki STM 32, przede wszystkim libc, czyliistandardow a biblioteka C oraz libm, czyli standardowa biblioteka funkcji m atematycznych. Ponadto należy zadbać o zoptym alizowanie, dostarczanej wraz z GCC, niskopoziom owej biblioteki czasu wykonania libgcc (ang. Iow level runtime library. W libgcc im plem entuje się konstrukcje języka C niewspierane bezpośrednio przez sprzęt - dla Cortex-M 3 jest to np. arytmetyka na typach, które nie są w ym ienione w tabeli 1.2. N ieco więcej o kompilowaniu tych bibliotek piszę w dodatku. Przykłady zam ieszczone w tej książce i wszystkie biblioteki kom pilowałem z następującym i opcjami optymalizacji: -02 -ffunction-sections -fdata-sections W tabeli 1.6 zam ieszczone są param etry kom pilatora ustawiające nazwy katalogów, w których należy poszukiwać plików nagłówkowych. Pierwszy wiersz konfiguruje ścieżkę do katalogu, w którym znajduje się w iększość plików nagłówkowych przykładów. Drugi w iersz w tej tabeli to ścieżka do pliku board_defh. Jak napisałem wyżej, przy omawianiu tego pliku, ścieżka ta może być inna i wskazywać na inny plik board_def.h, konfigurujący inny w ariant sprzętu. Kolejne cztery wiersze zaw ierają ścieżki do plików nagłów kow ych biblioteki STM 32. O statnie dwa wiersze to ścieżki do plików nagłówkowych biblioteki lwip. Omawiam tę bibliotekę dopiero w rozdziale 3, ale warto mieć zawczasu ustawione w szystkie potrzebne ścieżki. W rozdziale 1.3.8 napisałem, że biblioteka STM 32 wym aga zdefiniowania trzech stałych preprocesora. O dpowiednie param etry kom pilatora, definiujące te stale, są wym ienione w tabeli 1.7. Param etry te m uszą być rów nież ustawione podczas kom pilowania plików źródłowych włączających plik board_ d efh. Najlepiej jest ustawić je dla w szystkich plików źródłowych przykładów, czyli wszystkich plików z katalogu Jexam ples/src.
26 I. Podstawy 1.5. Wejścia i wyjścia binarne 27 Tab. 1.8. Katalogi z plikami źródłowymi biblioteki STIVI32 Jst/Ubraries/STM32F10x_StdPeriph_Driver/src./stlLibrarieslSTM32JTH_Driver/src Jst/Libraries/CMSIS/CM3/CoreSupport Jst/LibraneslCIVISIS/Cl/l3/DeviceSupport/ST/STM32F10x Tab. 1.9. Opcje konsolidatora Opcja -mcpu-cortex-m3 -mthumb -nostartfiles -L./make/lib/7.129arm+zi3eth -L./examples/scripts -Tstm32f107vc.Ids -Wl, gc-secfcions Znaczenie Wybór architektury mikrokontrolera Wyłączenie domyślnego pliku z procedurą startową Miejsce poszukiwania plików bibliotecznych Miejsce poszukiwania skryptu konsolidatora Nazwa skryptu sterującego procesem konsolidacji Uaktywnienie odśmiecania - usuwanie zbędnego kodu Aby utworzyć bibliotekę Iibstm 32fl0x.a, trzeba skom pilować do odpow iednich plików pośrednich z rozszerzeniem o wszystkie pliki źródłowe z rozszerzeniem c z katalogów w ym ienionych w tabeli 1.8, a następnie połączyć je w jeden plik biblioteczny. Każde Środowisko program istyczne można tak ustawić, aby wynikiem kompilacji zamiast pliku w ykonyw alnego była biblioteka. M ożna to też zrobić z linii poleceń za pom ocą program u ar, przykładowo: arm-elf-ar -rcs Iibstm32fł0x.a *.o Ostatnim etapem tworzenia programu jest konsolidacja. W tabeli 1.9 zawarto opcje wykorzystywane przy łączeniu (konsolidowaniu, linjkowaniu plików pośrednich i bibliotek do pliku wykonywalnego. Zakładam, że konsolidalor nie jest urucham iany bezpośrednio za pom ocą np. polecenia ann-elf-ld, a poprzez kom pilator poleceniem arm-elf-gcc. Taki sposób urucham iania konsolidatora jest wygodniejszy, gdyż jest wtedy autom atycznie dołączana standardow a biblioteka języka C. Podobnie jak podczas kompilacji, trzeba w yspecyfikować architekturę mikrokontrolera. Jeśli dołączamy nasz plik z procedurą startową, to trzeba w yłączyć dołączanie domyślnej procedury startowej. M ożna też ustawić ścieżkę do miejsca, gdzie poszukiwane mają być nasze biblioteki, jeśli nie znajdują się w katalogu bieżącym. Na razie mamy tylko jedną bibliotekę, czyli Iibstin32fl0x.a, ale w rozdziale 3 utworzymy drugą z im plementacją stosu TCP/IP. Konsolidatorowi trzeba też podać nazwę skryptu sterującego procesem konsolidacji i ścieżkę do katalogu, gdzie ma poszukiwać tego skryptu. Najprawdopodobniej najlepiej jest zdać się na skrypt konsolidatora dostarczony wraz ze środowiskiem programistycznym. Dla zainteresowanych zrozum ieniem, jak działa konsolidator, opisuję w rozdziale 1.8.3 prosty skrypt do architektury Cortex-M 3. Aby skutecznie minim alizować rozm iar generowanego programu, bardzo ważne jest też uaktywnienie opcji odśm iecania w fazie konsolidacji. 1.5. Wejścia i wyjścia binarne M ikrokontroler STM 32F107 ma pięć, oznaczonych literami od A do E, 16-bitowych portów w ejścia-w yjścia. Jak w każdym m ikrokontrolerze, ich konfigurow a nie i dostęp do nich odbyw a się za pom ocą rejestrów w przestrzeni adresowej wejść-wyjść. Jest to opisane szczegółow o w [8. N ajw ażniejsze rejestry, z któ- rych będziem y korzystać, przestaw ione są na rysunku 1.3. Dane z wejść czyta się z rejestru ID R (ang. input data register, który jest rejestrem tylko do odczytu. Stan wejść znajduje się w młodszych 16 bitach tego rejestru. Pozostałe bity nie są używane. Dane w yjściow e zapisuje się do rejestru ODR (ang. output data register. Dane zapisane do tego rejestru m ogą być rów nież odczytyw ane. Korzysta się przy tym tylko z młodszych 16 bitów rejestru. Pozostałe bity są zarezerwow ane. A tom ow e, nieprzeryw ane obsługą przerw ań, operacje na poszczególnych bitach można w ykonać za pom ocą rejestrów BRR (ang. bit reset register i BSRR (ang. bit set/reset register, które są rejestram i tylko do zapisu. Starszych 16 bitów rejestru BRR nie używ a się. Zapisanie jedynki na bicie B R x rejestru BRR zeruje odpow iedni bit O D R x w rejestrze ODR. Zapisanie jedynki na bicie B S x rejestru BSRR ustawia odpow iedni bit O D R x w rejestrze ODR. Zapisanie jedynki na bicie B R x rejestru BSRR zeruje odpow iedni bit O D R x w rejestrze ODR. Zapisyw anie zera na bitach B S x i B R x nie zm ienia w artości bitów rejestru ODR. Jeśli w rejestrze BSRR oba bity B S x i B R x są jednocześnie zapisyw ane w artością 1, to pierw szeństw o ma bit B S x - odpow iedni bit O D R x zostaje ustawiony. Tryb pracy portów w ejścia-w yjścia konfiguruje się za pom ocą rejestrów CRL (ang. configuration register Iow i CRH (ang. configuration register high. Każde w yprowadzenie można skonfigurow ać indyw idualnie. Dla każdego wyprowadzenia przewidziano po cztery bity konfiguracyjne: CNFxl, CNFxO, MODExl, MODExO. Rejestr CRL konfiguruje w yprowadzenia 0...7, a CRH - wyprowadzenia 8...15. Dostępne tryby pracy portów w ejścia-w yjścia i odpow iadające im ustawienia bitów konfiguracyjnych zam ieszczone są w tabeli 1.10. Należy zwrócić uwagę, że rezystory podciągające do zasilania i ściągające do masy są ustawiane za pom ocą rejestru ODR. W przypadku ustawienia funkcji alternatywnej w yprowadzenie może być też dw ukierunkowe (ang. bidirectional - wtedy kierunkiem w yprowadzenia steruje w łaściwy uktad peryferyjny. Gdy w yprowadzenie jest ustawione w trybie wyjściowym, bity MODExl i MODExO określają m aksym alną częstotliw ość sygnału na wyjściu, przy obciążeniu 50 pf i założeniach, że w spółczynnik wypełnienia sygnału wynosi 45-55%, a czasy narastania i opadania (m ierzone standardow o jako zmiana między 10% a 90% poziom u wysokiego nie przekraczają w sum ie 2/3 okresu sygnału. U stawienia tych bitów podane są w tabeli 1.11. Tab. 1.10. Tryby pracy portów wejścia-wyjścia, Tryb pracy Nazwa anglelska ;jtópexif MODExO. CNFxl CNFxO ODRx, Wejście analogowe analog input 0 0 0 0 Nieistotne Wejście pływające (stan po resecie floating input 0 0 0 1 Nieistotne Wejście ściągane do masy pull-down input 0 0 1 0 0 Wejście podciągane do zasilania pull-up input 0 0 1 0 1 Wyjście przeciwsobne push-pull output 0 0 0 lub 1 01 Wyjście z otwartym drenem open-drain output 10 0 1 0 lub 1 Funkcja alternatywna, alternate lunction push- 11 wyjście przeciwsobne pull output (patrz tabela 1 0 Nieistotne Funkcja alternatywna, alternate function open- 1.11 wyjście z otwartym drenem -drain output 1 1 Nieistotne
28 I. Podstawy 1.5. Wejścia i wyjścia binarne 29 31 30 29 2Q 27 26 25 24 23 22 21 20 19 18 17 16 Zarezerwow ane 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 IDR15 IDR14 IDR13 IDR12 IDR11 IDR10 IDR9!DR8 IDR7 IDR6!DR5 IDR4 IDR3 IDR2 IDR1 IDRO r r r r Tab. 1.11. Maksymalna częstotliwość sygnału wyjściowego M0DExl MODExO Częstotliwość 0 10 MHz 0 2 MHz 1 1 50 MHz 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 Zarezerwow ane 15 14 13 12 11 10 9 8 7 6 5 4 3 2 0 ODR15 ODR14 ODR13 ODR12 ODR11 ODR10 ODR9 ODR8 ODR7 ODR6 ODR5 ODR4 ODR3 ODR2 ODR1 ODRO rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw BRR 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 Zarezerwow ane 15 14 13 12 11 10 9 8 7 6 5 4 3 2 0 BR15 BR14 BR13 BR 12 BR11 BR10 BR9 BR8 BR7 BR6 BR5 BR4 BR3 BR2 BR1 BR0 w w w w w w w w w w w w w w w W 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 BR15 BR14 BR13 BR12 BR11 BR10 BR9 BR8 BR7 BR6 BR5 BR4 BR3 BR2 BR1 BR0 w w w w w w w w w w w w w w w W 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 BS15 BS14 BS13 BS12 BS11 BS10 BS9 BS8 BS7 BS6 BS5 BS4 BS3 BS2 BS1 BSO w w w w w w w w w w w w w w w W 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 CNF7[1:0 MODE7(1:0] CNF6(1:0j MODE6[1:0] CNF5{1:0! MODE5[1:0j CNF4[1:0] MODE4(1:0} rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw 15 14 13 12 11 10 9 8 7 6 5 4 3 2 0 CNF3 1:0] MODE3[1:0] CNF2[1:0] MODE2(1:0] CNF1(1:0] MODE1[1:0 CNF0(1:0 MODE0{1;0] rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw CRH 31 30 29 26 27 26 25 24 23 22 21 20 19 18 17 16 CNF15 1:0j MODE15[1:Oj CNF14 1:0] MODE14[1:0] CNF 13f 1:0] MODE13f1:QJ CNF12]1:0] MODE12[1;0] rw [ rw rw rw rw j rw rw i rw rw I rw rw rw rw rw rw rw 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 CNF11[1 :QJ MODE11[1:0J CNF10 1:0] MODE10[1:0J CNF9(1:0J MODE9{1:OJ CNF8[1;0] MODE8[1:0] rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw j rw Rys. 1.3. Podstawowe rejestry portów wejścia-wyjścia Zwykle dużo w ygodniejsze jest konfigurow anie portów w ejścia-wyjścia za pomocą funkcji bibliotecznej niż operow anie bezpośrednio na rejestrach C R L i CRH. W celu wygodnego konfigurow ania portów biblioteka STM 32 dostarcza strukturę typu G PIO_InitTypeD ef. typedef struct { uintl6_t GPIG^Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIOJtode; GPIO_IniŁTypeDef; Jako wartość składowej GPI0_Pin podaje się m askę bitową wyprowadzeń, które są konfigurow ane. Najlepiej jest używać alternatywy następujących stałych, zdefiniowanych w bibliotece STM32. idefine GPIO_Pin_0 ( uintl6_t0x0001 idefine GPIO_Pin l ((uintl6_t 0x0002 idefine GPI0_Pin_2 (uin116 t 0x0004 Idefine GPI0_Pin_15 (Uintl6_t 0x8000 idefine GPIO_Pin_All ((uintl6_t 0xFFFF Składowa GPIO_Speed określa maksymalną prędkość pracy wyprowadzenia. Dopuszczalne wartości zdefiniowane są jako typ wyliczeniowy GPIOSpeed_TypeDef. typedef enum { GPIO_Speed_10MHz - 1, GPIO_Speed_2MHz, GPIO~Speed_50MHz } GPIOSpeed_TypeDef; Składow a GPIO_Mode określa tryb pracy wyprowadzenia. Stale reprezentujące poszczególne tryby pracy zdefiniow ano w bibliotece STM 32 jako typ wyliczeniowy GPIOMode_TypeDef. Nazwy poszczególnych trybów jednoznacznie je identyfikują, a dodatkow o dla klarowności kolejność definicji na poniższym wydruku odpowiada kolejności opisów w tabeli 1.10. typedef enum { GPIO Mode_AIN = 0x0, GPIO_Mode_IN_FŁOATING = 0x04, GprO_Mode_IPD = 0x28, GPIO_Mode_IPU = 0x48, GPI0J4ode_0ut_PP 0x10, GPI0_Mode_0ut_0D 0x14, G PI0~Mode_AF_P P = 0x18, GPI0J4ode_AF_0D 0xlC GPIOMode_TypeDef; Aby skonfigurow ać wyprowadzenia portu, należy zainicjow ać strukturę typu GPIOMode_TypeDef i wywołać funkcję G P I0_Init, przekazując jako jej parametry nazwę portu i adres tej struktury, jak przedstaw iono to w rozdziale 1.3.3. Do zmie-
30 1. Podstawy 1.6. Wyświetlacz ciekłokrystaliczny 31 niania stanu wyjść oraz czytania stanu wejść i wyjść biblioteka STM 32 udostępnia następujące funkcje. uintl6_t GPIO_ReadInputData(GPIO_TypeDef *GPI0x; uint8 t GPIO_ReadInputDataBit(GPIG_TypeDe *GPI0x, uintl6_t GPI0_Pirt; uintl6_ł GPIO_ReadOutputData{GPIO_TypeDef *GPI0x; uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef *GPI0x, uintl6_t GPI0_Pin; void GPIO_ResetBits(GPIOJTypeDef *GPlOx, uint!6_t GPIO_Pin}; void GPIO_SetBits(GPIOJTypeDef *GPX0xf uintl6_t GPIO_Pin; void GPIO_Wrifce(GPIOJTypeDef *GPI0x, uint!6_t PortVai; void GPI0_WriteBit(GPIOJTypeDef *GPlOx, uintl6_t GPICJPin, BitAction BitVal; Nazwy tych funkcji są sam otłum aczące się. Param etr GPIOx musi w skazywać port w ejścia-w yjścia i przyjm uje jedną z wartości GPIOA, GPIOB, GPI0C itd. Param etr GPI0_Pin to maska bitowa określająca w yprowadzenia - najlepiej jest stosow ać alternatywę opisanych wyżej stałych GPI0_Pin_x, gdzie x jest num erem w yprowadzenia. Funkcje G PIO_ReadInputDataBit i GPIO_ReadOutputDataBit zw racają 1, gdy odpow iednio co najmniej jedno z wyspecyfikowanych wejść lub wyjść ma stan wysoki. Funkcja GPIO WriteBit pow inna właściwie nazywać się GPIO_Wri.teBi.ts, gdyż umożliwia jednoczesne w yzerow anie lub ustawienie wszystkich bitów w yspecyfikowanych za pom ocą param etru GPI0_Pin. Param etr B itv al przyjm uje wartość Bit_RESET lub Bi.t_SET, jak to już w idzieliśm y w rozdziale 1.3.3. Biblioteka STM 32 nie dostarcza niestety atom owej (niepodzielnej operacji, pozwalającej jednocześnie na wyzerowanie pewnych bitów i ustaw ianie innych. Taka operacja będzie nam potrzebna w następnym przykładzie i tam opiszę jej implementację. 1.6. Wyświetlacz ciekłokrystaliczny W kilku przykładach w ykorzystuję w yświetlacz ciekłokrystaliczny (LCD, ang. liquid crystal display. Zastosow ałem w yświetlacz graficzny typu W G 12864A z matrycą m onochrom atyczną szerokości 128 i wysokości 64 pikseli. W yświetlacz ten zbudowany jest w oparciu o dw a sterow niki kom patybilne z układem KS0108. Pojedynczy sterow nik obsługuje połów kę wyświetlacza, czyli fragment obrazu 64 na 64 piksele. O rganizację jego pam ięci obrazu pokazano na rysunku 1.4. Pamięć obrazu jest podzielona na 8 bloków po 64 bajty każdy. W dokum entacji w yświetlacza bloki nazywane są stronami (ang. page, co w odniesieniu do w yświetlaczy w y daje mi się bardzo mylące. Każdy blok odpow iada za wyświetlanie jednego wiersza składającego się z 8 linii. Pojedynczy bajt w bloku steruje w yświetlaniem kolum ny składającej się z 8 pikseli. Jeśli bit jest ustawiony, to piksel jest ciemny, a w przeciw nym przypadku - jasny. Są też produkow ane m odele wyświetlaczy, w których jest odw rotnie - obraz jest w yświetlany w negatywie. N ajmniej znaczący bit odpowiada pikselowi w górnej linii wiersza, a najbardziej znaczący - dolnej. Sterownik przechowuje trzy zm ienne (adresy. X jest adresem (num erem bieżącego wiersza. Y jest adresem (num erem kolumny. Z jest numerem linii (a nie w iersza, która jest w yświetlana jako pierwsza na górze ekranu. Zm ienna Z um ożliw ia realizację przewijania obrazu, ale nie będziem y z tego korzystać w przykładach. Zapis i odczyt pamięci obrazu zaw sze dotyczy bajtu wskazywanego przez zm ienne X i Y. Po zapisie lub odczycie adres Y jest autom atycznie zw iększany o jeden. - adres bloku (wiersza i Numer bitu * 0 1 LSB 0 1 2 3 4 5 6.MSB 7 LSB 0 1 2 3 4 5 6.M SB 7 LSB 0 1 2 3 4 5 6.M SB 7 Rys. 1.4. Organizacja pamięci obrazu LCD Z - adres pierwszej wyświetlanej llnll Y - adres ko lu m n y Blok 0 Blok 1 Blok 7 W yświetlacz W G 12864A podłącza się w zestawie ZL29ARM do złącza 20-stykowego. Trzeba też pamiętać o przestaw ieniu zworki DISPLAY w pozycję GRAPII. Schem at pokazujący podłączenie poszczególnych w yprowadzeń wyświetlacza do wyprowadzeń mikrokontrolera jest przedstaw iony na rysunku 1.5. W yświetlacz S T M 32F 107V C PEO PE1 Y:PE2 ÍÍÍPÉ3 Y PE4 n:pe5 PE6 PE7 PE8 Y PE9 PE10 PE11 PE12 nrst 98 0 1 9 2 10 3 11 4 12 5 13 38 14 39 4 40 6 41 5 42 15 43 16 14. - 17 LC D DO D1 D2 D3 D4 D5 D6 D7 RS E R /ffl CS1 C S2 i 2'^ R E S E T Rys. 1.5. Schemat podłączenia LCD R5T V SS V D D V EE V O LED A LED K 18-0 + 5 V D IS PLA Y JP 3 G R A PH C H A R 10k 63 _ 15 63
32 /. Podstawy 1.7. Przykład Ib - test wyświetlacza ciekłokrystalicznego 33 s 1000 ms Tab. 1.12. Polecenia sterownika KS0108 J 450 ms ^ 450 ms RS R/W W i D6 D5 D4 D3 D2 D1 DO Opis 0 0 0 0 1 1 1 1 1 C C = 0 - wyłącz wyświetlanie, C = 1 - włącz wyświetlanie 0 0 0 1 Y Ustaw adres kolumny Y S 140 ms > 10 ms 0 0 1 0 1 1 1 X Ustaw adres bloku, numer wiersza X 0 0 1 1 Z Ustaw numer początkowej linii Z R/W 0 1 B 0 C R 0 0 0 0 Czytaj status: B = 0 - gotowy, B = 1 - zajęty, C = 0 - wyświetlanie włączone, C = 1 - wyłączone, R = 0 - aktywny, R = 1 - w trakcie zerowania CS1, CS2 r RS \ r 1 0 zapisywany bajt Zapisz bajt do pamięci obrazu 1 1 odczytany bajt Czytaj bajt z pamięci obrazu D 0-7 Rys. 1.6. Cykl zapisu LCD W G12864A komunikuje się z mikrokontrolerem za pom ocą interfejsu rów noległego, który ma osiem dw ukierunkow ych linii danych D 0...D 7, linię RS wyboru między transm isją danych a w ysyłaniem komendy lub odczytem statusu, linię w y boru zapisu lub odczytu R/W, linię zezw olenia E (ang. enable oraz linię zerow ania RST. Zawiera też linie wyboru sterow nika CS1 i CS2 (ang. chip select, na których stanem aktywnym jest stan wysoki. Na rysunku 1.6 pokazano cykl zapisu oraz zależności czasowe podane w nocie katalogowej sterow nika KS0108, a na rysunku 1.7 - cykl odczytu. R/W D L a 1000 ms a 450 ms > 140 ms a 10 ms W tabeli 1.12 zestawione są polecenia w ykonywane przez sterownik wyświetlacza. Pierwsze polecenie włącza i wyłącza w yświetlanie. Dane z pamięci obrazu są przesyłane na ekran dopiero po w łączeniu w yświetlania. Kolejne trzy polecenia ustawiają wartości adresów X, Y i Z. Przedostatnie polecenie zapisuje dane do pamięci obrazu. Ostatnie polecenie odczytuje dane z pamięci obrazu. Po wydaniu każdego z wym ienionych wyżej poleceń sterow nik zm ienia status na zajęty. Przed wysianiem kolejnego z tych poleceń trzeba poczekać, aż sterow nik powróci do stanu gotowy. Stan sterow nika sprawdza się za pom ocą polecenia Czytaj status. Podczas operacji zapisu (bit R/W wyzerowany linie danych D 0...D 7 po stronie m ikrokontrolera m uszą być ustawione jako wyjścia, a podczas odczytu (bit R/W ustawiony - jako wejścia. 1.7. Przykład 1 b - test wyświetlacza ciekłokrystalicznego Przykład ten dem onstruje użycie opisanego w poprzednim podrozdziale graficznego wyświetlacza ciekłokrystalicznego. W tabeli 1.13 zawarto nazwy plików, które należy podać kom pilatorowi jako pliki źródłowe tego przykładu. Ponadto zawiera nazwy tych plików nagłów kow ych wykorzystywanych w tym przykładzie, które znajdują się w podkatalogu./exam ples/include. W kolejnych podrozdziałach opisuję zawartość poszczególnych plików. Zarów no w tym, jaki i w kolejnych przykładach nie opisuję plików, które opisałem już poprzednio. Tab. 1.13. Pliki przykładu 1b CS1, CS2 ' RS ł D 0-7 Rys. 1.7. Cykl odczytu LCD _ A _,a 20 ms -v/ e x jc d.c fonl5x8.c startup _stm 32jtld.c Sont5x8.h Źródłowe I biblioteczne board_lcd_ks0108.c b o a rd jle ł.h board_defs.h board Icd.h Nagłówkowe u tiljlelay.c utiljod. c uliljdelay.h u liljc d.li Ilbstm32f10x.a stm32hox_conl.h
34 I. Podstawy 1.7. Przykład Ib - test wyświetlacza ciekłokrystalicznego 35 Pliki boardj c d.h i board_lcd_ks0108.c Plik boctrdjlcd.h znajduje się w katalogu./exam ples/include. Definiuje on prosty interfejs program istyczny do obsługi wyświetlacza w trybie tekstowym. Interfejs len z założenia ma być niezależny od zastosow anego w yświetlacza i każdy typ w y św ietlacza powinien bez problemu móc go zrealizować. Oczywiście poszczególne typy w yświetlaczy będą różnić się liczbą wypisyw anych wierszy tekstu i liczbą znaków w wierszu. Na opisanym w rozdziale 1.6 wyświetlaczu będziem y w ypisywać 8 wierszy tekstu po 21 znaków w wierszu. Interfejs, zdefiniowany w pliku board J c d. /?, udostępnia tylko bardzo skrom ny zestaw funkcji: void LCDconfigure(void; void LCDciear(void; void LCDgoto{int textline, int charpos; void LCDputchar(char c; void LCDputcharWrap(char c; Jak nazwa wskazuje, funkcja LCDconfigure inicjuje wyświetlacz. M usi ona zostać w ywołana, zanim zostanie w ywołana jakakolw iek inna funkcja odw ołująca się do w yświetlacza. Funkcja LCDciear czyści (zeruje zaw artość pamięci obrazu. Funkcja LCDgoto ustawia bieżącą pozycję znaku (kursora, ale żaden znak kursora nie jest wyświetlany. Funkcje LCDputchar i LCDputcharWrap wypisują w pozycji kursora znak o podanym kodzie ASCII i przesuw ają kursor o jeden znak w prawo. Różnią się tym, że jeśli znak nie mieści się ju ż w wierszu, to funkcja LCDputcharWrap przesuwa kursor na początek kolejnego w iersza (zawija wiersz i tam wypisuje podany znak. Natomiast funkcja LCDputchar traktuje wienjz, jakby byl bardzo długi, ale tekst niem ieszczący się w wierszu jest ignorowany. Plik boardj c d J c s O l08.c znajduje się w katalogu./exam ples/src. Z aw iera on im plem entację przedstaw ionych w yżej funkcji dla w yśw ietlaczy ze sterow nikam i kom patybilnym i z układem K S0108 i ukryw a w szystkie szczegóły im plem entacji specyficzne dla tego sterow nika. D zięki takiem u podejściu, chcąc zastosow ać inny typ w yśw ietlacza, nie trzeba m odyfikow ać w ielu plików program u - w ystarczy tylko zm odyfikow ać ten plik lub podm ienić go na inny, im plem entujący taki sam interfejs dla posiadanego typu w yśw ietlacza. D latego nie opisuję w szystkich szczegółów im plem entacji, zw łaszcza że obsługa w yśw ietlacza nie jest głów nym tem atem tej książki. Skupiam się tylko na tym, co w ydaje mi się ciekaw e, a C zytelnik zaw sze m oże zajrzeć do archiw um, gdzie znajduje się pełny tekst źródłow y tego przykładu i gdzie zam ieściłem też dodatkow e kom entarze. Biblioteka STM 32 nie dostarcza w ygodnych funkcji do operow ania na dw ukierun-!s kowych liniach danych. W szczególności struktura GPIO_InitTypeDef i funkcja k G PIO _Init są co praw da bardzo w ygodne do konfigurow ania w yprowadzeń, ale ta i w ygoda okupiona jest dodatkow ym narzutem czasow ym - aby obsłużyć w szystkie przypadki, im plem entacja tej funkcji jest stosunkow o skom plikowana. Przy pew nych założeniach upraszczających m ożna to zrobić bardziej efektywnie. Załóżmy, że wyprowadzenia danych w yśw ietlacza rozm ieszczone są po kolei na w spólnym j: porcie. Definiujem y identyfikator portu DATA_PORT, do którego podłączone są linie danych w yświetlacza, i maskę bitow ą D0 D7 dla tych w yprowadzeń. Pozycję pierw szego w yprow adzenia definiujem y jako stałą D 0 _ S H IF T. O dpowiednie definicje dla ZL29A RM są następujące: łdefine DATA_PORT GPIOE łdefine D0_D7 (GPIO_Pin_0 GPIO_Pin_l GPI0_Pin_2 \ GPI0_Pin_3 GPIcfpinj! GPI0_Pln_5 \ GPI0_Pin_6 GPI0J>in_7 łdefine D0_SHIFT 0 O dczytyw anie danych nie nastręcza żadnych problemów. Im plem entujemy prostą funkcję, korzystającą z rejestru ID R (ang. input data register: static uint8_t Dataln(void return (DATA_PORT->IDR s D0_D7» D0_SHIFT; 1 Użycie rejestru ODR (ang. output data register w celu zapisywania danych w ydaje się naturalnym pomysłem. Sytuacja nie jest jednak tak prosta, jak przy odczycie. M odyfikujem y tyiko 8 bitów portu, a pozostałe mogą być używane przez inne urządzenia. N ajpierw należy odczytać aktualną zawartość rejestru ODR, a następnie zm odyfikow ać w łaściwe bity i zapisać nową wartość. Zauważm y jednak, że taka sekw encja nie jest atom owa. Potencjalnie między odczytem wartości ODR a zapisem nowej może zostać zgłoszone przerwanie. Jeśli w trakcie jego obsługi m odyfikowane są inne bity rejestru ODR tego sam ego portu, to mamy poważny problem - nasz program nadpisze modyfikacje w ykonane w procedurze obsługi przerwania. Rozwiązaniem jest w ykorzystanie rejestru B SRR (ang. bit set/reset register, który zapewni atom owość operacji m odyfikow ania bitów. Funkcja zapisująca dane w y gląda następująco: łdefine SET_BITS!x ((uint32_t(x łdefine CLR_BITS(x ((uint32_t(x «16 static void DataOut(uint8_t x ( DATA_PORT->BSRR = CLRJ3ITS (~ ((ulnt32_t X «D0_SIIIFT S D0_D7 SET~BITS( {(uint32_tx «D0_SHIFT «D0_D7; Pozostało jeszcze zaim plem entow anie operacji zmiany kierunku wyprowadzeń. Tu niestety nie uda się zapewnić alom owości tej operacji i trzeba uważać, aby uniknąć konkurencyjnych zmian, skutkujących niedeterm inislycznym zachow aniem się programu. Dodatkow y problem przy rekonfiguracji portu sprawia konieczność odw o ływ ania się do dw óch rejestrów, a m ianowicie do C R L (ang. configuration register Iow i CRH (ang. configuration register high. Funkcje przełączające w yprowadzenia danych w tryb w ejściowy i wyjściowy wyglądają tak: static void DataLinesIn(void ( uint32_t r; DATA_PORT->BSRR = SET_BXTS(DQ_D7; lif D0_SHIFT < 8 r - DATA PORT->CRL; r &= ~(OxffffffffU «(4 * D0_SHIFT; r i= 0x88888888U «(4 * D0_SHIFT; DATA_PORT->CRL = r; lehdif fif D0_SHIFT > 0 r = DATA PORT->CRH;
36 I. Podstawy 1.7. Przykład Ib - test wyświetlacza ciekłokrystalicznego 37 r &= ~(Oxff f fffu» (32-4 * DO_SHIFT; r (= 0x88888S88U» (32-4 * DO_SHIFT; DATA_PORT->CRH r; łendi static void DataLinesOut(void ( uint32_t r; #i D0_SHIFT < 8 r = DATA_PORT->CRL; r s= ~(0xf f U «(4 * D0_SHIFT; r = OxlinilllU «(4 * D0_SHIFT; DATA_PORT->CRL = r; iendif łif D0_SHIFT > 0 r = DATA_PORT->CRH; r s= ~(0xff ff fu» (32-4 * D0_SHIFT; r = OxlllllillU» (32-4 * D0_SHIFT; DATA_PORT->CRH = r; iendif Zrozum ienie pow yższego fragmentu program u w ym aga ponow nego spojrzenia na rysunek 1.3 oraz tabele 1.10 i 1.11. Stała 0x88888888U oznacza ustawienie w yprow adzeń w trybie w ejściowym z rezystorem podciągającym do zasilania lub ściągającym do masy. Przed przełączeniem wyprowadzeń w tryb wejściowy, ustawiamy je w stan wysoki (za pom ocą B S R R, co sprawia, że po przełączeniu będą ustawione rezystory podciągające. Dzięki temu unikam y stanów nieustalonych na w yprowadzeniach. Stała O x l l l l l i l l U oznacza ustawienie w yprowadzeń w trybie w yjściowym przeciwsobnym 10 M Hz. Przy przełączaniu kierunku wyprowadzeń pewnej uwagi wym aga też w łaściw a kolejność zmian kierunku i sygnału na linii R/W, którą sterujemy za pom ocą poniższej funkcji: łdefine CTM,_P0RT GPI0E łdefine RW_LINE GPIO_Pin_10 static void RW(int x ( if (x CTRL_P0RT->BSRR = RW_LINE; else CTRL_P0RT->BRR = RW_LINE; I Warto zaznaczyć, że dobry kom pilator potrafi rozw inąć funkcję RW w miejscu jej w ywołania i zoptym alizować instrukcje w arunkowe, jeśli argum ent tej funkcji jest stalą. K olejność zmian kierunku wyprowadzeń i sygnału R/W pow inna uniem ożliwić sytuację, gdy linie danych są ustawione w tryb wyjściowy jednocześnie w więcej niż jednym układzie do nich podłączonym. W łaściw e sekw encje przełączeń są następujące: DataLinesIn(; /* Najpierw ustaw jako wejścia. */ RW(1; /* Potem wystaw sygnał READ. */ R W (0; /* Najpierw wystaw sygnał WRITE. */ DataLinesOut(; /* Potem ustaw jako wyjścia. */ Podobną staranność należy też wykazać podczas konfigurow ania wyjść. Początkowy stan w yprowadzenia można ustawić, zanim to w yprowadzenie zostanie ustawione w tryb wyjściowy. Więcej o tym można przeczytać w komentarzach w pliku board _lcd_ksoj 08. c. Zapewne warto byłoby rozszerzyć przedstaw iony w tym podrozdziale interfejs program istyczny wyświetlacza np, o funkcje udostępniające informacje, ile wierszy i ile znaków w wierszu może być wyświetlonych. Taką rozbudowę pozostawiam Czytelnikow i jako ćwiczenie. 1.7.2. Pliki font5x8.h i font5x8.c W yświetlacz WG 12864A nie ma wbudowanego generatora znaków. Trzeba je generować programowo. Plik font5x8.h znajduje się w katalogu./examples/include. Deklaruje on tablicę font5x8 wzorów bitowych znaków o kodach ASCII od FIRST_ CHAR (spacja do LAST_CHAR (tylda. W zory znaków mają wysokość 8 pikseli i szerokość CHAR_WIDTH pikseli. (łdefine FIRST_CIIAR 32 łdefine LAST_CHAR 126 łdefine CHAR_WIDTH 5 extern const char ont5x8(last_char - FIRST_CHAR + 11(CHAR_WIDTH]; Plik font5x8.c znajduje się w katalogu Jexam ples/src i zawiera definicje wzorów bitowych poszczególnych znaków. Pojedynczy bajt definiuje jedną kolum nę znaku. Najbardziej znaczący bit jest na dole, a najm niej znaczący - na górze. Kolejne bajty definiują kolum ny od lewej do prawej. const char font5x8[last_char - FIRST_CHAR + 1][CHARJIIDTHI = ( (0x00, 0x00, 0x00, 0x00, 0x00], /* spacja */ (0x00, 0x00, Qx5F, 0x00, 0x00}, /*! */ (0x00, 0x07, 0x00, 0x07, 0x00}, /* " */ (0x10, 0x08, 0x08, 0x10, 0x08} /* ~ */ }; N iew ątpliwie warto byłoby mieć możliwość wyświetlania polskich liter ze znakami diakrytycznym i, czyli ą, ę, ć itd. N ajprościej jest uzupełnić powyższą tablicę o znaki kodow ane zgodnie z norm ą ISO/IEC 8859-2. Jak w poprzednim podrozdziale, takie rozszerzenie zbioru wyświetlanych znaków pozostawiam Czytelnikowi jako ćwiczenie..7.3. Pliki u tiljc d.h i u tiljc d.c Plik u tiljc d.c znajduje się w katalogu./examples/src, a odpowiadający mu plik nagłówkowy u tiljc d.h - w katalogu./exam ples/include. Pliki te udostępniają cztery przydatne wysokopoziom owe funkcje ogólnego przeznaczenia: void LCDwrite(const char *s; void LCDwriteWrap(const char *s; void LCDwriteLen(const char *s, unsigned n; void LCDwriteLenWrap(const char *s, unsigned n; Funkcje te korzystają z niskopoziom owego interfejsu zdefiniow anego w pliku boa rd J e d.h, dzięki czemu ich im plem entacja nie zależy od konkretnego modelu w y świetlacza. Pierwsze dw ie funkcje wypisują ciąg znaków zakończony terminalnym
38 I. Podstawy 1.8. Organizacja pamięci programu 39 zerem (tak zwykle kończą się napisy w C, z tym że druga zaw ija tekst, jeśli nie mieści się w wierszu. Kolejne dw ie funkcje służą do w ypisyw ania ciągu znaków niezakończonych term inalnym zerem. Trzeba wtedy jako drugi argum ent podać liczbę znaków do wypisania. A nalogicznie druga z tych funkcji zaw ija zbyt długi tekst. Jeśli, jak to zaproponow ałem w poprzednim podrozdziale, utworzym y tablicę znaków według normy 1SO/IEC 8859-2, to przedstaw ione w tym podrozdziale funkcje bez żadnych modyfikacji będą mogły ich używać. N ieco bardziej am bitnym ćw i czeniem jest dodanie, choćby w ograniczonym zakresie, w ypisyw ania ciągu znaków w kodowaniu UTF-8. 1.7.4. Plik e x jc d.c Plik e x jc d.c znajduje się w katalogu./examples/src. D em onstruje on użycie niektórych z wyżej opisanych funkcji. W nieskończonej pętli wyświetlane są na przem ian dw a testy. Pierwszy test wypisuje m.in. wszystkie znaki zdefiniow ane w pliku font- 5x8.c. Drugi testuje przem ieszczanie kursora po ekranie i zaw ijanie tekstu. 1.8. Organizacja pamięci programu sidata = etext FLASH 1.8.1. Sekcje W tym podrozdziale opisuję, jak w pam ięci mikrokontrolera rozm ieszcza się kod programu i jego zmienne, co dzieje się zanim zostanie w ywołana funkcja main, jak wywoływane są procedury obsługi przerwań oraz jak to kontrolow ać i modyfikować. Opis dotyczy języka C, architektury Cortex-M 3 i narzędzi GNU (GCC i Binutils, ale w innych językach program ow ania i ińnyeh architekturach wszystko dzieje się bardzo podobnie. Zakładani też, że program ładowany jest do pamięci Flash. Kod programu oraz dane, na których on operuje, są w pamięci zblokow ane w sekcjach. Na ry su n k u 1.8 przedstaw iono typow e rozm ieszczenie sekcji w pamięci m i krokontrolera. Sekcja,is r _ v e c to r jest specyficzna dla architektury Corlex-M 3. Zaw iera tablicę adresów procedur obsługi przerwań, a ponadto początkową wartość w skaźnika stosu oraz adres, od którego zaczyna się wykonywanie programu. Więcej o tym piszę w następnych podrozdziałach. Sekcja.te x t zawiera kod wykonywalny, czyli skom pilowane wszystkie funkcje naszego programu oraz w szystkie funkcje biblioteczne, które do niego dołączamy. Sekcja.ro d a ta zaw iera dane tylko do odczytu. Są to te zm ienne, które zostały zadeklarow ane jako stale z użyciem słow a kluczow ego c o n st i nie zostały zoptym alizow ane. K om pilator nie m usi alokow ać pam ięci dla zm iennej, która nie zm ienia sw ojej w artości i jej zakres w idoczności jest ograniczony do jednego bloku, jednej funkcji lub jednostki translacji. Zoptym alizow ana m oże być zm ienna lokalna (zadeklarow ana w ew nątrz bloku instrukcji otoczonego naw iasam i klam row ym i lub w ew nątrz funkcji w yspecyfikow ana jako c o n st lub s t a t i c c o n st albo zm ienna globalna w yspecyfikow ana jak o s t a t i c c o n st. Zm ienna globalna 0x08000000 Rys. 1.8. Typowa organizacja pamięci programu w yspecyfikow ana tylko jako c o n st nie m oże być zoptym alizow ana, gdyż jej nazw a jest w idoczna na zew nątrz jednostki translacji i potencjalnie m ożna odw ołać się do niej z innych jednostek translacji, a standardow y proces kom pilow ania ję zyka C nie przew iduje optym alizacji pom iędzy jednostkam i translacji. W sekcji. ro d a ta um ieszczane są też stale napisow e, niezależnie od tego, czy mają jaw nie zdefiniow aną nazwę. Sekcja.d a ta zawiera zm ienne globalne i statyczne, które są inicjowane w artościami niezerowym i. Wartość zerow a to taka, której binarna reprezentacja ma wszystkie bity w yzerowane. Wartość niezerowa to w artość, która nie jest zerowa, czyli jej reprezentacja binarna ma co najmniej jeden bit różny od zera. W tej sekcji znajdują się tylko zm ienne zadeklarowane bez kw alifikatora co n st. Są to wszystkie takie zm ienne globalne oraz te lokalne, które są dodatkow o opatrzone słowem kluczowym s t a t i c. Sekcja.b s s zawiera dokładnie te sam e kategorie zmiennych co sekcja.d a ta, ale są to te zmienne, które są inicjow ane w artościam i zerowymi lub nie podano żadnej wartości inicjującej. Zm ienna, dla której nie podano wartości inicjującej, jest (zw y kle dom yślnie inicjowana w artością zerową. Sekcja heap to sterta przydzielona program owi. Na stercie zmienne są alokowane za pom ocą funkcji bibliotecznych przydzielających pamięć, takich jak np. raalloc.
4 0 1. Podstawy 1.8. Organizacja pamięci programu 41 Sekcja s ta c k zawiera stos program u. Na stosie um ieszczane są zm ienne lokalne (automatyczne. Nazwa zm ienna autom atyczna oznacza, że zm ienna laka znika autom atycznie, gdy sterow anie program u w ychodzi poza zakres jej w idoczności. Na stosie odkładane są też wartości argum entów aktualnych w ywołania funkcji, czyli wartości tych argumentów, które zostały przekazane funkcji w aktualnym jej w yw o łaniu. Zm ienne autom atyczne i argum enty funkcji są najczęściej optym alizowane przez kom pilator i o ile jest to tylko możliwe, nie są przechow ywane na stosie, a w rejestrach procesora. Na stosie odkładane mogą też być adres powrotu z funkcji oraz wartości rejestrów, których funkcja używa i których wartości musi odtworzyć przed powrotem. W architekturze A RM nie odkłada się adresu powrotu na stos bezpośrednio - jest on trzymany w rejestrze procesora LR. Jeśli LR jest potrzebny do zagnieżdżonego wywołania funkcji, to dopiero wtedy jego wartość jest odkładana na stos. W ątpliwości, co do um ieszczania zmiennych globalnych w poszczególnych sekcjach, powinny w yjaśnić poniższe przykłady. Zm ienne a, b, c zostaną umieszczone w sekcji.ro d a ta : const int a ~ 5; const: int b = 0/ const int c; Zm ienne d, e, f zostaną um ieszczone w sekcji.ro d a ta lub zoptym alizowane: static const int d = 5; static const int e = 0; static const int f; Zm ienne g, j zostaną um ieszczone w sekcji.d a ta, a zm ienne h, i, k, m w sekcji.b ss: int g = 5/ int h = G; int i; static int j = 5; static,i.nt k = 0; static int m; Sytuacja nieco bardziej się kom plikuje, gdy używamy globalnych tablic lub wskaźników, gdyż kw alifikator c o n st może dotyczyć sam ego w skaźnika lub wartości, na którą wskazuje, co dem onstrują następujące przykłady. Tablice s l, s2 zostaną um ieszczone w sekcji.ro d a ta : static const char sl{} = "Ala ma kota."; const char s2[ = "To jest kot Ali."; Tablice s3, s4 będą um ieszczone w sekcji.d a ta : static char s3 j = "Ala ina kota."; char s4[] = "To jest kot Ali."; Tablice s5, s6 zostaną um ieszczone w sekcji.b ss: static char s5(10]; char S6110]; Napis i w skaźnik s7 będą um ieszczone w sekcji.d a ta : char * s7 = "Ala ma kota."; Napis zostanie um ieszczony w sekcji.ro d a ta, ale wskaźnik s8 w sekcji.d ata: const char * s8 = "Ala ma kota."; Napis będzie umieszczony w sekcji.d a ta, a wskaźnik s9 w sekcji.ro d a ta lub zostanie zoptym alizowany: char * const s9 = "Ala ma kota."; Napis i wskaźnik slo zostaną um ieszczone w sekcji.ro d a ta, wskaźnik może zostać zoptym alizowany: const char * const slo = "Ala ma kota."; W skaźniki s i l, s ł2 zostaną um ieszczone w sekcji.b ss: char * sil; char * sł2 = NULL; Kolejny przykład pokazuje sposób przydziału pntięci dla zmiennych lokalnych. void funkcja(char *p, int n { /* p - stos lub rejestr */ /* n - stos lub rejestr */ int i; /* stos lub rejestr */ char qf10]; /* stos */ static char x; /*.bss */ static char y = 'a'; /*.data */ const char z = 'b'; /*.rodata łub optymalizacja */ /* alokacja n bajtów na stercie */ p = malloc(n; /* napis w.rodata */ strcpy(p, "Ala ma kota."; i 1 for (i = 0; i < n; -Hi ( char s; /* stos lub rejestr */ static char t; /*.bss */ static char u =,c'; /*.data */ const char w = d'; /*.rodata lub optymalizacja */ Sekcje. isr vector,.te x t i.ro d a ta zaw ierają dane tylko do odczytu i dlatego mogą być um ieszczone w pamięci Flash. M ożna je też umieścić w RAM - mikrokontrolery ARM mogą w ykonywać kod z RAM. Sekcje.b ss, heap i s ta c k zawierają dane, które mogą być modyfikow ane przez program i dlatego muszą być um ieszczone w RAM. Zauważm y, że są dw ie sekcje.d a ta, jedna w Flash, a druga w RAM. Sekcja.d a ta zaw iera dane, które z jednej strony mają nadane jakieś niezerowe wartości początkowe, a z drugiej strony mogą być modyfikowane przez program. Sekcja.d a ta um ieszczona w pamięci Flash przechowuje wartości początkowe, które są przepisyw ane do RAM przed rozpoczęciem wykonywania programu. Ponadto przed rozpoczęciem wykonywania programu trzeba w yzerować sekcję.b ss, gdyż zawartość RAM po wyzerowaniu m ikrokontrolera jest niezdefiniowana. Zatem sekcje.d a ta i.b ss w ymagają program ow ego inicjowania. Zajm uje się tym procedura startowa, o której piszę w następnym podrozdziale.
4 2 I. Podstawy 1.8. Organizacja pamięci programu 43 1.8.2. Procedura startowa Procedura startow a zależy od architektury procesora i używanego kom pilatora, gdyż z reguły jest pisana w Asem blerze konkretnego procesora. Tu przedstawiam m inimalną, ale w pełni funkcjonalną i w większości przypadków zupełnie wystarczającą, procedurę startow ą dla architektury Cortex-M 3 i kom pilatora C z pakietu GCC. Na jej podstawie Czytelnik może stw orzyć własną, bardziej rozbudow aną i dostosow a ną do swoich potrzeb. Rozszerzenia dodane do języka C przez G NU C um ożliw iają napisanie procedury startowej bez użycia A semblera. Pełny tekst źródłowy dla m ikrokontrolera STM 32F107, opatrzony kom entarzam i, znajduje się w archiwum z przykładami w pliku startup_stm 32_cld.c, który jest w katalogu./exatnples/src. W tym miejscu opisuję tylko kluczow e fragmenty procedury startowej. Zaczynam y od zadeklarow ania pseudozm iennych, które w skazują granice sekcji. D eklarujem y je jak o e x te rn, gdyż są definiow ane przez konsolidator, co zobaczym y w następnym podrozdziale. Popatrzm y na rysunek 1.8. Z m ienna s id a ta zachow uje się tak, jakby była um ieszczona w pierw szych czterech bajtach sekcji.d a ta w pam ięci Flash jej adres je st adresem początku tej sekcji w Flash. U w aga, lokacja zm iennej _ s id a ta i pozostałych pseudozm iennych pokryw a się w pam ięci z praw dziw ym i zm iennym i naszego program u. Te pseudozm ienne służą tylko do określenia adresów, gdzie zaczynają się i kończą poszczególne sekcje, Zm ienna _ s d a ta jest położona w czterech pierw szych bajtach danych inicjow anych w RAM, a zm ienna edata zajm uje cztery bajty tuż za danym i inicjow anym i, czyli adres zm iennej sdata w skazuje i pierw szy adres sekcji.d a ta w RAM, adres zm iennej _ e d a ta - pierw szy adres ża tą sekcją. Z m ienna _ sb ss zajm uje początkow e cztery bajty danych zerow anych, a zm ienna _ eb ss - cztery bajty tuż za tym i danym i, czyli adres zm iennej sb s s w skazuje pierw szy adres sekcji.b ss, a adres zm iennej _ e d a ta - pierw szy adres za tą sekcją. A dres zm iennej e s ta c k w skazuje pierw szy adres za pam ięcią RAM, czyli początkow y w ierzchołek stosu. extern unsigned long _sidata; extern unsigned long sdata; extern unsigned long _edata; extern unsigned long _sbss; extern unsigned long _ebss; extern void _estack; Następnie powinniśmy zadeklarow ać sygnaturę głównej funkcji, od której rozpoczyna się w ykonywanie program u i która zwyczajowo nazywa się main. Funkcja ta musi być zdefiniowana (zaim plem entow ana w jakiejś jednostce translacji. Nasza funkcja main nie przyjm uje żadnych argumentów, choć nic nie stoi na przeszkodzie, aby takie argumenty zadeklarować, jeśli znajdziem y po temu sensowny powód. Konwencja nakazuje, aby funkcja main była typu in t, choć nie będziemy w ykorzystywać zwracanej wartości - w św iecie mikrokontrolerów funkcja ta nigdy nie kończy działania. Jednak kom pilatory traktują ją specjalnie i wypisują ostrzeżenie, jeśli dostarczym y jej im plem entację innego typu. int main(void; Definiujem y dom yślną procedurę obsługi przerwania D e fa u ltjła n d le r - nieoczekiwane przerwanie zaw iesza mikrokontroler. M ożna oczywiście, jeśli potrzebujemy, zaim plem entow ać bardziej w yrafinow aną obsługę. static void Defaultjłandler(void ( for (;;; 1 M usim y koniecznie napisać procedurę R eset_ H andler wywoływaną po wyzerowaniu m ikrokontrolera. M inim alna im plem entacja lej procedury rozpoczyna się od dw óch pętli. Pierwsza pętla kopiuje sekcję.d a ta z Flash do RAM, a druga - zeruje sekcję.b ss. Po tym można ju ż rozpocząć w ykonywanie program u - wywołujemy funkcję main. Funkcja ta zw ykle nigdy się nie kończy, ale gdyby jednak nastąpił z niej pow rót, zapętiamy się w nieskończoność. static void Reset_Handler(void { unsigned long *src, *dst; for (dst = &_sdata, src = &_sidata; dst < &_edata; ++dst, -i+src *dst = *src; for (dst = &_sbss; dst < &_ebss; ++dst *dst = 0; main (; for 1 Kolej na deklaracje procedur obsługi przerwań - dla każdego przerwania trzeba dostarczyć bezparam etrową funkcję typu void. Funkcje te deklarujem y z atrybutem weak, co wyjaśniam w dalszej części tego podrozdziału. M ikrokontroler STM 32F107 może zgłaszać kilkadziesiąt przerwań. Poniższy w ydruk obejm uje tylko fragm ent ich listy. Idefine WEAK attribute ((weak void WEAK NMIJ-Iandler (void ; void WEAK HardFault_Handler(void; void WEAK MemManage_Handler(void; void WEAK BusFault_Handler(void; void WEAK UsageEault_Handler(void/ void WEAK SVCJIandler (void; void WEAK DebugMon_Handler(void; void WEAK PendSV_Handler(void; void WEAK SysTick_Handler(void; void WEAK WWDG_IRQHandler(void; void WEAK PVD_IRQHandler(void; void WEAK TAMPER_IRQHandler(void; void WEAK RTC_IRQHandler(void; void WEAK RTCAlarm_IRQ!Iandler (void; void WEAK ETH_IRQHandler(void; void WEAK OTGJf SJRQHandler(void; Tablicę przerwań umieszczam y w specjalnie dla niej przeznaczonej sekcji,is r _ v e c to r. Jest ona zadeklarowana jako tablica stałych w skaźników do bezparametrowych funkcji typu void. Dla STM 32F107 tablica ta ma 84 elementy, ale nie wszystkie są obecnie używane - część jest zarezerwow ana do przyszłego w yko
4 4 1. Podstawy 1.8. Organizacja pamięci programu 45 rzystania. Pierwszy elem ent (o indeksie 0 tablicy przerwań zaw iera początkową wartość w skaźnika stosu, ładowaną do rejestru SP po wyzerowaniu mikrokontrolera, Drugi elem ent (o indeksie 1 wskazuje na procedurę wywoływaną po w yzerowaniu. Kolejne 14 elem entów to adresy obsługi procedur przerwań zdefiniowanych w architekturze Cortex-M 3. Pozostałe 68 elem entów to adresy obsługi przerwań układów peryferyjnych, specyficznych dla STM 32F107. Zarezerw ow ane elementy tablicy przerwań są inicjow ane zerami. attribute ((section(".isr_vector" void (* const g pfnvectors(](void = { &_estack, Resetjlandler, /* Przerwania Cortex-M3 */ MMIJiandler, HardFault_Handler, MemManage_Handler, BusFault_Handler, UsageFault_Handler, 0, 0, 0, 0, SVCJiandler, DebugMonJlandler, 0, PendSV_Handier, SysTick Handler, /* Przerwania układów peryferyjnych */ WWDG_IRQHandler, PVDJRQHandler, TAMPER_IRQHandler, RTC_IRQHandler, RTCAiarm_IRQHandler, ETH_IRQHandler, OTG_FS_IRQHandler ; Żeby tablica przerwań m ogła być skonsolidow ana z resztą programu, czyli żeby konsolidator mógł do niej pow staw iać właściwe adresy procedur, to dla każdej w y specyfikowanej w tablicy przerwań nazwy procedury musi być dostarczona jej im plem entacja. Procedury obsługi przerwań definiujem y zw ykle w różnych jednostkach translacji. Na ogól jednak nie chcem y im plem entow ać procedur dla w szystkich przerwań, a tylko dla tych używ anych w danym program ie - pozostałym najchętniej przypisalibyśm y adres dom yślnej procedury D efault_h andler. Do tego w łaśnie służy, w spom niany wyżej, m echanizm słabego (ang. w e a k w iązania nazw. Jeśli konsolidator znajdzie procedurę, której nazwa została um ieszczona w tablicy przerwań, to wstawi do tej tablicy jej adres. Jeśli jednak takiej procedury nie znajdzie, to wstawi adres procedury dom yślnej. Bez możliwości słabego wiązania nie udałoby się tego zrobić elegancko. Zaim plem entow anie dom yślnego zachow ania we wszystkich procedurach i próba dostarczenia później właściwej im plem entacji spow odowałaby błąd konsolidacji - dw ukrotnie zdefiniow ana ta sam a nazwa (ang. multiple dęfinition. Z kolei brak im plem entacji domyślnej wymagałby dostarczenia im plem entacji procedur obsługi w szystkich przerwań, gdyż inaczej konsolidator zgłosiłby bkjd odw ywołania do niezdefiniowanej nazwy (ang. w ulefm ed rejerence. fpragma weak NMI_Handler = Default_Hancłler fpragma weak HardFaułt_Handler = Defaultjiandler fpragma weak MemManage_ilandler = Defaułt_Handłer fpragma weak OTG_FS_IRQHandier = Default_Handler Powyższe instrukcje sprawiają, że nazwy procedur obsługi przerwań są słabymi aliasami nazwy domyślnej procedury obsługi. Jeśli konsolidator nie znajdzie definicji jakiejś procedury, to użyje aliasu, czyli wstawi do tablicy przerwań adres procedury domyślnej. Skrypt konsolidatora W wyniku kom pilacji, na podstawie każdej jednostki translacji, tworzony jest odpow iadający jej plik pośredni, zawierający w szystkie sekcje, których wygenerowanie było niezbędne dla tej jednostki translacji. Jest to plik relokowalny, co oznacza, że każda sekcja może być um ieszczona pod dow olnym adresem w pamięci procesora. Pliki pośrednie mogą być łączone w w iększe pliki, tworząc biblioteki. Aby powstał program wykonywalny, trzeba połączyć w łaściwe pliki pośrednie i biblioteki. W tym procesie łączenia scalane są w jedną sekcję wszystkie sekcje pełniące tę samą funkcję ze w szystkich plików poddanych łączeniu. Łączeniem plików pośrednich i rozm ieszczaniem poszczególnych sekcji w pamięci zajm uje się konsolidator. W przypadku konsolidatora Id z pakietu Binutils przebiegiem konsolidacji steruje skrypt, co um ożliwia bardzo elastyczne sterow ania tym procesem. W katalogu Jczatnples/scnpts znajdują się skrypty w ym ienione w tabeli 1.14. Dla każdego modelu m ikrokontrolera STM 32F107 jest tam umieszczony osobny skrypt. Są one jednak bardzo podobne, dlatego tu zam ieszczam tylko wydruk pliku stm 32fl 07vc. Ids: MEMORY ( FLASH (rx : ORIGIN = 0x8000000, LENGTH = 256K RAM (rwx : ORIGIN = 0x20000000, LENGTH = 6<1K I _mininuim_stack_and_heap_size = 8192; INCLUDE cortex-m3.1ds Za pom ocą instrukcji MEMORY deklarujem y dostępne obszary pamięci, ich atrybuty (r - upraw nienie do odczytu, w - możliw ość zapisu, x - zezwolenie na wykonywanie kodu z tej pam ięci, adres początku i rozmiar. Należy podkreślić, że alry- Tab. 1.14. Skrypty konsolidatora dla STM32F107 cortex-m3.lds Skrypt stm 32H07rb.lds stm 32H07rc.lds stm32l107vb.lds stm 32n07vc.lds Opis Część wspólna dla architektury Cortex-M3 Część specyliczna dla STM32F107RB Część specyficzna dla STM32F107RC Część specyficzna dla STM32F107VB Część specyficzna dla STM32F107VC
4 6 I. Podstawy 1.8. Organizacja pamięci programu 4 7 buty te są tylko inform acją dla konsolidatora i nie wym uszają żadnych ograniczeń dostępu do fizycznej pamięci mikrokontrolera. Następnie deklarujem y minimalny rozm iar pamięci, która musi pozostać wolna dla stosu i sterty (sekcje s ta c k i heap. Konsolidator zgłosi błąd, gdy w RAM zostanie za mało miejsca. W reszcie na końcu, za pom ocą instrukcji IN C L U D E, przechodzim y do w ykonania głównego skryptu cortex-m3.lds, wspólnego dla wszystkich modeli m ikrokontrolera. Jest to minim alny, ale w pełni funkcjonalny, skrypt dla architektury Corlex-M 3. Poznawszy jego strukturę, Czytelnik może stw orzyć własny, bardziej rozbudow any i dostosowany do swoich potrzeb. Skrypt ten w ygląda tak: SECTIONS (.text : f KEEP(*(.isr_vector * E.text * (.text.* *{.rodata * (.rodata.*. = ALIGN(4; _etext = _sidata = _etext; >FLASH =0xf f.data : AT(_sidata i. = ALIGN(4; _sdata = M.data M.data.*. = ALIGN(4; _edata = >RAM =0.bss : {. = ALIGN(4 ; _sbss =.; *(.bss * {.bss.* * (COMMON. = ALIGN(4; _ebss =.; >RAM _estack = (ORIGIN(RAM + LENGTH(RAM & OxFFFFFFFC; ASSERT(_ebss + _minimum_stack_and_heap size <= _estack, "There is not enough space in RAM for stack and heap." PROVIDE(end = _ebss; /* Teraz zostały już tylko informacje dla debuggera. */ Powyższy skrypt tworzy trzy sekcje:.t e x t,.d a ta i.b ss. W sekcji. te x t są um ieszczane wspólnie sekcje,is r _ v e c to r,.te x t i.ro d a ta - nie ma potrzeby tworzenia i osobnych sekcji. Na om aw ianym wydruku pom inąłem fragment tworzący sekcje dla debuggera, gdyż nie ma to wpływu na organizację pamięci programu - zainteresowany Czytelnik może po prostu zajrzeć do pliku./examples/scripts/cortex-m 3.lds. Poniżej opisuję szczegółowo poszczególne konstrukcje użyte w tym skrypcie. SECTIONS { 1 Instrukcja SE C TIO N S rozpoczyna głów ną część skryptu definiującą sposób przetwarzania sekcji z plików pośrednich na sekcje w pliku wynikow ym (wykonywalnym. Zawiera definicje poszczególnych sekcji w pliku wynikowym. W tym konkretnym przypadku są to sekcje.te x t,.d a ta i.b ss..text : 1 >FLASH =0xff Pow yższa instrukcja tworzy sekcję. te x t, która zostanie um ieszczona w pamięci Flash, a ew entualne puste, nieużyw ane miejsca zostaną wypełnione wartościami 0xf f..data : AT(_sidata I >RAM =0 Pow yższa instrukcja tworzy sekcję.d a ta. Adresy w tej sekcji mają być tak w yliczane (relokow ane, jakby była um ieszczona w RAM, ale zawartość tej sekcji zostanie fizycznie załadowana pod adres _ s id a ta, który jest pierwszym adresem w pamięci Flash tuż za sekcją.t e x t (połączonym i sekcjam i. is r_ v e c to r,.te x t i.ro d a ta. Ewentualne puste, nieużyw ane miejsca zostaną wypełnione zerami..bss : I >RAM Pow yższa instrukcja tworzy sekcję.b ss, która zostanie um ieszczona w RAM. * {. text *(.text.* Pow yższa instrukcja w ybiera określone sekcje z w yspecyfikow anych plików. W yrażenie regularne przed naw iasem je st w zorcem nazw y pliku - gw iazdka oznacza w szystkie pliki. W yrażenie regularne w naw iasie je st w zorcem nazwy sekcji, którą należy w ybrać z tego pliku. G w iazdka zastępuje dow olny ciąg znaków. W ybierane są w szystkie sekcje o nazw ach pasujących do w zorca ze w szystkich plików o nazw ach pasujących do w zorca. Sekcje, których nazw a ma dodatkow y sufiks rozpoczynający się kropką, pow stają, jeśli podczas kom pilow ania ustaw im y opcję um ieszczania w szystkiego w osobnych sekcjach (patrz ostatnie dw a w iersze w tabeli 1.5. Przykładow o funkcja o nazw ie xyz zostanie um ieszczona w tedy w sw ojej sekcji o nazw ie.te x t. x y z. Jeśli opcje te nie zostaną ustaw ione, to funkcja ta zostanie um ieszczone we w spólnej sekcji. te x t
48 I. Podstawy 'S 1.9. Styl pisania i komentowania tekstu źródłowego 49 w raz z innym i funkcjam i z tej sam ej jednostki translacji. Pow yższe instrukcje pokryw ają obie sytuacje. KEEP(*(.isr_vector Pow yższa instrukcja K E E P sprawia, że sekcja,is r _ v e c to r, czyli tablica adresów procedur obsługi przerwań oraz sam e procedury obsługi przerwań nie zostaną usunięte z pliku w ynikowego przez ods'miecacz (patrz opcja odśm iecania w tabeli 1.9. Do tablicy przerwań i do procedur przerwań nie ma zwykle żadnych odwołań w tekście źródłowym - procedur przerwań raczej nie w ywołujem y jaw nie - są na ogól wywoływane przez sprzęt, więc odśm iecacz miałby w szelkie podstawy, żeby je usunąć z pliku wynikowego.. = ALIGNH; Jeśli nie zdecydujem y inaczej, to konsolidator, analizując skrypt, układa scalane sekcje po kolei. Kropka oznacza adres, pod którym zostanie um ieszczona kolejna scalana sekcja, czyli docelow y adres początku tej sekcji. Znak rów ności jest operatorem przypisania. Jeśli do kropki przypiszem y wartość, to zmodyfikujem y bieżący adres i następna scalana sekcja zostanie um ieszczona pod tym nowym adresem. O perator ALIGN zwraca najbliższy adres, który jest w ielokrotnością jego argumentu i służy do w yrów nyw ania położenia sekcji w pamięci. ARM jest architekturą 32- -bitową. Architektury 32-bitow e w ym agają zwykle, aby adresy początków sekcji były wyrównyw ane do wielokrotności 4. _etext =.; Pow yższa instrukcja przypisuje sym bolow i etext bieżący adres. W tekście źródłow ym w C ten sym bol będzie w idoczny jako zm ienna um ieszczona pod tym adresem. _sidata = _etext; Pow yższa instrukcja sym bolowi _ s id a ta przypisuje wartość wyrażenia po prawej stronie znaku równości. W tym przypadku jest to wartość przypisana poprzednio sym bolow i e te x t. Zauważm y, że konsolidator operuje na adresach, ale definiowane sym bole są w tekście źródłow ym widziane jako zmienne i dla uzyskania adresu trzeba użyć operatora dereferencji &, czyli aby uzyskać adres przypisany przez konsolidator sym bolow i sidata, trzeba w C użyć wyrażenia & _sidata. Po prawej stronie operatora przypisania może pojaw ić się wyrażenie składające się z poprzednio zdefiniow anych sym boli połączonych za pom ocą większości operatorów arytm etyczno-logicznych, które są dostępne w języku C, a dla w ym uszenia kolejności działań można użyć nawiasów, jak w poniższym przykładzie. _estack = (ORIGIN(RAM I LEHGTH(RAM S OxFFFFFFFCj Pow yższa instrukcja definiuje początkow ą wartość rejestru S P, czyli w ierzchołka stosu. Rejestr SP wskazuje na ostatnio odłożoną na stos wartość. W architekturze ARM, jak w większości procesorów, stos rośnie w dól, w kierunku m niejszych adresów. Poniew aż jest to architektura 32-bitow a, na stosie odkładane są wielokrotności czterech bajtów, a wartość rejestru SP musi być podzielna przez cztery. Zatem początkowa wartość w ierzchołka stosu powinna być pierwszym adresem za RAM, a jeśli ten adres nie jest w ielokrotnością czterech, to należy go zm niejszyć do najbliższego podzielnego przez cztery. ASSERT (_ebss + jiunimuni_stack_and_heap_size <= _eatack, "There is not enough space in RAM for stack and heap." Instrukcja A S S E R T sprawdza popraw ność warunku. Jeśłi wyrażenie w pierwszym argum encie jest fałszywe, to konsolidator przerywa pracę i wypisuje tekst będący drugim argumentem. Zasady wyliczania wartości w yrażenia w arunkowego są analogiczne jak w języku C. W tym konkretnym przykładzie konsolidator zgłosi błąd, gdy po scaleniu w szystkich sekcji, które mają znaleźć się w RAM, zostanie za mało miejsca na stos i stertę. PROVIDE (end = ebss ; Pow yższa instrukcja P R O V ID E pow oduje, że jeśli w jakiejś jednostce translacji jest odw yw ołanie do zew nętrznego sym bolu o nazwie end, a symbol ten nie został nigdzie zdefiniow any, to zostanie on zdefiniow any przez konsolidator i zostanie mu przydzielony adres będący w artością w yrażenia um ieszczonego po prawej stronie operatora przypisania. W tym konkretnym przypadku będzie to pierwszy wolny adres za sekcją.b ss. Zm ienna o nazw ie end jest w ykorzystywana przez standardową bibliotekę języka C do w skazania początkow ego końca sterty. M ianowicie adres tej zmiennej jest używ any do zainicjow ania zmiennej heap_end, która pamięta pierwszy wolny adres nad stertą, więc na początku działania program u sterta ma zerow y rozmiar. Jeśli trzeba przydzielić nową pam ięć i nie ma miejsca na stercie, to spraw dza się, czy można rozszerzyć stertę. Sterta jest rozszerzana, jeśli między heap_end a wskaźnikiem stosu (rejestr S P jest dostateczna ilość wolnego m iejsca. Zm ienna heap_end jest tylko zw iększana, nigdy zm niejszana. Zarządzaniem rozm iarem sterty zajm uje się funkcja, której nazwa zaw iera zw ykle napis sbrk - w bibliotece Newlib dla architektury ARM jest to funkcja _sbrk. W niektórych im plem entacjach biblioteki standardow ej zm ienna używ ana do w skazania początkow ego końca sterty nazywa się _end. T rzeba wtedy odpow iednio zmodyfikować instrukcję PR O VID E. 1.9. Styl pisania i komentowania tekstu źródłowego Tekst źródłowy programu jest czytany nie tylko przez kom pilator (lub interpreter, któremu styl program ow ania jest obojętny. Jest też czytany przez ludzi, którzy chcą lub m uszą go pielęgnować i dalej rozwijać. Dobrze napisany program komputerowy daje się łatwo m odyfikować i rozszerzać o nową funkcjonalność. Program żyje, gdy jest rozwijany i poprawiany, często przez kolejne pokolenia programistów, którzy w zajem nie się nie znają. Program, którego nie m ożna zmodyfikować, ma dużą szansę stać się martwy i przestać być używany. Całość lub fragmenty dobrze napisanego programu powinno dać się w ykorzystać w kolejnych projektach. Jedną z w ażniejszych przyczyn trudności m odyfikow ania programu jest nieczytelnie napisany tekst źródłowy, którego po pewnym czasie nawet autor nie rozumie. Dołożyłem w szelkich starań, aby zam ieszczone w tej książce przykłady były napisane elegancko, w dobrym stylu. Programy kom puterow e mają jednak taką przypadłość, że zawsze można w nich coś poprawić i napisać lepiej. W ażnym elem entem popraw iającym czytelność program u są komentarze. Nie należy jednak przesadzać i kom entować każdej linijki - to wbrew pozorom może u trud-
50 1. Podstawy niać czytanie tekstu źródłowego. Należy bezw zględnie kom entować te fragmenty, których działanie jest nieoczywiste albo powód ich napisania trudno odgadnąć. K omentarze powinny być pisane pełnym i zdaniami i popraw nym językiem. Nie należy um ieszczać komentarzy trywialnych, opisujących to, co widać w tekście źródłowym programu. Zam iast długich opisów często wystarczy w łaściwie dobrać nazwy funkcji, ich param etrów i zmiennych. O dpowiednie nazwy sprawiają, że tekst źródłowy sam odokum entuje się, a niew ielka ilość komentarzy w ystarcza do jego zrozum ienia. Taki sam okom entujący się tekst źródłowy czyta się bardzo przyjem nie. Teksty źródłowe zaw arte w archiw um z przykładami są skom entow ane, lecz nie przesadnie. Pewnym usprawiedliw ieniem jest fakt, że cała treść książki jest swego rodzaju kom entarzem do tych przykładów. Z oszczędności miejsca, w głównym tekście książki, we fragmentach program ów ilustrujących opisyw ane zagadnienia i przykłady pomijam w iększość komentarzy. Nie są one tu potrzebne, gdyż sam tekst książki stanowi dostateczne wyjaśnienie. Intersieci
2.1. Model warstwowy 53 Intersieć (ang. internet składa się z wielu autonom icznych, współdziałających sieci lokalnych (LAN, ang. local area network. N ajw iększą intersiecią, jaką dotychczas stworzono, jest Internet. Intersieci zarządzane są w sposób rozproszony - poszczę- * gólne organizacje, firnty nadzorują sw oje fragm enty sieci. Zapew nienie popraw- $ nej współpracy w takim środowisku wym aga standaryzacji. W iększość aspektów ;p działania intersieci jest opisana w dokum entach RFC (ang. Request fo r Continents. rj N iektóre z nich uzyskały status oficjalnego standardu, ale większość nie ma charak- g teru norm atywnego, będąc tylko niezobow iązującym i zaleceniami albo inform acja-/^, mi, lecz mimo to są pow szechnie stosow ane i przestrzegane przez twórców oprogram owania sieciowego. D okum enty RFC są publikowane na stronie http://www. j- rfc-editor.org..;1 Ten rozdział zaczynam od przedstaw ienia modelu warstwow ego intersieci. Następnie # opisuję zasady działania Ethernetu, czyli najpowszechniejszej obecnie technologii 'jjj budowy przewodowych sieci lokalnych. Potem wyjaśniam, jak sieci lokalne oparte '; na Ethernecie łączy się w intersieć. Sporo miejsca pośw ięcam adresowaniu w in- ;r tersieciach. Podaję tylko niezbędne m inim um informacji potrzebnych do programo- V w ania usług sieciowych, a chcących wiedzieć więcej odsyłam do studiow ania RFC. ; Jako przykład kończący ten rozdział proponuję napisanie prostego monitora sieci. 'jf 'i* Terminem pakiet nazywam dow olną porcję danych wysyłanych przez dowolny!i. protokół sieciowy. W odniesieniu do poszczególnych protokołów stosuję bardziej precyzyjne nazwy. Pakiet może w ięc oznaczać ram kę Ethernetu, datagram IP, datagram UDP, segm ent TCP, kom unikat jakiegoś protokołu pom ocniczego lub aplikacyjnego. Ś~ 2.1. Model warstwowy O program ow anie sieciow e tw orzy się warstwowo. W arstwa definiuje protokół kom unikacyjny udostępniający pew ną usługę. Typowe warstwy, które możem y wyodrębnić w intersieciach, przedstaw ione są na ry su n k u 2.1. U m ieściłem na nim też skróty nazw protokołów, które pojaw iają się w tej książce. Przyporządkowanie konkretnego protokołu do określonej warstwy nie zawsze jest proste i jednoznaczne. W arstwa korzysta z usług warstwy niższej, znajdującej się w modelu warstwowym bezpośrednio pod nią, na podobnej zasadzie jak chcący wysiać przesyłkę korzysta z firmy kurierskiej. Podział kom unikacji sieciowej na warstwy ma szereg zalet. Poszczególne warstwy są projektow ane i im plem entow ane niezależnie. Z usług udostępnianych przez daną warstwę m ożna korzystać jak z gotow ego komponentu, nie trzeba go sam odzielnie im plem entow ać. W arstwę można traktow ać jak czarną skrzynkę - żeby jej używać, nie trzeba znać szczegółów w ewnętrznych - wystarczy znać interfejs program istyczny udostępniany przez im plem entację tej warstwy, Im plem entację warstwy można łatwo zastąpić inną, o ile ma podobny interfejs programistyczny. W praktyce pełna separacja i niezależność warstw nie jest osiągana. Na szczycie modelu w arstwow ego znajduje się warstwa aplikacji. Protokoły tej warstwy definiują sposób interakcji między aplikacjam i. Przykładowo HTTP (ang. H ypertext Transfer Protocol służy do przesyłania zawartości stron www. Definiuje fi. on między innymi, jak przeglądarka ma poprosić o zaw artość strony, jak serwer. odsyła odpow iedź z tą zawartością, jak przesłać do serwera zaw artość formularza i1 I wypełnionego na stronie przez użytkow nika itd. HTTP nie musi nic wiedzieć, jak te dane są przesyłane przez sieć. Tym zajm uje się protokół warstwy transportowej. Podstawow ym i protokołami transportowym i w intersieciach są TCP (ang. Transmission Contro! ProtocoI i UDP (ang. User Datagram Protocol. HTTP akurat korzysta z TCP, który gwarantuje, że jeśli dane dotrą do odbiorcy, to dotrą we właściwej kolejności. Zapew nia też, że dane zostaną wysiane ponownie (będą retransm itowane, gdy ich otrzym anie nie zostanie potwierdzone w pewnym ustalonym czasie. Protokół transportowy z kolei nie musi znać topologii sieci - nie musi wiedzieć, gdzie w sieci znajduje się odbiorca danych. Znajdowaniem drogi do odbiorcy, czyli trasowaniem (ang. routing, zajm uje się warstwa sieciowa. W intersieciach jest jeden podstawowy protokół w tej warstwie, mianowicie IP (ang. Internet Protocol, który działa niezależnie od zastosow anego medium transm isyjnego - można w tym celu użyć kabli elektrycznych, światłowodów, fal radiowych, a nawet gołębi pocztowych (patrz RFC 1149. Za obsługę konkretnego medium odpow iada warstwa dostępu do sieci. W arstwę tę zw ykle dzieli się na dwie podwarstwy. Warstwa łącza zajm uje się kodow aniem liniowym, synchronizacją odbiornika do nadajnika i kontrolą jakości transmisji. Kodowanie liniowe ma na celu dopasow anie sekwencji transm itow anych bitów do własności nośnika, np. gdy niepożądane jest w ysyłanie długich ciągów sam ych zer lub jedynek. Warstwa fizyczna definiuje param etry elektryczne nośnika. Zależnie od użytego medium są to przykładowo poziom y napięć w kablu, długość fali, rodzaj modulacji itp. Definiuje też param etry m echaniczni złączy. W popularnym siedm iowarstw owym modelu OSI (ang. Open System Interconnection Reference M odel, prom owanym przez ISO (ang. International Organization fo r Standardization, między warstwą aplikacji a transportową wyróżnia się dwie dodatkowe. Warstwa prezentacji ma za zadanie ustalenie jednolitego formatu danych. Są Rys. 2.1. Model warstwowy intersieci
2.2. Ethernet 55 to między innymi: porządek bajtów (little-endian lub big-endian, kodow anie liczb (uzupełnieniowe do dw ójki, uzupełnieniow e do jedynek itp. i napisów (ASCII, EBCDIC, UTF-8 itp.. Warstwa sesji odpow iada za m ultiplcksowanie danych różnych aplikacji. W tych w arstwach m oże leż odbyw ać się szyfrowanie danych, czego przykładem st protokoły SSH (ang. Secure Shell i TLS/SSL (ang. Transport Layer Security, Secure Socket Layer. Ethernet Ethernet jest jedynym przedstaw icielem warstwy dostępu do sieci rozważanym w tej książce. Sieć Ethernet m ożna zbudow ać w oparciu o różne rodzaje medium transmisyjnego. Na początku stosow any był gruby, a potem cienki kabel koncentryczny. O becnie najpowszechniej w ykorzystuje się kable skrętkow e, popularnie nazywane skrętką. M ożna też użyć św iatłow odu w ielom odowego lub jednom odowego. W spółpracę z w łaściwym medium transm isyjnym zapew nia nadajnik-odbiornik (ang. transceiver, obecnie częściej nazywany zew nętrznym urządzeniem warstwy fizycznej (ang. external physical layer device, a w żargonie skrótowo PHY. Obsługą sam ego protokołu Ethernetu zajm uje się układ sterow ania dostępem do medium M AC (ang. M edia A ccess Control. M AC i PifY połączone są za pomocą ustandaryzow anego interfejsu, np. MII (ang. M edia Independent Interface, RMII (ang. Reduced M edia Independent Interface lub GM II (ang. Gigabit Media Independent Interface, co uniezależnia M AC od zastosow anego rodzaju medium transm isyjnego i gw arantuje popraw ną współpracę układów różnych producentów. M ożna powiedzieć, że M AC realizuje warstwę łącza, a PHY w arstw ę fizyczną. W tabeli 2.1 zestawione są w ybrane warianty Ethernetu stosow ane w przeszłości i obecnie. Dla kabli skrętkow ych podano liczbę w ykorzystywanych par, a dla światłowodu najczęściej używaną długość fali. Podane są też typow e zasięgi dla standardow ego okablow ania - lepszej jakości kable lub św iatłow ody umożliwiają zw iększenie zasięgu. Tab. 2.1. Wybrane warianty Ethernetu Przepływność Oznaczenie Opis Zasięg 10 Mb/s 10BASE5 Gruby kabel koncentryczny 500 m 10BASE2 Cienki kabel koncentryczny 185 m 10BROAD36 Kabel współdzielący różne usługi, np. z telewizją kablową 3600 m 10BASE-T Skrętka min. kategorii 3, dwie pary 100 m 100BASE-T4 Skrętka min. kategorii 3, cztery pary ÏOOrrT 100 Mb/s 100BASE-TX Skrętka min. kategorii 5, dwie pary 100 m 100BASE-FX Światłowód wielomodowy, 1310 nm 412 m 1000BASE-T Skrętka min. kategorii 5, cztery pary 100 m 1 Gb/s 1000BASE-SX Światłowód wielomodowy, 850 nm 220 m 1000BASE-LX Światłowód jednomodowy, 1310 nm 5000 m 10GBASE-T Skrętka min. kategorii 6, cztery pary 55 m 10 Gb/s 10GBASE-SR Światłowód wielomodowy, 850 nm 82 m 10GBASE-LR Światłowód jednomodowy, 1310 nm 10 000m Kategoria kabla skrętkowego określa jego własności elektryczne. O gólnie, czym w yższa jest kategoria, tym szersze pasm o przenoszenia i mniejsze są przesłuchy między parami. Dla 10BASE-T wystarczy kabel kategorii 3, przenoszący pasmo 16 M Hz. Dla 100BASE-TX potrzebny jest kabel kategorii co najmniej 5 o paśmie 100 MHz. W nowych instalacjach stosuje się okablow anie kategorii 5e, 6 lub 6a. Kable kategorii 5e mają zredukow ane przesłuchy w stosunku do kategorii 5. Kable kategorii 6 i 6a mają pasm o przenoszenia odpow iednio 250 M Hz i 500 MHz. Na potrzeby bardzo szybkich sieci zdefiniow ano kategorie 7 i 7a, które mają pasmo przenoszenia odpow iednio 600 M Hz i 1000 M Hz. Stosowane są kable nieekranowane UTP (ang. unshielded tw isted pair lub ekranow ane. Ekran może być w ykonany z oplotu lub folii metalowej. Ekran może obejm ow ać cały kabel, każdą parę oddzielnie lub jednocześnie każdą parę i cały kabel. Kable ekranow ane mają różne oznaczenia, np.: FTP (ang. foiled tw isted pair, STP (ang. shielded twisted pair, SFTP (ang. screened fu lly shielded tw isted pair. Przem ysł telekom unikacyjny stosuje ustandaryzow ane oznaczanie przewodów kolorami. Skrętka używana w Ethernecie ma cztery pary w kolorach biało-niebieskim, biało-pom arańczow ym, biato-zielonym i biało-brązowym. Pierwszy przewód w parze ma izolację w kolorze białym z paskiem lub paskam i w drugim kolorze. Drugi przewód w parze ma izolację w drugim kolorze z opcjonalnym paskiem lub paskami w kolorze białym. W szystkie komponenty sieci Ethernet zbudowanej w oparciu 0 skrętkę wyposaża się w złącza RJ-45, przedstaw ione na ry su n k u 2.2. Kartę sieciową z koncentratorem (ang. hub, przełącznikiem (ang. switch lub gniazdem sieci strukturalnej łączy się za pom ocą kabla prostego (ang. patch cable, patch cord, którego dw a w arianty są zam ieszczone na ry su n k u 2.3. W arianty te mają identyczne połączenia elektryczne, różnią się przyporządkow aniem par do poszczególnych końców ek złącza. W szafach krosowniczych sieci strukturalnej też stosuje się kable proste. D o połączenia dw óch kart sieciow ych 10BASE-T lub 100BASE-TX trzeba użyć kabla skrzyżowanego (ang. crossover cable z ry su n k u 2.4. W 10BASE-T 1 100BASE-TX w ykorzystywane są tylko pary biało-pom arańczow a i bialo-zielona oraz styki 1, 2, 3 i 6 złącza RJ-45. Jako zwykli użytkownicy sieci najczęściej spotykam y się z Ethernetem w wersji 100BASE-TX lub 10BASE-T. W szystkie urządzenia obsługujące wariant 100BA SE-TX obsługują też 10BASE-T. U rządzenia obsługujące tylko 10BASE-T są aktu- Rys. 2.2. Złącze RJ-45 RJ-45 Male 87654321 w m m 12345678 RJ-45 Female 12345678
56 2. ntersieci 2.2. Ethernet 57 Rys. 2.3. Kabel prosty Wersja A Blalo-zieiony ^ Zletono-blały ^ <D- Biało-pomarańczowy - ^ Niebiesko-blaly ^ 0 -------------------------------- ^ ---- l-------------------------- 0 B la ło -n ie b ie s k i <D- ^ Pomarańczowo-bialy ^ ^ ^ Bialo-brązowy q Brązowo-bialy Wersja B Błalo-poroarańczowy ^ Pomarańczowo-bialy ^ aatojgto^r----------------------@ Nieblesko-bialy ^ *' ^ Bialo-niebieski Zielono-bialy ^ Bialo-brązowy ^ Brązowo-bialy alnie w zaniku. Jeszcze niedawno Ethernet 1 Gb/s stosowany byl przede wszystkim do łączenia urządzeń sieciow ych, które m uszą obsługiwać duży ruch (przełączniki, rutery, serwery, ale obecnie coraz częściej w interfejs mogący pracować również jako 1000BASE-T w yposażane są komputery osobiste. Układ testowy użyty do napisania tej książki, czyli zestaw ZL29ARM z modułem ZL3ETH, obsługuje 10BASE-T i 100BASE-TX. Dalszy opis będzie dotyczył przede w szystkim tych wariantów. Brązowo-bialy Rys. 2.4. Kabel skrzyżowany dla 10BASE-T i 100BASE-TX - 8 oktetów 6 oktetów 6 oktetów 2 oktety 46 do 1500 oktetów 4 oktety PREAMBUŁA ADRES ODBIORCY Rys. 2.5. Ramka Ethernetu ADRES NADAWCY DŁUGOŚĆ TYP DANE SEKWENCJA KONTROLNA Dane w sieci Ethernet są przesyłane w ramkach. Istnieje kilka standardów opisujących format ramki. Nie wchodząc w niepotrzebne szczegóły, wszystkie one definiują strukturę przedstaw ioną na ry su n k u 2.5. W terminologii sieciowej oktet (ang. octet oznacza 8 kolejnych bitów. Pojęcie to wprowadzono dla odróżnienia od bajlu, który w przeszłości nie zaw sze oznaczał 8 bitów. Przy zapisywaniu wysyłanego siecią ciągu oktetów obow iązuje konwencja, że oktety wysyłane są od lewej do prawej. Podobnie zapisuje się ciąg bitów przesyłanych siecią. Pierwszy wysyłany bit jest zapisywany po lewej. Natom iast pewnej uwagi wymaga konwencja, która określa, że w Ethernecie bity oktetu transm itowane są od najmniej znaczącego. Ram ka zaczyna się od preambuły, która zawiera na przemian bity 1 i 0, począwszy od jedynki. W yjątkiem jest ostatni bit, który też jest jedynką - pream buła kończy się dw om a jedynkam i. Pream buła wyglądu zatem następująco: 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101011 W zapisie szesnastkow ym jest to ciąg ośm iu oktetów: 55 55 55 55 55 55 55 d5. Pream buła w Ethernecie ło M b/s zapew nia synchronizację odbiornika do nadajnika. O dbiornik może zgubić początkowe bity ram ki, ale wystarczy, aby poprawnie rozpoznał koniec preambuły. Gubienie początkow ych bitów pream buły niczym nie grozi. W wariantach Ethernetu o większej przepływności stosuje się bardziej wyrafinowane kodow anie bitów, zapobiegające gubieniu początku ramki, ale pream buła została zachowana ze względu na kom patybilność. Pream buła jest obsługiwana przez sprzęt. O program owanie tworzy ramki bez pream buły i otrzym uje ramki z usuniętą preambułą. Kolejne dw a pola ramki to adres odbiorcy i nadawcy. Każdy interfejs w sieci Ethernet musi mieć unikalny adres, który nazyw am y adresem sprzętowym lub adresem M AC. Jeśli pierwszy bit adresu odbiorcy jest zerem, to jest to adres pojedynczego interfejsu. Jeżeli pierwszy bit adresu odbiorcy jest jedynką, to jest to adres grupow y (ang. multicast. W iele interfejsów może mieć wspólny adres grupowy. W ysianie ramki na adres grupowy oznacza wysłanie jej do wszystkich interfejsów w spółdzielących ten adres. Adres odbiorcy składający się z samych jedynek, czyli szesnastkow o adres ff ff ff ff ff ff, jest adresem rozgłoszeniowym (ang. broadcast i oznacza wszystkie interfejsy w danej sieci lokalnej. Jako adres nadawcy um ieszcza się adres interfejsu, który w ysyła ramkę. A dres nadawcy nie powinien być adresem grupow ym ani rozgłoszeniowym. Jeśli drugi bit adresu jest zerem, to jest to adres adm inistrow any globalnie. W tedy kolejne 22 bity adresu są unikalnym kodem producenta danego urządzenia (karty sieciow ej, a ostatnie 24 bity są unikalnym numerem tego urządzenia nadaw anym przez producenta. W ten sposób zapewnia się, że w jednej sieci nie znajdą się dw a interfejsy o tym samym adresie. Jeśli drugi bit adresu jest jedynką, to jest to adres administrow any lokalnie. W tedy pozostałe 46 bitów adresu możemy przydzielić według w łasnego uznania, pamiętając,
2.2. Ethernet 59 Tab. 2.2. Wybrane wartości pola typ ramki Ethernetu Wartość w polu TYP 0x0800 IP wersfa 4 0x0806 0x8100 ARP Protokół przesyłany w polu danych VLAN 0x86dd IP wersa 6 aby adresy w jednej sieci lokalnej się nie powtarzały. Dawniej adresy sprzętowe j były zapisywane na stale w ROM urządzenia. Obecnie najczęściej są zapisywane w pamięci Flash i m ogą być m odyfikow ane programowo. Pamiętajmy, że w zapisie i szesnastkow ym pierwszy bit adresu to najm łodszy bit pierwszego oktetu. K olejnym polem ramki jest pole DLUGOŚĆ-TYP, które można interpretow ać dwojako: jako długość pola danych lub jako typ pola danych. Jeśli w tym polu jest war- > tość od 46 do 1500, to należy go interpretow ać jako liczbę oktetów w polu danych, % Je.śli natomiast jest tam wartość w iększa lub równa 1536 (0x0600, to pole to należy. $, interpretować jako typ pola danych, określający protokół warstwy wyższej przesy- :Ę lany w tej ramce. Przykładowe, najczęściej spotykane typy pola danych są zamieszczone w tabeli 2.2. O protokołach tych piszę więcej w dalszej części tego rozdziału. ' Jeśli pole D LU GOŚĆ-TYP jest interpretow ane jako długość danych, to typ danych,1 może być określony za pom ocą dodatkow ego nagłówka, um ieszczonego w początku pola danych. W praktyce stosuje się wyłącznie ramki z polem D LUGOŚĆ-TYP interpretowanym jako typ. Poniew aż sprzęt jest w stanie rozpoznać koniec nadawania ramki, to długość pola danych nie musi być przesyłana. Jeśli pole TYP ma wartość 0x8100, to mamy do czynienia z ram ką sieci wirtualnej (VLAN, ang. virtual local area netw ork, przedstaw ioną na ry su n k u 2.6. Takie f t ramki umożliwiają w ydzielenie w jednej sieci fizycznej wielu sieci wirtualnych, M rozróżnianych za pom ocą znacznika (ang. tag VLAN. W łaściw e pole DŁUGOŚĆ- T -TY P jest wtedy um ieszczone za tym znacznikiem. X Pole danych może mieć zm ienną długość, zależnie od ilości danych, które mają być przesiane. Pole danych nie może być krótsze niż 46 oktetów (42 oktety dla ramek : j VLAN. Jeśli jest mniej danych do przesiania, to pole danych jest uzupełniane zera-.'m mi do minimalnej długości. M inim alna długość pola danych wynika z potrzeby za- j pewnienia pewnej ustalonej m inim alnej długości ramki, co jest potrzebne do prawi- m dlowego wykrywania kolizji, o których poniżej. M aksym alny rozm iar pola danych ( ogranicza ilość danych, które m ożna przesiać w pojedynczej ramce. Maksymalna długość pola danych nazywa się M TU (ang. maximum transmission unit sieci. H MTU Ethernetu wynosi 1500 oktetów. J Ostatnim polem ramki jest sekw encja kontrolna (ang. fram e check sequence. J e s t;;j j ona liczona za pomocą algorytmu C RC-32 (ang. cyclic redundancy check opisanego j w normie IEEE 802.3 i służy do weryfikacji integralności ramki. Sekwencja kontrolna ramki jest wyliczana przez nadajnik i weryfikowana przez odbiornik sprzętowo. 8 oktetów 6 oktetów 6 oktetów 2 oktety 2 oktety PREAMBUŁA ADRES ODBIORCY ADRES NADAWCY 0x8100 Rys. 2.6. Ramka Ethernetu ze znacznikiem VLAN ZNACZNIK VLAN 2 oktety "d ł u g o ś ć " TYP 42 do 1500 oktetów 4 oktety DANE SEKWENCJA KONTROLNA W1 k J W7 Koncentrator W2 I lub 1 W6 przełącznik ---------- Rys. 2.7. Topologia sieci Ethernet opartej o kabel skrętkowy W4 Ethernet oparty na skrętce ma topologię gwiazdy, pokazaną na ry su n k u 2.7. Każde urządzenie sieciow e ma jeden port (interfejs sieciowy, za pom ocą którego łączy się ze w spólnym centralnym urządzeniem wieloportow ym, czyli koncentratorem (ang. hub lub przełącznikiem (ang. switch. W celu zbudow ania większej sieci koncentratory i przełączniki można łączyć ze sobą hierarchicznie. K oncentrator i przełącznik różnią się zasadą pracy. K oncentrator jest urządzeniem biernym. Każdą odebraną ram kę retransm ituje do wszystkich swoich portów, z w yjątkiem oczyw iście portu, z którego ram kę odebrał. Przełącznik natom iast analizuje adresy w nagłówkach ramek i zapam iętuje adresy sprzętow e (M AC urządzeń podłączonych do poszczególnych portów. Jeśli przełącznik wie, do którego portu jest podłączony adresat ramki, to w ysyła ją tylko do tego portu. Poniew aż urządzenia m ogą zmieniać port, do którego są podłączone, przełączniki nie pam iętają przyporządkowania adresu do portu w nieskończoność - jeśli urządzenie nie jest aktyw ne przez pewien ustalony czas, to informacja o jego adresie jest zapom inana. Ponow na aktywność urządzenia odśw ieża tę informację. Przełącznik m ożna zw ykle konfigurow ać, aby filtrował i separował ruch sieciow y między pewnymi portam i i tw orzyć w ten sposób sieci wirtualne. Przełączniki, w porów naniu z koncentratoram i, zm niejszają zbędny ruch w sieci. Obecnie w nowo budowanych sieciach instaluje się w yłącznie przełączniki. Rozważm y sytuację, gdy w centrum sieci z rysunku 2.7 jest koncentrator. Koncentrator retransm ituje natychm iast każdą odebraną ramkę do wszystkich portów, oprócz tego, z którego tę ram kę odebrał. O znacza to, że żaden port nie może jednocześnie nadawać i odbierać. Zatem wszystkie interfejsy pracują w trybie naprzem iennym (ang. half-cluplex. Ponadto w danym m om encie tylko jedno urządzenie podłączone do koncentratora może nadawać. Jeśli więcej niż jedno urządzenie nadaje, pow staje kolizja i żadna ram ka nie jest popraw nie odbierana. Musi więc istnieć jakiś sposób sprawiedliw ego rozstrzygania o kolejności nadawania ramek. W Ethernecie jest stosowany rozproszony protokół bazujący na śledzeniu stanu dostępności m edium transm isyjnego i w ykrywaniu kolizji (CSM A/CD, ang. Carrier Sense M ultiple Access with Collision D etection. Urządzenie może rozpocząć nadawanie, gdy ustali, że żadne inne urządzenie nie nadaje przez ustalony w standardzie czas odstępu m iędzyram kowego (ang. interframe gap. Jeśli jakieś urządzenie rozpocznie nadaw anie ramki, pozostałe odbierają ją (koncentrator rozsyła ją do w szystkich i wstrzym ują się z nadawaniem. Sygnały w sieci nie rozchodzą się jednak nieskończenie szybko. M oże się więc zdarzyć, że dw a urządzenia wykryją, że medium jest w olne i rozpoczną nadawanie praw ie rów nocześnie. Jednak po jakim ś czasie oba stwierdzą, że w ystąpiła kolizja. W tedy przestają nadawać. Po wykryciu kolizji każde urządzenie odczekuje pseudolosow y czas, zanim znów zacznie testować do W5
60 2. Intersieci 2.3. IP - protokół intersieci 61 2.3. stępność medium. Pseudolosow ość zm niejsza szansę w ystąpienia ponownej kolizji i zapew nia sprawiedliwy dostęp do medium. Po każdej kolejnej kolizji zw iększa-1 ny jest przedział wartości, z którego losowany jest czas oczekiwania na kolejna 5 próbę transmisji. Jeśli wysianie ramki nie pow iedzie się kilkanas'cie razy, ramka jest porzucana. Aby mechanizm w ykrywania kolizji działał poprawnie, pierwszy bit 0 ramki musi dotrzeć do najdalszego zakątka sieci Ethernet, zanim zostanie wysiany " ostatni bit tej ramki. Należy przy tym uwzględnić opóźnienia wnoszone przez kable 'i i wszystkie interfejsy sieciow e po drodze. D latego właśnie w standardzie zdefiniowano m inim alną długość ramki. Najkrótsza ram ka bez pream buły ma 64 oktety. Należy w yraźnie zaznaczyć, że kolizje nie są niczym negatywnym, są nieodzowny częścią protokołu rozstrzygania o kolejności dostępu do medium. Rozważm y teraz sytuację, gdy w centrum sieci jest przełącznik. Sieć nadal może \ pracować w trybie half-duplex i stosow ać CSM A /CD. Jednak przełącznik umożli- j w ia bardziej efektywne w ykorzystanie medium. Nie rozsyła on każdej odebranei ; ram ki do wszystkich portów. Interfejsy mogą być teraz skonfigurow ane w trybii jednoczesnym { mg. full-duplex, czyli mogą jednocześnie nadawać i odbierać ram- ji!' ki. Nie w ystępują kolizje i nie używ a się CSM A /CD. W Ethernecie 1 Gb/s i 10 Gb/s R? w praktyce stosuje się tylko tryb jednoczesny. Interfejsy ethernetow e um ożliw iają zw ykle negocjow anie trybu pracy. Wybierany jest tryb o największej wspólnie obsługiw anej przepływności. Ponadto preferowanj jest tryb jednoczesny. IP - protokół intersieci Podstawow ym protokołem intersieci jest IP (ang. Internet Protocol. M im o że dawno temu opracow ano ju ż jego w ersję 6, obecnie nadal najpowszechniej używana. jest w ersja 4, która jest opisana w RFC 791 i przyjęto jako standard intersieci STD 5. W dalszej części książki pom ijam numer wersji i jeśli piszę o IP, to należy dom niem ywać wersję 4. IP definiuje adresy sieciow e, nazyw ane adresami IP. Adres IP jest przypisany do interfejsu sieciowego, a nie do urządzenia. Pojedynczy węzeł sieci (ang. nade, host może mieć wiele interfejsów i każdy z nich ma swój unikalny adres IP. Jednemu interfejsowi można przypisać w iele adresów IP. Kojarzenie adresu IP z węzłem sieci, spotykane w zwrotach typu adres IP kom putera, wynika stąd, że wiele kom puterów (osobistych ma tylko jeden interfejs sieciowy i w tedy taki zw rot jesi jednoznaczny. Adres IP jest liczbą 32-bitową. Posługiw anie się zapisem dw ójkowym jest niewygodne, dlatego zw ykle stosuje się notacją dziesiętną z kropkami 32 bity adresu dzieli się na 4 oktety. Każdy oktet zapisuje się jako liczbę dziesiętną. Poszczególne liczby oddziela się kropką. Przykładowo adres 11000000 10101000 00110011 01010100 zapisuje się jak o 192.168.51.84. Adres IP składa się z prefiksu i sufiksu. Prefiks wyróżnia się za pom ocą maski, która jest, tak jak adres IP, liczbą 32-bitową. Pozycje bitów, na których w masce jest jedynka, to pozycje bitów adresu należące do prefiksu, a pozycje bitów, na których w masce jest zero, to pozycje bitów adresu należące do sufiksu. Przykładowo maska 11111111 11111111 11111111 00000000 oznacza, że prefiks adresu IP składa się ' z początkowych 24 bitów, a sufilcs to końcow e 8 bitów. M aski, podobnie jak adresy, zapisuje się za pom ocą notacji dziesiętnej z kropkam i. Powyższa maska jest więc zapisywana jako 255.255.255.0. K iedyś rozw ażano stosow anie masek, w których jedynki i zera mogły się dow olnie przeplatać, ale obecnie takich masek się nie używa. Zatem maskę można jednoznacznie zdefiniować, podając liczbę od 0 do 32, określającą liczbę jedynek znajdujących się na początku maski. Adres wraz z maską zapisuje się alternatywnie, podając tę liczbę po ukośniku. Przykładowo napis 192.168.51.84/24 oznacza adres 192.168.51.84 z maską 255.255.255.0. Prefiks adresu wraz z maską wyznacza pulę adresów, do której należą wszystkie adresy mające ten sam prefiks. Przykładowo 192.168.51.0/24 oznacza przedział adresów od 192.168.51.0 do 192.168.51.255. Pewne przedziały adresów mają specjalne znaczenie. Adresy 127.0.0.0/8, czyli adresy od 127.0.0.0 do 127.255.255.255, są zarezerwow ane dla tzw. pętli zwrotnej. Adresy te nie mogą pojawić się w żadnej sieci fizycznej. Pętla zwrotna jest fikcyjnym interfejsem sieciowym, dostarczanym przez oprogram ow anie stosu protokołów sieciow ych, stosowanym do testowania programów - wszystko wysłane przez ten interfejs wraca do niego. Zwykle w tym celu używa się adresu 127.0.0.1. N astępujące pule adresów są zarezerwowane dla intersieci pryw atnych i nie mogą pojaw iać się w Internecie: - od 10.0.0.0 do 10.255.255.255, - od 172.16.0.0 do 172.31.255.255, - od 192.168.0.0 do 192.168.255.255. Pula od 169.254.0.0 do 169.254.255.255 jest przeznaczona na adresy lokalne, które węzły mogą sobie przydzielać autom atycznie, ale są to adresy nierutowalne, czyli nie mogą pojawić się w Internecie. Adresy od 224.0.0.0 do 239.255.255.255 są adresami grupowym i (ang. multicast. Adres grupowy wskazuje wiele interfejsów sieciowych. Intersieci buduje się z mniejszych sieci, nazyw anych podsieciam i, sieciami fizycznymi lub sieciam i lokalnymi. Podsieć m ożna zbudow ać jako sieć Ethernet. Podsieć można również utworzyć jako sieć wirtualną, stosując technikę VLAN. Podsieci łączy się za pom ocą ruterów. Ruter (ang. router to węzeł, który ma wiele interfejsów sieciow ych - po jednym w każdej łączonej podsieci. W sieciach wirtualnych interfejsy rutera również mogą być wirtualne - jeden interfejs fizyczny może być w spółdzielony przez wiele sieci wirtualnych. Przykładowa intersieć jest pokazana na ry su n k u 2.8. W szystkie adresy IP w obrębie jednej podsieci mają wspólny prefiks. Prefiks adresu IP wyznacza adres tej podsieci. Sufiks adresu IP oznacza unikalny adres w podsieci. Pulę adresów podsieci określa się, podając prefiks adresu IP i maskę, np. 192.168.51.0/24 oznacza adresy od 192.168.51.0 do 192.168.51.255. Adresu z sufiksem składającym się z sam ych zer nie przydziela się ze względów historycznych. Adres z sufiksem składającym się z sam ych jedynek jest adresem ukierunkowanego rozgłaszania - adresatami są wszystkie interfejsy w danej podsieci. Przykładowo dla sieci 192.168.51.0/24 jest to adres 192.168.51.255. Adres 255.255.255.255 jest adresem rozgłaszania (ang. broadcast w bieżącej podsieci. Adres, którego prefiks składa się z sam ych zer, jest zawsze adresem w bieżącej podsieci, np. adres 0.0.0.84 w podsieci 192.168.51.0/24 w skazuje interfejs o adresie 192.168.51.84.
62 2. Intersieci' 2.3. IP - protokół intersieci 63 Tab. 2.3. Tablica tras węzła W1 Adres odbiorcy 10.0.0.0/24 Interfejs etho Następny Bezpośrednio Dowolny etho 10.0.0,1 bezpośrednio rutery R1 i R2. A dresy w tej podsieci należą do puli 192.168.1.128/ 30, czyli można w niej rozdzielić tylko dw a adresy: 192.168.1.129 i 192.168.1.130. i? Pierwszy z nich przydzielono intefrejsowi elh l rutera R l, a drugi - interfejsowi --i etho rutera R2. W tej podsieci adresem rozgłaszania jest 192.168.1.131, choć mało prawdopodobne, aby rozgłaszanie było w ykorzystywane w sieci dwuwę-. zlowej. Ruter R l ma jeszcze interfejs eth2, który łączy naszą przykładow ą sieć V z Internetem. Po prawej stronie rysunku widzim y podsieć 172.19.0.0/20. Można. w niej przydzielić 4094 adresy: od 172.19.0.1 do 172.19.15.254. Adresem rozgłaszania jest 172.19.15.255. Węzły końcowe sieci mają po jednym interfejsie sieciowym. Każdy ruter ma co najmniej dwa interfejsy. Każdy interfejs pracujący w warstwie sieciowej (patrz ry- sunek 2.1 ma przypisany adres IP. Interfejsy (porty przełączników PI i P2 ethernetowyeh nie mają przypisanych adresów IP, gdyż przełącznik pracuje w warstwie dostępu do sieci. Pora teraz wyjaśnić, jak IP przesyła dane w intersieciach. Dane do wysiania pakowane są w datagram y IP i w ysyłane w polu danych ramki ethernetow ej, której pole / TY P ma wartość 0x0800 (patrz tabela 2.2. Ten proces nazyw a się kapsulkowaniem (ang. eiicapsulrition. Datagram IP zaw iera w nagłówku adres odbiorcy, na którego podstawie węzeł podejm uje decyzję, co zrobić z tym datagram em. Węzeł posługuje się w tym celu tablicą tras (ang. routing labie i wykonuje algorytm wyznaczania tras, nazyw any też algorytm em trasow ania (ang. routing algorithm. Szczególną rolę w algorytm ie trasow ania odgryw ają rutery. W niektórych system ach ruter bywa nazywany bram ą (ang. gateway. Przypuśćmy, że węzeł W1 chce wysiać datagram IP do węzła W 2. Posługuje się w tym celu sw oją tablicą tras, przedstaw ioną w tabeli 2.3. O program owanie trasujące przegląda pierwszą kolum nę tablicy tras, w iersz po wierszu, w poszukiwaniu wzorca zgodnego z adresem odbiorcy. W tym przypadku pierwszy w iersz opisuje przedział, do którego należy adres węzła W2. Po znalezieniu zgodności datagram IP jest w ysyłany przez interfejs w ymieniony w drugiej kolum nie tego w iersza do następnego węzła, którego adres jest um ieszczony w trzeciej kolumnie. W rozw ażanym przypadku zostanie użyty jedyny interfejs w ęzła W l, czyli etho, a datagram IP zostanie wysiany bezpośrednio do w ęzła W 2. B ezpośrednie wysłanie jest możliwe, gdyż W l i W 2 są podłączone do w spólnego przełącznika. Pierwszy w iersz tablicy tras w ęzła W l opisuje w łaśnie te węzły, do których W l może wysyłać datagramy IP bezpośrednio. Rozważm y teraz sytuację, gdy węzeł W l chce w ysłać datagram IP do węzła W4. Nie może tego zrobić bezpośrednio, bo węzły te są w różnych podsieciach. Adres W 4 pasuje do wzorca adresów w drugim wierszu tablicy tras węzła W l. Zwykle ostatni w iersz w tablicy tras opisuje dom yślne (ang. clefault zachow anie algorytmu trasow ania i obejm uje wszystkie adresy, które nie pasują do poprzednich w ierszy. W tym przypadku węzeł W l wyśle datagram na adres 10.0.0.1, czyli do rutera R l. W datagram ie w ysyłanym do rutera adres odbiorcy to nadal adres docelowego odbiorcy, a nie rutera. Ruter, otrzym aw szy ten datagram, posłuży się w celu jego dostarczenia sw oją tablicą tras, przedstaw ioną w tabeli 2.4. W tej tablicy tras adres w ęzła W 4 pasuje do wzorca w drugim wierszu i dlatego datagram IP zostanie wysiany przez interfejs etlil do kolejnego rutera o adresie 192.168.1.130, czyli do rutera R2, który już jest we wspólnej podsieci z węzłem W 4 i może dostarczyć datagram IP bezpośrednio do odbiorcy. Tablica tras rutera jest zazwyczaj bardziej rozbudow ana niż tablica tras węzła końcowego. W ęzłowi końcowemu w ystarczają zw ykle dw a wpisy - jeden dotyczący podsieci, w której jest ten węzeł, a drugi domyślny, kierujący wszystkie datagramy do rutera. Ruter ma po jednym wpisie dla każdej bezpośrednio do niego podtączo- Tab. 2.4. Tablica tras rutera R1 Adres odbiorcy Interfejs Następny 10.0.0.0/24 etho Bezpośrednio 172.19.0.0/20 ethl 192.168.1.130 1 92.168.1.128/30 ethl Bezpośrednio. 212.85.125.0/24 eth2 Bezpośrednio Dowolny eth2 212.85.125.1
64 2. Intersieći 2.3. IP - protokół inlersieci 65 WERSJA D L NAGŁ. TYP OBSŁUGI DŁUGOŚĆ CAŁKOWITA [0:3] [4:7] [8:15] [16:31] IDENTYFIKACJA ZNACZNIKI PRZESUNIĘCIE FRAGMENTU [0:15] [16:18] [19:31] CZAS ŻYCIA PROTOKÓŁ SUMA KONTROLNA NAGŁÓWKA [0:7] [8:15] [16:31[ Rys. 2.9. Podstawowy nagłówek datagramu IP ADRES IP NADAWCY [0:31] ADRES IP ODBIORCY [0:31] nej podsieci oraz po jednym wpisie dla każdej podsieci, która nie jest bezpośrednio : podłączona, ale ruter zna do niej trasę. R uter ma też zw ykle wpis domyślny, który kieruje do innego rutera. Ruter nie musi znać tras do w szystkich podsieci - data gramy do nieznanych podsieci przesyła temu dom yślnem u ruterowi w nadziei, że l tamten zna trasę. W wyżej przedstawiony sposób rutery przekazują sobie datagram y IP, aż do pomyślnego dostarczenia do odbiorcy lub w ystąpienia błędu - ruter na ścieżce może być ; uszkodzony lub przeciążony, ruter może nie znaleźć właściwego wpisu w tablicj tras, interfejs może być wyłączony lub uszkodzony, dostarczanie na pew ne adresy m oże być zablokow ane adm inistracyjnie itd. IP jest usługą bezpołączeniow ą - dla każdego datagram u trasa w inlersieci jest w yznaczana indywidualnie. Teoretycznie datagram y IP m iędzy tą sam ą parą węzłów mogą poruszać się różnym i trasami. IP jest usługą zaw odnego dostarczania w ram ach dostępnych m ożliwości. O znacza to. że oprogram ow anie IP stara się dostarczać datagramy, używ ając wszelkich dostępnych tras, ale datagram y mogą być gubione i dostarczane w innej kolejności niż zostały wysiane. Teoretycznie datagram y mogą też być duplikowane, czyli odbiorca m oże dostać ten sam datagram więcej niż jeden raz. Datagram IP składa się z nagłów ka i przesyłanej po nim zawartości. Podstawowy nagłów ek jest pokazany na rysunku 2.9. Jest to typowy sposób przedstawiania struktur danych w intersieeiach, używ any w dokum entach RFC. Każdy w iersz reprezentuje kolejne 32 bity, czyli 4 oktety struktury. O ktety są wysyłane wierszami od góry do dołu, a w wierszu od lewej do prawej. Num eracja bitów jest od lewej do prawej - od 0 do 31. Jeśli jakieś pole jest interpretow ane jako liczba dwójkowa, to najbardziej znaczący bit (M SB, ang. most significant bit jest pierwszy od lewej, a najmniej znaczący (LSB, ang. least significant bit - pierwszy od prawej. W ynika z tego, że liczby, których reprezentacja zajm uje więcej niż jeden oktet, są w ysyłane w tak zw anym sieciow ym porządku oktetów, tożsam ym z porządkiem grubokońców kow ym (ang. big-endian, czyli jako pierwszy jest przesyłany najbardziej znaczący oktet, a jako ostatni - najmniej znaczący. Zauważmy, że sieciowa kolejność oktetów jest odw rotna do kolejności oktetów stosowanej w komputerach osobistych o architekturze x86 i m ikrokontrolerach STM 32, które są maszynami ; cienkokońcówkow ym i (ang. little-eiulian. W rócę jeszcze do tego przy opisie adaptacji biblioteki lwip do architektury STM 32. 31 Pole W ERSJA zajm uje pierwsze 4 bity nagłówka IP i jest w nim numer wersji, czyli 4. Pole DL. NAGŁ. (ang. internet header length zajm uje kolejne 4 bity i zawiera długość nagłówka w w ielokrotnościach 4 oktetów. Podstawowy nagłówek ma 20 oktetów, więc w tym polu jest zw ykle liczba 5. N agłówek może zawierać dodatkow e opcje i być dłuższy, ale zawsze jego długość jest uzupełniana do wielokrotności 4 oktetów. Zatem pierwszy oktet nagłów ka IP zaw iera zwykle wartość szesnastkow ą 0x45. Pole TY P OBSŁU GI (ang. type o f service określa priorytet datagramu. Pole D ŁU GO ŚĆ CAŁKOW ITA zaw iera sum aryczną długość nagłówka i zawartości datagram u w oktetach. Poniew aż pole to jest 16-bitowe, maksymalna długość datagram u wynosi 65 535 oktetów, a maksym alna długość jego zawartości nie może przekroczyć 65 515 oktetów. Kolejne trzy pola ID EN TY FIK A CJA, ZN A CZN IK I i PRZESU N IĘCIE FRAG M EN TU w ykorzystuje się podczas fragm entow ania i składania datagram ów IP. M imo że datagram może mieć m aksym alnie 65535 oktetów, maksym alna jego długość w konkretnej technologii dostępu do sieci nie może przekroczyć MTU tej sieci. Przypom inam, że M TU Ethernetu w ynosi zw ykle 1500 oktetów. Jeśli zachodzi potrzeba w ysiania datagram u IP dłuższego niż MTU sieci, to taki datagram musi zostać podzielony na fragm enty o rozm iarach nie w iększych niż MTU. Sytuacja taka może wystąpić u pierw otnego nadawcy datagram u lub w ruterze łączącym sieci o różnych M TU. Po podzieleniu poszczególne fragm enty są przesyłane osobno. Składaniem tych części w całość zajm uje się końcow y odbiorca. Jest to możliw e, gdyż w szystkie fragm enty datagram u IP mają tę samą wartość w polu ID ENTYFIKACJA. Pole PR ZESU N IĘC IE FRAGM EN TU (ang. fragm ent offset inform uje o położeniu fragm entu w oryginalnym datagram ie. Pole ZNACZNIKI zaw iera dw a znaczniki: - nie fragmentuj - tego datagram u IP nie w olno fragmentować; jeśli ten datagram jest za długi, należy go porzucić; - więcej fragm entów - informuje, że to nie jest ostatni fragment, pozwala rozpoznać ostatni fragment. Pole CZAS ŻYCIA (TTL, ang. time to live określa, ile razy ten datagram IP m ożna przesiać przez sieć. Każdy węzeł (zw ykle jest to ruter, przesyłając datagram dalej, zm niejsza wartość tego pola o jeden. Jeśli wartość czasu życia osiągnie zero, to datagram jest usuwany. Chodzi o to, aby datagramy, których nie można dostarczyć, nie krążyły w sieci w nieskończoność. Datagram może być niedostarczalny np. z powodu błędu w konfiguracji tablic tras w ruterach, skutkującego powstaniem cyklu. Pole PROTO KÓ Ł zaw iera num er protokołu kapsulkowanego (przenoszonego w tym datagram ie IP. Najczęściej spotykane wartości tego pola są zam ieszczone w tabeli 2.5. TCP i U DP są protokolarni warstwy transportowej. ICM P (ang. Internet Control M essage Protocol jest protokołem diagnostycznym warstwy sieciowej i transportowej, umożliw iającym między innymi wykrywanie problemów z trasowaniem. Na przykład kom unikat ICM P wysyła się do pierwotnego nadawcy datagram u IP, jeśli datagram ten usunięto z powodu upłynięcia jego czasu życia - patrz też rozdział 3.8.
2.5. Sieć testowa 67 Tab. 2.5. Wybrane wartości pola PROTOKÓŁ nagłówka IP Wartość w polu PROTOKÓŁ Kapsulkowany protokół 1 ICMP 6 TCP 17 UDP Pole SUM A K ONTROLNA N AGŁÓ W KA służy do sprawdzania integralności n u -1 główka. Pola ADRES IP NADAW CY i ADRES IP O DBIO RCY zawierają odpo-fj w iednie adresy, zgodnie z nazwami tych pół. ARP - tłumaczenie adresów sieciowych na adresy sprzętowe W poprzednim podrozdziale pom inąłem bardzo istotny problem. Napisałem, że węzeł może wysłać datagram IP bezpośrednio do innego węzła (w tym rutera w swojej podsieci, kapsułkując go w ram ce ethernetowej. Powstaje probiem. gdyż datagramy posługują się adresami sieciowymi IP, a Ethernet używa adresów sprzętow ych MAC. Potrzebny jest jakiś mechanizm pozwalający węzłowi ustalić na podstawie adresu sieciow ego odbiorcy jego sprzętowy adres, który trzeba umieścić w nagłówku ramki ethernetow ej. Tłum aczeniem adresów sieciowych na sprzętowe zajmu je się ARP (ang. Acldress Resolution Protocol, opisany w RFC 826 i przyjęty jako standard intersieci STD 37. A RP może tłum aczyć adresy dow olnego protokołu sieciowego na adresy dow olnego protokołu sprzętpwego. W dalszym ciągu ograniczam się do najbardziej interesującego nas przypadku, czyli do zam iany adresów IPna adresy ethernctowe. Komunikat ARP podróżu je w polu danych ramki ethernetow ej, która ma w polu typ wartość 0x0806 (patrz tabela 2.2. Struktura komunikatu ARP jest przedstawiona na ry su n k u 2.10. Pole RODZAJ ADRESU SPRZĘTOW EG O dla Ethernetu ma wartość 1. Pole RODZAJ ADRESU SIECIO W EG O dla IP ma wartość 0x0800. Pole DL. ADR. SPRZĘTÓW, określa długość adresu sprzętow ego w oktetach i dla Ethernetu ma wartość 6. Pole DL. ADR. SIECIOW. określa długość adresu siecio- RODZAJ ADRESU SPRZĘTOWEGO [0:15] RODZAJ ADRESU SIECIOWEGO [16:31] DL. ADR. SPRZĘTÓW. D L ADR. SIECIOW. OPERACJA [0:7] [8:15] [16:31] ADRES SPRZĘTOWY NADAWCY (oktety 5-6 [0:15] ADRES SIECIOWY NADAWCY (oktety 3-4 [0:15] ADRES SPRZĘTOWY NADAWCY (oktety 1-4 [0:311 ADRES SPRZĘTOWY ODBIORCY (oktety 3-6 [0:31] ADRES SIECIOWY ODBIORCY (oktety 1-4 10:31] Rys. 2.10. Format komunikatu ARP dla Ethernetu i IP ADRES SIECIOWY NADAWCY (oktety 1-2 [16:31] ADRES SPRZĘTOWY ODBIORCY (oktety 1-2 [16:31] 31..! î - wego w oktetach i dla IP ma wartość 4. Pole O PERACJA przyjmuje wartość 1 dla prośby o przetłum aczenie adresu, a wartość 2 dla odpowiedzi na tę prośbę. Kolejne pola to odpow iednie adresy. D ługości tych pól są określone przez wartości pól DL. ADR. SPRZĘTÓW, i DL. ADR. SIECIOW. Węzeł, chcąc wysiać datagram IP do odbiorcy, którego adresu ethernetow ego nie zna, rozgłasza do w szystkich węzłów w swojej podsieci prośbę A RP (wartość 1 w polu OPERA CJA. Prośba jest w ysyłana za pom ocą sprzętow ego mechanizmu rozgłaszania na adres elhernetow y ff ff ff ff ff ff. Nadawca wypełnia pola ADRES SPRZĘTOW Y NADAW CY i ADRES SIECIO W Y NADAW CY swoimi adresami. W polu ADRES SIECIO W Y O DBIO RCY um ieszcza adres IP węzła, którego adres ethernetow y chce otrzym ać. W prośbie zaw artość pola ADRES SPRZĘTOW Y ODBIO RCY jest nieistotna. Węzeł, który ma poszukiwany adres IP, odpow iada bezpośrednio pytającem u, gdyż zna jego adres ethernetowy, bo przed chw ilą otrzyma! od niego ram kę z prośbą. W odpowiedzi pole O PERA CJA ma wartość 2. Nadawca odpowiedzi um ieszcza sw oje adresy w polach ADRES SPRZĘTOW Y NADAW CY i ADRES SIECIO W Y NADAWCY. W polach ADRES SPRZĘTOW Y ODBIORCY i ADRES SIECIOW Y O DBIO RCY um ieszcza odpow iednie adresy węzła, który przysłał prośbę - te pola są kopiow ane z odpow iednich pól komunikatu z prośbą. Gdyby ograniczyć się tylko do przedstaw ionego wyżej algorytm u, kom unikacja byłaby bardzo nieefektyw na - wysianie każdego datagram u IP wymagałoby przesiania trzech ram ek ethernetow ych, w tym jednej rozgłoszeniowej. Aby poprawić efektywność kom unikacji, węzły utrzym ują pamięć podręczną (ang. cache ARP, gdzie przechow ują poznane dotychczas przyporządkow ania adresów ethernetowych do adresów IP. W ystarczy więc tylko raz na początku komunikacji zapytać o adres ethernetowy. Dodatkowo węzeł proszony o adres zapam iętuje w swojej pamięci podręcznej A R P pow iązanie adresu IP proszącego z jego adresem ethernetowym, gdyż prośba A RP poprzedza zw ykle w ysłanie datagram u IP i jest wysoce praw dopodobne, że węzeł proszony o adres ethernetow y za chw ilę będzie musiał odpowiedzieć na otrzym any datagram - wysianie prośby A RP jest zwykle początkiem kom unikacji dwukierunkow ej. Poniew aż węzły w sieci mogą znikać i zmieniać adresy, to wpisy w pamięci podręcznej A RP uaktualnia się co pewien czas przez w ysianie ponow nego zapytania oraz na podstaw ie analizy nagłówków otrzym yw a nych ram ek, również tych kierowanych do innych węzłów. Jeśli węzeł przestaje być aktywny i nie odpow iada na prośby ARP, to dotyczący go wpis usuwa się z pamięci podręcznej ARP. Sieć testowa Aby uruchom ić kolejne przykłady, potrzebujem y stworzyć małą sieć testową. Można ją zbudow ać w oparciu o zestaw ZL29A RM z mikrokontrolerem STM 32F107, który jest wyposażony w interfejs Ethernet zgodny z norm ą IEEE 802.3-2002. W samym mikrokontrolerze jest wbudowany układ M AC. U zyskanie w pełni funkcjonalnego interfejsu wym aga dołączenia PIIY. W zestawie ZL29ARM jest gniazdo do włożenia. modułu ZL3ETH z PHY zrealizow anym na układzie DP83848C. Potrzebny nam jest jeszcze kom puter wyposażony w kartę elhernetow ą z gniazdem RJ-45, obsługującą 100BASE-TX lub 10BASE-T. K om puter z modułem ZL3ETH łączy
68 2. Inlersieci się za pom ocą kabla skrzyżowanego, jak to pokazano na ry su n k u 2.11. Niestety. ; nie w szystkie przykłady uda się przetestow ać w tak prostej sieci. Aby sprawdzić.«i dynam iczne przydzielanie adresów, trzeba na kom puterze uruchom ić serwer Dl l( P. ' Przykład 8 w ym aga dostępu do Internetu - aby go uruchom ić, kom puter musi miet jeszcze jakiś' inny interfejs sieciowy, łączący go z Internetem. Może to być np. draga karta Ethernet, interfejs W i-fi, modem A DSL podłączony do USB lub modem GSM. Trzeba wtedy na kom puterze uruchom ić funkcję rutera. Zapewne lepszym i rozw iązaniem jest zbudow anie małej sieci lokalnej. Przykłady, które opisuję w tej książce, testowałem w sieci przedstawionej na ry su n ku 2.12. Zbudowałem ją na bazie wielofunkcyjnego urządzenia W RT54GL firmy Linksys, która jest częścią koncernu Cisco. U rządzenie to jest nazywane ruterem ; dom ow ym, ale zaw iera znacznie więcej niż tylko ruter. W RT54GL zaw iera prze- i. j- łącznik ethernetowy, którego cztery porty w yprowadzone są na gniazda RJ-45. Do jednego z nich podłącza się układ z mikrokontrolerem. Pozostałe można wykorzy- J stać do rozbudowy sieci i podłączenia kom puterów lub kolejnych m ikrokontrolerów. K om putery m ożna też podłączać bezprzew odowo, w ykorzystując wbudowany punkt dostępowy (ang. access point Wi-Fi. Konfigurowanie ustawień sieciowych kom puterów w sieci testowej, czyli uzyskiwanie adresu IP, maski podsieci, adresu rutera i adresu serw era nazw (ang. D NS server zapew nia zintegrow any serwer / ; D H CP (ang. D ynam ie H ost Configuration Protocol. Sieć testową z Internetem łączy ruter, będący centralną częścią W RT54GL. Dostęp do Internetu zapew nia nio- dem A D SL (ang. A sym m etric D igital Subscriber Line. Ruter od strony Internetu ; ma klienta DHCP, co um ożliw ia uzyskanie od dostawcy Internetu (ang. Internet ; service provider potrzebnych ustawień sieciowych. Ruter ma w budowaną prostą ścianę ogniow ą (ang. firew all, która chroni sieć testow ą przed niektórym i zagrożeniami pochodzącym i z Internetu. Całe urządzenie konfiguruje się za pom ocą przeglądarki internetowej, łącząc się z w budow anym serwerem zarządzającym. W sieci testowej, której używałem, skonfigurowałem pulę adresów 192.168.51.80/28. Adres 192.168.51.81 przypisałem ruterowi. Adresy od 192.168.51.82 do 192.168.51.87 s> 2.6. Przykład 2 - monitor sieci 69 Rys. 2.12. Typowa domowa sieć lokalna zarezerwowałem do konfigurowania manualnego. Adresy od 192.168.51.88 do 192.168.51.94 przeznaczyłem do konfigurowania za pomocą serwera DHCP. Adresem ukierunkowanego rozgłaszania w tej sieci jest 192.168.51.95. Sieć testowa używa adresów z puli prywatnej. Ponieważ adresy takie nie mogą pojawiać się w Internecie, trzeba zastosować ich konwersję. Zastosowany ruter ma wbudowaną usługę NAT (ang. NetWork Address Translation. NAT polega na podmienianiu w locie adresów w nagłówkach datagramów IP. Sieć testowa jest widziana w Internecie, jakby była pojedynczym węzłem końcowym o adresie przyznanym przez dostawcę Internetu. Dodatkową zaletą stosowania NAT jest zwiększenie poziomu bezpieczeństwa. Węzły sieci testowej mogą inicjować komunikację do Internetu, ale nie jest możliwe zainicjowanie komunikacji z Internetu do węzła sieci testowej. Na rynku jest dostępnych wiele urządzeń funkcjonalnie podobnych do wyżej opisanego. Różnią się m.in. liczbą portów. M ogą mieć wbudowany modem ADSL. Czasem są też oferowane przez dostawcę Internetu za złotówkę. Rys. 2.11. Prosta sieć testowa ZL29ARM 2.6. Przykład 2 - monitor sieci Jako przykład kończący ten rozdział proponuję napisanie prostego monitora sieci. Będzie on wyświetlał na LCD inform acje o ram kach ethernetow ych i protokołach przenoszonych przez te ramki. Jeśli sieć zbudow ana jest w oparciu o koncentrator, to mamy szansę oglądać wszystkie ramki. JesTi zawiera przełącznik, to zobaczymy tylko te ramki, które przełącznik wyśle do naszego monitora. Ekran LCD, którym
2.6. Przykład 2 - monitor sieci 71 2.6.1. Tab. 2.6. Pliki przykładu 2 Źródłowe 1biblioteczne ex eth.c board init.c util delay, c Iibstm32f10x.a lont5x8.c board led ks0108.c util led.c startup slm 32_cld.c board led.c util led.c font5x8.li Nagłówkowe Nr kolejny ramki Długość ramki Komunikat ARP Prośba ARP Datagram IP TCP board_det.h board_dels.li board jn it.h boardjc d.h board led.h Rys. 2.13. Wynik działania monitora sieci util_delay.h u tiljc d.h u tilje d.li Jeef IFfFfCffffEffH looog] 1192.168."5 1.88p ; -» fo o o il (192. 168.51. B2\- ID 0 0 0 0 3 9 4 3 ee 6 ^ ( T T l lq 025 9 c4 4 0 cd d h - HOBOOl 192.1 6 8. 51. 88 i61 1 1 9 3.0.9 6.21-» stm32f10x_cont.h - Adres ethernetowy nadawcy - Ramka rozgłoszeniowa - Adres IP nadawcy - Adres IP, o którego adres ethernetowy jest prośba ~Adres ethernetowy odbiorcy - Adres IP odbiorcy dysponujem y, nie jest w ielki i m ieszczą się na nim tylko inform acje o dw óch ostatnio odebranych ram kach. W ys'wietlane są: num er kolejny odebranej ramki ethernc-1 towej, jej długość i pola z nagłów ka, czyli ethernetow y adres odbiorcy, ethernetowy^ adres nadawcy oraz zaw artość pola dlugość-typ. Jeśli ram ka zaw iera datagram IP,\ to dodatkow o w yśw ietlane są: protokół kapsulkowany w tym datagram ie or: resy IP nadawcy i odbiorcy. Jeśli ram ka zaw iera kom unikat ARP, to dodatkowo.,, w yśw ietlane są: operacja A RP oraz adresy IP. Na LCD powinniśm y zobaczyć obi a/ podobny do przedstaw ionego na rysunku 2.13. W tabeli 2.6 zaw arto nazw y plików, które należy podać kom pilatorow i jako pliki;; źródłowe tego przykładu. Ponadto zaw iera ona nazwy tych wykorzystywanych u tym przykładzie plików nagłów kow ych, które znajdują się w podkatalogu./exam- ; pies/include. W kolejnych podrozdziałach prezentuję nowe pliki, których opis nie pojawił się w poprzednim rozdziale. Poniew aż używam y w yświetlacza graficznego, to w zestawie ZL29A R M należy ustawić zw orkę DISPLAY w pozycji GRAPH. Ponadto w module ZL3ETH trzeba ustawić zw orkę PW R_DN w pozycji uc, co w yjaśniam pod koniec następnego podrozdziału. Pliki boardj n it h i boardj n il c Pliki b o a rd jn it.h i boardj n it.c odpow iadają za zainicjow anie w ejść-wyjść binarnych, skonfigurow anie taktow ania m ikrokontrolera i modułu ethernetow ego ora/ uruchom ienie interfejsu MII (lub RM1I między m ikrokontrolerem a modułem ethernetow ym. Inicjow anie niektórych układów m ikrokontrolera, np. oscylatorów, wym aga pew nego czasu. Często nie można w ykonywać dalszej części programu, zanim nie zakończy się urucham ianie takiego układu. Z reguły najprościej jest poczekać aktyw nie na zakończenie tego procesu. W tym celu w pliku board jn it.h definiujem y m akro activ e _ c h e c k. jfdefine active_check(cond, limit \ int. i; \ for (i = (limit; (cond; i \ if (i <= 0 \ return -1; \ } M akro to sprawdza w pętli, czy jest spełniony w arunek cond. W arunek dobieramy tak, aby przyjął wartość niezerową, gdy urucham ianie zakończy się powodzeniem. W tedy makro kończy działanie. Żeby jednak m akro nie zapętlilo się w nieskończoność, liczba sprawdzeń jest ograniczona param etrem lim it. Jeśli limit sprawdzeń zostanie przekroczony, makro pow oduje zakończenie bieżącej funkcji z błędem. Przyjm ujem y często stosow aną konw encję, że funkcja zw raca zero, gdy kończy się sukcesem, a wartość ujemną, gdy wystąpił błąd. Przykłady użycia tego makra zobaczymy niżej. Pliki b o a rd jn it.h i board J n it.c udostępniają następujące funkcje: void AllPinsDisable(void; int CLKconfigure(void; int ETHconfigureMII(void; void ETHpowerDown(void; void ETHpowerUp(void; Funkcja A łlp in sd isa b le konfiguruje w szystkie wyprowadzenia m ikrokontrolera jako wejścia analogow e (ang. analog input m ode. W yłączone zostają wejściowe bramki Schm itta (ang. Schm itt trigger off, co zm niejsza pobór prądu przez mikrokontroler i zw iększa odporność układu na zakłócenia elektrom agnetyczne. Funkcję tę wywołujem y na początku programu, a potem konfigurujem y tylko używane wyprow adzenia - te nieużyw ane pozostaną wejściam i analogowymi. Biblioteka STM 32 udostępnia porty w ejścia-w yjścia poprzez stale GPIOA, GPIOB itd. Poszczególne m odele m ikrokontrolerów mają różną liczbę portów. Czasem chcem y iterować po w szystkich portach w ejścia-w yjścia, jak na przykład w funkcji A llp in sd isa b le, ale chcem y też uniezależnić program od liczby portów. W tym celu deklarujem y w pliku b o a rd jn it.h tablicę gpio zawierającą identyfikatory wszystkich portów oraz stalą gpioc ount określającą liczbę portów. extern GPIO_TypeDef * const gpio[]; extern const int gpiocount; Teraz m ożem y odw oływ ać się do kolejnych portów, pisząc g p io [0 ], g p io [ l],..., g p io [g p io C o u n t-l]. D efinicje tablicy g p io i stałej gpioc ount znajdują się w pliku board J n it.c. Dla STM 32F107V C, który ma pięć portów wejścia-wyjścia, są one następujące: GPIO_TypeDef * const gpiou = IGPIOA, GPIOB, GPIOC, GPIOD, GPIOEI; const int gpiocount = sizeoffgpio} / sizeof(gpio(o; Funkcja CLKconfigure konfiguruje oscylator kw arcowy m ikrokontrolera, pętle fazowe (PLL, ang. phase locked loop oraz sygnały taktujące rdzeń i peryferie, w tym m oduł ethernetowy. D ystrybucja sygnałów zegarow ych w STM 32F107 jest w uproszczony sposób przedstaw iona na rysunku 2.14. Rdzeń jest taktowany sygnałem HCLK, którego m aksym alna częstotliw ość wynosi 72 M Hz. Peryferie są taktowane, sygnałami PCLK1 i PCLK 2 otrzym yw anym i z HCLK za pom ocą dzielników wstępnych (ang. prescalers APB1 i APB2. Sygnał IICLK otrzym ywany jest
72 2. lntersie 2.6. Przykleili 2 - monitor sieci 73 HSI HS1 8 MHz Rys. 2.14. Uproszczony schemat dystrybucji sygnałów taktujących w STM32F107 z wyjścia dzielnika AHB, na którego w ejście podawany jest sygnał SYSCLK. Może to być sygnał 8 M Hz z w ewnętrznego oscylatora RC HSI (ang. high speed internal - tak dzieje się po włączeniu zasilania lub po wyzerowaniu mikrokontrolera, sygnał USE (ang. high speed external z oscylatora kw arcowego lub sygnał PLLCLK z wyjścia PLL1. Do w ejścia PLL1 można dołączyć sygnał HSI podzielony przez 2 lub sygnał z wyjścia dzielnika PRED1V1. Do w ejścia dzielnika PRED1V1 m ożna dołączyć sygnał HSE lub sygnał PLL2C LK z wyjścia PLL2. Na wejście PLL2 podawany jest sygnał HSE przez dzielnik PRED IV 2. Dobierając wartości współczynników podziału i mnożniki pętli fazowych, możemy uzyskać wiele różnych częstotliw ości taktowania rdzenia. Sygnał potrzebny do taktowania modułu etliernetowego można uzyskać z wyjścia MCO (ang. microcontroller clock output, na które można podać następujące sygnały: SYSCLK, HSI, HSE, PLLCLK podzielony przez 2, PLL2CLK, PLL3CLK, PLL3CLK podzielony przez 2, XT1 bezpośrednio z wyprowadzenia kwarcu. Sygnał PLL3CLK pochodzi z wyjścia pętli fazowej PLL3, która ma wspólne wejście z PLL2. Funkcja CLKconfigure zwraca zero, gdy zakończyła się sukcesem lub wartość ujemną, gdy nie udało się wystartować oscylatora kw arcowego lub któraś pętla fazowa się nie zsynchronizowała. Konfigurowanie zaczynam y od przywrócenia ustawień domyślnych. Następnie próbujem y uruchom ić oscylator kwarcowy HSE. Jeśli się nie udało, funkcja kończy działanie, sygnalizując błąd. RCC_DeInit(; j RCCJISEConf ig(rcc_hse_on ; if (RCC_WaitForHSEStartUp( == ERROR return -1; O program owanie sieciowe wymaga stosunkow o dużej mocy obliczeniowej. Aby uzyskać krótkie czasy reakcji m ikrokontrolera, będziem y taktować rdzeń z maksymalną dopuszczalną częstotliw ością, czyli 72 MHz. N iestety przy tej częstotliwości pamięć Flash nie może dostatecznie szybko dostarczać kolejnych instrukcji do wykonania i aby odczyty z Flash nie stanowiły wąskiego gardła, należy włączyć bufor wstępnego ładowania instrukcji (ang. prefetch buffer. Żeby pamięć Flash w ogóle działała przy tej częstotliwości, trzeba jej ustawić dw a cykle opóźnienia (ang. latency. FLASH_PrefetchBu fercmd(flash_prefetchbuf ferjsnable; FLASH_SetLatency{FLASH_Latency_2; Aby uzyskać sygnał HCLK o częstotliwości 72 M Hz, ustawiamy dzielnik AHB na 1. Sygnały PCLK1 i PCLK2 mogą mieć maksym alne częstotliwości odpowiednio 36 i 72 M Hz, dlatego ustawiamy wartości dzielników APB1 i APB2 odpowiednio na 2 i 1. RCCJiCLKConfig(RCC_SYSCLK_Divl ; RCC_PCLKlConfig(RCC_HCLK_Div2 ; RCC_PCLK2Con fig(rccjiclk_divl; Zakładamy, że oscylator kwarcowy HSE ma częstotliw ość 5, 10, 15, 20 lub 25 MHz. Sygnał z tego oscylatora przez dzielnik RREDIV2 jest podawany na wejście PLL2. Wartość dzielnika RCC_PREDIV2_DivX dobieram y tak, aby na wejściu PLL2 mieć zawsze sygnał o częstotliwości 5 MHz. M nożnik PLL2M U L ustawiamy na 8, za-
74 2. Intersieci 2.6. Przykład 2 monitor sieci 75 tem na wyjściu PLL2 uzyskujem y sygnał PLL2CLK o częstotliw ości 40 MHz, Następnie włączamy PLL2 i czekam y na jej zsynchronizow anie się. Sdefine RCC_PREDIV2_DivX RCC_PREDIV2_Div2 RCC_PREDIV2Config(RCC_PREDIV2_DivX; RCC_PLL2Con ig(rcc_pll2mul_8; RCC_PLL2Cmd(ENABLE; active_check(rcc_getflagstatus(rcc_flag_pll2rdy, maxattempt5; Jeśli PLL2 się nie zsynchronizuje, m akro a c tiv e _ c h e c k zakończy działanie funkcji, sygnalizując błąd. Lim it sprawdzeń definiujem y jako stałą maxattempts. static const int maxattempts = 1000000; Sygnał PLL2CLK podajem y na w ejście dzielnika PR ED IV I, który ustawiam y na.k 5. Zatem na wyjściu dzielnika PREDIV1 mamy sygnał o częstotliw ości 8 MHz.-;Jf. Sygnał ten podajemy na wejście P L L l. M nożnik PLLM U L ustawiam y na 9, aby uzyskać na wyjściu PL L l sygnał PLLCLK o częstotliw ości 72 M Hz, po czym cze-;,- kamy na zsynchronizow anie się PL L L RCC_PREDIVlConfig(RCC_PREDIVl_Source_PLL2, RCC_PREDIVl_Div5; RCC_PLLCon ig(rcc_pllsource_predivl, RCC_PLLMul_9; i; RCC_PLLCmd(ENABLE; 'i, active_check(rcc_getflagstatus(rcc_flag_pllrdy, maxattempts; Jako sygnał SYSCLK, podawany na wejście dzielnika AHB, w ybieram y sygnał PLLCLK z wyjścia P L L l i czekam y na synchronizację. RCC_SYSCLKCon ig(rcc_sysclksource_pllclk; active_check(rcc_getsysci,ksource( ==0x08, maxattempts ; M ikrokontroler STM 32F107 obsługuje dw a w arianty interfejsu m iędzy M AC a PHY: MII (ang. Media Independent Interface i RM II (ang. Reduced M edia Independent y Interface. MII w ym aga taktow ania sygnałem o częstotliw ości 25 M Hz, a RMII - 50 M Hz. Wartości tych częstotliw ości wynikają z szerokości szyn danych w tych interfejsach, co stanie się jasne po przeczytaniu dalszej części tego podrozdziału. 5 Jeśli potrzebujem y na M CO sygnału 25 M H z i mamy akurat taki kwarc, to najprościej jest użyć sygnału H SE lub XT1. RCC_MCOConfig(RCCJCO_HSE; RCC_MCOConfig(RCC_MC0_XT1; W przeciwnym przypadku trzeba w ykorzystać PLL3. Poniew aż PLL2 i PLL3 1 mają wspólny dzielnik wstępny PRED IV 2, wyżej opisana konfiguracja sprawia, > że na wejściu PLL3 mamy sygnał 5 M H z z tego dzielnika. U staw iam y mnożnik m PLL3M UL na 10, dzięki czem u na wyjściu PLL3 dostajem y sygnał PLL3CLK o częstotliwości 50 M Hz. Następnie czekam y na zsynchronizow anie się PLL3. RCC_PLL3Con ig(rcc_pll3mul_10; RCC_PLL3Cmd(ENABLE; active_check(rcc_getflagstatus{rcc_flag_pll3rdy, maxattempts; Jeśli używamy PHY z interfejsem RM II, to na M CO podajem y bezpośrednio sygnał PLL3CLK. RCC_MCOConfig(RCC_MC0_PLL3CLK; Jeśli używam y PHY z interfejsem M II, to na M CO podajem y sygnał PLL3CLK przez dzielnik o współczynniku 2. ; 'Jj ;f RCC_MCOCotlfig (RCC_MCO_PLL3CLK_Div2 ; Jeśli konfigurow anie sygnałów taktujących zakończy się pow odzeniem i wszystkie oscylatory się zsynchronizują, funkcja CLKconfigure kończy działanie, zwracając zero. Funkcja ETHconfigureM Il konfiguruje interfejs między układem M AC w mikrokontrolerze a zew nętrznym PHY. M oże to być interfejs MII lub RMII. Każdy z tych interfejsów m ożna skonfigurow ać na dw a sposoby: z domyślnym albo alternatyw nym (ang. remaped rozkładem w yprowadzeń. Szczegóły są w tabeli 2.7. W yprowadzenia oznaczone literą I konfiguruje się jako w ejścia pływające. Na w y prow adzeniach oznaczonych literą O należy skonfigurow ać funkcję alternatywną z w yjściem przeciwsobnym 50 MHz. M II używ a 17 sygnałów: - M II_COL (ang. collision detect - w ystąpienie kolizji w trybie naprzem iennym (ang. half-duplex, - M II_CRS (ang. carrier sense - obecność nośnej, czyli sygnału w m edium (ang. non-idle, - M II_ R X D 0...MI1_RXD3 (ang. receive data - czterobitow a szyna odbiorcza, - M 1I_RX_CLK (ang. receive clock - zegar szyny odbiorczej, 25 M Hz dla 100 M b/s lub 2,5 M Hz dla 10 Mb/s, - M II_RX_DV (ang. data valid - na szynie odbiorczej są dane do odczytania, - MII_RX ER (ang. receive error - odebrano błędny symbol w trybie 100 Mb/s, - M II TXD0...M II_TXD3 (ang. transm it data - czterobitowa szytia nadawcza, Tab. 2.7. Warianty konfiguracji MII/RMII w STM32F107 Sygnał MII Sygnał RMII Kierunek Wyprowadzenie domyślne MII C0L I PA3 MII CRS I PAO W yprowadzenie alternatywne MII RXD0 RMII RXD0 I PC4 PD9 MII RXD1 RMII RXD1 I PC5 PD10 MII RXD2 I PB0 PD11 MII RXD3 I PB1 PD12 MII RX CLK RMII REF CLK I PA1 MII RX DV RMII CRS DV I PA7 PD8 MII RX ER I PB10 MII_TXD0 RMII TXD0 0 PB12 MII TXD1 RMII TXD1 0 PB13 MII TXD2 0 PC2 MII TXD3 0 PB8 MII TX CLK I PC3 MII TX EN RMII TX EN 0 PB11 M D I0 0 PA2. MDC 0 PC1
76 2. Intersieci'È 2.6. Przykład 2 - monitor sieci 77 - M1I_TX_CLK (ang. transm it d o c k - zegar szyny nadawczej, 25 M Hz dlai 100 M b/s lub 2,5 M Hz dla 10 M b/s, - M11_TX_EN (ang. transmit enable - na szynie nadawczej są dane do wysiania,i; - M D IO (ang. m anagement data input/outpnt - dw ukierunkow a linia danych interfejsu służącego do konfigurow ania układu PHY, - M DC (ang. m anagem ent data d o c k - linia taktująca interfejs M DIO. a 2 H O F a: o a: O d d cc a Sygnał zegarowy na liniach M 1I_RX_CLK i M II_TX _CLK jest generow any prze/ PHY. Jego częstotliw ość wynika z szybkości przesyłania danych (10 lub 100 MbA ^ i szerokości szyny danych (4 bity. RM1I używ a tylko 9 spośród wyżej opisanych'.-;' sygnałów. Ponieważ w RM1I dane są przesyłane szynami dw ubitow ym i, sygnał ze garowy RM II_REF_CLK musi m ieć częstotliw ość 50 MHz. Zależnie od wybranego wariantu konfiguracji MII lub RM1I funkcja ETHconf iguremi; wywołuje jedną z czterech funkcji konfiguracyjnych: L Q _ static void ETHinterfaceMIIconfigure(void; static void ETilinterfaceRemapedMIIconfigure (void ; static void ETHinterfaceRMIIconfigure(void; static void ETHinterfaceRemapedRMIIconfigure(void; (. Schem at modułu ethernetow ego ZL3ETH jest przedstaw iony na ry su n k u 2.15, ÿ N atom iast sposób jego podłączenia w zestawie ZL29A RM znajduje się na rysun- ;V k u 2.16. Jak widać, zastosow ano MII z alternatywnym układem wyprowadzeń, $ D latego zaim plem entow ano tylko funkcję ETHinterfaceRemapedMIIconfigure. Implem entację pozostałych funkcji pozostaw iam jako ćw iczenie Czytelnikow i, który będzie ich potrzebował. K onfigurowanie M il rozpoczynam y od w łączenia taktowania peryferii i ustawienia właściwego wariantu interfejsu. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPI0A RCC_APB2Periph_GPI0B I RCC_APB2Periph_GPI0C RCC_APB2Periph_GPI0D RCC_APB2Periph_AFI0, ENABLE; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ETH_MAC RCC_AHBPeriph_ETH_MAC_Tx RCC_AIIBPe riph_eth_mac_jtx, ENABLE; GPI0_PinRemapCon ig (GPIG Reniap_ETH, ENABLE ; GPIOJSTHJfedialnterfaceConfig (GPIO_BTH_MediaInterface_MII ; " 1 <-81=1 X <N -T- LED SPD 2B LED LINK 29 nrst 30 MDIO 31 MDC 32 i;> 33 34 35 Q, Z h -1 36 2 o, a, y at. % z to ; to O Q O O rr» RESERVED RESERVED RESERVED RESERVED : RESERVED PWR_DOW N/INT TXD 37SNLM 0DE TXD_2 TXD 1 TXD 0 TX EN TX CLK 5 e a x i 4 z a x i 3 t a x i 2 o a x i 1 N3 X I X1D X I GND Następnie konfigurujem y w yprow adzenia zgodnie z tabelą 2.7, a na koniec wyprow adzenie sygnału zegarow ego M C O (PA8. Dla MII z alternatywnym układem w yprowadzeń odpow iedni fragm ent funkcji ETHinterfaceRemapedMIIconfigure w ygląda następująco: GPIO_InitTypeDef GPI0_InitStructure; GPI0_InitStructure.GPI0_Mode = GPI0_M ode_in_fl0ating ; GPI0 InitStructure.GPI0_Speed = 0; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 i GPI0_Pin_l GPI0_Pin_3; GPI0_Init(GPIOA, ŁGPIO_InitStructure; GPIO_InitStructure.GPI0_Pin = GPIO_Pin_10; GPI0_Init (GPI0B, &GPI0 InitStructure ; GPI0_InitStruciure.GPI0_Pin = GPIO_Pin_3; GPÎ0 Init(GPI0C, &GPI0 InitStructure; Rys. 2.15. Schemat modułu ZL3ETH
2.6. Przykład 2 - monitor sieci 79 GPIO_InitStructure.GPIO_Pin = GPIO Pin_8 GPIO_Pin_9 GPIO~Pin_10 t GPIO_Pin_l1 I GPI(Tpin_12; GPIO Init(GPIOD, &GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GP10_Init(GPIOA, &GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 GPIO_Pin_ll I GPIO Pin_12 GPIO_Pxn_13; GPIO_Init (GPIOB, &GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_l I GPIO_Pin_2; GPIO Init (GPIOC, SGPIO_I ni tstructure ; GPIO_InitStructure.GPXO_Pin = GPIO_Pin_8; GPIO_Init(GPIOA, &GPIO_InitStructure; Moduł ZL3ETH można w prowadzić w ryb niskiego poboru prądu (ang. pow er clown modę. Bywa to istotne w urządzeniach zasilanych bateryjnie. Interfejs Ethernet pobiera niestety sporo prądu. W edług danych katalogowych układu DP83484C, zastosowanego w ZL3ETH, w trybie 10 M B/s pobiera on typowo 92 ma, a w trybie 100 M b/s - typowo 81 ma. Do tego jeszcze kilka ma pobierają diody sfwiecące zainstalowane w gnieździć RJ-45. N atom iast w trybie niskiego poboru prądu układ RXD1 57 R XD2 58 R XD3 59 R X ER t 47 1 RX C LK 24 R X DV 55 TX C LK 18 C O N12/JP2 M D IO 25 MDC 16 CRS, 23 C D I. 26 STM 32F107VC PB12 TXD1 t 53 PB13 TXD2, 17 PC2 TXD3, 95 P B 8!* --- 25 M H z 67 PAB TX EN, 48 PB11 ---- 3.3V P W R D W N, 15 14 1 0 0 n : Rys. 2.16. Podłączenie modułu ethernetowego w ZL29ARM PD9 PD10 PD11 PD12 ż PC3 PA2 P C I? PAO PA3 PCO nrst D P83484C pobiera typowo 14 ma. W ten tryb można go w prowadzić, podając niski poziom na wyprowadzenie PW RDOW N. W yprow adzenie to jest podłączone do wyjścia inwertera. Jeśli zw orka PW R_DN (JP3 w module ZL3ETH jest w pozycji O ff (2-3, na wejściu tego inw ertera jest w ym uszony poziom niski, co wymusza na wejściu PW RDOW N stan wysoki i moduł ZL3ETH jest zawsze w stanie aktywnym. Ustaw ienie zworki PWR DN w pozycji uc (1-2 um ożliw ia programowe sterow anie trybem oszczędzania prądu. Do w ejścia inwertera jest podłączone w y prow adzenie PCO m ikrokontrolera. W yprowadzenie to konfigurujem y jako wyjście z otwartym drenem. Jeśli podajem y na nie stan niski, moduł ZL3ETH jest w stanie aktywnym. Jeśli w yprowadzenie PCO jest w stanie wysokiej im pedancji, rezystor na wejściu inwertera wym usza, że moduł ZL3ETH jest w stanie niskiego poboru prądu. W yprowadzenie PCO jest w stanie wysokiej im pedancji, zanim zostanie skonfigurow ane, a rów nież wtedy, gdy układy peryferyjne mikrokontrolera nie są zasilane, co pow oduje autom atyczne w yłączanie modtilu ZL3ETH, gdy m ikrokontroler przestaje być zasilany lub gdy przechodzi w tryb oszczędzania prądu z wyłączonym i peryferiam i. W yprow adzenie PCO konfiguruje się za pom ocą funkcji ETHpowerConfigure. static void ETHpowerConfigure(void; Pow yższa funkcja jest zadeklarowana jako s t a t i c, ponieważ jest używana tylko w ewnątrz jednostki translacji. Zasilanie m odułu ethernetow ego włącza funkcja ETHpowerUp. Przełączenie w tryb oszczędzania prądu w ykonuje funkcja ETHpowerDown. Na zakończenie swojego działania funkcja ETHconfigureM II w łącza zasilanie m o dułu ZL3ETH, a następnie wykonuje zerow anie interfejsu ethernetowego. Jeśli zerow anie się powiodło, funkcja zw raca zero, w przeciwnym przypadku zwraca wartość ujem ną, sygnalizującą błąd. ETHpowerConfigure(; ETHpowerUp0; ETiI_DeInit{; ETH_SoftwareReset(; active_check(eth GetSoftwareResetStatus{ == RESET, maxattempts; return 0; Funkcje i stałe, których nazwy zaczynają się od ETH_, są zdefiniow ane w bibliotece STM 32 w pliku stm32_eth.h. Im plem entacje są w pliku stm32_eth.c. Dla nazw zaczynających się od FLASH_, GPI0_ i RCC są to odpow iednio pliki stm 32f!0x_ Jlash.li, stm 3 2 fl0 xjla sh.c, stm 32fl0x_gpio.h, stm 32fI0x_gpio.c, stm32fj0x_rcc.li, stm 32fl 0x_rcc.c. 2.6.2. Plikf u tilje d.h i u łilje d.c Pliki u tilje d.h i u tilje d.c dostarczają dw óch pom ocniczych funkcji diagnostycznych. Funkcja OK miga n razy zieloną diodą świecącą. Funkcja E rro r miga n razy czerw oną diodą świecącą. void 0K(int n; void Error(int n; Ponadto w u tilje d.h definiujem y makro error check, które wywołujemy, aby sprawdzić, czy jakaś funkcja zakończyła się poprawnie. Jako pierwszy argument
2.6. Przykład 2 - monitor sieci 81 um ieszczam y wywołanie funkcji, która zwraca zero przy popraw nym zakończeniu, '!' a wartość ujem ną, gdy wystąpił błąd. Jako drugi argum ent podajem y num er błędu : - liczbę m ignięć czerwonej diody. i łdefine error_check(expr, err \ ( if ((expr < 0! ( \ '(» for (;; ( \ 'i Error(err; \ \ i ł :,j Plik ex_eth.c W pliku ex_eth.c zaim plem entow ane są dwie funkcje: - ETHconf igurerx konfiguruje odbieranie ram ek przez interfejs Ethernet, - main konfiguruje sprzęt, a potem w nieskończonej pętli czyta odebrane ramki i wypisuje ich zawartość na LCD. Do jednego MAC m ożna podłączyć w iele układów PHY. Aby je rozróżnić, każdy musi mieć przypisany unikalny adres, który jednak nie ma nic wspólnego z adresem ethernetow ym. PHY w module ZL3ETH ma adres 1. Najlepiej jest zdefiniow ać go jako stalą: łdefine PHY_ADDRESS 1 Zacznijm y od funkcji konfigurującej odbieranie ramek. Jej argum entem jest adres '* PHY. Funkcja ta zw raca zero, gdy konfigurow anie zakończyło się sukcesem, a war- <$ tość ujem ną, gdy wystąpił błąd. Jej sygnatura jest następująca: ' ' i1 static int ETHconfigureRX(uint8_t phyaddress; M ikrokontroler STM 32F107 używ a układu DMA do odbierania i w ysyłania r; ethernetowych. Więcej o DMA piszę w rozdziale 3.3. W funkcji ETHconfigureRX deklarujem y ETHRXBUFNB odbiorczych deskryptorów DMA oraz po jednym buforze odbiorczym na każdy deskryptor. Bufory mają rozm iar ETH_MAX PACKET SIZE. Stała ta jest zdefiniow ana w pliku stm32_etli.h i ma wartość 1520, co jest maksymalnym rozm iarem ramki ethernetow ej (bez pream buły pow iększonym do wielokrotności 4, gdyż bufory DMA powinny być w pamięci pod adresami wyrównanym i do wielokrotności 4 bajtów. Fragm ent inicjujący odbiorczą część DM A wygląda tak: int ETHconfigureRX(uint8_t phyaddress łdefine ETHRXBUFNB 4 / static ETH_DMADESCTypeDef DMARxDscrTab[ETHRXBUFNB /' attribute ((aligned (4; Static uint8_t RxBu f[ethrxbufnb[eth_max_packet_size attribute ((aligned (4; 1 ETH_DMARxDescChainInit(DMARxDscrTab, SRxBuf [0][OJ, ETHRXBUENB; Param etry pracy interfejsu Ethernet zebrane są w strukturze ETH_InitTypeDef. W większości przypadków najlepiej jest ustawić wartości dom yślne za pomocą funkcji ETH_StructInit. Warto spróbować ustawić negocjow anie trybu pracy interfejsu. Poniew aż nasz program ma być monitorem sieci, musimy skonfigurować odbieranie w szystkich ram ek, niezależnie od adresu odbiorcy. Inicjow aniem inter- ' fcjsu ethernetow ego zajm uje się funkcja ETH Init, która zwraca zero, gdy wystąpił błąd - wtedy nasza funkcja musi zwrócić wartość ujemną. int ETHconfigureRX(uint8_t phyaddress { i ETH_Init'fypeDef e; ETH_StructInit(&e; e.eth_autonegotiation = ETH_AutoNegotiation_Enable; e.eth_receivealł - ETH_ReceiveAlł_Enable; if (ETH_Init(&e, phyaddress == 0} return -1; Jeśli powyższe nie zadziała, można wyłączyć negocjowanie trybu pracy i spróbować ustawić go manualnie. Przydatne ustawienia są następujące: e.eth_autonegotiation = ETii AutoNegotiation_Disable; e.eth_speed = ETH_Speed_100M; e.eth_speed = ETH_Speed_10M; e.ethjłode = ETH_Mode_FullDnpIex; e.eth_mode = ETH_Mode_HałfDuplex; Jeśli funkcja ETH^Init zwróci wartość niezerową, czyli jeśli inicjowanie interfejsu się powiedzie, możemy włączyć odbieranie ram ek za pom ocą funkcji ETH_Start i zakończyć funkcję ETHconfigureRX, zw racając 0, oznaczające sukces, int ETHconfigureRX(uint8_t phyaddress ( ETH_Start(; return 0; 1 Przejdźmy teraz do opisu funkcji main. Zaczyna się ona od typowej sekwencji inicjującej kolejne układy: int main( { Delay(1000000; AllPinsDisable(}; LEDconfigure{ / RedLEDoni; LCDconfigureO ; LCDtirite ("CLK "; error^check(clkconfigure(, 1; LCDwrite("PASS\n"; LCDwrite("MII "; error_check(ethconfiguremii (, 'I ; LCDwrite{"PASS\n"; LCDwrite("ETH "; error_ciieck(ethconfigureri (PHV_ADDRESS, 5; LCDwrite (ł,pass\n"; RedLEDoff 0; Najpierw czekam y pól sekundy - wymagają tego niektóre kombinacje JTAG i programu go obsługującego. Podczas całego procesu konfigurow ania świeci czerwona dioda, a jej w yłączenie sygnalizuje zakończenie konfigurow ania sprzętu. M iganie czerwonej diody oznacza wystąpienie błędu. Pojedyncze mignięcia oznaczają problem z, synchronizacją sygnałów taktujących. M iganie w rytmie: 4 mignięcia - dłuższa przerwa, oznacza błąd interfejsu MII (m iędzy M AC a PHY - np. nie
82 2. Inter «E jest włożony moduł ZL3ETH. M iganie w rytmie: 5 m ignięcia - dłuższa przerwa; oznacza błąd konfiguracji Ethernetu - np. sieć nie działa albo do ZL3ETH nie je sl podłączony kabel. Informacja o postępie urucham iania program u jest też wyświeilana na LCD. Po uruchom ieniu interfejsu ethernetow ego program w nieskończonej pętli czytaj] kolejne odebrane ramki za pom ocą funkcji ETH_BandłeRxPkt z biblioteki STM32'fl i w yświetla icli zawartość. A rgum entem funkcji ETH_HandleRxPkt jest adres bufo-jjj ra, do którego zostanie skopiow ana odebrana ramka. Pream buła i sekw encja kon-' trolna ramki nie są kopiowane. Jeśli jakaś ram ka została odebrana, to funkcja tal] zwraca liczbę skopiowanych bajtów. Najkrótsza ram ka ethernetow a bez preambuły»! i sekwencji kontrolnej ma 60 oktetów. Szkielet końcowego fragmentu funkcji raair. w ygląda następująco: int main ( { unsigned pkts; pkfcs = 0; for (;; f static uir>t8_t packet (ETH_MAX_PACKET_SIZE]; uint32_t size; size = ETH_HandleRxPkt(packet; if (size >= 60 { pkts++;... /* Wypisz ramkę na LCD. */ StOS TCP/IP
3.1. Przegląd implementacji 85 Stosem TCP/IP nazywa się kom plet protokołów intersieci (ang. internet protocol, suite. Centralnym protokołem tego stosu jest 1P, opisany w poprzednim rozdziale,, M inim alna im plem entacja musi zaw ierać obsługę protokołów transportowych TCP i UDP. O bowiązkową częścią TCP/IP jest też ICMP, który opisuję pod koniec tegov rozdziału. Ponadto im plem entacja stosu zaw iera zwykle kilka pom ocniczych protod kolów, np. w spom niany w poprzednim rozdziale ARP. Aby tworzyć aplikacje korzystające z intersieci, m usim y mieć im plem entację,stosu; TCP/IP. Nie ma potrzeby sam odzielnego im plem entow ania całego stosu. To bylohyi zbyt pracochłonne i niepotrzebnie pow tarzało ju ż w ykonaną przez innych pracę,, Ten rozdział zaczynam od pobieżnego przeglądu dostępnych im plem entacji stosu! TCP/IP. Następnie skupiam się na wybranej im plem entacji. Przedstawiam, jak dotj pasować ją do architektury m ikrokontrolera i jak ją skonfigurow ać. Potem opisuję! zainstalowany w m ikrokontrolerze STM 32F107 przeznaczony do obsługi Ethernetu, układ DMA. Głów nym celem tego rozdziału jest przedstaw ienie im plem entacji sterow nika (ang. driver Ethernetu, pośredniczącego m iędzy stosem protokołów sl ciow ych a sprzętem. Om awiam trzy w ersje sterownika: - pierwsza używa dodatkow ych buforów do przekazyw ania ram ek ethernetowy"' między stosem a DMA; - druga nie potrzebuje dodatkow ych buforów w części odbiorczej, DM A kopiuje! odebrane ramki bezpośrednio do buforów stosu; - trzecia nie ma rów nież dodatkow ych buforów w części nadawczej. Pozbycie się dodatkow ych buforów odbiorczych lub nadaw czych zm niejsza zaję-,'^ tość RAM i sprawia, że ramki nie są niepotrzebnie kopiow ane (ang. zero copy!-m Powodem prezentacji aż trzech sterow ników jest też chęć przedstaw ienia różnych i trybów pracy DMA. Zaim plem entow ane sterowniki porów nuję pod względem jętości pamięci RAM i Flash oraz szybkości działania. Na koniec tego rozd podaję nieco użytecznych inform acji o protokołach ICM P i DHCP. Przegląd implementacji Dostępne są zarówno kom ercyjne, jak i darm owe im plem entacje stosu TCP/IP, ';( Przykładem rozwiązań kom ercyjnych są stosy oferowane przez firm ę InterNiche Inform acje o nich można znaleźć na stronie http://www.iniche.com. Jest tam wie wszystko, czego m ożem y potrzebow ać w systemie wbudowanym. Jednak cenyi ; poszczególnych pakietów są zaporow e naw et dla dużej firmy, nie wspomii o hobbystach. InterNiche, aby zachęcić do swoich produktów, oferuje darm owy N ichelite [4]. Zaim plem entow ano w nim okrojoną w ersję IP nieobslugującą nag mentacji. N ichelite zaw iera protokoły TCP, UDP, ICMP, A RP oraz klienty p kolów BOOTP, DHCP, DNS, TFTP. Jest też dem onstracyjna w ersja serwera H I 1P N ichelite udostępnia interfejs program istyczny minisockets, naśladujący inlt gniazd (ang. sockets, dostępny w uniksowych system ach operacyjnych i systemach W indows, ale nie w pełni z nim kompatybilny. Pow ażną wadą stosu N ichilite licencja, na której jest udostępniany. Zabrania ona łączenia N ichilite z oprogramowaniem, które jest rozpow szechniane jako oprogram ow anie otw arte (ang. open", source software i tw orzenia aplikacji używającycli N ichilite za pom ocą otwartych narzędzi program istycznych, np. narzędzi pow stających w ramach projektu GNU i udostępnianych na licencji G PL (ang. general public license. Na drugim biegunie są w pełni darm owe im plem entacje stosu TCP/IP. Przykładem jest projekt ulp (m ikro IP, którego strona znajduje się pod adresem littp://www. sics.se/~adain/uip. Oryginalnie stworzono go dla mikrokontrolerów 8-bilowych, ale został przeniesiony też na architekturę ARM. Źródła tego stosu są przyzwoicie udokum entowane i skom entowane. W ymaga on niewielkiej pamięci, zarówno RAM, jak i Flash. Im plem entuje podstawow y zestaw protokołów: IP, TCP, UDP, ICMP, ARP, SLIP. Dostępne są też dem onstracyjne im plem entacje serwera i klienta HTTP, klienta DNS, klienta SM TP oraz serwera protokołu Telnet. Jest udostępniany na licencji umożliwiającej jego kom ercyjne wykorzystanie. Do napisanie tej książki wybrałem stos lwip (ang. lightweight IP [5]. Projekt lwip ma sw oją stronę http://savannah.nongnu.org/projects/lwip. Biblioteka lwip jest dystrybuow ana na bardzo liberalnej licencji, która um ożliwia tworzenie produktów kom ercyjnych zawierających lwip i ich sprzedaw anie bez konieczności udostępniania tekstu źródłowego. W bibliotece lw IP zaim plem entow ano standardowy zestaw protokołów: IP, TCP, UDP, ICMP, ARP. Ponadto lwip zawiera dodatkowe moduły: - D HCP - klient konfigurujący ustawienia sieciow e węzła, czyli uzyskujący adres 1P, m askę podsieci, adres rutera i adres serw era nazw (w podsieci musi działać serw er tej usługi; - DNS - klient tłumaczący nazwy dom enow e na adresy IP; - A utoip - protokół APIPA (ang. Autom atic Private IP Addressing, udostępniający alternatywny do D H CP sposób zdobyw ania adresu IP, opisany w RFC 3927, przydziela adresy nierutow alne z puli 169.254.0.0/16, nie wymaga żadnego serwera; - SN M P (ang. Sim ple Network M anagem ent Protocol - protokół służący do zdalnego monitorowania węzłów sieci i zarządzania urządzeniami sieciowymi, np. ruterami (wbrew nazwie wcale nie jest to prosty protokół; - 1GMP (ang. Internet Group M anagem ent Protocol - protokół służący do zarządzania grupami węzłów, osiąganym i pod w spólnym adresem grupowym (ang. m ul t i cast address', - SLIP (ang. Serial Line Internet Protocol - kapsulkowanie IP w łączach szeregowych; - PPP (ang. Point to Point Protocol - protokół warstwy łącza używany w wielu interfejsach szeregowych. Biblioteka lw IP udostępnia trzy interfejsy programistyczne: - Interfejs surowy (ang. raw, który jest podstawow ym interfejsem biblioteki lwip. - Interfejs sekwencyjny (ang. neteonn, który obsługuje komunikację sieciową w paradygm acie otw órz-czytąj-pisz-zam knij. W ywołania funkcji tego interfejsu są blokujące. W ynika stąd, że biblioteka i aplikacja m uszą działać w osobnych w ątkach - potrzebny jest system operacyjny, który um ożliwia urucham ianie wielu wątków lub procesów.
86 3. Stos TCP/ip 3.2. Biblioteka lwip 87 - Interfejs gniazd (ang. sockets, który obudow uje interfejs sekwencyjny i w zalo-; żenili ma być zgodny z interfejsem gniazd używ anym w dużych komputerach - nie jest jednak z nim w pełni zgodny. Biblioteka lw IP jest ustawicznie rozw ijana, co kilka miesięcy pojawia się jej nowa wersja. W niniejszej książce korzystam z wersji 1.3.2. Jest niemal pewne, żcw chwili ukazania się tej książki będzie ju ż dostępna kolejna w ersja tej biblioteki, Jest to jednak zaleta, a nie wada, gdyż nie warto pośw ięcać czasu na poznawanie oprogram owania, które nie jest popraw iane i rozwijane. Oprogram owanie, które nie jest rozwijane, po jakim ś czasie staje się bezużyteczne. N ie należy też obaw iać się. że napisane dotychczas programy nie będą działać z nowymi wersjami biblioteki, Zapew ne mogą w ystąpić jakieś problemy, ale twórcy biblioteki lwip, będąc przecież jednocześnie jej użytkow nikam i, nie będą chcieć utrudniać sobie pracy i na pew no będą starać się zachować w steczną kom patybilność., ; W następnych rozdziałach staram się szczegółowo wyjaśnić, jak pisze się aplikacje sieciowe korzystające z biblioteki lw IP i używ ające protokołów transportowych T C P i UDP. Spośród dodatkow ych modułów prezentowane przykłady korzystają z Dł-ICP i DNS. W szystkie przykłady używają interfejsu surowego. Pozwala on na lepszą integrację aplikacji ze stosem TCP/IP. Stos i aplikacja działają we wspólnym wątku. Program jest sterowany zdarzeniam i (ang. event based za pom ocą funkcji, zwrotnych (ang. callback functions w ywoływanych przez stos. Źródłem zdarzeń są przerwania. Interfejs surowy jest preferowany w system ach wbudowanych, gdy/ ' z trzech interfejsów dostarczanych przez bibliotekę lwip ma najm niejsze narzuty "i, pam ięciowe i czasowe. Interfejs surowy jest trudniejszy do opanow ania przez pro--'-' gram istę od pozostałych dwóch, ale jednym z celów tej książki jest właśnie przybliżenie go Czytelnikowi. Żaden z wyżej opisanych darm owych stosów nie im plem entuje IP w wersji fi. ' Twórcy lwip podjęli próbę zaim plem entow ania tej wersji IP, ale w chwili rozpoczynania pisania książki im plem entacja nie była jeszcze w pełni gotowa. 3.2. Biblioteka lwip Wielką zaletą biblioteki lw IP jest m ożliw ość bardzo elastycznego jej skonfigurowania. Dzięki temu możemy skom pilować tylko te protokoły i moduły, które są nam rzeczywiście potrzebne. Im plem entacje protokołów są sparam elryzow ane i możemy je łatwo dopasować do wymagań konkretnej aplikacji. M anipulując param etrami biblioteki, możem y leż w szerokich granicach zm ieniać zapotrzebow anie na pamięć, co pozwala pisać aplikacje zarów no na systemy dysponujące gigabajtam i pamięci operacyjnej, jak i na mikrokontrolery wyposażone w kilkadziesiąt kilobajtów RAM. Oczywiście zm niejszenie rozm iaru potrzebnej pamięci nic jest za darm o, wiąże się zwykle ze zm niejszeniem w ydajności lub ograniczeniem liczby węzłów, z którymi można się równocześnie kom unikować. 3.2.1. Dopasowanie do architektury mikrokontrolera - plik cortex-m3.h Za adaptację biblioteki lwip do konkretnej architektury odpow iadają pliki nagłówkowe um ieszczone w katalogu./exam ples/include/arch. W pliku perf.h można zde- -.w,- t z y pij.- liniować makra do pomiaru wydajności (ang. perform ance im plem entacji. W tej książce nie korzystamy jednak z tej m ożliwość, pozostawiając domyślny plik, dostarczany w raz z biblioteką. W szelkie pozostałe adaptacje należy umieścić w pliku cc.h. Żeby jednak nie m odyfikow ać pliku dostarczonego z biblioteką, tworzymy własny plik cortex-m3.h, w którym um ieszczam y wszystkie niezbędne definicje, a w pliku cc.h umieszczam y tylko dyrektyw ę preprocesora: finclude "cortex-m3.h" Rdzenie ARM mogą pracow ać jako grubokońców kow e (ang. big-endion lub cieńkokońców kow e (ang. little-endian. W STM 32 rdzeń Cortex-M 3 ma na stale ustawioną cienkokońców kow ą kolejność bajtów (ang. byte order. Bibliotekę lwip trzeba poinstruow ać o rodzaju końców kowości za pom ocą definicji: łdefine BYTE_ORDER L i m E _ E N D M N Biblioteka lw IP używ a własnych nazw dla typów reprezentujących liczby całkowite (ang. integral types. Definiujem y te typy, korzystając z definicji typów dostarczanych przez standardow ą bibliotekę języka C w pliku stdint.h. Typ mem p t r t jest ogólnym wskaźnikiem. Nie jest on zdefiniow any jako void*, jakby tego należało oczekiwać w języku C, aby m ożna były w ykonyw ać na nim w szystkie operacje arytmetyczne. typedef int8_fc s8_t; typedef uint8_t u8_l; typedef intl6_t sl6_t; typedef uint!6_t ul6_t; typedef int32_t s32_t; typedef uint32_t u32 t; typedef u32 t mem_ptr_t; Poniew aż standard języka C pozostawia jego im plem entatorom pewną swobodę, typy 16-bitowe i 32-bitow e mogą być różnie zdefiniow ane w różnych architekturach. D la architektur ARM typy in t l6 _ t, u in tl6 _ t, in t3 2 _ t, u in t3 2 _ t są zdefiniow ane w stdint.h odpow iednio jak o s h o r t, unsigned s h o rt, long, unsigned long. D la tych typów biblioteka lwip potrzebuje specyfikatorów form atowania dla funkcji p r i n t f, s n p r in tf itp. Specyfikatory zaczynające się od litery X służą do w ypisyw ania danych szesnastkow o. Ponadto definiujem y speeyfikator form atow a nia SZT_F dla typu size t. ffdefine S16_F "hd" Idefine U16_F "hu" idefine X16 F "hx" Idefine S32~F "Id" Idefine U32~F "lu" Idefine X32~F "lx" Idefine SZT_F "zu" Przykład użycia jednej z pow yższych stałych widzieliśm y ju ż w pliku util_lcd.c, gdzie znajduje się następujący fragm ent programu: uintl6_t port; char buf[8; snprintf(buf, sizeof(buf, ":V'U16_F, port; Sieciowy porządek oktetów jest porządkiem grubokońców kowym, a zastosowany m ikrokontroler jest cienkokońcówkowy, zatem oprogram ow anie sieciowe bardzo
88 3. Stos T C P /n t 3.2. Biblioteka lwip 89 często musi odwracać kolejność oktetów (bajtów. Biblioteka lwip ma zaimplemen-'l; towane odpow iednie funkcje i jeśli nie zdefiniujem y własnych operacji odwracania, to zostaną skom pilowane te dom yślne funkcje, które są jednak napisane w C, aby., ' były przenośne i w związku z tym nie są optym alne. A rchitektura ARM ma instruk- fe' cje asem blerowe REV16 i REV um ożliw iające szybkie (realizowane sprzętow o od-, wracanie kolejności bajtów odpow iednio w słowie dw ubajtow ym i czterobajtowym, Biblioteka STM 32 dostarcza funkcji korzystających z tych instrukcji, co u m o żliw iaj proste zdefiniowanie potrzebnych operacji. idefine LWIP_PLATFORM_HTONS (x _REV16(x»define LWIP_PLATFORM_HTONL(x REV(x! fl W nazwach pow yższych m akr skrót HTONS (ang. host to netw ork short ozna-ij cza zamianę kolejności w słowie dw ubajtow ym (ang. short z porządku używanego "$ przez daną maszynę, węzeł (ang. host na porządek sieciowy. N atom iast H TO N L $ (ang. host to netw ork long oznacza analogiczną zamianę w słowie czterobajtowym (ang. long. Dualnych operacji NTOHS (ang. netw ork to host short i NTO HL (ang.vf netw ork to host long nie m usim y definiow ać, bo są identyczne. Pow yższe makra (ï są przeznaczone tylko do użycia w ewnątrz biblioteki lwip. W naszych programach pow inniśm y włączać plik Iwip/inet.h (lub jakiś inny go włączający i używać zdr- ' finiowanych w nim m akr htons, n to h s, h to n l, n to h l, które mają standardow e na- p zwy, pow szechnie używ ane w oprogram ow aniu sieciowym. ul6 t htons(u!6_t x; u!6_t ntohs(u!6_t x; u32_t htonl(u32_t x; u32 t ntohl(u32_t x; Powyższe makra zaim plem entow ano za pom ocą makr LWIP_PLATFORM_HTONS i LWI P_PLATFORM_HTONL. Im plem entację tych m akr można jeszcze bardziej zoptym alizować, likwidując narzut w ywołania fu n k cji REV16 i REV przez zdefiniowa- nie wstawek asem blerowych. Jest to jednak bardzo zależne od użytego kompilatora. Niestety wstawki asem blerowe często blokują kom pilatorowi m ożliwość optym ah- t zacji kodu wynikowego. GCC ma mechanizm definiow ania takich w stawek nieblo- / : kujący optym alizacji kodu. O kupione jest to jednak bardzo skom plikow aną składnią, którą zaprezentuję na przykładzie makra HTONS. M akro H TONL definiuje się analogicznie.»define LWIP_PLATFORM_HTONS (x (( \ ji u!6_t result; \ asm ("revl6 $0, '-U" :"=r"(result : "r" <x> ; \ result; \ i Treść makra otaczają podw ójne naw iasy składające się z nawiasu okrągłego i kłamrowego. M akro operuje na dw óch zmiennych: swoim argum encie x oraz zadeklaro-. wanej wewnątrz makra 16-bitowej zmiennej r e s u lt. U m ieszczenie nazwy r e s u lt ( w ostatnim wierszu treści makra oznacza, że jest to wartość zw racana przez makro. W iersz zaczynający się od słowa kluczow ego asm definiuje operację, którą chcemy wykonać. Instrukcja rev!6 40, %1 pobiera dwa młodsze bajty z rejestru %1, zam ienia ich kolejność i um ieszcza wynik w rejestrze %0. Przypisanie konkretnych rejestrów pozostawiamy jednak kom pilatorowi. W napisie "=r" (result litera r oznacza, że argument %0 musi być rejestrem. Instrukcja re v l6 nie może umieścić wyniku w pamięci. Znak równości oznacza, że rejestr jest modyfikowany przez tę instrukcję. Nazwa zmiennej w naw iasach oznacza, że rejestr ten musi przechow ywać wartość tej zmiennej. Napis "r" (x oznacza, że argum ent %1 musi być rejestrem, gdyż drugi argument instrukcji re v i 6 rów nież musi być rejestrem. Brak znaku równości oznacza, że wartość tego rejestru nie jest modyfikow ana przez instrukcję. Ponadto rejestr len musi przechowywać wartość zmiennej x. Biblioteka lwip zawiera sporo kodu diagnostycznego. Jeśli chcem y z tego skorzystać, m usim y zaim plem entow ać urządzenie, na które będą wypisywane komunikaty diagnostyczne i makra w ypisujące te komunikaty. O pcja ta jest przydatna przede w szystkim twórcom biblioteki, dlatego definiujem y dwa puste makra:»define LWIP_PLATFORM_ASSERT(x ((void0»define LWIP_PLATFORM_DlAG(x ((void0 Argument makra LWIP_PLATFORM_ASSERT jest wskaźnikiem na komunikat, który ma być wypisany. K om unikat jest napisem w stylu C i musi być zakończony term i nalnym zerem. Argument makra LWIP_PLATFORM_DIAG jest otoczonym nawiasami okrągłymi ciągiem argumentów, które akceptuje funkcja p r i n tf. Zakładając, że dysponujem y funkcją p r i n t f, w ypisującą gdzieś wynik swojego działania, makra to mogłoby być więc zaim plem entow ane tak:»define LWIP PLATFORM_ASSERT (x printf (x»define LWIP_PLATFORM_DIAG(x printf X A plikacje, które prezentuję w tej książce, dla zapew nienia współbieżności korzystają z systemu przerwań. Funkcje biblioteki lwip są wywoływane w programie głów nym i w procedurach obsługi przerwań. N iektóre krytyczne fragmenty programu nie mogą być przerywane. Jednak funkcje biblioteczne w wielu przypadkach nie są w spółużyw alne (ang. reentrant. O gólnie każdą funkcję, która odw ołuje się do jakichś globalnych zasobów, np. zmiennych globalnych czy układów peryferyjnych mikrokontrolera lub używa zmiennych statycznych, należy traktować jako niewspółużywalną. Funkcje zarządzające pam ięcią program u (przydzielające i zw alniające pamięć nie są współużywalne, gdyż korzystają z globalnej struktury, jaką jest sterta. N iew spółużyw alność oznacza, że jeśli w ykonywanie takiej funkcji zostanie przerwane (np. przez procedurę obsługi przerwania i zostanie wywołana ta sama lub inna, ale korzystająca z tych sam ych zasobów funkcja, to istnieje poważne ryzyko błędnego działania programu. M ożna oczyw iście zabronić wywołania niewspółużyw ałnych funkcji w procedurach obsługi przerwań, ale zapewne lepszym rozwiązaniem jest wykonywanie krytycznych fragmentów programu z wyłączonymi przerwaniami. W bibliotece lwip przew idziano w tym celu trzy makra.»define SYS_ARCH_DECL_PROTECT(x \ u32 t X
3.2. Biblioteka IwlP 91 łdefine SYS_ARCII_PROTECT(x \ (x = get_primask(, disable_irq( łdefine SYS_ARCH_UNPROTECT(x \ 'i f _set_primask(x Ą M akro SYS_ARCH_DECL_PROTECT deklaruje zm ienną x, w której zapisuje się aktualif' maskę przerwań. M akro SYS_ARCH_PROTECT zapisuje w zmiennej x aktualną zawati; tos'ć rejestru PRIMASK i w yłącza przerwania. Używa do tego fu n k c ji get_prlmas;j i disable_irq z biblioteki STM 32. Rejestr PRIMASK jest jednym ze specjalnyoi rejestrów rdzenia Cortex-M 3 i służy do maskowania przerwań o konfigurow alny^ priorytecie. W Cortex-M 3 są to w szystkie przerwania z w yjątkiem zerowania (ang^ reset, przerwania niem askow alnego NM I (ang. non-m askable interrupt i poważ* nego niepowodzenia (ang. hard fault. Jeśli najm łodszy bit rejestru PRIMASK jej' wyzerowany, przerwania są włączone, a jeśli jest ustawiony - w yłączone. Pozostał bity tego rejestru nie są używane - są zarezerwow ane do przyszłego wykorzystania'5 F u n k c ja disable_irq wywołuje instrukcję Asem blera cpsid (ang. change prók. cessor state, disable interrupt, która ustawia najm łodszy bit w rejestrze PRIMASK, M akro SYS_ARCH UNPROTECT przyw raca poprzednią, zapam iętaną w zmiennej x;: wartość rejestru PRIMASK. Jako przykład użycia pow yższych m akr rozw ażm y chronione w ersje standardów wych funkcji zarządzających pam ięcią: m alloc, fre e, c a llo c, r e a llo c. Wersje chronione nazwijm y odpow iednio: p ro te c te d _ m a llo c, p ro te c te d _ f re e, p ro tected calloc, p r o te c te d _ r e a llo c. W ołają one odpow iednie funkcje niechronionc przy wyłączonych przerwaniach. Żeby ich w yw ołanie było efektyw ne, są zadekhw rowane jako rozw ijane w miejscu (ang. inline. S ta ła INLINE dla poszczególnych kom pilatorów jest zdefiniow ana w pliku core_cm 3.h, który jest częścią biblioteki;. STM 32 i znajduje się w katalogu Jst/Libraries/C M SIS/C M 3/C oresupport/. Niżej podałem przykłady dw óch funkcji w wersji chronionej. Im plem entacja dw óch pozostałych jest analogiczna. static INLINE void *protected_malloc(size_t size { void *ret; SYS_ARCH_DECL_PROTECT(v; SYS_ARCH_PROTECT(v; ret = malloc(size; SYS_ARCH_UNPROTECTfv; return ret; static INLINE void protected_free(void *ptr { SYS_ARCH DECL_PROTECT (v ; SYS_ARCH_PROTECT(v; free (ptr; SYS_ARCH_UNPROTECT(v; 3 Jak poprzednio, jeśli używamy G CC, możemy makra zaim plem entow ać efektywniej, bezpośrednio w Asemblerze. Instrukcja mrs (ang. move from special register to register przepisuje zaw artość rejestru specjalnego do rejestru ogólnego przeznaczenia. Instrukcja msr (ang. m ove from register to special register przepisuje zawartość rejestru ogólnego przeznaczenia do rejestru specjalnego. Znaki \ n \ t po tej instrukcji oznaczają przejście do now ego w iersza i tabulator. Służą one tylko do formatowania tekstu, który jest przekazyw any do asemblacji. Idefine SYS_ARCH_DECL_PROTECT(x \ u32_t x»define SYS ARCH_PROTECT(x!l \ asm volatile ( \ "mrs %0, PRIMASK\n\t" \ "cpsid i" : \ "=r" (x : \ ; \ 1»define SYS_ARCH_UNPROTECT(x> (1 \ asm volatile C'msr PRIMASE, 10" : : r" (xi; \ I Opisany dotychczas mechanizm może być niewystarczający. W ykonywanie długich fragm entów program u przy wyłączonych przerwaniach jest niepożądane. Zwykle wystarczy zablokow ać tylko pew ną grupę przerwań. M ożna to zrobić, w ykorzystując priorytety. W architekturze Corlex-M 3 m ożna przerwaniom przydzielać priorytety od 0 do 15. Przerwania, których priorytet nie jest konfigurowalny, mają na stale przypisany priorytet ujemny: zerow anie ma priorytet -3, NM I ma priorytet -2, a pow ażne niepow odzenie priorytet -1. Jak to zw ykle bywa, wyższy priorytet ma mniejszą wartość. Przerw ania są wywłaszczalne. Jeśli w trakcie wykonywania procedury obsługi przerwania pojawi się przerwanie o wyższym priorytecie, to aktualnie w ykonywana procedura obsługi zostanie przerwana i procesor przejdzie do obsługi przerwania o w yższym priorytecie. Przerwana procedura zostanie dokończona, gdy nie będzie ju ż żadnych przerwań o w yższym priorytecie do obsłużenia. Priorytet przerwania jest zapisywany na czterech bitach, które możemy podzielić na dw a pola: priorytet w yw łaszczania (ang. preem ption priority i podpriorytet (ang. subpriority. Przerwanie może być w yw łaszczone tylko przez przerwanie o w yższym priorytecie wywłaszczania. Podpriorytet decyduje o kolejności obsługi przerwań w ram ach tego sam ego priorytetu wywłaszczania. Na potrzeby tej książki ustalamy za pom ocą stałej PRE MPTION_PRIORITY_BITS, że dw a bity priorytetu określają priorytet wywłaszczania, a dwa pozostałe podpriorytet. M am y wtedy m ożliwość zdefiniow ania czterech priorytetów wywłaszczania. W ystarczą nam trzy. Z najw yższym priorytetem, oznaczonym HIGH_IRQ_PRIO, obsługujem y przerwania w ym agające szybkiej reakcji. Ich czas obsługi musi być krótki i nie mogą w nich być wołane żadne funkcje biblioteki lwip. Średni priorytet, oznaczony LWIP_IRQ_PRIO, przypisujem y przerwaniom biblioteki lwip. Pozostałym przerwaniom nadajemy niski priorytet LOW_IRQ_PRIO. Idefine PREEMPTION_PRIORITY_BITS 2 łdefine l!igh_irq_prio 1 łdefine LWIP IRQ_PRIO 2 łdefine LOW_IRQ_PRIO 3 M akro SET_IRQ_PROTECTION uaktyw nia m ożliwość blokow ania przerwań zależnie od ich priorytetu i musi być w ywołane na początku programu, zanim włączymy jakiekolw iek przerwanie. Aby ustawić liczbę bitów przeznaczonych na priorytet wywłaszczania, najlepiej jest wywołać funkcję NVIC SetPriorityGrouping z biblioteki STM32. idefine SET_IRQ_PROTECTION( \ NVIC_SetPriorityGrouping(7 - PREEMPTION_PRIORITY_BITS
92 3. Stos TCP/lp$ ]W A by ustawić priorytet przerwania, można użyć funkcji NVIC_SetPriority, do-j" starczanej przez bibliotekę STM 32, a dokładniej przez CM SIS. Niestety funkcja^ ta nie uwzględnia zmienionej liczby bitów priorytetu w yw łaszczania i używa dof' tego stałej NVIC PRIO_BITS, która ma wartość 4, Żeby zm ienić tę wartość, na-ji. leżałoby zm odyfikować pliki źródłowe biblioteki CM SIS. Stała la jest zdefiniowal i na w pliku stm 32fI0x.h, który znajduje się w katalogu Jst/Libraries/CM SIS/CM 3/Ą D evicesupport/st/stm 32 F 10x, a potem używana w pliku core_cm3.h. Funkcja bi- blioteczna nie uwzględnia rów nież podpriorytetu. Aby nie m odyfikować biblioteki, co m ogłoby spow odować problem y z kompatybilności;! naszych programów z jej przyszłymi wersjami, definiujem y własne makro SET_PRIORITY, które umieszcza,, priorytet wywłaszczania w starszych bitach, a podpriorytet w młodszych bitach ' priorytetu. łdefine SET_PRIORITY(irq, prio, subprio \ NVIC_SetPriority((irq, (subprio [ ((prio << \ ( NVIC_PRIO_BITS - PREEMPTION_PRIORITY_BITS M ożem y teraz zdefiniow ać makra analogiczne do wyżej opisanych m akr SYS ARCH. M akro IRQ_DECL_PROTECT deklaruje zmienni! p rv przechow ującą informację 0 poprzedniej wartości priorytetu, powyżej którego przerwania nie są blokow ane., M akro IRQ_PROTECT zapisuje w zm iennej prv stan aktualnie blokow anych przerwań ' 1blokuje przerwania o priorytecie niższym lub równym lv ł. M akro IRQ_UNPROTECT przyw raca stan blokow ania przerwań zapisany w zmiennej prv. Priorytet, powyżej którego przerwania nie są blokow ane, przechowywany jest w mikrokontrolerach Cortex-M 3 w rejestrze BASEPRI. Do tego rejestru możemy odw oływ ać się za pomocą fu n k cji get BASEPRI i set_basepri z biblioteki STM 32. Wartość argumentu łv l trzeba przesunąć o odpow iednią iiczbę bitów w lewo, we właściwe miejsce; w rejestrze BASEPRI. łdefine IRQ_DECL_PROTECT(prv \ u32 t prv Idefine IRQ_PROTECT(prv, lvl \ (prv = get_basepri{, \ _set_basepriiilvl «(8 - PREEMPTION_PRIORITY_BITS} Idefine IRQ UNPROTECT (prv \ _set_basepri(prv Pow yższych m akr używa się podobnie jak m akr SYS_ARCH. Jak poprzednio, dła GCC możemy je zdefiniow ać bardziej optym alnie. Zm ienną p rv przechow ujem j w rejestrze ogólnego przeznaczenia %0. Zakładamy, że jako argum ent lv l zawsze podaw ana jest jedna z wyżej zdefiniow anych stałych IRQ_PRIO. Poniew aż argumentem instrukcji msr nie może być stała, m usim y najpierw tę stałą załadować do rejestru ogólnego przeznaczenia 41 (zm ienna tmp za pom ocą instrukcji movs. Napis " i" oznacza, że argum ent %2 jest stałą. W naw iasach za tym napisem jest podana wartość tej stałej. łdefine IRQ_DECL_PROTECT(prv \ u32_t prv łdefine IRQ_PROTECT(prv, lvl (( \ u32_t tmp; \ asm volatile ( \ "mrs *0, BASEPRI\n\t" \ "movs %1, t2\n\t" \ "msr BASEPRI, %1" : \ 3.2. Biblioteka IwlP 93 "~r" (prv, "=r" (tmp : \ "i" ((lvl «(8 - PREEMPTION_PRIORITY_BITS \ ; \ łdefine IRQJNPROTECT(prv (( \ asm volatile ("msr BASEPRI, 1,0" ; : "r" (prv; \ I Przestaw ione tu sposoby ochrony krytycznych i niewspółużywalnych fragmentów programu mogą wydać się przesadzone jak na potrzeby stosunkowo prostych przykładów zam ieszczonych w kolejnych rozdziałach. Jednak w bardziej skom plikowanych aplikacjach porządne przem yślenie tego problemu i zaim plem entowanie odpowiednich mechanizmów chroni przed bardzo trudnymi do zlokalizowania błędami, których usuw anie jest bardzo czasochłonne. Zaprezentowane w tym podrozdziale rozw iązania należy traktować jako przykład i inspirację do własnych przemyśleń na ten temat. Aby pisać programy, które używają konstrukcji specyficznych dla kompilatora, dobrze jest posługiwać się instrukcjami kompilacji warunkowej i stałymi definiow a nymi w tym celu przez poszczególne kompilatory. Stałe dla popularnych kom pilatorów zam ieszczone są w tabeli 3.1. Popatrzmy na następujący przykład kompilacji w arunkowej, zależnej od użytego kompilatora. H f defined GNUC idefine ALIGN4 attribute {{aligned (4 ielse terror Define ALIGNS macro for your compiler, iendif? Tab. 3.1. Stale identyfiku ące kompilator Kompilator ARMCC GCC IAR TASKING CC ARM GNUC ICCARM TASKING Siata Cortex-M 3 jest architekturą 32-bitową i czasem, ze względu na efektywność, dobrze jest w yrównać położenie danych w pamięci do adresu będącego wielokrotnością 4 bajtów. Przykład użycia atrybutu a lig n e d widzieliśmy już w poprzednim rozdziale, przy deklaracji deskryptorów i buforów DM A. Ponieważ wyrównywanie nie jest częścią standardu języka C, każdy kom pilator ma w tym celu swoje specyficzne instrukcje. Pow yższy fragment program u jest przeznaczony dla GCC. Inny kom pilator wypisze błąd przypom inający, że należy dodać właściwą definicję. 3.2.2. Parametry konfiguracyjne - plik Iwipopts.h W szystkie parametry, które możem y ustawić w bibliotece IwlP, są zgromadzone w jednym miejscu - znajdują się w pliku opt.h. K ażda opcja jest opatrzona kom entarzem i ma zdefiniow aną wartość domyślną. Plik ten jest w archiwum w katalogu,/lw ip-1.3.2/src/include/lwip, ale nie należy go modyfikować. Plik opt.h włącza plik Iwipopts.h, który powinniśm y dostarczyć. W szelkie zmiany param etrów należy umieścić w pliku Iwipopts.h. Jeśli nie zdefiniujem y jakiegoś param etru, zostanie użyta jego wartość domyślna. Plik Iwipopts.h dla przykładów prezentowanych w tej
3.2. Biblioteka Iw IP 95 książce znajduje się w katalogu./exam ples/include. O piszę teraz po kolei zdefinio-i; wane w tym pliku param etry konfigurujące bibliotekę lwip. Idefine LWIP_PLATFORM_BYTESWAP 1 Wartość 1 włącza używanie przez bibliotekę lwip operacji odw racania kolejności,;ji bajtów LWIP_PLATFORM_HTONS i LWIP_PLATFORM HTONL, które przedstaw iłem w p0. przednim podrozdziale. Wartość 0 oznacza, że używane są operacje dom yślne, do - ;» starczane wraz z biblioteką. Idefine SYS_LIGHTWEIGHT_PROT 1 Wartość 1 sprawia, że funkcje biblioteczne przydzielające i zw alniające bufory chronione wewnątrz makrami SYS_ARCH_PROTECT i SYS_ARCH_UNPROTECT. WailnA % 0 w yłącza tę ochronę. Idefine NO SYS 1 ' Wartość 1 oznacza, że nie ma systemu operacyjnego - cala aplikacja działa jako po- ijfl. jedynczy wątek. W artość 0 oznacza, że używam y systemu operacyjnego, w którym i,i można urucham iać wiele wątków lub procesów. Idefine LWIPJJETCONN 0 idefine LWIP SOCK T 0 Powyższe ustawienia w yłączają interfejsy sekw encyjny i gniazd, pozostawiając tyl- i ko podstawow y interfejs surowy. idefine LWIP_STATS 0 W artość 0 wyłącza zbieranie statystyk, co przyspiesza działanie stosu. Ustawienie,4 wartości 1 um ożliwia zbieranie statystyk. W yświetlanie statystyk wym aga zaimplementowania, w spom nianego w poprzednim podrozdziale, makra LWIP_PLATFORM DIAG. Statystyki te są niew ątpliw ie bardzo użyteczne dla program istów chcących - rozwijać bibliotekę lwip, ale ich interpretacja wym aga głębokiego wniknięcia w tekst źródłowy. ''tw idefine MEM_LIBC_MALLOC 1 W artość 1 sprawia, że biblioteka lwip używa sterty i funkcji zarządzających pam ięcią na stercie dostarczanych przez standardow ą bibliotekę C, czyli funkcji lo c, fre e, c a llo c i r e a llo c. U stawienie wartości 0 powoduje, że biblioteka używa swojej wewnętrznej sterty i własnego mechanizmu alokacji pamięci. Trzeba wted\ zdefiniować rozm iar MEM_SIZE tej wewnętrznej sterty. idefine MEM_SIZE (0 * 102-1 Jeśli aplikacja wysyła dużo danych, które wym agają kopiowania, sterta ta powinna V być duża. K opiowanie jest potrzebne, jeśli aplikacja przekazuje dane do wysiania w buforze i używa ponow nie tego bufora, zanim dane zostaną faktycznie wysiane. Sterta w ewnętrzna jest alokow ana w sekcji,bss. Jeśli staia MEM_LIBC_MALLOC ma wartość I, sterta w ewnętrzna nie jest alokowana. {(define mem_free idefine mem_calloc idefine mem_malloc idefine mem_realloc protected_free prot:ected_calloc protected_malloc protected_realloc Biblioteka lwip nie wywołuje bezpośrednio funkcji zarządzających stertą. W szystkie wywołania realizuje poprzez identyfikatory poprzedzone prefiksem mera_. Dzięki temu pow yższe definicje um ożliw iają użycie chronionych wersji tych funkcji, które przedstaw iłem w poprzednim podrozdziale. idefine MEM_ALIGNMENT -I Powyższa stała definiuje w yrównyw anie struktur alokowanych w pamięci. Różni się od opisanego w poprzednim podrozdziale m akra A LIGN-l tym, że tam to makro służy do w yrównyw ania w czasie kom pilacji, a staia MEM_ALIGNMENT jest używana w czasie wykonywania programu. K olejne stale definiują rozm iary w ewnętrznych struktur biblioteki i mają istotny wpływ na zajętość RAM. Stale te zostały dobrane do prezentowanych w tej książce przykładów. Inne aplikacje będą z pew nością w ym agały dopasow ania tych w artości. Znaczenie poszczególnych struktur będzie się stopniow o wyjaśniać w miarę poznaw ania kolejnych przykładów użycia biblioteki lwip. Idefine PBUF_P00L_SIZE 16 Definiuje liczbę buforów puli zarządzanej przez bibliotekę lwip. Z puli tej alokowane są między innymi bufory, w których um ieszczane są odbierane ramki ethernetowe. Idefine PBUF_POOL_BUFSIZE 152-1 Definiuje rozm iar pojedynczego bufora w puli. R ozm iar ten jest dobrany tak, aby w buforze zm ieściła się cala ram ka ethernetow a, która bez preambuły, ale z uw zględnieniem sekwencji kontrolnej, m oże mieć maksym alnie długość 1518 bajtów. Na przyszłość uw zględniam y też dodatkow e 4 bajty na znacznik VLAN. R ozm iar bufora w yrównujem y do w ielokrotności 4 bajtów. Idefine MEMP_NUM_PBUF 16 Definiuje liczbę struktur typu p b u f, które są alokow ane jako PBUF_REF lub PBUF_ ROM, patrz rozdział 3.2.5. Struktury te opisują między innymi dane umieszczone w pam ięci stałej, czyli ROM lub Flash. Jeśli aplikacja w ysyła dużo danych bezpośrednio z pam ięci stałej, bez ich kopiow ania do RAM, to liczba tych struktur pow inna być duża. (define MEMP_NUM_UDP_PCB 5 Definiuje liczbę jednocześnie otw artych deskryplorów UDR Liczba ta determinuje ilość sym ultanicznie prow adzonych kom unikacji za pom ocą UDP i musi uw zględniać deskryptory używane przez D HCP i DNS. D eskryptory UDP są potrzebne tylko wtedy, gdy używa się U DP lub protokołów korzystających z UDP. Idefine MEMP_NUM_TCP_PCB 2 Definiuje liczbę jednocześnie otw artych połączeń TCP. Jeśli TCP nie jest używany, można zrezygnować z tych struktur. Idefine MEMP_NUM_TCP_PCB_LISTEN 3 Definiuje rozm iar kolejki połączeń TCP oczekujących na otwarcie. Kolejka jest potrzebna w aplikacji działającej jako serw er TCP. Nie jest natom iast potrzebna w aplikacji działającej jako klient.
96 3. Stos TCP/a '. i; łdefine MEMP_NUM_TCP_SEG 8 Dane przesyłane za pomocą TCP są dzielone na segmenty, które są przesyłanej w datagram ach IP. Stała ta definiuje liczbę jednocześnie kolejkowanych segmentów',4i TCP. Idefine MEMP_NUM_REASSDATA 4 O dbierane datagram y IP mogą przychodzić pofragm entowane. Stała ta definiuje'^; liczbę jednocześnie kolejkowanych datagram ów IP (całych datagramów, a nie frag-!-. m entów czekających na złożenie. Kolejne definicje ustalają szczegółow e opcje dla poszczególnych protokołów'.;^. W iększość z nich nie w pływ a na zajętość RAM lub wpływa w niewielkim stopniuj;.*, ale ma istotne znaczenie dla działania biblioteki lwip. Sdefine ARP QUEUEING 1 W ' ' ' i W łącza kolejkowanie datagram ów IP, które powinny zostać wysiane, ale oczekują"/; na przetłum aczenie za pom ocą A RP adresu IP na adres ethernetowy. V łdefine MEMP_NUM_ARP_QUEUE 3.ifc D efiniuje m aksym alną liczbę jednocześnie kolejkowanych datagram ów IP, czeka- -lejących na przetłum aczenie adresu za pom ocą ARP. O pcja ta wymaga, aby stała. iy ARP_QUEUEING m iała wartość 1. T-." łdefine ARP_TABLE_SIZE 4 Definiuje rozm iar pamięci podręcznej ARP. W artość ta powinna być równa przewi dywanej maksymalnej liczbie węzłów, do których w danej podsieci będą bezpośred-, d nio w ysyłane datagram y IP, z uw zględnieniem rutera. i,. łdefine ETHARP_TRUST_IP_MAC 1 Wartość 1 oznacza, że pamięć podręczna A RP będzie uaktualniana na podstawie informacji z nagłówków w odbieranych ram kach ethernetow ych. W artość 0 blokuje d takie uaktualnianie - w tym przypadku pamięć podręczna A RP może być m o d y f ik o -. wana tylko po wysianiu prośby A RP i odebraniu na nią odpowiedzi. Zablokow anie' tej opcji wprowadza dodatkow e, niew ielkie opóźnienia w tłum aczeniu adresów, a]e też w prowadza pewne zabezpieczenie przed zatruwaniem pam ięci podręcznej ARP (ang. spoofing. Jeśli wszystkie w ęzły w podsieci są zaufane, m ożem y śm iało uaktyw nić tę opcję. łdefine ETHARP_SUPPORT_VLAN 0 W artość 1 uaktyw nia odbieranie ram ek ethernetow ych VLAN. Znacznik VLAN ramek, które mają być odbierane, należy zdefiniow ać jako stalą ETHARP_VLAN_CHECK. Jeśli stała ta nie jest zdefiniow ana, to odbierane są ramki ze w szystkim i znaczni-, kami. łdefine IP_FORWARD 0 W artość 1 uaktyw nia przekazyw anie datagram ów IP, co oznacza, że węzeł mo/e być ruterem. Jeśli węzeł ma jeden interfejs sieciowy, raczej nie będzie ruterem i na-': leży tej stałej przypisać wartość 0. łdefine IP OPTIONS ALLOWED 1 3.2. Biblioteko lwip 97 Jeśli stała ta ma wartość 0, odebrane datagram y IP, których nagłówek ma dodatkowe opcje, nie będą przetwarzane. Jeśli stała ta ma wartość 1, wszystkie odebrane datagram y IP będą przetwarzane, ale dodatkow e opcje umieszczone w nagłówku datagram u IP będą ignorowane. Biblioteka lwip nie obsługuje dodatkowych opcji w nagłów kach IP. łdefine IP_REASSEMBLY 1 W artość 1 włącza składanie odbieranych pofragm entowanych datagramów 1P. W artość 0 powoduje, że przetwarzane będą tylko niepofragm entow ane datagramy, a wszystkie odebrane fragmenty datagram ów IP będą ignorowane. łdefine IP_ERAG 1 W artość 1 włącza fragm entowanie wysyłanych datagram ów IP, jeśli ich rozm iar przekracza M TU sieci. Wartość 0 pow oduje, że próba wysiania datagramu dłuższego niż M TU się nie powiedzie. łdefine IP_REASS_MAXAGE 3 D efiniuje m aksymalny czas oczekiwania na otrzym anie w szystkich fragmentów datagramu IP. Jeśli w tym czasie w szystkie fragm enty nie zostaną odebrane, to cały datagram jest porzucany. Czas jest liczony w w ielokrotnościach kwantu czasu określonego za pom ocą stałej IP_TMR_INTERVAL. D om yślnie stała ta definiuje interwal 1000 ms. łdefine IP_REASS_MAX_PBUE;S 10 Definiuje maksym alną liczbę buforów odbiorczych z czekającymi na złożenie fragmentami datagram ów IP. Wartość ta musi być m niejsza niż PBUF_P00L_SIZE, żeby stos lwip byl w stanie odbierać kolejne datagram y i się nie zablokował. łdefine IP_DEFAULT_TTL 64 D efiniuje dom yślną wartość czasu życia (TTL, ang. time to live umieszczaną w nagłówkach wysyłanych datagram ów IP. łdefine LWIP_ICMP 1 łdefine ICMPJTTL 64 łdefine LWIP_BROADCAST_PING 0 łdefine LWIP_MULTICAST_PING 0 Pierwsza z pow yższych opcji w łącza obsługę ICMP. Druga ustawia wartość czasu życia um ieszczaną w nagłówkach datagram ów IP, w których są wysyłane kom u nikaty ICMP. D wie ostatnie opcje w yłączają odpow iadanie na komunikaty ECHO (używ ane przez program ping wysyłane na adres rozgłoszeniowy (ang. broadcast i grupow y (ang. multicast. Ze względu na bezpieczeństw o węzeł nie powinien odpowiadać na takie komunikaty, gdyż m ogą one być próbą ataku typu odm ow a usługi (DoS, ang. denial o f service. O pcje te możem y uaktyw nić w zamkniętej sieci, w której wszystkie węzły są zaufane, ijdefine LWIP_RAW 0 Wartość 1 tej stałej w łącza możliwość bezpośredniego korzystania z warstwy sieciowej. M ożem y być wtedy pow iadam iani o odebraniu datagramu IP i możemy wysyłać w łasne datagram y IP. O pcja ta jest użyteczna, gdy chcem y rozszerzyć bibliotekę lwip o obsługę dodatkow ych opcji 1P lub zaim plem entow ać własny protokół transportowy. Jeśli używam y standardow ych protokołów transportowych TCP
98 3. Slos TCP/IpL 3.2. Biblioteka lwip 99 i UDP, nie potrzebujem y bezpośredniego dostępu do warstwy sieciowej i dlatego y przypisujem y tej staiej wartość 0. łdefine LWIPJJDP 1 'j»define UDP_TTL 64 Pierwsza z powyższych staiych w łącza obsługę UDP. D ruga ustawia wartość czasu$ życia um ieszczaną w nagłów kach datagram ów 1P, w których są w ysyłane datagra-..^ my UDP. łdefine LWIPJTCP 1 łdefine TCP_TTL 64 Pierwsza z powyższych stałych w łącza obsługę TCP. D ruga ustawia wartość czasu życia um ieszczaną w nagłów kach datagram ów IP, w których są w ysyłane segmenty... TCP. łdefine TCP_MSS (1500-40 Definiuje maksymalny rozm iar segm entu TCP, tak aby w ysyłane datagram y IP za-':/; wierające segm enty TC P nie m usiały być fragmentowane. Wartość tej stałej powinna być równa wartości M TU sieci pom niejszonej o sum ę rozm iarów nagłówków ll>.. i TCP. M TU Ethernetu wynosi zw ykle 1500 oktetów. Nagłówki IP i TCP genero-/ wane przez bibliotekę lwip mają po 20 oktetów - porównaj w pliku pbuf.h definicje^ stałych PBUF_IP_HLEN i PBUF_TRANSPORT_HLEN, które określają odpow iednio długo-" ści tych nagłówków i obie mają w artość 20. łdefine TCP_WND (4 TCP_MSS Definiuje maksymalny rozm iar okna odbiorczego JC P. Okno odbiorcze musi pomieścić co najmniej dw a segm enty danych. O kno odbiorcze jest w irtualnym buforem służącym do kontroli przepływu. R ozm iar okna w yznacza ilość danych, które X' węzeł jest skłonny przyjąć. Węzeł odbierający inform uje węzeł w ysyłający o swi im aktualnym rozm iarze okna. W ysłanie zerowego rozm iaru okna oznacza piośbę 0 w strzym anie transm isji, gdyż w ęzeł odbierający nie przetworzył jeszcze dotych- f ; czas otrzymanych danych. Późniejsze zaproponow anie niezerowego rozmiaru oznacza prośbę o w znowienie transm isji. W bibliotece lwip okno odbiorcze nie jest / alokow ane jako ciągły obszar RAM. Tworzą go bufory alokow ane z puli i połącząne w listę. Dlatego wartość tej stałej nie wpływa istotnie na zajętość pamięci. łdefine TCP_QUEUE_0OSEQ 0 Datagram y IP mogą być dostarczane w innej kolejności niż zostały wysłane. Wartość 1 tej stałej sprawia, że stos kolejkuje segm enty TCP, które zostały odebrane w złej kolejności. Wartość 0 wyłącza to kolejkowanie. D ostarczanie datagram ów w zmienionej kolejności w praktyce zdarza się bardzo rzadko, a w sieciach lokalnych prawie nigdy. Systemy w budowane m ają mało pamięci, a kolejkowanie blokuje bufory,, dlatego przypisujem y tej stałej w artość 0. łdefine TCP_SND_BUF (4 * TCP_MSS Definiuje rozm iar w irtualnego bufora nadaw czego TCP. Bufor ten pełni lunkcję podobną do okna odbiorczego, ale po stronie nadawczej. Sieć nie działa nieskończenie szybko. Ponadto odbiorca może wstrzymać nadaw cę, wysyłając mu zerowy ; rozm iar okna. Bufor nadawczy służy do przechow ywania danych czekających na wysłanie. A plikacja przed w ysłaniem kolejnej porcji danych pow inna sprawdzić. ; czy jest dostatecznie dużo miejsca w buforze nadawczym. Jeśli nie ma, próba wysłania zakończy się niepow odzeniem - w ywołanie funkcji wysyłającej zakończy się błędem. Okno nadaw cze nie jest alokow ane jako ciągły obszar pamięci. Tworzą go połączone w listę bufory z ram kam i do w ysłania. D latego wartość tej stałej nie wpływa istotnie na zajętość pamięci. łdefine TCP SND_QUEUELEN (4 * TCP_SND_BUF / TCP_MSS D efiniuje m aksym alną liczbę fizycznych buforów, które mogą tworzyć bufor nadawczy. Dla praw idłowego działania biblioteki param etr ten musi mieć wartość co najm niej 2*TCP_SND_BUF/TCP_MSS. W artość lego param etru nie w pływa istotnie na zajętość pamięci, łdefine LWIP_DHCP 1 W artość 1 włącza kom pilow anie klienta DHCP, który umożliwia uzyskanie adresu IP, maski podsieci, adresu rutera i adresu serwera nazw. Wartość 0 wyłącza m ożliwość konfigurow ania za pom ocą DHCP. łdefine LWIP_DNS 1 łdefine DNS_TABLE_SI2E 3 łdefine DNS_MAX_NAME_LENGTH 48 łdefine DNS_MAX_SERVERS 2 Pierwsza z pow yższych stałych włącza kom pilow anie klienta DNS. Kolejne definiują rozm iary struktur danych używ anych przez tego klienta. Stała DNS_TABLE_ SI ZE określa, ile odw zorowań nazw dom enow ych na adresy IP będzie pamiętanych. Stała DNS_MAX_NAME_LENGTH definiuje m aksym alną długość nazw, które będą obsługiwane. Stała DNS_MAX_SERVERS definiuje m aksym alną liczbę serwerów, które są do dyspozycji klienta DNS. O pcje te mają wpływ na zajętość RAM. łdefine DNS_SERVER_ADDRESS inet_addr("194.204.152.34" Klient DNS potrzebuje adresu serwera DNS. Adresy serwerów DNS można konfigurow ać na kilka sposobów: - Jeśli nie podamy żadnego adresu, to w pliku dns.c zdefiniowany jest adres domyślny 208.67.222.222. Plik dns.c jest w katalogu Jlwip-1,3.2/src/core. - A dres dom yślny można podać, przedefiniując stałą DNS_SERVER_ADDRESS. - Adres można uzyskać za pom ocą DHCP. - A dres m ożna skonfigurow ać za pom ocą funkcji d n s_ s e tse rv e r. Użyta wyżej funkcja in e t addr jest przestarzała (ang. obsolete, ale jest nadal używana w pliku dns.c. W nowych projektach należy raczej używać funkcji in e t_ a to n. Jeśli nie chcem y korzystać z DNS łub nie ma dostępu do serwera DNS, można skonfigurow ać listę stałych odw zorowań nazw na adresy IP - patrz opis stałej DNS_ L0CAL_H0STLI ST w pliku opt.h. Ostatnia grupa opcji, które możemy skonfigurować, dotyczy obliczania sum kontrolnych. Jak być może pamiętamy, sekwencja kontrolna w ramce elhernelowej jest wyliczana i sprawdzana sprzętowo przez układ M AC mikrokontrolera. M ikrokontroler STM 32F107 może też sprzętowo wyliczać i sprawdzać sumy kontrolne w nagłówkach protokołów IP, TCP, U DP i ICMP. Jest to kusząca propozycja, gdyż realizacja sprzętowa jest zawsze szybsza od im plem entacji programowej. Niestety wyliczanie sum kontrolnych protokołów intersieci przez sprzęt łamie fundamentalną zasadę po
100 3. Stos TCP/IP 3.2. Biblioteka lwip 101 3.2.3. działu na warstwy. Poprawna im plem entacja sum kontrolnych wymaga uwzględnienia wszystkich opcji protokołu, co w praktyce jest niemożliwe, gdyż oznacza konieczność zaim plem entowania całości protokołu. Jak należało się tego spodziewać, im plem entacja sum kontrolnych w STM 32F107 jest niepełna. Nie uwzględnia fragmentacji IP. Jest też prawdopodobne, że nie poradzi sobie z dodatkowymi opcjami" IP. Ponadto w bibliotece lwip nie przew idziano prostego sposobu przekazania stosowi informacji o stwierdzeniu przez sprzęt błędnej sum y kontrolnej. W łączenie przez sprzęt wyliczania sum kontrolnych ICM P wymaga dodatkow o modyfikacji tekstu źródłowego biblioteki lwip, co może powodować problemy z kompatybilnością apli kacji z przyszłymi wersjami tej biblioteki. W szystko to przem awia przeciw włączaniu sprzętowych sum kontrolnych. W poniższych definicjach wartość 0 oznacza uaktywnienie realizacji sprzętowej, a 1 oznacza włączenie implementacji programowej. łdefine CHECKSUM_GEN_IP 0 Sum a kontrolna w ysyłanych datagram ów IP jest generow ana sprzętowo. Tę opcję" ' możem y włączyć, poniew aż lwip praw dopodobnie nigdy nie wygeneruje nagłówka:; IP z dodatkow ym i opcjami, łdefine CHECKSUM_CHECK_IP 1 Sum a kontrolna odbieranych datagram ów IP jest sprawdzana program ow o, gdyż ; mogą pojawić się nagłówki IP z dodatkow ym i opcjami. łdefine CHECKSUM_GEN_UDP 1 łdefine CHECKSUM_GEN_TCP 1 łdefine CHECKSUM_CHECK_UDP 1 łdefine CHECKSUM_CHECK_TCP 1 Sumy kontrolne UDP i TC P są generow ane i sprawdzane programowo. Kompilowanie - plik Iiblw ip4.a Bibliotekę lwip należy skom pilować, posługując się wskazówkam i podanymi w rozdziale 1.4. Dalej zakładam, że skom pilowana biblioteka została umieszczona w pliku liblwip4.a. Nazwy katalogów, które zaw ierają pliki biblioteki lwip, s ą, zebrane w tabeli 3.2. Ponadto potrzebny jest plik nagłówkowy Iwipopts.h, który Tab. 3.2. Katalogi z plikami biblioteki lwip Źródłowe Jlw ip-1.3.2/src/api Jlwip-1.3.2/srclcore Jlwip-1.3.2/src/carelipv4 Jlw ip -1.3.2/src/core/snm p Jlw ip-1.3.2/src/netif Tab. 3.3. Pliki źródłowe biblioteki lwip Nagłówkowe Jlwip-1,3.2/srclinclude/ipv4/lwip Jlw ip -1.3.2/srclincludellwip Jlwip-1.3.2/srclinclude/netif Jexampleslinclude/arch Inicjowanie 1 obsługa pamięci A R P IIP Warstwy transportowa 1aplikacji! Jlwip- 1.3.2/src/core/inlt.c. /Iw ip-1.3.2/src/core/m em. c Jlwip- 1.3.2lsrclcorelmemp. c Jlwip- 1.3.2lsrc/core/pbuf.c Jlwip-1.3.2/src/netifletharp.c Jlwip- 1.3.2/src/core/neti!.c. /Iw ip -1.3.2lsrc/core/ipv4/icmp. c Jlw ip-1.3.2/src/core/ipv4/inet. c Jlwip-1.3.2/src/corelipv4/inet_chksum.c Jlw ip-1.3.2/srclcorelipv4/ip. c Jlwip-1.3.2/src/corelipv4/ip_addr.c Jlwip- 1.3.2/src/corelipv4/lp Irag.c Jlw ip-1.3.2/srclcore/dhcp. c Jlwip- 1.3.2/srclcore/dns.c Jlw ip-1.3.2/src/core/lcp.c Jlwip- 1.3.2/src/coreltcpjn.c. Ilw ip-1.3.2/src/core/tcpjiut. c Jlwlp-1.3.2/src/core/udp.c znajduje się w katalogu./exam ples/include. Opisana w poprzednim podrozdziale konfiguracja biblioteki lwip wym aga skom pilowania tylko plików, których nazwy są zam ieszczone w tabeli 3.3. 3.2.4. Kody błędów Funkcje biblioteki lwip i funkcje współpracujące z nią zwracają wartość typu e r r _ t, sygnalizującą popraw ne zakończenie lub kod błędu, gdy wykonanie funkcji zakończy się niepowodzeniem. Stale reprezentujące wartości typu e r r _ t są zdefiniowane w pliku err.h. Znaczenie poszczególnych stałych jest opisane w tabeli 3.4. Stała E R R _ 0 K ma wartość 0 i oznacza popraw ne zakończenie funkcji. Pozostałe stale mają wartości ujem ne i oznaczają wystąpienie błędu. W przykładach stosuję podobne podejście. Jeśli funkcja ma poinform ow ać, że zakończyła się poprawnie, to zwraca zero. Gdy wystąpił błąd, funkcja zwraca wartość ujemną. Tab. 3.4. Wartości zwracane przez funkcje biblioteki lwip ERR OK ERR MEM ERR BUF Stała ERR TIMEOUT ERR RTE ERR ABRT ERR RST ERR CLSD ERR CONN ERR VAL ERR ARG ERR USE ERR IF ERR ISCONN ERR INPROGRESS 3.2.5. Struktura pbuf Poprawne zakończenie Brak pamięci Problem z buforem Przekroczony czas Problem z trasowaniem Połączenie przerwane Połączenie skasowane Połączenie zamknięte Połączenie nieotwarte Błędna wartość Problem z argumentem Adres lub port zajęty Opis Niskopoziomowy błąd interfejsu sieciowego Połączenie już zestawione Operacja w trakcie Biblioteka lwip przechow uje pakiety sieciow e w buforach, które są wskazywane za pom ocą struktury typu pbuf zdefiniowanej w pliku pbuf.h. Zrozum ienie zasad posługiw ania się buforami jest bardzo istotne. Bez tego trudno jest pisać poprawne i efektywne aplikacje używ ające biblioteki lwip. Struktura typu pbuf opisuje pojedynczy bufor. struct pbuf ( struct pbuf *next; void *payload; ui6 t tot len; ul6_t len; ub t type; u8_t flags; ul6 t. ref; ;
102 3. Stos TCP/ip 3.2. Biblioteka lwip 103 Składow a payload jest wskaźnikiem do bufora przechow ującego dane. Bufor może, być zarówno w pamięci operacyjnej (RAM, jak i w pamięci staiej (ROM, Flash, W skaźnik next łączy struktury w listę. W skaźnik ten w ostatniej strukturze na li: scic ma wartość NULL. Lista struktur pbuf um ożliw ia podział pakietu na fragmenty, i umieszczenie poszczególnych fragm entów w kolejnych buforach. Taka lista na-: żywa się w dokum entacji biblioteki lwip łańcuchem buforów (ang. buffer chain; Za pom ocą w skaźnika next pakiety m ogą też być łączone w kolejkę (ang. packetqueue. Zatem kolejka pakietów składa się z jednego lub większej liczby pakietów,' z których każdy jest um ieszczony w łańcuchu buforów. Łańcuch może zawierać'} jeden lub wiele buforów. Koniec łańcucha można rozpoznać za pom ocą składowych -; tot_len i len. Składow a len zaw iera długość bufora. Składow a to t_ le n zawieracałkow itą długość pakietu, czyli sum ę długości w szystkich buforów w łańcuchu., O statnia struktura w łańcuchu m a rów ne wartości tych składowych i jest to jed y n v poprawny sposób rozpoznaw ania końca łańcucha buforów. Ponadto dla łańcucha buforów obow iązuje niezmiennik: p->tot_len == p->len + (p->tot_len!= p->len? p->next->tot_len ; 0 Składowa type oznacza sposób alokacji bufora i może przyjm ow ać następujące; wartości: - PBUF RAM- bufor jest alokow any w jednym kawałku na stercie; - PBUF_R0M - struktura opisuje dane tylko do odczytu, bufor jest w pamięci stałej; A; - PBUF_REF - struktura jest referencją do innej struktury typu pbuf, żaden bufor nic ij jest alokowany; ( - PBUF_P00L - bufor jest alokow any z puli buforów biblioteki lwip o stałym roz- miarze; jeśli żądamy bufora o rozm iarze większym niż PBUF_P00L_SIZE, to jest-'(j!> przydzielany łańcuch buforów zawierający wiele buforów..!; Składow a fla g s jest przeznaczona do przechow ywania znaczników, ale wydaje się, że nie jest używana przez bibliotekę lwip. Składow a r e f jest licznikiem referencji, zawiera liczbę w skaźników odw ołujących się do tej struktury. M oże to być wskażnik next w innej strukturze typu pbuf lub jakiś inny w skaźnik w ew nątrz biblioteki. lw ip lub aplikacji. W pliku phuf.c zaim plem entow ane są funkcje obsługujące bufory. Najważniejsze z nich opisuję niżej. struct pbuf * pbuf_alloc{pbuf_layer layer, ul6_t length, pbuf_type type; Funkcja pbuf alloc alokuje łańcuch buforów. Zwraca w skaźnik do pierwszej. struktury typu pbuf w łańcuchu łub wartość NULL, gdy alokacja się nie powiodła. Argument type ma to sam o znaczenie, co składowa o lej samej nazwie w strukturze typu pbuf i określa sposób alokacji buforów. Argum ent length określa żądanj rozm iar bufora. Jeśli żądam y alokacji z puli buforów biblioteki lw ip i rozm iar teri jest większy niż rozm iar pojedynczego bufora w puli, to przydzielony będzie łańcuch składający się z odpow iedniej liczby buforów. Argument layer określa, ile dodatkow ego miejsca na początku bufora ma być zarezerwow ane na nagłówki pro tokołów. Dzięki temu dokładanie tych nagłów ków nie w ym aga polem dodatkowej alokacji pamięci czy kopiow ania zaw artości bufora. A rgum ent ten może przyjm o wać następujące wartości: - PBUF_TRANSPORT - alokujemy bufor na dane protokołu transportowego (TCP lub UDP; - PBUF IP - alokujem y bufor na pole danych datagram u 1P; - PBUF_LINK - alokujem y bufor na pole danych protokołu dostępu do sieci, np. pole danych ram ki ethernetow ej; - PBUF RAW- alokujemy bufor na surowe dane, np. kompletną ramkę ethernetową. void pbuf_realloc(struct pbuf *p, u!6_t new_len; Funkcja pbuf_realloc zm niejsza rozm iar łańcucha buforów wskazywanego przez p do podanego rozm iaru new len. Zależnie od podanej nowej długości łańcuch może zostać skrócony, a niepotrzebne bufory mogą być zwolnione. W brew nazwie, funkcja ta nie może pow iększyć rozm iaru bufora. Jeśli bufor jest typu PBUF REF łub PBUF_R0M, m odyfikow ane są tylko składowe tot_len i len struktury typu pbuf. void pbuf_ref(struct pbuf *p: Funkcja pbuf_ref zwiększa licznik referencji w podanej strukturze typu pbuf. u8_t pbuf_ ree(struct pbuf *p; Funkcja pbuf fr e e zmniejsza licznik referencji i jeśli licznik ten osiągnie zero, zw alnia nieużyw ane bufory i struktury je opisujące. Dla łańcucha buforów operacja ta jest pow tarzana dla w szystkich struktur na liście, aż do natrafienia na pierwszą, która po zm niejszeniu nadal ma licznik referencji większy od zera. Zatem, jeśli wszystkie liczniki referencji w łańcuchu mają w artość jeden, cały łańcuch jest zw alniany. Funkcja ta zwraca liczbę zw olnionych struktur typu pbuf. u8_t pbuf_clen(struct pbuf *p; Funkcja pbuf c le n zw raca liczbę struktur na liście, począw szy od wskaźnika p, aż do napotkania w skaźnika n ex t zaw ierającego NULL. err_t pbuf_take(struct pbuf *buf, const void *dataptr, u!6 t len; Funkcja pbuf_take kopiuje len bajtów danych wskazywanych przez dataptr do łańcucha buforów w skazywanego przez buf. Zw raca ERR_0K, gdy kopiow anie powiodło się, a ERR ARG, gdy jeden z podanych wskaźników jest zerowy lub w łańcuchu buforów nie ma dostatecznie dużo miejsca.
104 'I 3. Stos TCP/ft* vj 3.2. Biblioteka IwiP 105 3.2.6. ul6_t pbuf_copy_partial{struct pbuf *buf, void *dataptr, ul6_t len, ul6 t offset; Funkcja pbuf_copyj?artial kopiuje dane z łańcucha buforów buf do bufora dataptr. K opiowanych jest len bajtów, ale nie więcej niż buf->tot_len. Argument'! o ffse t jest przesunięciem, od którego dane są kopiowane z łańcucha buforów,tj Funkcja ta zwraca liczbę skopiow anych bajtów lub zero, gdy wystąpił błąd. Struktura n e t i f Biblioteka lwip przechow uje konfigurację interfejsu sieciowego w strukturze typu netif, która jest zdefiniow ana w pliku netif. h. Struktura ta ma wiele pól. Niektóre $ pola są kom pilow ane warunkowo, zależnie od ustawień w plikach opt.h i lwiopts.h]$b Poglądow a definicja tej struktury, ale zupełnie w ystarczająca do zrozum ienia /as.i- $ dy działania sterow nika interfejsu sieciow ego, jest następująca: struct netif { struct netif *next; struct ip_addr ip_addr; struct ip_addr netmask; struct ip_addr gw; err_t {* input(struct pbuf *p, struct netif *inp; r(* err_t {* output(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr; '. T err_t (* linkoutput(struct netif *netif, struct pbuf *p; void *state; struct dhcp *dhcp; ul6_t mtu; J- u8_t hwaddr_len; " $, u8_t hwaddr [NETIF_MAX HWADDR_LEN ; u8_t flags; char name(2; v. u8_t num; l; Biblioteka lwip może obsługiw ać w iele interfejsów sieciowych. Struktury typu t i f opisujące kolejne interfejsy tw orzą listę. Składow a next wskazuje na następną strukturę na lis'cie i jest używana tylko wewnątrz biblioteki lwip. Składow e ip addr, netmask, gw zaw ierają odpow iednio adres IP, m askę podsieci i adres rutera. Dane te są zapisyw ane w sieciow ym porządku oktetów. Składowi te są inicjow ane w funkcji netif_add, której opis znajduje się w dalszej c rozdziału. Ponadto są też m odyfikow ane przez klienta DHCP, jeśli zostanie urucho- * miony. Składow a input zaw iera adres funkcji odbiorczej, którą sterow nik interfejsu sieciow ego w ywołuje, aby przekazać odebrany pakiet do stosu TCP/IP. Składowa l jest konfigurow ana za pom ocą funkcji netif_add. Pierwszym argum entem funkcji-'a* odbiorczej jest w skaźnik do struktury typu pbuf, która opisuje bufor zawiei odebrany pakiet. Drugim argum entem jest wskaźnik do struktury typu netif, opisującej interfejs sieciowy, który odebrał pakiet. Funkcja odbiorcza zależy od użył warstwy dostępu do sieci. Dla Ethernetu biblioteka lwip ma już gotową funkcję odbiorczą ethernet input, która jest zadeklarowana w pliku etharp.h, a jej im plem entacja znajduje się w pliku etharp.c. Funkcja ethernet_input analizuje nagłów ek ram ki ethernetowej i zajm uje się obsługą ARP, dlatego w tym przypadku bufor powinien zawierać ram kę ethernelową. Jeśli używamy warstwy dostępu do sieci innej niż Ethernet, to jako funkcji odbiorczej można użyć funkcji ip_input, która jest zadeklarowana w pliku ip.h, a jej im plem entacja znajduje się w pliku ip.c. Funkcja ip_input oczekuje w buforze datagram u IP i w tym przypadku to sterow nik interfejsu sieciow ego musi przeanalizow ać nagłów ek warstwy dostępu do sieci, a następnie usunąć go z odebranego pakietu. Składowej output należy podczas inicjow ania sterownika przypisać adres funkcji nadawczej, którą stos TCP/1P powinien wywołać, aby wysiać datagram IP. Dla Ethernetu należy użyć funkcji etharp_output, która obsługuje A RP i jest częścią biblioteki lwip. Funkcja ta jest zadeklarowana w pliku etharp.h, a jej im plem entacja jest w pliku etharp.c. Pierwszym argum entem funkcji nadawczej jest wskaźnik do struktury typu n etif, opisującej interfejs sieciowy, przez który ma być wystany datagram. D rugim argum entem jest w skaźnik do struktury typu pbuf, opisującej łańcuch buforów z datagram em IP do wysiania. Trzeci argument jest wskaźnikiem do.struktury zawierającej adres IP, na który należy wysiać datagram. Pamiętajmy, że datagram często jest wysyłany na inny adres niż ten umieszczony w jego nagłówku, np. na adres rutera. I Składowej linkoutput należy podczas inicjow ania sterow nika interfejsu sieciowego przypisać adres niskopoziom owej funkcji nadawczej, która pow inna być wywołana w celu wysiania pakietu przez ten interfejs. Ta niskopoziom owa funkcja musi być zaim plem entow ana w sterowniku i zwykle nazywa się low_level_output. Pierwszym jej argum entem jest w skaźnik do struktury typu netif, opisującej interfejs sieciowy, przez który ma być wysłany pakiet. Drugim argumentem jest w skaźnik do struktury typu pbuf, opisującej łańcuch buforów zawierający wysyłany pakiet, który musi zawierać nagłówki w szystkich warstw komunikacji. Funkcja ta nie pow inna m odyfikować wysyłanych danych. Składow a s t a t e jest przew idziana do w ykorzystania przez sterownik interfejsu sieciowego. Biblioteka lw IP jej nie czyta i nie modyfikuje. W tej składowej można przechow ywać adres struktury zaw ierającej pryw atne dane sterownika. Składow a dhcp zaw iera adres struktury w ykorzystywanej przez klienta DHCP zw iązanego z interfejsem sieciowym. Składow a mtu zaw iera M TU sieci. Składow a hwaddr_len zawiera długość w oktetach adresu sprzętowego. Składow a hwaddr zaw iera adres sprzętowy. Stała NETIF_ MAX_HWADDR_LEN ma wartość 6. Składow e te muszą być ustawione podczas inicjowania sterow nika interfejsu sieciowego. Składow a fla g s jest zbiorem jednobitow ych znaczników opisujących stan interfejsu sieciowego. Znaczniki zdefiniow ano w pliku n e tif h. Są one zamieszczone w ta b e li 3.5. Znaczniki należy m odyfikować za pom ocą operatora alternatywy logicznej, gdyż biblioteka lwip rów nież m odyfikuje niektóre z nich. - pili c
3.3. DMA 107 Tab. 3.5. Znaczniki opisujące stan interfejsu sieciowego Znacznik NETIF FLAG UP NETIF FLAG BROADCAST NETIF FLAG POINTTOPOINT NETIF FLAG DHCP NETIF FLAG LINK UP NETIF FLAG ETHARP NETIF FLAG IGMP Opis Warstwa sieciowa jest aktywna - interfejs przetwarza pakiety Ten interfejs może wysyłać pakiety rozgloszeniowe Jest to interfejs typu punkt-punkt Dla tego interfejsu został skonfigurowany klient DHCP Warstwa dostępu do sieci jest aktywna Jest to interfejs ethernetowy używający ARP Ten interfejs używa IGMP Składow a name zawiera dw uznakow ą nazwę interfejsu sieciowego. Nazwa pozwala-1; w yszukiwać interfejs za pom ocą funkcji n e tif _ f in d. O pcja ta jest przydatna, gdy.', jest wiele interfejsów sieciowych. Składow a num zaw iera num er interfejsu sieciowe- " go. Pozwala to rozróżniać interfejsy o tej samej nazwie. Składow a nura jak dotąd nie : jest używana w ewnątrz biblioteki lwip. DMA Do obsługi interfejsu ethernetow ego przew idziano w m ikrokontrolerze STM32F107 układ DM A, który używa dw óch cyklicznych kolejek buforów. DM A kopiuje odebrane przez układ M AC ramki do kolejki odbiorczej, gdzie czekają na obsłużenie % przez aplikację. A plikacja w staw ia ramki do kolejki nadawczej, gdzie czekają na *ł.wf tttt skopiowanie ich przez DM A do układu M AC. Kolejki zorganizowane są za pomocą deskryptorów DM A. D eskryptor ma dw ie składowe, które przechow ują adresy. i f Kolejka buforów może tworzyć łańcuch (ang. chain, jak widać to na ry su n k u 3.1. Jeden z adresów jest wtedy używany do w skazania bufora w pamięci, a drugi wskazuje następny deskryptor w kolejce. M ożna też użyć tablicy deskryptorów, jak na ry su n k u 3.2, Ten w ariant nazyw any jest w dokum entacji pierścieniem (ang. ring. W tedy oba adresy w deskryptorze m ogą wskazywać bufory w pam ięci, a o kolejności deskryptorów w kolejce decyduje indeks w tablicy. Aby uzyskać zapętlenie, po ostatnim deskryptorze następuje pierwszy - o zerow ym indeksie. Kolejki działają według zasady: pierwszy zgłoszony - pierwszy obsłużony (ang. first in, first out. Deskryptor 0 Deskryptor 1 Deskryptor n - Rys. 3.1. Łańcuch deskryptorów Bufor Bufor Bufor Deskryptor 0 Deskryptor 1 Deskryptor n-1 Rys. 3.2. Pierścień deskryptorów Bufor 1 Bufor 2 Bufor 1 Bufor 2 Bufor 1 Bufor 2 Jeśli ram ka nie mieści się w pojedynczym buforze, m oże być um ieszczona w kilku kolejnych buforach, w skazywanych przez kolejne deskryptory. W trybie pierścieniowym każdy deskryptor może w skazywać na dw a kolejne bufory. Zastosowany w m ikrokontrolerze STM 32F107 układ DM A nie może obsługiwać jednocześnie obu kolejek. Jeśli w kolejce nadawczej czekają ramki do wysłania i rów nocześnie są odbierane ram ki, które trzeba skopiować do kolejki odbiorczej, potrzebny jest arbitraż. Przew idziano dwa w arianty arbitrażu: - stosowany jest karuzelowy algorytm szeregow ania (ang. round robin scheduling algorithm ; - zaw sze preferowana jest kolejka odbiorcza, ram ki są kopiow ane z buforów nadawczych do M A C tylko wtedy, gdy żadna odebrana ram ka nie czeka na skopiow anie do bufora odbiorczego. Tab. 3.6. Składowa sta tu s deskryptora odbiorczego Pola OWN status O co Bity 31 O FL 2 9...1 6 ES 15 FS 9 LS 8 Opis Wartość 1 oznacza, że DMA jest właścicielem (ang. own deskryptora. DMA zeruje ten bit, gdy zakończy kopiowanie odebranej ramki i wypełni bufor lub bufory wskazywane przez ten deskryptor. Program ustawia ten bit, aby zwrócić deskryptor odbiorczy układowi DMA Jest to pole statusu, wypełniane przez DMA po zakończeniu kopiowania. Najważniejsze bity tego pola opisane są w następnych wierszach Całkowita długość w bajtach odebranej i skopiowanej ramki (ang. tramę length. To pole jest istotne tylko dla deskryptora, który ma wyzerowany bit e s i ustawiony bit ls Wartość 0 oznacza, że kopiowanie zakończyło się poprawnie. Wartość 1 oznacza, że wystąpił błąd (ang. error summary. Dokładny opis błędu zawierają inne bity pola status Wartość 1 oznacza, że jest to pierwszy w kolejce deskryptor opisujący odebraną ramkę. Innymi słowy, ten deskryptor wskazuje na bufor zawierający pierwszy fragment ramki (ang. lirst segment Wartość 1 oznacza, że jest to ostatni w kolejce deskryptor opisujący odebraną ramkę. Innymi słowy, ten deskryptor wskazuje na bufor zawierający ostatni fragment ramki (ang. last segment Tab. 3.7. Składowa controlbuffersize deskryptora odbiorczego Polo Biły Opis DIC 31 RBS2 Ustawienie tego bitu blokuje zgłaszanie przerwania po odebraniu ramki (ang. disable interrupt on completion. Gdy jest wyzerowany, przerwanie jest zgłaszane po odebraniu i skopiowaniu ramki 30...29 Zarezerwowane CO CO RER 15 RCH 14 RBSł 1 2...0 CD Gdy bit r c h ma wartość 0, pole to zawiera długość w bajtach bufora (ang. receive buffer 2 size wskazywanego przez składową Bu fer2nextdescaddr. Wartość w tym polu musi być wielokrotnością 4. Gdy bit r c h ma wartość 1, pole to nie jest używane Gdy bit r c h ma wartość 0, to wartość 1 w tym polu oznacza, że jest to ostatni deskryptor w pierścieniu (ang. receive end o! ring Wartość 0 oznacza, że ten deskryptor jest skonfigurowany w trybie pierścieniowym. Wartość 1 oznacza, że ten deskryptor jest skonfigurowany w trybie łańcuchowym (ang. receive chain, a składowa Bu fer2nextdescaddr zawiera adres następnego deskryptora 13 Zarezerwowane Zawiera długość w bajtach bufora (ang. receive buffer 1 size wskazywanego przez składową Buf feriaddr. Wartość w tym polu musi być wielokrotnością 4
3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 109 Jeśli stosujem y arbitraż karuzelowy, to można dodatkow o ustawić w spółczynnik określający, ile razy częściej jest w ybierana kolejka odbiorcza w stosunku do koji lejki nadawczej. W spółczynnik ten może mieć jedną z czterech wartości: 1 do 1, ' Ą do 1, 3 do 1, 4 do 1. typecief struct { uint32_t Status; uint32_t ControlBufferSize; uint32_t BufferlAddr; uint32_t Buffer2NextDescAddr; } E?H DMADESCTypeDef; Tab. 3.8. Składowa status deskryptora nadawczego Polo Bity Opis OWN 31 IC 30 LS 29 FS 28 DC 27 DP 26 TTSE 25 CIC 2 3...2 2 TER 21 TCH 20 TTSS 17 status 1 6...0 ES 15 Wartość 1 oznacza, że DMA jest właścicielem (ang. own deskryptora. DMA zeruje ten bit, gdy zakończy t kopiowanie z bufora lub buforów wskazywanych przez ten deskryptor. Program ustawia ten bit, aby zasygnalizować układowi DMA, że deskryptor wskazuje na dane, które są gotowe do wysiania Wartość 1 oznacza, że po zakończeniu transmisji ramki, opisanej tym deskryptorem, zostanie zgłoszone przerwanie (ang. interrupt on completion Wartość 1 oznacza, że jest to ostatni w kolejce deskryptor opisujący ramką do wysiania, Innymi słowy, ten' deskryptor wskazuje na bufor zawierający osfafni fragment ramki (ang. iast segment Wartość 1 oznacza, że jest to pierwszy w kolejce deskryptor opisujący ramkę do wysiania. Innymi stówy, ten deskryptor wskazuje na bufor zawierający pierwszy fragment ramki (ang. //rei segment Wartość 1 blokuje automatyczne dołączanie przez MAC sekwencji kontrolnej (ang. disable CRC do wysyłanej ramki. Ten bit jest istotny tylko dla deskryptora, w którym bit fs m a wartość 1 Wartość 1 blokuje automatyczne uzupełnianie przez MAC wysyłanej ramki do minimalnej długości (ang. disable pad. Ten bit jest istotny tylko dla deskryptora, w którym bit f s ma wartość 1 Wartość 1 aktywuje dla wysyłanej ramki, opisanej tym deskryptorem, znacznik czasu zgodny z normą IEEE 1588 (ang. transmit time stamp enable. Ten bit jest istotny tylko dla deskryptora, w którym bit fs ma wartość 1 24 Zarezerwowane To pole steruje wstawianiem sum kontrolnych (ang. cliecksum insertion contro1 w protokołach intersieci: 00 - sumy kontrolne nie są wstawiane, 01 - wstawiana jest tylko suma kontrolna nagłówka datagramu IR 10 - wstawiane są suma kontrolna nagłówka datagramu IP oraz sumy kontrolne ICMR TCP i UDR ale suma kontrolna pseudonaglówka nie jest wyliczana sprzętowo, 11 - wstawiane są suma kontrolna nagłówka datagramu IP oraz sumy kontrolne ICMR TCP I UDR suma kontrolna pseudonaglówka jest wyliczana sprzętowo Gdy bit TCH ma wartość 0, to wartość 1 w tym polu oznacza, że jest to ostatni deskryptor w pierścieniu (ang. transmit end o1 ring Wartość 0 oznacza, że ten deskryptor jest skonfigurowany w trybie pierścieniowym. Wartość 1 oznacza, że ten deskryptor jest skonfigurowany w trybie łańcuchowym (ang. transmit Chain, a składowa Buf fer2nextdescaddr zawiera adres następnego deskryptora 19...18 Zarezerwowane Wartość 1 oznacza, że składowe BufferlAddr i Buf fer2nextdescaddr zawierają znacznik czasu wystanej ramki, opisanej tym deskryptorem, zgodny z IEEE 1588 (ang. transmit time stamp status. Ten bit jest istotny tylko dla deskryptora, w którym bit l s ma wartość 1 Jest to pole statusu, wypełniane przez DMA po zakończeniu transmisji. Najważniejszy bit tego pola jest opisany w następnym wierszu Wartość 0 oznacza, że transmisja przebiegła poprawnie. Wartość 1 oznacza, że wystąpił błąd (ang. error summary. Dokładny opis błędu zawierają pozostałe biiy pola status % A ' X* i i Tab. 3.9. Składowa controibuffersize deskryptora nadawczego Pole Bity Opis 31...29 Zarezerwowane TBS2 2 8...16 Jeśli pole TCH składowej status ma wartość 0, pole to zawiera długość w bajtach drugiego bufora (ang. transmit butler 2 size, wskazywanego przez składową Buffer2NextDescAddr. Jeśli pole t c h składowej status ma wartość 1, pole to nie jest używane TBS1 12...0 15...13 Zarezerwowane Długość w bajtach butora wskazywanego przez składową BufferlAddr (ang. transmit bul/er 1 size Deskryptor zajm uje 128 bitów. W bibliotece STM 32, w pliku stin32_elh.li zdefiniowano go jako strukturę ETH_DMADESCTypeDef składającą się z czterech słów 32-bilowych. Składow a BufferlAddr struktury typu ETH_DMADESCTypeDef zawiera adres pierwszego bufora. Składow a Buffer2NextDescAddr w trybie łańcuchowym zaw iera adres następnego deskryptora, a w trybie pierścieniowym - adres drugiego bufora. Poszczególne bity dwóch pierwszych składowych dla deskryptora odbiorczego są om ów ione w tabelach 3.6 i 3.7. Opisy tych składowych dla deskryptora nadaw czego zam ieszczone są w tabelach 3.8 i 3.9. Bity w tabelach num erowane są typow o dla procesora cienkokońcówkow ego, czyli od najmniej znaczącego do najbardziej znaczącego. N ajstarszy bit składowej Status wyznacza właściciela deskryptora. Gdy w łaścicielem deskryptora jest DM A, programowi nie wolno go m odyfikować i nie powinien też go czytać. Bardziej szczegółowyopis deskryptorów m ożna znaleźć w 1.8]. O mówienie znaczników czasu i normy IEEE 1588 w ykracza poza zakres tej książki. Sumy kontrolne i pseudonaglówki U DP i TCP są opisane odpow iednio w RFC 768 i RFC 793. Suma kontrolna ICM P jest opisana w RFC 792. 3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu Podstawow ym celem tego przykładu jest napisanie sterownika pośredniczącego między biblioteką lwip a m ikrokontrolerem. Nazwy plików, które zawierają implem entację przykładu, zam ieszczone są w tabeli 3.10. Sterownik prezentowany w tym przykładzie jest pierwszym z trzech, które opisuję. Stosuje on najprostsze podejście, polegające na alokowaniu statycznych buforów odbiorczych i nadaw czych. DM A kopiuje odebraną ram kę ethernetow ą do takiego bufora odbiorczego. Następnie sterownik kopiuje odebraną ram kę z bufora odbiorczego do bufora biblioteki lwip. W ysyłaną ram kę sterow nik kopiuje z bufora biblioteki lwip do bufora nadawczego, z którego DMA kopiuje ją do układu MAC. 3.4.1. Pliki u tiljim e.h i u tiljim e.c Protokoły sieciow e wym agają odm ierzania czasu. Jest to potrzebne na przykład po to, aby nie czekać w nieskończoność na odpowiedź. Biblioteka lwip jest sterow ana zdarzeniami. Jednym ze zdarzeń jest odebranie pakietu sieciowego. Innym rodzajeni zdarzenia jest upłynięcie czasu. Biblioteka lwip potrzebuje zegara, który będzie zgłaszał cykliczne zdarzenia. Jednak im plem entacja takiego zegara jest
110 3. Stos TCP/lfiI 3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 111 Tab. 3.10. Pliki przykładu 3a Źródłowe i biblioteczne exjp.c board conf.c util delay, c Iiblwip4.a font5x8.c board init.c util eth.c Iibstm32l10x.a startup _stm32 cld. c board_lcd_ks0108.c boardjed. c utiljed. c util Jed_ex,c util Jed. c util Iwip.c util time.c Maylówkowe lont5x8.h board conf.h util delay.h cc.h board def.h util eth.h cortex-m3.li board deis.h util led.h Iwipopts.h board init.h util led ex.h stm 32/10x conf.h b o ard jed.h board jed.h u tiljed.h ulhjwip.h util time.h bardzo zależna od sprzętu. W rdzeniach Cortex-M 3 przew idziano do tego celu 24- ; -bitowy licznik system owy (ang. system timer, nazywany w dokum entacji SysTick.. W ykorzystam y ten licznik do cyklicznego zgłaszania przerwania, które będzie sygnalizowało bibliotece lwip upływ czasu. W tym podrozdziale opisuję moduł ob- sługujący SysTick. Jest on napisany na tyle ogólnie, że może być w ykorzystany nie'; tylko z biblioteką lwip. W pliku nagłówkowym util_tim e.h definiujem y częstotliwos'ć zegara biblioteki lwip w Hz i jego okres tykania w milisekundach. Lcjkalny czas będziemy mierzyć,' w m ilisekundach za pom ocą 64-bitow ych liczb bez znaku. W praktyce pozwala to;., odm ierzać dow olnie długie odstępy czasu. Zm ienne przechow ujące lokalny czas; będą typu ltim e_t. Ponadto definiujem y typ time_callback_t funkcji zwrotnej,;, którą będzie można skonfigurow ać, aby była cyklicznie wywołana w procedurze, obsługi przerwania zegara system ow ego. Podana funkcja zwrotna będzie generowa-; la właściwe zdarzenia czasowe. D efinicje te są następujące: ( define SYSTICK_FREQUENCY 100 łdefine SYSTICK_PERIOD_MS (1000 / SYSTICK_FREQUENCY typedef unsigned long long ltime_t; typedef void (*time_callback_t(void; W pliku utiljtim e.c deklarujem y dw ie zm ienne globalne. Zm ienna localtime od-: mierzą czas od uruchom ienia mikrokontrolera. Zm ienna timecallback zawiera adres funkcji zwrotnej. Adres o wartości zero oznacza, że żadnej funkcji zwrotnej nie skonfigurow ano. Zmienne te są zadeklarow ane jako s t a t i c, aby nie były widoczne na zew nątrz modułu obsługującego SysTick. Zm ienna localtime jest ponadto zadeklarowana jako ulotna (ang. volatile, gdyż jest m odyfikow ana w procedurze obsługi przerwania. static volatile ltime_t localtime» 0; static time callback t timecallback = 0; Procedura obsługująca przerwanie licznika system ow ego nazywa się SysTick Handler. W tej procedurze zw iększam y czas, który upłynął od uruchom ienia mikrokontrolera i jeśli skonfigurow ano funkcję zwrotną, w ywołujem y ją. void SysTickJtandler(void ( localtime += SYSTICK_PERIOD_MS; if (timecallback timecallback(; I Licznik system owy może być taktowany z tą sam ą częstotliw ością co rdzeń lub przez dzielnik z częstotliw ością 8 razy m niejszą. Licznik ten zlicza w dół do zera, po czym jest przeładowyw any w artością początkową. Po osiągnięciu zera może zgłosić przerwanie. A by go skonfigurow ać, najw ygodniej jest użyć funkcji SysTick_Conf ig z biblioteki STM 32. Jako argum ent należy podać okres, który jest równy wartości początkowej licznika zw iększonej o jeden. Funkcja SysTick_Config aktywuje przerwanie licznika system ow ego i ustawia jakiś priorytet, który trzeba zm ienić zgodnie z naszym w ymaganiem. Licznikowi system ow em u przypisujem y wysoki priorytet, aby czas biegi rów nież podczas obsługi przerwań o niższych priorytetach. Funkcja RCC_GetClocksFreq z biblioteki STM 32 oblicza częstotliw ości sygnałów, którymi taktowany jest m ikrokontroler (sygnał SY SCLK, rdzeń (sygnał HCLK i peryferie (sygnały PCLK1, PCLK2, A D CCLK i um ieszcza ich wartości w strukturze typu RCC ClocksTypeDef. Częstotliwości te są w yrażone w hercach. Konfigurowanie licznika system ow ego im plem entujem y w funkcji LocalTimeConfigure. int LocalTimeConfigure(void I RCC_ClocksTypeDef RCC_Clocks; RCC_GetClocksFreq(&RCCClocks; if (SysTick Config(RCC_Clocks.HCLK_Frequency / SYSTICK_FREQUENCY return -1; SET_PRIORITY(SysTick_IRQn, HIGH_IRQ_PRIO; return 0; } Funkcję zw rotną konfigurujem y za pom ocą funkcji TiraerCallBack. M ożna ją skonfigurować w dow olnym m om encie, dlatego odbyw a się to przy wyłączonych przerwaniach. Podanie zerow ego adresu pow oduje usunięcie funkcji zwrotnej. void TimerCallBack(time_callback_t f ( IRQ_DECL_PROTECT(xi IRQ_PROTECT(x, HIGH_IRQ_PRIO; timecallback = Ł; IRQJJNPROTECT(X; } M oduł obsługujący licznik system ow y pow inien udostępniać wartość czasu, który upłynął od uruchom ienia m ikrokontrolera. B ezpośrednie udostępnienie globalnej zmiennej localtime jest bardzo złym pom ysłem. Lepiej jest zaim plem entować w tym celu funkcję, którą nazwiem y LocalTime. Poniew aż zmienna localtime jest 64-bitow a, jej odczyt nie jest atomowy. Aby zapew nić odczytanie spójnej wartości, odczyt odbyw a się przy zablokow anym przerwaniu SysTick. W ymaga to zadeklarow ania zmiennej tym czasowej i zadeklarow ania wartości zwracanej przez funkcję jako ulotnej, żeby kompilator, optym alizując, nie usunął tej zmiennej tym czasowej. volatile ltime_t LocalTime(void ( ltime_t t; IRQ DECL PROTECT(x;
112 3. Stos TCP/ip IRQ_PROTECT(x, HIGH_IRQ_PRIO ; t = looaltime; IRQJJNPROTECT(x; return t; I Funkcja LocalTime zwraca czas w m ilisekundach. Dla wygody dobrze jest też mieć funkcję, która zwraca czas w bardziej przyjaznym formacie. W module obsługującym licznik system owy im plem entujem y funkcję GetLocalTime, która podaje czas od uruchom ienia m ikrokontrolera w dniach, godzinach, minutach, sekundach i milisekundach. Jako argum enty należy podać wskaźniki do zmiennych, w których mają być zapisane te wartości. void GetLocalTime(unsigned *day, unsigned hour, unsigned minute, unsigned second, unsigned milisecond; 3.4.2. Pliki u tiljeth.h i utu eth.c - inicjowanie interfejsu sieciowego Pliki opisane w tym i kolejnych dw óch podrozdziałach zaw ierają właściwą implementację sterow nika interfejsu ethernetow ego. W tym podrozdziale opisuję inicjowanie interfejsu. W pliku util_eth.h zdefiniow any jest adres układu PHY oraz struktura typu e th n e t if, która przechow uje ten adres i statystyki działania interfejsu Układ PHY w module Z L3ETII ma adres 1. Statystyki zawierają: - RX_packets - liczba prób w ysiania ramki, - TX_packets - liczba wszystkich odebranych ramek, - R X _errors - liczba nieudanych prób w ysiania ramki, - TX_errors - liczba odebranych ram ek, które okazały się błędne. idefine PHYJtDDRESS 1 struct ethnetif f uint8 t phyaddress; unsigned RX_packets; unsigned TX_packets; unsigned RX_errors; unsigned TX_errors; i; W pliku util_eth.h zadeklarowana jest też funkcja ETHinit, która wykonuje niskopoziom owe inicjowanie interfejsu ethernetowego. Adres tej funkcji jest przekazywany jako argument wspomnianej ju ż funkcji netif add biblioteki IwIP. Funkcja ETHinit ma jeden argument. Jest to wskaźnik do struktury typu n e tif opisującej inicjowany interfejs. Przekazywana struktura n e tif musi mieć w składowej hwaddr adres sprzętowy interfejsu. Ponadto składowa s ta te musi zawierać wskaźnik do struktury typu eth n etif związanej z tym interfejsem. Ta struktura eth n etif musi mieć zainicjowaną składową phyaddress. Składowa s ta te struktury n e tif jest inicjowana w funkcji netif_add. Gdy inicjowanie interfejsu się powiodło, funkcja ETHinit zwraca wartość ERR_OK. W przeciwnym przypadku funkcja ta zwraca kod błędu wg tabeli 3.4. Im plem entacja funkcji ETHinit znajduje się w pliku util_eth.c. err t ETHinit (struct netif netif { struct ethnetif ethnetif = netif->state; if (ETHconfigurePHY(ethnetif->phyAddress < 0 return ERR IF;.» 3.4. Przykład 3ct - pierwsza wersja sterownika Ethernetu 113 ETHconfigureMAC(netif; ETHconfigureDMA{netif, LWIP_IRQ_PRIO, 0; netif->name(0] =,s'; netif->name(ł] «, t'; netif->output = etharp_output; netif->linkoutput = low_level_output; ethnetif->rx_packets = 0; ethnetif->tx_packets = 0; ethnetif~>rx_errors = 0; ethnetif->tx_errors = 0; ETH_Start(; netif->flags i= NETIF_FLAG_LINK_UP; return ERR_OK; 1 W łaściwe inicjow anie zostało podzielone pomiędzy, opisane niżej, funkcje ETHconfigurePHY, ETHconfigureMAC, ETHconfigureDMA. Każda z tych funkcji, zgodnie ze sw oją nazwą, konfiguruje odpow iedni podukład interfejsu ethernetowego. Po skonfigurow aniu tych podukładów inicjow ane są składowe struktur typu n e tif i eth netif. Składowej output przypisujem y adres funkcji etharp_output, która jest dostarczana przez bibliotekę IwIP, zajm uje się pakow aniem datagramów IP w ram ki ethernetow e i obsługą ARP. Jeśli przed wysłaniem pakietu sieciowego trzeba wykonać jakieś działania, np. sprawdzić stan łącza, to tej składowej można przypisać adres w łasnej funkcji, która w ykonuje to zadanie, a następnie wywołuje funkcję etharp output. Składowej linkoutput przypisujem y adres niskopoziomowej funkcji low_level_output, która zajm uje się w ysyłaniem gotowych ramek elhernetow ych i jest opisana w następnym podrozdziale. Na koniec wywołujemy funkcję ETH_Start, która jest dostarczana przez bibliotekę STM 32 i włącza interfejs ethernetowy. Funkcja ETHconfigurePHY zajm uje się przede w szystkim konfigurowaniem układu PHY, ale też, z uwagi na strukturę biblioteki STM 32, konfiguruje niektóre ustawienia układów M A C i DMA. Argum entem tej funkcji jest adres układu PHY. Funkcja ta zw raca zero, gdy zakończyła się sukcesem, a wartość ujem ną w przeciwnym przypadku. Konfigurowanie może zakończyć się niepowodzeniem, jeśli nie uda się skontaktow ać z układem PHY. static int ETHconfigurePHY(uint8_t phyaddress { ETH_InitTypeDef e; ETłi_Struct2nit (Se; e.eth AutoNegotiation = ETH_AutoNegotiation_Enable; e.eth_broadcastframesreception = ETH BroadcastFramesReception_Enable; e.eth ReceiveOwn = ETH_ReceiveOwn_Disable; U f CHECKSUM_GEN IP == 0 e.eth ChecksuroOffload = ETJ!_ChecksumOffload_Enable; e.eth DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Disabłe; iendif e.eth_secondframeoperate = ETH_SecondFrameOperate_Enable; e.eth_fixędburst = ETH_FixedBurst_Enabłe; e.eth_rxdmaburstlength = ETll_RxDMABurstLength_32Beat; e.eth TxDMABurstLength = ETH_TxDMABurstLength 32Beat;
114 3. Stos TCP/ip 3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 115 if (!ETH_Init(&e, phyaddress return -1; #if ETH BOARD == ZL3ETH Idefine PHYCR 0x19 Sdefine LED_CNFG0 0x0020 idefine LED_CNFG1 0x0040 uintl6_t phyreg; phyreg = ETH_ReadPHYRegister(phyAddress, PHYCR; phyreg &= - (LED CNFG0 I LED_CUFG1; ETH_WritePHYRegister(phyAddress, PHYCR, phyreg; lendif return 0; I Aby ułatwić konfigurow anie interfejsu ethernetow ego, w bibliotece STM 32 zdefi-j niow ano strukturę ETH_InitTypeDef oraz funkcję ETH_StructInit, która inicjuje^ tę strukturę wartościam i dom yślnym i. W iększości ustawień dom yślnych nie trzeba/jjj zm ieniać. W arto natom iast ustawić autom atyczne negocjow anie trybu pracy inter- >. fejsu ethernetow ego. Jeśli autom atyczne negocjow anie nie działa, m ożna spróbo-f wać ustawień opisanych w rozdziale 2.6.3. Na pew no też należy uaktyw nić odbić-1 ranie ram ek rozgloszeniow ych. G dy zam ierzam y używać sprzętow ego wsparcia M dla sum kontrolnych (ang. checksum offload protokołów intersieci, trzeba uaktyw- -; nić tryb zapisz i wyślij (ang. store and forw ard dla w ysyłania i odbierania. Wtedy.fi cala ram ka jest grom adzona w FIFO, zanim zostanie przesiana, aby m ożna bylo'i * w staw ić lub zw eryfikow ać sum ę kontrolną. Tryb zapisz i wyślij jest ustawiany! dom yślnie przez funkcję ETH_StructInit. D om yślhie też ustaw iany jest karuzc- $ Iowy algorytm szeregow ania kolejek nadawczej i odbiorczej ze współczynnikiem 1 do 1. W funkcji ETHconfigurePHY ostatnie cztery przypisania do pól struktu-.-w ry e dotyczą DMA. Ich celem jest zoptym alizow anie działania transakcji DMA. W Aby skonfigurow ać interfejs, w yw ołana jest funkcja ETH_Init z biblioteki STM 32/'-, Pierwszym jej argum entem jest w skaźnik do struktury e, a drugim argum entem ; jest adres układu PHY. Na zakończenie funkcji ETHconfigurePHY konfigurujem y ustaw ienia zależne od r', użytego układu PHY. Każdy układ PH Y ma rejestry konfiguracyjne, pozwalaj; dostosować jego ustawienia do potrzeb konkretnej aplikacji. D ostęp do rejestrów! układu PHY um ożliw iają funkcje ETH_ReadPHYRegister i ETH_WritePHYRegister z biblioteki STM 32. W module ZL3ETI-I zastosow ano układ D P83848C oraz gniaz- j>: do RJ-45 z dw om a diodam i św iecącym i. Po szczegóły odsyłam do schem atu modu- f. łu ZL3ETH i noty katalogowej układu DP83848C. Jako przykład ilustrujący użycie d rejestrów układu PIIY zmieńmy konfigurację tych diody świecących. Wyzerowanie bitu LED_CNFG0 i bitu LED_CNFG1 w rejestrze PHYCR (ang., P H Y contro! register 'A. powoduje, że: - zielona dioda sygnalizuje status łącza (ang. link status: świeci się, gdy łącze pracuje popraw nie (ang. good link, nie świeci się, gdy w kablu nie ma właściwego sygnału lub kabel jest odł; ny (ang. no link, miga, gdy transm itow ane są ram ki (ang. activity: - pom arańczowa dioda sygnalizuje przepływność (ang. speed: świeci się, gdy jest to 100 Mb/s, * nie świeci się, gdy jest to 10 Mb/s. Funkcja ETHconfigureMAC konfiguruje adres sprzętowy M AC, M TU sieci i znaczniki. Należy zadbać, aby w artość, opisanej wcześniej, stałej TCP_MSS, zdefiniowanej w pliku Iwipopts.h, była spójna z w artością M TU ustawianą przez tę funkcję. Stała MAX_ETH_PAYLOAD jest zdefiniow ana w pliku stm 32_eth.h i ma wartość 1500. Stała ETHARP_HWADDR_LEN jest zdefiniow ana w pliku etharp.h i ma wartość 6. static void ETHconfigureMAC{struct netif *netif { netif->hwaddr_len = ETHARP_HWADDR_LEN; neti ->mtu = MAX_ETH_PAYLOAD; neti ->flags = NETIF_FLAG_BROADCAST NETIF_FLAG_ETHARP; ETH_MACAddressConfig(ETH_MAC_AddressO, netif->hwaddr; i Ostatnim układem do skonfigurow ania jest DM A. Trzeba określić, jaką liczbą buforów nadawczych i odbiorczych będzie dysponow ać DM A. Poniższe wartości są w ybrane dość swobodnie, przy założeniu, że przykładow e aplikacje nie generują dużego ruchu sieciowego. łdefine ETH_RXBUFNB 4 {(define ETH_TXBUFNB 4 DM A będzie zgłaszało przerwanie po odebraniu ramki. Procedura przerwania musi mieć dostęp do struktury typu n etif, opisującej interfejs sieciowy. N ajprostszym rozw iązaniem jest zadeklarowanie globalnego wskaźnika, który będzie przechow y wał adres tej struktury. static struct netif *p_netif; DMA konfigurujem y za pom ocą funkcji ETHconfigureDMA. Pierwszym jej argumentem jest wskaźnik do struktury typu netif. Drugim i trzecim argum entem są odpow iednio priorytet w yw łaszczania i podpriorytet, z jakim i ma być obsługiwane przerwanie DMA. static void ETHconfigureDMA(struct netif *netif, uint8_t priorityi uint8_t subpriority ( static ETH_DMADESCTypeDef DMARxDscrTab[ETII_RXBUFNB] ALIGN4; static ETH_DMADESCTypeDef DMATxDscrl'ab[ETH_TXBUFNB ALIGN4; static uint8_t RxBuff{ETH_RXBUFNBJ{ETH_MAXJ>ACKET_SIZE] ALIGN4; static uint8_t TxBuff ETH_TXBUFNB[ETH_MAX_PACKET_SIZE1 ALIGN4; ETH_DMARxDescChainInit(DMARxDscrTab, SRxBuff[0[0], ETH_RXBUFNB; ETH_DMATxDescChainInit(DMATxDscrTab, STxBuff(OJ[OJ, ETHJFXBUFNBi NVIC InitTypeDef NVIC_InitStruct; int i; p netif = netif; MVIC_InitStruct.NVIC_IRQChannel = ETH_IRQn; NVIC InitStruct.NVIC_IRQChannelPreemptionPriority = priority;
3.4. Pnyldad 3a - pierwsza wersja sterownika Ethernetu 117 NVIC_InitStruct.NVIC_IRQChannelSubPriority = subpriority; NVIC_InitStruct.NVIC_IRQChanneiCmd * ENABLE; NVIC_Init(SNVIC_InitStruct; ETH_DMAITConfig(ETH_DMA_IT_NIS! ETH_DMA_IT_R, ENABLE; for (i = 0; i < ETH_RXBUFNB; ++i ETH DMARxDescReceiveITConfig (&DMARxDscrTab{i], ENABLE; iif CHECKSUM_GEN IP == 0 for (i = 0; i < ETH_TXBUFNB; ++i ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab(i, ETH_DMATxDesc_CIC_IPV4HeaderJ; iendif i Na początku funkcji ETHconfigureDMA deklarujem y deskryptory oraz bufory o biorcze i nadawcze. Później za pom ocą funkcji ETH_DMARxDescChainInit z 1 blioteki STM 32 konfigurujem y łańcuch deskryptorów odbiorczych, a za ponio funkcji ETH_DMATxDescChainInit łańcuch deskryptorów nadawczych. Pierwszy argum entem tych funkcji jest tablica deskryptorów, choć w trybie łańcuchowy deskryptory nie m uszą zajm ować ciągłego obszaru pamięci. Używając tych fun cji, nie m ożem y zm ienić rozm iaru buforów, gdyż obie funkcje jak o drugi argumeijl oczekują adresu ciągłego obszaru pam ięci o rozm iarze ETH_MAX_PACKET_SIZE ra/\ liczba buforów. Pow iększenie rozm iaru buforów jest konieczne, jeśli chcielibyśmy używ ać ram ek VLAN, gdyż stała ETH_MAX_PACKET_SIZE, zdefiniow ana w pliku stm32_eth.h, ma wartość 1520, co nie uwzględnia dodatkow ych pól w ramkach'' V LAN. Trzecim argum entem tych funkcji jest liczba deskryptorów w łańcuchu, bęv dąca zarazem liczbą buforów. Jak widać, funkcje z biblioteki STM 32 nie pozwalają na dow olne konfigurow anie łańcucha deskryptorów. N astępnie w funkcji ETHconfigureDMA konfigurujem y przerwanie DM A. Wszystkie] źródła przerwań zw iązane z interfejsem ethernetow ym zgłaszają przerwanie. 1 IRQn. Przerwanie ustawiamy tak, aby było zgłaszane, gdy DM A odbierze ramkę i skopiuje ją do swojego bufora odbiorczego. W procedurze obsługi tego przerwania ram ka będzie kopiowana z bufora DM A do bufora biblioteki lwip, po czym będ zię; w ywołana funkcja biblioteki lwip przetwarzająca tę ramkę. Najpierw inicjujemy,1 globalny wskaźnik p_netif, aby procedura obsługi miała dostęp do struktury typy netif. Następnie korzystamy z funkcji biblioteki STM32: - konfigurujem y priorytet przerw ania i uaktywniam y go za pom ocą funkcji NY ; In it; - ustawiamy źródło przerw ania za pom ocą funkcji ETH_DMAITConfig, ustawieni^ bitu ETH_DMA_IT_R aktyw uje przerw anie odbiorcze (ang. receive interrupt, usta* w ienie bilu ETH_DMA_IT_NIS aktyw uje podstawow e przerwania DMA (ang. norm al interrupt sum m ary; - aktyw ujem y zgłaszanie przerwania (zerujem y bit DIC w deskryptorach odbior czych za pom ocą funkcji ETH_DMARxDescReceiveITConfig. Na koniec funkcji ETHconfigureDMA, jeśli została skonfigurow ana opcja sprzęt, w ego w stawiania sum kontrolnych w nagłówkach datagram ów IP, trzeba ustawić tę( opcję we w szystkich deskryptorach nadawczych za pom ocą funkcji ETH_DMATxDt ChecksumlnsertionConfig. Plik u tilje th.c - wysyłanie ramek ethernetowych Aby wysłać ram kę, trzeba znać adres pierwszego w olnego deskryptora w kolejce nadawczej DM A i wstawić tę ramkę do tej kolejki. W bibliotece STM 32 w pliku stm 32_eth.c zdefiniow ano w tym celu zm ienną DMATxDescToSet, która wskazuje bieżący, pierwszy wolny deskryptor nadawczy. D ostęp do tej zmiennej zapewnia poniższa deklaracja. extern ETH_DMADESCTypeDef *DMATxDescToSet; W stawianiem ram ek ethernetow ych do kolejki nadawczej zajm uje się funkcja łow_ level_output, którą musimy zaim plem entować. Pierwszym argumentem tej funkcji jest wskaźnik do struktury typu n e tif opisującej interfejs sieciowy, przez który ma być wysiana ramka. Drugim argum entem jest wskaźnik do łańcucha buforów zaw ierających ramkę do wysiania. Funkcja ta zwraca wartość ERR_0K, gdy zakończyła się powodzeniem, a w przeciwnym przypadku właściwy kod błędu. static err_t low level_output (struct netif *netif, struct pbuf *p { ((struct ethnetif *netif->state->tx_packets++; if (p == NULL ((struct ethnetif* netif->state ->TX_errors-H-; return ERR_ARG; 1 if (p->tot len > ETH MAX_PACKET_SIZE - ETH_CRC ( ((struct ethnetif *netif->state->yx_errors++; return ERRJ3UF; } if (DMATxDescToSet->Stiatus & ETH_DMATxDesc_OWN ( ((struct ethnetif *netif->state->tx_errors++; return ERR_IF; 1 pbuf_copy_partial(p, (void *DMATxDescToSet->BufferlAddr, p->tot_len, 0; DMATxDescToSet->ControlBufferSize - p->tot_len EYH_DMATxDesc_TBS2; DMATxDescToSet->Statuś 1= ETH_DMATxDesc_FS E'i H_DMATxDesc LS 1 ETH_DMATxDesc_OWN; if (ETH->DMASR h ETH_DMASR_TBOS i ETH->DMASR = ETH_DMASR_TBUS; ETH->DMATPDR = 0; DMATxDescToSet = (ETH_DMADESCTypeDef *DMATxDescToSet->Buffer2NextDescAddr; return ERR OK; 1 Funkcja low_level_output najpierw sprawdza popraw ność argum entów i uaktualnia statystykę wysłanych ramek. W ysyłana przez bibliotekę lwip ram ka ethernetow a nie zaw iera sekwencji kontrolnej, która jest w yliczana przez układ MAC. D latego przekazana do wysiania ram ka nie może być dłuższa niż maksymalny rozm iar ramki ethernetowej (siała ETH_MAX_PACKET_SIZE pomniejszony o długość sekwencji kontrolnej (stała ETH_CRC. Funkcja sprawdza też, czy bieżący deskryptor nadawczy nie jest zajęty przez DM A, czyli czy jest wyzerowany bit OWN. Jeśli sprawdzenia wypadną pozytywnie, funkcja kopiuje ram kę do bufora wskazywanego przez bieżący deskryptor i ustawia w tym deskryptorze długość tej ramki (pole TBS1. Następnie informuje DM A, że bufor zaw iera całą ramkę (ustawia bity ES
3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 119 i LS oraz zwraca deskryptor układowi DMA (ustawia bit OWN. N a koniec trze ba jeszcze sprawdzić, czy przeglądanie kolejki nadawczej nie zostało wstrzymani Sygnalizowane jest to przez ustawienie bitu TBUS w rejestrze DMASR (ang. DMA stai lus register. Przeglądanie zatrzym uje się, gdy w łaścicielem kolejnego deskryptor ' w kolejce nie jest DM A, czyli gdy nie ma nic do wysiania. Przeglądanie kolcjk'1' nadawczej wznawia się przez w yzerow anie bitu TBUS (wpisując do niego jedynkę' i zapisanie jakiejkolw iek wartości do rejestru DMATPDR (ang. DMA transm it poll de'ir ntancl register. O statnią czynnością jest przesunięcie w skaźnika bieżącego deskryp'i tora na następny deskryptor w łańcuchu. Plik utiljeth.c - odbieranie ramek ethernetowych Adres pierwszego deskryptora w kolejce odbiorczej, gdzie zostanie umieszczona kor lejna odebrana ramka elhernetowa, przechowywany jest w zmiennej DMARxDescToGelj* zdefiniowanej w bibliotece STM 32 w pliku stm32_eth.c. extern ETH_DMADESCTypeDef DMARxDescToGet; Odebrane ramki są przetwarzane w procedurze ETH_IRQHandler obsługującej przeć rwanie, które jest zgłaszane przez DM A po um ieszczeniu ramki w kolejce odbiorczej;' Ponieważ jedno przerwanie może zostać zgłoszone dla wielu ram ek, sprawdzam ^ w pętli, czy bieżący deskryptor, w skazywany przez zm ienną DMARxDescToGet, miii wyzerowany bit OWN. W yzerowanie tego bitu oznacza, że deskryptor został zwolnioi ny przez DMA i wskazuje na odebrane dane. Ram ka jest przetwarzana przez funk^ cję e th e rn e tif in p u t, po której zakończeniu w skaźnik DMARxDescToGet wskazujó$ na następny deskryptor w kolejce. Aby nie zgubić,żadnego przerwania, bity oc/e- kującego (ang. pending bit przerw ania ETH_DMA_IT _NIS i ETH_DMA_IT_R muszą hyć,'n wyzerowane przed sprawdzeniem bitu OWN w kolejnym deskryptorze. void ETH_IRQHandler(void ( ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS I ETH_DMA_IT_R; while {!(DMARxDescToGet->Status 6 ETH_DMARxDesc_OWN ethernetif input (p_netif}; Argumentem funkcji e th e r n e tif _ in p u t jest w skaźnik do struktury n e t i f opisującej interfejs sieciowy. Zgodnie z konw encją obow iązującą w bibliotece lwip funkcja ta zwraca wartość ERR O K, gdy zakończyła się sukcesem, lub właściwy kod1 błędu w przeciwnym przypadku. Jednak w szelkie błędy zw racane przez tę funkcję są ignorowane, gdyż nie ma realnej m ożliwości ich obsłużenia w procedurze ob- j sługi przerwania. Zam iast tego funkcja ta uaktualnia w strukturze typu ethnetif-.' statystykę odebranych ramek. static err_t ethernetif input{struct netif *netif { struct pbuf *p; err_t err; ((struct ethnetif *netif->state->rx_packets++; p = Iow level_input(netif; if (p == NULL { ((struct ethnetif *netif->state->rx_errors++; return ERR_MEM; err = netif->input(p, netif; if (err!= ERR OK ( 1 ((struct ethnetif *netif->state->rx_errors++; pbuf_free(p; 1 return err; Biblioteka lwip wymaga, aby odebrana ram ka została um ieszczona w buforze opisyw anym strukturą typu pbuf. Zajm uje się tym funkcja lo w _ łev el_ in p u t, która alokuje bufor i kopiuje do niego ram kę. Zwrócony przez tę funkcję wskaźnik do bufora jest przekazyw any do funkcji wskazywanej za pom ocą składowej in p u t struktury n e ti f. B iblioteka lwip nie zw alnia przekazanego jej bufora, jeśli przetwarzanie ram ki zakończy się błędem. D latego w przypadku błędu trzeba w funkcji e th e r n e tif _ in p u t zw olnić przekazany bibliotece bufor. static struct pbuf * low_level_input(struct netif netif ( struct pbuf *p = NULL; uint32 t len; if ((DMARxDescToGet->Status fieth_dmarxdesc_ S =*= 0 && {DMARxDescToGet~>Status fi TH_DMARxDesc_LS!= 0 && (DMARxDescToGet->Status & ETH_DMARxDesc_FS 1= 0 { len = ETH_GetDMARxDescFrameLength(DMARxDescToGet; if (len >= ETHJiEADER + MIN ETH_PAYLOAD + ETH_CRC { len ETH_CRC; p «pbuf_alloc(pbuf^raw, len, PBUF_P00L; pbuf take(p, (void *DMARxDescToGet->BufferlAddr, len; } DMARxDescToGet~>Status = ETH DMARxDesc_OWN; DMARxDescToGet - (ETH_DMADESCTypeDef *DMARxDescToGet >Buffer2NextDescAddr; if (ETH->DMASR & ETH_DMASR_RBUS { ETH->DMASR = ETH DMASR RBUS; ETH->DMARPDR = 0; } return p; Argum entem funkcji io w _ le v e l_ in p u t jest w skaźnik do struktury n e t i f opisującej interfejs sieciowy. Funkcja ta najpierw sprawdza, czy bieżący deskryptor zawiera popraw nie odebraną ram kę i czy ram ka ta nie jest za krótka. Funkcja ETH_ GetDMARxDescFraraeLength z biblioteki STM 32 zwraca długość odebranej ramki, łącznie z sekw encją kontrolną. Jeśli sprawdzenie w ypadnie pozytywnie, alokowany jest nowy bufor i kopiow ana jest do niego ram ka bez sekwencji kontrolnej. Jeśli alokacja bufora nie pow iedzie się i do funkcji pbuf ta k e zostanie przekazany zerowy w skaźnik p, to funkcja ta nie w ykona żadnego kopiowania. Funkcji pbuf tak e jest odporna na zerową wartość pierwszego argumentu. Następnie funkcja low le - v e l_ in p u t zw raca deskryptor układow i DM A (ustawia bit OWN i przesuwa w skaźnik bieżącego deskryptora na następny deskryptor w łańcuchu. Na koniec trzeba jeszcze sprawdzić, czy przeglądanie kolejki odbiorczej nie zostało wstrzymane. Sygnalizowane jest to przez ustawienie bitu RB US w rejestrze DMASR. Przeglądanie zatrzym uje się, gdy w łaścicielem kolejnego deskryptora w kolejce nie jest DMA (w yzerowany jest bit OWN. M oże się to zdarzyć, gdy kolejka odbiorcza zapełni się, bo aplikacja nie nadąża przetwarzać odebranych ramek. Przeglądanie kolejki od-
3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 121 biorczej wznawia się przez w yzerowanie bitu RBUS (wpisując do niego jedynkę i zapisanie jakiejkolw iek wartości do rejestru DMARPDR (ang. DMA receive poll (/e m and register. 3.4.5. Pliki u tiljw ip.h i u łiljw ip.c - inicjowanie interfejsu sieciowego Plik u tiljw ip.h zaw iera deklarację funkcji LWIPinterfacelnit inicjującej bibli tekę IwIP, a następnie konfigurującej interfejs sieciowy do współpracy z tą bibli teką. int LWIPinterfacelnit(struct netif *netif, struct ethnetif *ethnetif; Im plem entacja tej funkcji znajduje się w pliku u tiljw ip.c. Jej argum entam i są, opisane w poprzednich podrozdziałach, wskaźniki do struktur, które przechow ują daru? konfigurow anego interfejsu. Jeśli składow a ip_addr w strukturze netif ma wartość różną od zera, interfejs sieciowy zostanie skonfigurow any statycznie z adresem 1 m aską podsieci i adresem rutera przekazanym i w tej strukturze. Jeśli składowa t ma wartość zero, to w celu pozyskania ustawień sieciow ych dla tego interfejs zost'-' nie podjęta próba uruchom ienia klienta DHCP. W przekazanej strukturze typu n. tif musi być w ypełniony adres sprzętowy. W przekazanej strukturze typu ethn tif musi być wypełniony adres układu PHY. Funkcja zwraca zero, gdy zakończył się sukcesem, a wartość ujem ną, gdy wystąpił błąd. Funkcja jest przeznaczona do; wywołania z programu głównego, dlatego wszystkie występujące w niej wywołania' funkcji biblioteki IwIP są otoczone makrami chroniącym i przed konkurencyjnj w ykonywaniem. Funkcja ta korzysta z niżej przedstaw ionych funkcji biblioteki', IwIP. void Iwip_init(void; Funkcja lwip_init jest zadeklarowana w pliku init.h i zaim plem entow ana w pliku init.c. Inicjuje wszystkie skom pilow ane moduły biblioteki IwIP. struct netif * netif add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip addr *gw, void *state, err_t (* init(struct netif err_t (* input(struct pbuf *netif, *p, struct netif *netif; Funkcja netif_add jest zadeklarow ana w pliku netif.h i zaim plem entow ana w pliku netif.c. Dodaje interfejs sieciowy do listy interfejsów obsługiw anych przez bibliotekę IwIP. Param etr netif jest w skaźnikiem do struktury opisującej dodawany inter-i. fejs. Param etry ipaddr, netmask i gw są odpow iednio wskaźnikam i do struktur zaw ierających adres IP, m askę podsieci i adres rutera. Jeśli zam ierzam y użyć DHCT, należy podać zerow e wartości adresów i maski. Param etr State jest wskaźnikiem, do struktury zaw ierającej pryw atne dane sterow nika interfejsu sieciowego. W na-, szym przypadku przekazujem y w skaźnik do struktury typu ethnetif. Parametr. init musi zawierać adres funkcji inicjującej sterownik. Przekazujemy adres funkcji ETHinit, która została opisana w poprzednim podrozdziale. Param etr input musi zawierać adres funkcji przetwarzającej odebrane ramki sieciowe. Przekazujemy adres funkcji ethernet input z biblioteki IwIP. Gdy funkcja zakończyła się sukcesem, zwraca wskaźnik do skonfigurow anej struktury typu netif, a gdy wystąpił błąd, zw raca wartość NULL. void netif_set_defauit(struct netif *netif; Funkcja n e tif _ s e t_ d e f a u lt jest zadeklarowana w pliku netif.h i zaim plem entow a na w pliku n etif c. Ustawia interfejs sieciowy w skazywany przez param etr n e t i f jako domyślny. W szystkie datagram y IP bez jaw nie określonej trasy są wysyłane przez interfejs domyślny. err_t dhcp_start(struct netif *netif; Funkcja d h c p _ s ta rt jest zadeklarow ana w pliku dhcp.h i zaim plem entow ana w pliku dhcp.c. U rucham ia klienta D H CP w celu zdobycia ustawień sieciowych dla interfejsu w skazyw anego za pom ocą argum entu netif. Jeśli klient DHCP był ju ż uruchom iony dla tego interfejsu, restartuje tego klienta. Zw rócenie wartości ERR OK oznacza tylko pom yślne uruchom ienie klienta, a nie zakończenie konfigurow ania ustawień siećiow ych. K lient działa niejako w tle. Gdy klient zdobędzie ustaw ienia sieciow e, odblokow uje interfejs sieciowy, ustawiając znacznik NETIF_ FLAG UP. void netif_set_up(struct netif *netif; Funkcja netif set_ up jest zadeklarowana w pliku netif.h i zaimplementowana w pliku netif.c. Jeśli konfigurujemy statyczne ustawienia sieciowe, powinniśmy wywołać tę funkcję w celu odblokowania interfejsu sieciowego przez ustawienie znacznika NETIF_FLAG UP. Jeśli używamy DHCP, wywołanie tej funkcji jest zbędne. O prócz pow yższych funkcji, użyteczne mogą być rów nież inne funkcje dostarczane w plikach netif.h i netif.c. Za pom ocą następujących funkcji można zablokować interfejs sieciowy, sprawdzić jego stan i skonfigurow ać statyczny adres IP, maskę podsieci oraz adres rutera. void netif_set_down(struct netif netif; u8_t netif_is_up(struct netif netif; void netif_set ipaddr (struct netif netif, struct ip addr ipaddr; void netif_set netmask (struct netif netif, struct ip_addr netmask; void netif_set_gw(struct netif netif, struct ip_addr *gw; K onfigurowanie ustawień sieciowych za pom ocą D H CP trwa pewien czas. Przed jego zakończeniem nie ma sensu podejm owow anie jakiejkolw iek komunikacji sieciowej. W funkcji DHCPwait im plem entujem y aktyw ne (w pętli oczekiwanie na zakończenie procesu konfigurowania. int DHCPwait(struct netif netif, unsigned timeout, unsigned tries;
122 3. Stos TCP/ip 3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 123 Funkcja DHCPwait zwraca zero, gdy konfigurow anie za pom ocą D H CP zakończy-; lo się sukcesem lub gdy nie jest używ any klient DHCP. Funkcja zw raca wartość1 ujem ną, gdy konfigurow anie nie zakończyło się w ciągu tim eo u t sekund lub klient1 wykonał bez pow odzenia co najm niej t r i e s prób. Jeśli aktyw ne oczekiwanie na i zakończenie procesu konfigurow ania jest nieakceptow alne, to m ożna użyć funkcji: zwrotnej informującej o zm ianie stanu interfejsu. W tym celu należy w pliku Zwi-: popis, h zdefiniować następującą opcję: łdefine LWIP_NETIF_STATUS_CALLBACK 1 Funkcja zwrotna s ta tu s _ c a llb a c k pow inna być typu v o id i przyjm ow ać jeden argument, który musi być wskaźnikiem do struktury typu n e t i f opisującej interfejs sieciowy. Funkcję zwrotną ustawia się za pom ocą funkcji n e t i f s e t s t a tu s c a l lback. void netif_set_status_callback(struct netif *netif, void (* status_callback(struct netif *netif; Przydatne mogą okazać się rów nież następujące funkcje dostarczane w plikach dhcp.h i dhcp.c. void dhcp_stop(struct netif netif; Funkcja dhcp_ stop zatrzym uje działanie klienta DHCP. err_t dhcp_release(struct netif netif; Funkcja d h cp _ release zw alnia w ynajęte od serwera DFICP ustaw ienia sieciowe. Serw er DHCP w ynajm uje ustaw ienia sieciow e na określony czas - patrz rozdział 3.9. Funkcja ta jest zwykle w yw ołana przed funkcją dhcp_ stop. err t dhcp_renew(struct netif *netif; Funkcja dhcp renew odnawia w ynajem ustawień sieciowych. N orm alnie nie ma potrzeby wywołania lej funkcji, gdyż klient D HCP autom atycznie próbuje odnowić wynajem przed jego zakończeniem. 3.4.6. Pliki u tiljw ip.h i u tiljw ip.c - budziki Biblioteka lwip musi być pobudzana do działania w regularnych odstępach czasu. W szystkie jej budziki opisane są w tab eli 3.11. Pierwsza kolum na zaw iera nazwę funkcji, która ma być wywołana. Druga kolum na zaw iera nazw ę stałej definiującei odstęp czasu, w którym ma być w yw ołana ta funkcja. Trzecia kolum na zawiera dom yślną wartość tego czasu. O prócz budzików biblioteki lw ip będziem y jeszcze potrzebować budzika dla protokołu warstwy aplikacji. Całość im plem entacji budzików znajduje się w plikach u tiljw ip.h i u tiljw ip.c. W dalszym ciągu zam ieszczam tylko tekst źródłowy dla Tab. 3.11. Budziki biblioteki lwip Funkcja Stała dolinlująca Interwal czasowy wywołania ip reass_tmr IP_TMR_INTERVAL 1000 ms etharp_tmr ARP_TMR_INTERVAL 5000 ms tcp_tmr TCP TMR INTERVAL 250 ms dhcp_fine_tmr DHCP FINE TIMER MSECS 500 ms dhcp coarse tmr DHCP_COARSE_TIMER_MSECS DHCP_COARSE_TIMER_SECS Domyślna wartość Interwalu 60 000 ms 60 s dns tmr DNS_TMRJNTERVAL 1000 ms autoip_tmr AUTOIP_TMR_INTERVAL 100 ms igmp tmr IGMP TMR INTERVAL 100 ms budzików D H CP i budzika aplikacji. Im plem entacja pozostałych budzików jest analogiczna. N ajpierw deklarujem y zm ienne odm ierzające czas. static ltime_t DHCPfineTimer; static ltime_t DHCPcoarseTimer; static ltime_t apptimer; Następnie deklarujem y typ app_callback_t dla funkcji zwrotnej budzika aplikacji. Typ ten oznacza w skaźnik do bezparam etrowej funkcji typu void. Zmienna app trar przechow uje w skaźnik tego typu, czyli adres budzika aplikacji. Zmienna app tmr_interval przechow uje interwal czasow y budzika aplikacji. typedef void (*app_callback_t(void; static app_callback_t app_tmr = 0; static unsigned app_tmr_interval = 0; Czas jest odm ierzany w m ilisekundach za pom ocą licznika system ow ego SysTick. Funkcja LWIPtimersStart inicjuje budziki biblioteki lw ip i ustawia funkcję zw rotną LWIPtmr, która będzie w ywołana cyklicznie w przerwaniu SysTick. Funkcja LWIPtimersStart jest przew idziana do w yw ołania z program u głów nego i m anipuluje zm iennym i używanymi w procedurze obsługi przerwania, dlatego wykonuje się przy zablokow anych przerwaniach. Początkow e wartości budzików lw ip ustalamy tak, aby uniknąć czasow ej korelacji ich wywołania. void LWIPtimersStart(void { I RQ DECL PROTECT (X}; ltime_t localtime = LocałTimeO; IRQ_PROTECT(x, LWIP_IRQ_PRIO; DHCPfineTimer = localtime + 31/ DHCPcoarseTimer = localtime; SET_PRIORITY (PendSV IRQn, LWIP_IRQ_PRIO, 0; TimerCallBack(LWIPtmr; IRQ_UN PROTECT(x; 1 Przerwanie SysTick ma wyższy priorytet (HIGH_IRQ_PRIO niż priorytet przerwań biblioteki lw ip (LWIP_IRQ_PRIO, gdyż czas musi biegnąć podczas przetwarzania pakietów przez bibliotekę. D latego funkcja zw rotna LWIPtmr nie wywołuje bezpośrednio budzików, a tylko zgłasza przerwanie PendSV, które jest obsługiwane
3.4. Przykład 3a - pierwsza wersja sterownika Ethernetu 125 3.4.7. z tym sam ym priorytetem co przerw ania biblioteki IwIP. Przerwanie PendSV nię jest zw iązane z żadnym sprzętem. Jest to przerwanie programowe, przeznaczone clij'1 wywoływania niskopoziom owych usług system u operacyjnego, np. do przełączania' kontekstu w s'rodowisku wielozadaniow ym, a zatem doskonale nadaje się do wyi w ołania budzików. Jedynym sposobem w yzwolenia tego przerwania jest ustawieni^ bitu PENDSVSET w rejestrze ICSR (ang. interrupt control and state register, któr'$ znajduje się w bloku rejestrów sterujących SCB (ang. system control block rdzenia1' Cortex-M 3. static void LWIPtmr(void { SCB->ICSR * SCB_ICSR_PENDSVSET; Dopiero procedura obsługi przerw ania PendSV PendSV_Handler wywołuje właścbj we budziki biblioteki IwIP. 64-bitow a zm ienna typu zliczająca milisekundy, przepełni się po ok. 584 milionach lat. Zatem w praktyce możem y założyć, że nigdy 1;, się nie przepełnia, co upraszcza im plem entację. void PendSV_Handler(void { ltime_t localtime = LocalTime(; if (localtime >= DHCPfineTimer { DHCPfineTimer += DHCP_ITINE_TIMER_MSECS; dhcp_fine tmr(; i if (localtime >= DHCPcoarseTimer ( DHCPcoarseTimer += DHCP_COARSEJHMER_MSECS; dhcp_coarse tmr (; I if (app_tmr interval > 0 && localtime >= apptimer { apptimer += app_tmr interval; if (app_tmr app tmr(; Funkcja LWIPsetAppTimer konfiguruje budzik aplikacji. Funkcja ta manipuluje ; zmiennymi używanymi w procedurze obsługi przerwania, dlatego wykonuje się-; przy zablokow anych przerwaniach. Pierwszym jej argum entem jest adres funkcji: zwrotnej. Drugim argum entem jest interwal czasowy wywołania budzika aplikacji'! w milisekundach. void LWIPsetAppTimer(app_callback t tmr, unsigned tmr interval { IRQ_DECL_PROTECT(x; IRQ_PROTECT(x, LWIP_IRQm PRIO; app_tmr = tmr; app_tmr_interval = tmr_interval; apptimer = LocalTime(; IRQ_UNPROTECT(x; } Pliki board_conf.h i board_conf.c Ustaw ienia sieciowe w przykładow ych program ach mogą być konfigurow ane sta-.;, tycznie lub dynam icznie za pom ocą DHCP. Sposób konfiguracji jest wybierany pr/> starcie programu. Zastosow ałem typow e rozwiązanie, które uzależnia działanie pro-;;; graniu od stanu pewnych wyprowadzeń m ikrokontrolera. W zestawie ZL29ARM można do tego wykorzystać zworkę BOOT1, podłączoną do wyprowadzenia PB2. Zworka ta w ym usza na tym wyprowadzeniu stan niski lub wysoki. Stan zworki odczytujem y na początku programu za pom ocą funkcji GetConfBit, która znajduje się w pliku board_conf.c. D eklaracja tej funkcji znajduje się w pliku boarcl_conf.lt. idetine GPIO_CONF GPIOB łdetine CONF_PIN GPI0_Pin_2 idefine RCC_PERIPH_CONF RCC_APB2Periph_GPI0B uint8_t GetConfBit (void ( uinł8 t bit; RCC_APB2PeriphClockCmd(RCC_PERIPH_CONF, ENABLE; bit = GPIO ReadInputDataBit (GPIO_CONF, CONFJ?IN; RCC APB2PeriphClockCmd (RCC_PERIPH_CONF, DISABLE ; return bit; i 3.4.8. Pliki u tiljc d je x.h i u tiijcd jex.c Pliki u tiljcd _ ex.il i u tiljc d _ e x.c udostępniają bardzo przydatne funkcje przeznaczone do wypisyw ania specyficznych danych: void LCDwritelP(const struct ip_addr *ip; void LCDwriteXPport(const struct ip_addr *ip, uintl6_t port; void LCDwriteRXTX(unsigned rx_pkts, unsigned rx_errs, unsigned tx_pkts, unsigned tx_errs; void LCDwriteTime(time_t rtime; Powyższe funkcje korzystają z funkcji LCDwrite. Funkcja LCDwritelP wypisuje adres 1P w postaci czytelnej dla człow ieka, czyli jako cztery liczby dziesiętne oddzielone kropkam i. Jej argum entem jest adres struktury reprezentującej adres IP w ewnątrz biblioteki IwIP. Funkcja LCDwriteIPport w ypisuje adres IP i numer portu oddzielone dwukropkiem. Funkcja LCDwriteRXTX w ypisuje liczby odebranych i wysianych pakietów oraz błędów transmisji. Statystyk tych będzie nam dostarczał sterow nik modułu ethernetowego. W reszcie funkcja LCDwriteTime wypisuje datę i czas w formacie czytelnym dla człowieka. Jako argum ent pobiera liczbę sekund, które upłynęły od początku epoki uniksa, czyli od godziny 0:00:00 dnia 1 stycznia 1970 roku. 3.4.9. Plik e x jp.c Plik e x jp.c zaw iera funkcję main przykładu. Na początku tej funkcji czytamy stan zworki BOOT1, od której ustawienia będzie zależeć konfiguracja adresów. int main ( { uint8_t confbit; confbit = GetConfBit{ ; i Potem konfigurujem y poszczególne podukłady mikrokontrolera, analogicznie jak bylo to w przykładzie 2. Podczas konfigurow ania świeci czerwona dioda. int main( ( AllPinsDisable(;
3.5. Przykład 3b - sterownik Ethernetu bez kopiowania 127 LEDconfigure{; RedLEDonO; LCDconfigure {; SET_IRQ_PROTECTION(; LCDwrite("Clock "; error_check{clkconfigure(}, 1; LCDwrite("PASS\n"; LCDwrite("Local Time "; error_check(localtimeconfigure(, 2; LCDwrite("PASS\n"}; LCDwrite("Ethernet "; error_check(ethconfiguremii(, 4; LCDwrite("PASS\n"; Liczba odebranych ramek Liczba prób wysłania ramki C l o c k PASS L o c a l T im e PASS E t h e r n e t PASS l w I P s t a c k P A S S.. 1P 1192,166,51.89 Rys. 3.3. Wygląd LCD podczas działania programu int raaind ( Informacje o uruchomieniu " poszczególnych podukładów Skonfigurowany adres IP Liczba błędnie odebranych ramek Liczba błędów podczas wysyłania ramek Każde urządzenie w sieci Ethernet pow inno mieć inny adres M AC. Wykorzystujeiij dostępne stale, aby utw orzyć niepow tarzalny adres. Przydzielam y adres M A C adni nistrowany lokalnie. Jeśli zw orka BOOT1 jest w pozycji 0, konfigurujem y ustawie nia sieciowe statycznie. Jeśli zw orka ta jest w pozycji 1, używam y DHCP. Poniżśajf fragment programu konfiguruje interfejs sieciowy i bibliotekę lwip. int maino ( static struct netif netif; static struct ethnetif ethnetif = {PHY_ADDRESS}; LCDwrite("lwIP stack "; netif,hwaddr[0] netif.hwaddr(l netif.hwaddr[2] netif,hwaddr[3] netif.hwaddr(4} netif.hwaddr[5] if (iconfbit { 2; (BOARD_TYPE» 8 & Oxff; BOARDJTYPE 6 Oxff; (ETHJ30ARD» 8 & Oxff; ETH_BOARD & Oxff; 1 + confbit; IP4_ADDR(&neti.ip_addr, 192, IP4_ADDR(&netif.netmask, 255, IP4_ADDR(finetif.gw, 192, 168, 255, 168, else { IP4_ADDR(Snetif.ip addr, 0, 0, 0, 0 IP4_ADDR ({ netif.netmask, 0, 0, 0, 0 IP4_ADDR(&neti.gw, 0, 0, 0, 0 51, 84; 255, 240; 51, 81; error_check (LWIPinterfacelnit (Snetif, { ethnetif, 5; LWIPtimersStart(; LCDwrite("PASS\n"; Ostatnią czynnością zw iązaną z konfigurow aniem jest poczekanie na zakoric/cnie zdobywania ustawień sieciow ych przez klienta DHCP. Czekam y maksymalnie 10 sekund lub 4 próby. Wartości te w ybrano dość swobodnie i mogą wymagać mo-' dylikacji. Funkcja DHCPwait kończy działanie natychm iast, sygnalizując poprawne zakończenie, jeśli nie jest w ykorzystywany klient DHCP. Skonfigurow any adres II wyświetlamy na ekranie LCD. Zgaśnięcie czerwonej diody oznacza zakończenie konfigurowania. LCDwrite("IP error_check{dhcpwait(snetif, 10, 4, 6; LCDwritelP(snetif.ip_addr; RedLEDoff 0; I W głównej pętli programu wyświetlam y statystyki odebranych i w ysłanych ram ek ethernetow ych. W ygląd w yświetlacza ciekłokrystalicznego przedstawiono na ry su n k u 3.3. int main{ 1 for (;;} { LCDgoto(6, 0; LCDwriŁeRXTX(ethnetif,RX_packets, ethnetif.rx_errors, ethnetif,tx_packets, ethnetif.tx_errors; Delay(1000; ł Przykład 3b - sterownik Ethernetu bez kopiowania W tym przykładzie prezentuję ulepszony sterow nik interfejsu ethernetowego. Jest to podstawow y sterow nik dla wszystkich przykładów w kolejnych rozdziałach. Jest on kompatybilny z poprzednim i we w szystkich przykładach oba mogą być stosowane zamiennie. U lepszenie polega na rezygnacji z buforów odbiorczych DM A i kopiowaniu odebranych ram ek bezpośrednio do buforów biblioteki lwip. Ponadto w tym sterowniku w kolejkach odbiorczej i nadawczej używam y pierścieni deskryptorów. Nazwy plików, które zawierają im plem entację tego przykładu, zam ieszczone są w tabeli 3.12. W stosunku do przykładu poprzedniego jest tylko jedna różnica. Plik util_eth.c zastąpiono plikiem util_etli_nc.c. W dalszym ciągu opisuję tylko różnice między tymi plikami. Plik u tilje th jjc.c - inicjowanie interfejsu sieciowego W tej wersji sterow nika bufory odbiorcze pochodzą z puli buforów biblioteki lwip. Do ich obsługi definiujem y dw ie globalne tablice. W tablicy rxbuf ferflags przechow ujem y znaczniki. W tablicy rxbuffer przechow ujem y wskaźniki do struktury typu pbuf opisującej bufory odbiorcze. Tablice te mają tyle samo elem entów co pierścień deskryptorów, czyli po jednym elem encie dla każdego deskryptora.
128 3. Stos TCp/ffl 3.5. Przykład 3b - sterownik Ethernetu bez kopiowania 129 Tab. 3.12. Pliki przykładu 3b ex_ip.c font5x8.c startup_stm32_cld. c font5x8.h Zrńdlnwi! i biblioteczne board_conf.c boardjnit.c board_lcd_ks0108. c board led.c board_conl.li board_def.fi board_defs.li b o a rd jn it.li b o a rd jc d.li b o a rd je d.h Nagłówkowe static uint8_t rxbufferflags[eth_rxbufnb; static struct pbuf *rxbuffer(etijrxbufnb; utiljfelay.c util_eth_nc.c u tiljc d.c u tiljcd _ex.c u tiljed.c utiljw ip.c util time.c util_delay.il uth_eth.h u tiljcd.h u tiljcd_ex.li u tiljed.h utiljw ip.h util time.h tibtwip4.a Hbstm32f10x.a CC.Il cortex-m3.h Iwipopts.h stm32l10x_conf.li Bufory odbiorcze alokuje funkcja AllocRxBuffers w ywołana w funkcji ETHiniii przed skonfigurow aniem DM A. Zm iana w funkcji ETHinit jest następująca: if (AllocRxBufferso < 0 return ERR MEM; ETHconfigureDMA(netif, L W I P J R Q J R I O, 0}; Funkcja AllocRxBuffers zwraca zero, gdy zakończyła się sukcesem, a warlo? ujem ną w przeciwnym przypadku. Bufory alokujem y z puli buforów bibliote! lwip. Poniew aż alokujem y bufory o rozm iarze równym buforom w puli, przydziel ne łańcuchy buforów mają po jednym elem encie, czyli bufory są w jednym kawat (patrz opis stałej PBUF_POOL_BUFSIZE zdefiniowanej w pliku Iwipopts.h. static int AllocRxBuffers(void ( int i, j; for (i = 0; i < ETHJWBUFNB; ++i { rxbufferflags(i = 0; rxbuffer[il = pbuf_alloc(pbuf_raw, PBUF_POOL_BUFSIZE, PBUFJOOL; if (rxbufferii] == NULL ( for (j = i - 1; j >= 0; j pbuf free(rxbuffer(i; return -1; } I return 0; K olejną różnicą w stosunku do poprzedniego sterow nika jest użycie pierścienia < skryptorów. D eklarujem y dw ie tablice, w których zorganizujem y te pierścienie < dw a indeksy, które będą w skazyw ać odpow iednio bieżący deskryptor odbiorczy i i nadawczy. static ETH_DMADESCTypeDef DMArxRing{ETH_RXBUFNB] ALIGN4; static ETH_DMADESCTypeDef DMAtxRing[ETHJ XBUFNB] ALIGN4; static int DMArxIdx; static int DMAtxIdx; Do wysyłania ram ek będziemy, jak w poprzednim sterow niku, używać statycznych buforów nadawczych. Jednak, aby ominąć ograniczenia ukryte w implementacji biblioteki STM 32, nie skorzystam y z funkcji oferowanych przez tę bibliotekę. Dzięki temu możem y na przykład zdefiniow ać w łasny rozm iar buforów nadawczych, aby uw zględniał ramki VLAN, które być może w przyszłości zechcemy obsługiwać.»define TX_BUFFER_SIZE (ETIi MAX_PACKET_SIZE ł VLAN_TAG - ETH_CRC static uint8_t TxBuff(ETH_TXBUFNBJ TX_BUFFER_SIZEj ALIGN4; Funkcja ETHconf iguredma ma podobną strukturę do jej odpow iednika z poprzedniego sterownika. Różnica polega na tym, że teraz konfigurujem y kolejki deskryptorów jako pierścienie i nie używamy do tego funkcji z biblioteki STM32. Najpierw konfigurujemy pierścień deskryptorów odbiorczych. Składow ą ControlBufferSize we wszystkich deskryptorach zerujemy m iędzy innymi po to, aby w yzerować bit DIC i tym sam ym uaktyw nić przerwanie odbiorcze. W łaściw ym inicjowaniem deskryptorów odbiorczych zajm uje się, opisana niżej, funkcja DMArxBufferSet. Następnie konfigurujem y pierścień deskryptorów nadawczych. Zerujemy też indeksy bieżących deskryptorów. Reszta funkcji jest analogiczna jak w poprzednim sterowniku. static void ETHconfigureDMA(struct netif netif, uint8_t priority, uint8_t subpriority { NVIC_InitTypeDef NVIC_IniŁStruct/ int i; DMArxIdx = 0; j for {i * 0; i < ETH_RXBUFNB; ++i i DMArxRing[ij.ControlBufferSize = 0; DMArxBufferSet(&DMArxRing{i], rxbuffer[i]; } DMArxRingfETH_RXBUFNB - 1.ControlBufferSize = ETH_DMARxDesc_RER; ETH->DMARDLAR = (uint32_tdmarxring; DMAtxIdx = 0; for (i = 0; i < ETHJXBUFNB; ++i! i if CHECKSUMJ5ENJP =* 0 DMAtxRing[i.Status = ETHJMATxDesc_CICJPV4Header; jjelse DMAtxRingEi].Status = 0; tfendif DMAtxRing[i].ControlBufferSize = 0; DMAtxRing[i].BufferlAddr = (uint32_ttxbuff(i; DMAtxRing(i].Buffer2NextDescAddr = 0; DMAtxRing (ETHJ XBUFNB - 1].Status = ETH_DMATxDesc JER; ETH->DMATDLAR = (uint32_tdmatxring; p_netif = netif; NVIC_InitStruct.NVICJRQChannel = ETHJRQn; NVICJnitStruct.NVICJRQChanneiPreemptionPriority = priority; NVICJnitStruct.NVICJRQChannelSubPriority = subpriority; NVICJnitStruct.NVICJRQChannelCmd * ENABLE; NVICJnit(SNVICJnitStruct; ETH_DMAITConfig(ETH_DMAJT_NIS! ETH_DMAJT_R, ENABLE; Pierwszym argum entem funkcji DMArxBuf ferset jest wskaźnik do inicjowanego deskryptora odbiorczego. Drugim argumentem jest w skaźnik do struktury typu pbuf
130 3. Si os TCP/ip< 3.5. Przykicui 3b - sterownik Ethernetu bez. kopiowania 131 opisującej bufor, do którego ma być skopiowana odebrana ramka. Bufor jest przy-, dzielony w jednym kawałku, więc wykorzystujemy tylko składową BufferlA dd: Adres drugiego bufora Buffer2NextDescA ddr zerujemy. Na koniec zerujemy polo statusu i przekazujemy własność dcskryptora układowi DMA. static void DMArxBufferSet(ETH_DMADESCTypeDef *rxdesc, struct pbuf *p { rxdesc->controlbuffersize &= ~ (E'i'H_DMARxDesc_RBSl j ETH_DMARxDesc_RBS2 ; rxdesc->controlbuffersize = p->len & ETH_DMARxDesc_RBSl; rxdesc->bufferladdr = (uint32_t p->payload; rxdesc->buffer2nextdescaddr = 0; rxdesc->status = ETH DMARxDesc OWN; 3.5.2. Plik util_eth_nc.c - wysyłanie ramek ethernetowych Jedyną istotną różnicą w funkcji low_level_output wysyłającej ram ki elhernetovu' w stosunku do poprzedniego sterow nika jest użycie pierścienia zam iast łańcucha deskryptorów. static err_t low_level_output(struct netif *netif, struct pbuf *p { ((struct ethnetif *netif->state->tx_packets++; if (p == NULL { {(struct ethnetif *netif->state->tx_errors++; return ERR_ARG; if (p->tot_łen > TX_BUFFER_SIZE { ((struct ethnetif *netif->state->tx_errors+-ł; return ERR_BUF; i I if (DMAtxRing[DMAtxIdx].Status & ETH_DMATxDesc_OWN { ((struct ethnetif * netif->state ->TX errors+ł; return ERR_XF; i pbuf_copy_partial(p, (void *DMAtxRing(DMAtxIdx.BufferlAddr, p->tot_len, 0; DMAtxRing[DMAtxIdx].ControlBufferSize = p->tot_len & ETH_DMATxDesc TBSl; DMAtxRing(DMAtxIdx.Status j= ETH_DMATxDesc_FS I ETH_DMATxDesc_LS i ETH_DMATxDesc_OWN; if (ETH->DMASR & ETH_DMASR_TBUS ( * ETH->DMASR = ET H_DMAS R_T BUS; ETH->DMATPDR = 0; if (DMAtxRing(DMAtxIdx].Status 6 ETHJ3MATxDescJFER DMAtxIdx = 0; else -i+ DMAtxIdx; return ERR OK; 3.5.3. Plik util_eth_nc.c - odbieranie ramek ethernetowych DMA kopiuje odebraną ram kę do bufora, który przekazujem y bibliotece lwip. Od tego momentu w łaścicielem tego bufora jest biblioteka. Biblioteka zwolni bufoi, gdy już nie będzie jej potrzebny. Zw alniany bufor wraca do puli buforów biblioteki. Zatem bufor przekazany bibliotece nie może ju ż dłużej być w skazywany przez de- skryptor w kolejce odbiorczej i w jego m iejsce trzeba przydzielić nowy bufor z puli. A lokow aniem nowych buforów zajm uje się funkcja ReallocRxBuffers, wywołana w procedurze obsługi przerw ania ETH_IRQHandler. void ETH_IRQHandłer(void ETH_DMACiearITPendinqBit(ETH_DMR_IT_NIS ETH_DMA_IT_R; while (!(DMArxRinglDMArxIdxJ.Status s ETH_DMARxDesc_OWN 1 ethernetif_input(p_netif; ReallocRxBuffers(t Funkcja ReallocRxBuffers przegląda cyklicznie kolejkę odbiorczą. Statyczna zm ienna idx jest indeksem w pierścieniu deskryptorów. W skazuje deskryptor, który należy uaktualnić. U stawiony znacznik RX_BUF_USED_UP oznacza, że deskryptor został wykorzystany i trzeba go zwrócić układowi DM A. Wartość NULL w tablicy rxbuffer oznacza, że bufor został przekazany bibliotece lwip i trzeba przydzielić nowy bufor. Jeśli nie uda się przydzielić bufora, funkcja kończy działanie oczekując, że uda się to przy następnym jej wywołaniu. Jeśli bufor jest przydzielony, czyli wskaźnik w tablicy rxbuffer jest niezerowy, to zerujemy znacznik RX_BUF_USED_ UP i inicjujemy deskryptor za pom ocą funkcji DMArxBufferSet. Idefine RX_BUF_USED_UP 1 static void ReallocRxBuffers(void { static int idx = 0; while (rxbufferflags(idx & RX_BUF_USED_UP { if (rxbuffer[idx] == NULL { rxbufferfidx] = pbufj*lloc(pbuf_raw, PBUF_POOL BUFSIZE, PBUF POOL ; if (rxbufferfidx] == NULL return; rxbufferflags(idx] &= ~RX_BUF_USED_UP; DMArxBufferSet(SDMArxRing(idx, rxbuffer(idx; if (DMArxRing(idx].ControlBufferSize & ETH_DMARxDesc_RER idx * 0; else ++idx; } Jak poprzednio przekazaniem odebranej ram ki bibliotece lwip zajm uje się funkcja ethernetif_input. Ponieważ funkcja ta jest w ywołana w procedurze obsługi przerw ania, gdzie nie można zrobić wielkiego użytku ze zw racanego przez nią kodu błędu, ta jej w ersja nie zwraca żadnej wartości. Funkcja la uaktualnia statystykę odebranych ramek. Należy pamiętać, że jeśli wywołanie funkcji przetwarzającej ramki w bibliotece lwip, czyli funkcji wskazywanej przez składową input struktury netif, zakończy się pow odzeniem, to bufor został zwolniony. Jeśli natomiast zakończy się błędem, to m usim y zw olnić ten bufor. static void ethernetif_input(struct netif *netif { struct pbuf *p; ((struct, ethnetif * netif->state ->RX_packets-M; p = low łevel_input (netif; if (p «= NULL
3.6. Przykład 3c - eksperymentalny sterownik Ethernetu bez. kopiowania 133 ((sfcruct ethnetif *netif->state->rx_errorsi+; else if <netif->input(p, netif!«* ERR_OK { pbuf_free(p/ {(struct ethnetif *netif->state->rx_errors++; } N iskopoziom ow ą obsługą odebranej ramki zajm uje się funkcja low_level_inputf Funkcja la najpierw sprawdza, czy bieżący deskryptor zaw iera popraw nie odebraną, ram kę i czy ram ka nie jest za krótka. Jeśli sprawdzenie w ypadnie pozytywnie, funkcja zwróci wskaźnik do struktury typu pbuf opisującej bufor z odebraną rani-' ką. Zanim jednak zakończy działanie, wpisuje do tablicy rx B u ffe r wartość NUL aby poinform ow ać funkcję R eallo cr xb uffers, że trzeba przydzielić nowy bufoi*- Biblioteka łw IP oczekuje ram ki ethernetowej bez sekwencji kontrolnej. Funkcj pbuf realloc ustawia właściwy rozm iar ramki. Dla buforów typu PBUF_POOL funk* cja ta niczego nie kopiuje, uaktualnia tylko składowe le n i tot len struktury typ'1 pbuf. Na koniec, jak poprzednio, trzeba jeszcze wznowić przeglądanie kolejki odbiorczej, jeśli zostało wstrzymane. static struct pbuf * low_level_input(struct netif *netif { struct pbuf *p = NULL; uint32_t len; if {(DMArxRing[DMArxIdxl.Status ETH_DMARxDesc_ES == 0 (DMArxRing[DMArxIdx.Status ETH_DMARxDesc_LS!= 0 (DMArxRing[DMArxIdx.Status ETH_DMARxDesc_FS!= 0} I len = ETl!_GetDMARxDescFrameLength ( DMArxRing(DMArxIdxj; if (len >= ETH_HEADER + MIN_ETH_PAYLOAD + ETH_CRC f len -= ETH_CRC; p = rxbuffer(dmarxidx; pbuf_realloc(p, len; rxbuffer(dmarx!dx] = NULL; i } rxbufferflags(dmarxidx j» RX_BUF_USED_UP; if (DMArxRing[DMArxIdx}.ControlBufferSize ETH_DMARxDesc_RER DMArxIdx = 0; else + t-dmarxidx; if (ETH->DMASR ETH_DMASR_RBUS { ETH->DMASR = ETH_DMASR_RBUS; ETH->DMARPDR = 0; I return p; Przykład 3c - eksperymentalny sterownik Ethernetu bez kopiowania W tym przykładzie prezentuję kolejne ulepszenie sterow nika interfejsu sieciowego. Ulepszenie zm ierza w kierunku pełnej realizacji idei zero-copy. Rezygnujemy z buforów nadaw czych DMA i w ysyłam y ramki bezpośrednio z buforów biblioteki IwIP. Nazwy plików, które zaw ierają im plem entację, zebrane są w tabeli 3.13. W stosunku do poprzedniego przykładu plik util_eth_nc.c został zastąpiony plikiem util_eth_zc.c. W dalszym ciągu opisuję tylko różnice między tymi plikami. Tab. 3.13. Pliki przykładu 3c BX_ip.C lonl5x8.c startup_stm32_cld. c font5x8.h board_conl.c board jn it.c boardjcd_ks0108.c boardjed.c board_conl.lt board_del.lt board_deis.it boardjnit.h board_lcd.lt b o a rd jed.h Źródłowe i biblioteczne Nagłówkowe util_delay.c util_eth_zc.c u tiljc d.c utiljcd_ex.c u tilje d.c utiljw ip.c utiljim e.c util_delay.h util_eth.li u liljc d.h utiljcd_ex.it u tiljed.h uliljw ip.h util time.b Iiblwip4.a Iibstm32l10x.a CC.il corlex-m3.li Iwipopts.li stm32l10x_con/.h Sterownik ten jest eksperymentalny, gdyż nie będzie działał ze wszystkimi przykładami. Powody są następujące: - -Biblioteka IwIP um ieszcza wysyłaną ram kę w łańcuchu buforów. Zakładam, że łańcuch składa się z co najwyżej dwóch struktur typu pbuf. Ten sterownik nie potrafi wysiać ramki rozm ieszczonej w łańcuchu składającym się z większej liczby elementów. Zaobserw owałem jednak, że biblioteka sporadycznie generuje dłuższy łańcuch, eh^tć zawsze było to w sytuacji, gdy wcześniej wystąpił problem z siecią. W praktyce, gdy sieć działa stabilnie, sterownik spisuje się zadowalająco. - Biblioteka IwIP może m odyfikować zaw artość bufora z ram ką przekazaną do wysiania, jeśli wykonuje retransm isję segm entu TCP. Zatem sterownik musi zdążyć wysiać ram kę, zanim biblioteka podejm ie decyzję o retransmisji. W praktyce, gdy sieć pracuje poprawnie, zaw sze zdąży. - DM A ma problem z w ysyłaniem danych um ieszczonych w pamięci Flash. Zwykle jednak wysyłane dane są przygotowyw ane w buforze, który jest w RAM, więc problem ten nie jest dotkliwy. Jeśli jednak dane do wysiania są we Flash, trzeba je najpierw skopiować do RAM. Jest to sprzeczne z ideą zero-copy przyświecającą stworzeniu tego sterownika. Do problem u w ysyłania danych um ieszczonych w pamięci Flash wrócę jeszcze w kolejnym przykładzie. M im o pewnych niedoskonałości tego sterow nika, postanowiłem go jednak zaprezentow ać w takiej postaci, aby nie kom plikow ać tekstu źródłowego. Napisanie na tej podstawie w pełni funkcjonalnego sterow nika pozostawiam Czytelnikowi jako ćwiczenie. Plik util_eth_zc.c - inicjowanie interfejsu sieciowego Ta wersja sterow nika nie używa statycznych buforów nadawczych. Zamiast nich używa buforów z puli biblioteki IwIP. W globalnej tablicy tx B u ffe r przechowujem y otrzym ane od biblioteki IwIP wskaźniki do struktur typu pbuf. Tablica ta ma tyle sam o elementów, ile jest deskryptorów w pierścieniu nadawczym, czyli po jed nym dla każdego deskryplora.
3.6. Przykład 3c - eksperymentalny sterownik Ethernetu bez, kopiowania 135 static struct pbuf txbufferiethjtxbufnb]; Teraz w funkcji ETH init przed skonfigurow aniem DM A, oprócz alokowania bii' forów odbiorczych, trzeba jeszcze zainicjow ać pow yższą tablicę wskaźnikó," M odyfikacja jest następująca: Aj if (AilocRxBuffers( < 0 return ERR_MEM; InitTxBuffers{; ETHconfigureDMA(netif, LWIP IRQ_PRIO, 0 ; Funkcja in itt x B u ff e r s po prostu zeruje wszystkie wskaźniki. W łaściw e wartośęi będą przypisywane dynam icznie w funkcji wysyłającej ramki. N iezerow a warlośd w skaźnika będzie oznaczać, że w skazuje on na łańcuch buforów. static void InitTxBuffers(void ( int i; for (i = 0; i < ETH_TXBUFNB; ++i txbuffer[i] = NULL; 1 :!'/ Funkcja ETHconfigureDMA różni się od swojej poprzedniczki tylko dw om a instrukcjami: DMAtxRing[i].BufferlAddr = 0; ETH_DKAXTConfig(ETH_DMA_IT_NIS TH_DMA_IT R ETH_DMA_IT_T, ENABLE; Nie znamy adresów buforów z wysyłanym i ramkami, więc na razie zerujemy skla* dową B ufferla ddr we w szystkich deskryptorach nadawczych. Aby móc dynamicz^ nic obsługiwać bufory nadawcze, potrzebujemy informacji o zakończeniu transmisji; Dlatego, oprócz przerwania zgłaszanego po skopiowaniu odebranej ramki, uaktywniamy też przerwanie informujące o zakończeniu przez DM A transmisji wysyłanej ramki (ustawiamy dodatkowo bit ETH_DMA_IT_T. Plik util_eth_2c.c - wysyłanie ramek ethernetowych Poniew aż uaktywniliśm y now e źródło przerwania, musimy zm odyfikow ać proce durę ETH_IRQHandler obsługującą przerwania Ethernetu. Część obsługująca pi/c-1 rw anie odbiorcze jest w zasadzie identyczna jak w poprzedniej wersji sterownik W części obsługującej przerw anie nadawcze w ywołujem y funkcję FreeTxBuffers', zwracającą do puli te bufory, których zaw artość została ju ż w ysłana przez DMA. void ETH_IRQHandler(void ( ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS; if (ETH_GetDMAITStatus(ETH_DMA_IT_T ( ETH_DMAClearITPendingBit(ETH_DMA_IT_T; FreeTxBuffers(p_netif; if (ETH_GetDMAITStatus(ETH_DMA_IT_R ( ETH_DMAClearITPendingBit(ETH_DMA_IT_R; while (!(DMArxRing[DMArxIdx.Status S ETIl_DMARxDesc_OWN ( ethernetif_input(p_netif; ReallocRxBuffers(; Buforów nadawczych jest niewiele, więc funkcja FreeTxBuf f e r s może przy każdym wywołaniu przeglądać je naiw nie, liniowo. Jeśli znajdzie w tablicy txb uff e r niezerowy adres łańcucha buforów i deskryptor o tym sam ym indeksie został zw olniony przez DMA, to zw alnia ten łańcuch buforów. Funkcja ta uaktualnia też statystykę błędów. static void FreeTxBuffers(struct netif *netif ( int i;! for (i = 0; i < ETHJTXBUFNB; ++i I if (txbuffer(i SS (DMAtxRingli].Status s ETH_DMATxDesc_OWM == 0 ( if (DMAtxRingli.Status S ETH_DMATxDesc_ES ((struct ethnetif * netif->state ->TX_errorst *f; pbuf_free(txbuffer(i}}; txbuffer(i = NULL;, Za w staw ienie ram ki do kolejki nadawczej odpow iada, jak poprzednio, funkcja lo w _ lev eł_ o u tp u t. Funkcja ta najpierw spraw dza popraw ność argum entów i dostępność w olnego deskryptora w kolejce nadawczej oraz uaktualnia statystykę w y słanych ramek. Następnie zw iększa licznik referencji łańcucha buforów p i zapisuje jego adres w tablicy tx B u ffer. B iblioteka lw IP po popraw nym zakończeniu funkcji lo w _ łev el_ o u tp u t wywołuje na łańcuchu buforów funkcję pbuf free. Zwiększenie licznika referencji sprawia, że bufory zostaną zw olnione dopiero wtedy, gdy funkcja p b u f_ fre e zostanie w ywołana po raz drugi w funkcji FreeTxBuf fe r s. D eskryptor jest inicjow any za pom ocą funkcji DMAtxBuffe rs e t. N a koniec wznawiane jest przeglądanie kolejki nadawczej i indeks DMAtxIdx jest przesuwany na następny deskryptor w pierścieniu. static err t low level_output (struct netif *netif, struct pbuf *p ( {(struct ethnetif *netif->state->tx_packets+-f-; if (p NULL ( ({struct ethnetif *netif->state->tx_errors++; return ERR ARG; 3 if (pbuf_chain_łen(p > 2 j txbuffer[dmałxidx] DMAtxRing[DMAtxIdx].Status & ETH_DMATxDesc_OWN { ({struct ethnetif *netif->state->tx_errors++; return ERR_IF; } pbuf_ref(p; txbuffer[dmatxidx] = p; DMAtxBufferSet(SDMAtxRingfDMAtxIdx], p; if (ETff->DMASR & ETH_DMASR_TBUS i ETH->DMASR - ETH_DMASR_TBUS; ETH->DMATPDR = 0; if (DMAtxRing(DMAtxIdxj.Status & ETH_DMATxDesc_TER DMAtxIdx «= 0; else f+dmatxidx; return ERR_0K;
3.8. ICMP - komunikaty kontrolne i komunikaty o błędach 137 Jak napisałem wcześniej, zakładamy, że wysyłana ram ka będzie umieszczony w 7łańcuchu buforów o długości co najwyżej dwa. Długość łańcucha sprawdzarriyj, za pom ocą funkcji p b u f_ ch ain _ len, która jest bardzo podobna do funkcji pb c le n z biblioteki łwip, ale w przeciwieństwie do niej jest poprawna. static iiit pbuf_chain_len(struct pbu int len; pi i len = p!= NULL; while (p i= NULL &S p->tot_len!= p->len { H len; p p~>next:; } return len; Funkcja DMAtxBufferSet inicjuje deskryptor w kolejce nadawczej i przekazuje go układowi DMA. Ustawia też bit IC, aby po zakończeniu transmisji zostą ło zgłoszone przerwanie. Zależnie od długości łańcucha buforów p wykot/yi stywany jest tylko adres jednego bufora lub oba adresy (składowe B ufferla dd' i Buf fer2nextdescaddr. static void DMAtxBufferSet(ETH_DMADESCTypeDef *txdesc, struct pbuf *p { txdesc->controłbuffersize = (uint32_tp->len & ETHJ3MATxDesc_TBSl; txdesc->bufferladdr = (uint32_tp->payload; if (p->tot_len > p->len { p - p->next; txdesc->controlbuffersize = ((uint32_tp->len «16 S, ETH_DMATxDesc_TBS2; txdesc~>buffer2nextdescaddr = (uint32_t(p->payload; else txdesc->buffer2nextdescaddr = 0; txdesc->słatus 1= ETH_DMATxDesc_IC ETH_DMATxDesc_FS ETH_DMATxDescJLS i ETH_DMATxDesc_OWN; i Jeśli chcielibyśm y zaim plem entow ać obsługę łańcuchów buforów o długości w i c i szej niż dwa, to do wysłania jednej ram ki m ożna użyć kilku kolejnych deskryptoiów W pierwszym z tych deskryptorów ustawiamy tylko bit FS, a w ostatnim tylko bi^ LS. Oczywiście wtedy odpow iednio kom plikuje się też zw alnianie buforów. Nalęż rów nież pamiętać, żeby bit OWN w pierwszym z tych deskryptorów ustawić dopierona końcu, po zainicjow aniu w szystkich deskryptorów. W przeciwnym przypadk DM A może rozpocząć transm isję, zanim cała ram ka będzie golow a do wysłania. Testy sterowników W tabeli 3.14 zam ieszczone jest porów nanie wyżej opisanych sterowników. W ko?; lum nie Flash jest podana całkow ita zajętość pamięci program u, uwzględniająca ta-* blicę przerwań, sekcję.te x t (w tym sekcję.ro d a ta oraz sekcję.d a ta. W kolunp nie RAM jest podana zajętość pam ięci danych, uw zględniająca sekcje.d a ta i J'-: 3 ale nieuw zgłędniająea pam ięci alokowanej dynam icznie na stosie i stercie. Wida Tab. 3.1 4. Porównanie sterowników Przykład Sterownik Flash HAM Czas odpowiedz! 3a util_eth.c 53488 42988 917 ± 1 2 p s 3b util eth nc.c 53556 36924 877 ± 3 2 ps 3c u til_ e th jc.c 53768 30860 859 ± 2 5 ps Tab. 3.15. Użyte parametry programu p in g Parametr Linux Parametr Windows Opis -c liczba -n liczba Wysyła podaną liczbę próśb o echo -s liczba -1 liczba Tworzy komunikat z polem danych o podanej długości. Wysyłając więcej niż 1472 oktety danych, można przetestować poprawność działania tragmentacji i składania datagramów IP -n Nie podejmuje próby przetłumaczenia adresu IP na nazwę węzła 192.168.51.89 Adres węzła, do którego są wysyłane prośby o echo że kolejne wersje sterowników mają coraz bardziej skom plikowany tekst źródłowy i zajm ują coraz więcej pamięci programu, ale przyrost ten jest nieznaczny i nie ma praktycznego znaczenia. N atom iast likwidacja statycznych buforów odbiorczych w przykładzie 3b, a następnie rów nież statycznych buforów nadawczych w przykładzie 3e, wyraźnie zmniejsza zajętość pamięci danych. Pozostaje więcej miejsca na stertę i stos, co pozwala bardziej elastycznie gospodarować niewielką pamięcią mikrokontrolera. Aby sprawdzić, czy pozbycie się kopiow ania ramek przyspiesza ich obsługę, zm ierzyłem za pom ocą programu ping czasy odpowiedzi programu na komunikaty prośba o echo protokołu ICM P - patrz rozdział 3.8. W Linuksie polecenie jest następujące: ping -c 100 -s 1472 -n 192.168.51.89 W system ach W indows wygląda tak: ping -n 100-1 1472 192.168.51.89 Użyte param etry opisane są w tabeli 3.15. Test polegał na wysianiu 100 próśb, z których każda zaw ierała po 1472 oktety danych, aby wypełnić całą ram kę ethernetową. Ostatnia kolum na tabeli 3.14 zaw iera średni czas oczekiwania na otrzym anie odpowiedzi i jego odchylenie standardowe, wypisane przez linuksową wersję programu ping. W ersja w system ie W indows mierzy czas zbyt mało dokładnie. M im o sporego rozrzutu tych czasów widać, że likwidacja niepotrzebnego kopiowania przyspiesza przetwarzanie ramek. 3.8. ICMP - komunikaty kontrolne i komunikaty o błędach ICM P (ang. Internet Contro/ M essage Protocol to intersieciowy protokół kom u nikatów kontrolnych, opisany w RFC 792. Jest on obow iązkow ą częścią IP, co oznacza, że każda im plem entacja IP musi też im plem entow ać ICMP. Przyjęto go
138 3.8.1. Tab. 3.16. Wybrane wartości pola TYP komunikatu ICMP Typ Komunikat 0 Odpowiedź z echem 3 Odbiorca nieosiągalny 4 Tłumienie nadawcy 8 Prośba o echo 11 Przekroczenie czasu 12 Problem z parametrem TYP KOD SUMA KONTROLNA [0:7] [8:15] [16:31] Rys. 3.4. Format komunikatu ICMP IDENTYFIKACJA (0:31 DANE 3' V,','v lifts wspólnie z 1P, jako standard intersieci STD 5. ICM P um ożliwia diagnozowanie prdł blcmów w działaniu intersieci. ICM P pow iadam ia pierwotnego nadawcę datagram! IP o problemie z tym datagram em. Komunikaty ICMP są kapsulkowane w data* graniach IP, aby mogły być dostarczane w dowolne m iejsce intersieci. Mimo ICM P nie jest uważany za protokół warstwy transportowej. JesTi wystąpi probier z dostarczeniem datagraniu przenoszącego kom unikat ICMP, nie w ysyła się koloj^ nego kom unikatu ICM P inform ującego o tym błędzie, aby nie spowodować lawiny komunikatów, które mogłyby zapchać sieć. Form at komunikatu ICM P jest przedstaw iony na ry su n k u 3.4. W ybrane, najczyściej spotykane wartości pola T Y P zam ieszczone są w tabeli 3.16. W polu SUN KONTROLNA um ieszcza się sum ę sprawdzającą integralność komunikatu. Pól. KOD, ID ENTYFIKACJA i D A N E są interpretow ane zależnie od wartości poi: TYP. Pole DANE jest opcjonalne i nie musi występować. Sprawdzanie osiągalności odbiorcy Aby sprawdzić osiągalność odbiorcy, w ysyła się kom unikat prośba o echo (warto 8 w polu TYP. Pole KOD zaw iera 0. Pole ID ENTYFIKACJA składa się wtedy, z dw óch 16-bitowych pól. W pierwszych 16 bitach um ieszcza się unikalny identyfh;: kator komunikatu. W pozostałych 16 bitach um ieszcza się kolejny num er komunikatu. Pole DANE ma dow olną zaw artość. Węzeł, który odbierze prośbę o echo, odpo.-; wiada kom unikatem odpow iedź z. echem. O dpowiedź zaw iera w polu TY P wartość 0. W polu KOD też jest wartość 0. Pola ID ENTYFIKACJA i D ANE zawierają danfe. ^ skopiowane z odpow iednich pól komunikatu z prośbą. Pole IDENTYFIKAt umożliwia powiązanie odpow iedzi z prośbą. K om unikaty prośba o echo i odpow iedź z echem są używ ane przez progi ping do spraw dzania osiągalności węzłów. Poniew aż kom unikaty IC M P wędrują^, w datagram ach IP, otrzym anie popraw nej odpow iedzi św iadczy, że podstaw 3.8. ICMP komunikaty kontrolne i komunikaty o błędach 13 9 kom ponenty intersieci działają praw idłow o - oprogram ow anie IP nadaw cy w y syła i odbiera datagram y, rutery pośrednie m iędzy nadaw cą a odbiorcą praw idłow o przesyłają datagram y, w ęzeł docelow y je st uruchom iony i działa na nim oprogram ow anie IP. N ależy jednak zaznaczyć, że w ęzły nie m uszą odpow iadać na prośbę o echo - m oże to być ze w zględów bezpieczeństw a zablokow ane adm inistracyjnie. 3.8.2. Powiadamianie o nieosiągalności odbiorcy Jeśli datagraniu IP nie można dostarczyć, w ysyła się jego pierwotnem u nadaw cy kom unikat odbiorca nieosiągalny. Pole TY P zawiera wartość 3. Pole KOD zaw iera szczegółową inform ację o przyczynie odrzucenia datagraniu. Pole ID ENTYFIKACJA nie jest używane i zaw iera 0. Pole DANE zaw iera początkowy fragment datagraniu, który spow odował problem. Fragm ent ten zaw iera nagłówek datagraniu i początkowe 64 bity jego pola danych. Początkowe 64 bity poła danych datagraniu IP zawierają zwykle kluczow e inform acje z nagłówka protokołu transportowego. Dzięki temu odbiorca kom unikatu ICM P może łatwiej zidentyfikować przyczynę problemu. O dbiorca m oże być nieosiągalny z wielu powodów. M oże wystąpić aw aria odcinająca podsieć, w której znajduje się węzeł odbiorcy. Węzeł może być po prostu w yłączony. Trzeba jednak jasno zaznaczyć, że w sieci Ethernet nie ma prostego sposobu stwierdzenia, że jakiś węzeł nie działa. M im o działania węzła odbiorcy, może nie działać aplikacja, do której adresowany jest datagram IP. K om unikat o nieosiągalności może też pojaw ić się, gdy kom unikacja z odbiorcą jest zablokow ana adm inistracyjnie. K om unikat odbiorca nieosiągalny um ożliw ia rów nież poznanie M TU ścieżki do odbiorcy. M TU ścieżki jest to najm niejsze M TU wśród wszystkich sieci, przez które wędruje datagram IP. Znajom ość M TU ścieżki um ożliw ia wysyłanie datagraniów IP o takiej długości, aby nie m usiały być po drodze fragmentowane. Aby poznać M TU ścieżki, w ysyła się próbne datagram y IP o różnych długościach z ustawioną w nagłów ku IP opcją nie fragm entuj. Jeśli datagram IP jest za długi, to ruter porzuci go i poinform uje nadawcę kom unikatem odbiorca nieosiągalny z polem KOD rów nym 4, co oznacza, że konieczna jest fragm entacja, ale ustawiona jest opcja nie fragm entuj. M TU ścieżki to długość najdłuższego datagram u IP, który dotrze do odbiorcy. O dotarciu datagram u IP do odbiorcy można przekonać się za pom ocą następującego triku. W ysyła się datagram IP zaw ierający datagram UDP, skierowany do usługi, która nie jest uruchom iona w w ęźle odbiorcy. Jeśli taki datagram UDP dotrze do odbiorcy, odeśle on nadawcy kom unikat odbiorca nieosiągalny z polem KOD rów nym 2 lub 3, inform ującym o nieosiągalności usługi, co jednak ew identnie św iadczy o osiągalności węzła odbiorcy. 3.8.3. Kontrola przepływu Jeśli ruter jest przeciążony i nie jest w stanie przetwarzać odbieranych datagraniów IP, w ysyła ich nadaw com komunikaty tłumienie nadawcy. Pole TY P ma wartość 4. Pole K OD zaw iera 0. Pole ID EN TY FIK A CJA nie jest używane i ma wartość 0.
140 3. Stos TOjM 3.9. DHCP konfigurowanie ustawień sieciowych węzła 141 Pole D ANE zawiera, jak poprzednio, początkowy fragment dalagram u, którego ni m ożna przetworzyć. Przeciążenie (ang. congestion może być spow odow ane przez jeden bardzo szff ki komputer, który w ysyła datagram y IP szybciej niż ruter potrafi je obsłużyć Ruter może też być przeciążony przez wiele komputerów, wysyłających do n ić * rów nocześnie datagram y IP, mimo że żaden z tych komputerów osobno nie wywo lałby tego problemu. Przeciążenie m oże być objawem ataku typu odm owa usług Przeciążenie może pojawić się też, gdy ruter łączy dwie sieci o różnych przepłyń nos'ciach, na przykład szybką sieć lokalną (LAN, ang. local area network z wo» ną siecią rozległą (WAN, ang. wide area network. W tedy datagram y naplywajäf* z sieci lokalnej m ogą pojaw iać się zbyt szybko, aby w szystkie mogły być p r z e zane do sieci rozleglej. Ruter broni się przed przeciążeniem, stosując buforowanie, które zabezpiecza prze chw ilow ym zw iększeniem natężenia ruchu. Jeśli zwiększony ruch trwa dostateczni' długo, to każdy bufor wreszcie zapełni się i datagram y IP będą tracone. Ruter mozje" stosow ać różne strategie. M oże w ysyłać po jednym kom unikacie tłumienie nadawi cy dla każdego straconego datagram u. Ruter może wysyłać ten komunikat tylko cii nadaw ców przysyłających najwięcej datagramów. Ruter może też próbow ać uniknij przeciążenia, wysyłając tłumienie nadaw cy, gdy jego bufor będzie blisko przepej nienia. Węzeł, który otrzym uje kom unikaty tłumienie nadaw cy, powinien zmniojj szać liczbę wysyłanych datagram ów, aż przestanie otrzym yw ać te komunikaty. Pty pew nym czasie może spróbować zw iększyć liczbę wysyłanych datagramów, dopókj znów nie zacznie być tłumiony. 3.8.4. Przekroczenie czasu Komunikat przekroczenie czasu ma w polu TY P wartość 11. Pole KOD ma wartość/'' 0, gdy wyczerpał się czas życia datagram u IP, a wartość 1, gdy upłynął czas nąskładanie fragmentów. Pole ID EN TY FIK A CJA nie jest używ ane i zaw iera 0. Pole'' DANE zaw iera początkowy fragm ent datagram u, który został porzucony. Poniew aż rutery podejm ują decyzje o przesyłaniu datagram ów IP na podstawie;]; lokalnych tablic tras, błąd w ich konfiguracji mógłby spow odow ać nieskończone:/ krążenie datagram ów w sieci. N agłów ek datagram u IP zaw iera licznik czasu życia, (ang. time to live, nazywany też licznikiem etapów. Ruter zm niejsza licznik czasu* życia datagram u przed jego przetw orzeniem i porzuca datagram, gdy licznik tetk osiągnie wartość zero. K om unikat przekroczenie czasu pozw ala wykrywać zbyt diu/*' gie lub zapętlone trasy. Licznik czasu na składanie datagram u IP startuje, gdy przychodzi pierwszy frag' ment datagramu. Węzeł porzuca wszystkie dotychczas otrzym ane fragmenty datagramu i w ysyła kom unikat przekroczenie czasu, gdy w ustalonym czasie nie odbieg, rze w szystkich fragmentów. 3.8.5. Problem z parametrem Jeśli zostanie stwierdzony błąd w nagłówku dalagram u IP, jego pierwotnem nadawcy odsyłany jest kom unikat problem z param etrem. Taki kom unikat odsyłany 'A '.iii jest również wtedy, gdy pojawi się problem, który nie da się opisać żadnym innym kom unikatem ICMP. Pole TY P ma wartość 12. Pole KOD zawiera 0 lub 1. Jeśli ma wartość 0, to pierwsze 8 bitów pola ID EN TY FIK A CJA wskazuje na oktet, który spow odował problem, a pozostałe 24 bity tego pola są wypełnione zerami. Jeśli pole KOD ma wartość różną od 0, to pole ID ENTYFIKACJA nie jest używane i zaw iera 0. Pole D ANE zawiera początkowy fragment datagramu, który spow odował problem. DHCP - konfigurowanie ustawień sieciowych węzła D H CP (ang. D ynam ie Most C onfiguration Protocol jest protokołem dynam icznego konfigurow ania węzłów. O pisany jest w RFC 2131. Za pom ocą D HCP w ę zeł m oże uzyskać w szystkie niezbędne ustaw ienia sieciow e, do których należą adres IP, m aska podsieci, nazw a w ęzła, adres rutera, adres serw era DNS, adresy serw erów innych usług. K om unikaty D H C P są kapsułkow ane w datagram ach UDP, które z kolei są kapsułkow ane w datagram ach IP. Pierw szy problem, który w ym aga rozw iązania, to sposób, w jaki w ęzeł m oże używ ać IP, zanim pozna sw ój adres. D H CP rozw iązuje go tak, że pierw szy kom unikat jest w ysyłany za pom ocą rozgłaszania UDP. W ten sposób m ożna w ysłać kom unikat do w szystkich w ęzłów w podsieci. O dpow iedź też m oże być w ysłana za pom ocą rozgłaszania. Po otrzym aniu odpow iedzi w ęzeł poznaje adres drugiej strony i dalsze kom unikaty m ogą być w ysyłane ju ż bezpośrednio. R ozgłaszanie może odbyw ać się tylko w obrębie podsieci, więc D H CP w ym aga, aby w każdej podsieci znajdow ał się serw er tego protokołu albo jeg o pośrednik (ang. relay agent, który działa w im ieniu serw era. Węzeł, rozpoczynając pracę, staje się klientem DHCP. Najpierw rozgłasza kom unikat mający na celu poznanie serwerów DHCP. Klient czeka przez określony czas na oferty od serwerów. Po upłynięciu tego czasu, jeśli nie dostał żadnej oferty, ponawia rozgłoszenie. Jeśli dostał co najmniej jedną ofertę, w ysyła do wybranego serwera żądanie wynajmu ustawień sieciowych. Jeśli w szystko poszło dobrze, serw er potw ierdza wynajem. U stawienia w ynajm ow ane są na określony czas. Jeśli upłynęła połow a czasu wynajmu, klient zwraca się do serwera, od którego wynajął ustawienia, o przedłużenie czasu wynajmu. Jeśli serw er potwierdzi nowy czas wynajmu, to klient zaczyna odliczać go od początku. Jeśli klient nie uzyskał przedłużenia wynajm u i minęło 87,5% czasu wynajmu, klient próbuje przedłużyć wynajem od wszystkich serwerów, które poznał po fazie rozgłaszania. Jeśli mimo to klient nie uzyskał przedłużenia wynajm u i miną! cały czas wynajmu, klient rezygnuje z używania otrzym anych ustawień sieciow ych. W tej sytuacji klient musi rozpocząć całą procedurę od nowa. W praktyce zdarza się to rzadko i zwykle jest spowodowane aw arią serwera DHCP. Klient w każdej chwili może też sam zrezygnować z dalszego wynajmu. Serw er D H CP rozpoznaje klienta za pom ocą identyfikatora przesyłanego w fazie rozgłaszania. Zwykle identyfikatorem jest adres sprzętowy klienta. Strategia przy
dzielania adresu IP może być konfigurow ana. Serw er może przydzielać klientowi zaw sze ten sam adres. U żywanie klienta DHCP za pom ocą biblioteki lwip opisałem w rozdziale 3.4,5ę O protokole UDP, rozgłaszaniu i modelu klient-serw er piszę więcej w dalszych rozdziałach. Programowanie w modelu klient-serw er
144 4. Programowanie w modelu klient-seięk 4.1. Numer portu 145 Serw er to program kom puterow y św iadczący jakąś usługę sieciową. Przykłada5 usług najczęściej oferowanych w sieciach kom puterow ych są wysyłanie e-matí przesyłanie stron www, przesyłanie plików czy tłum aczenie nazw domenowych! adresy IP. Klient to program kom puterow y korzystający z usługi oferowanej pr2 serwer. Serwerem często też nazywa się kom puter (węzeł sieci, na którym dziff serwer. Podobnie klientem można nazywać węzeł, na którym działa klient, je'^j z kontekstu wynika, o które z tych znaczeń chodzi. Z punktu w idzenia komunik cji sieciowej serw er jest stroną pasyw ną, a klient stroną aktywną. Serw er o czek na kom unikaty od klienta. Klient inicjuje kom unikację sieciow ą z serwerem, v.y syłając prośbę lub żądanie realizacji określonej usługi..serwer realizuje tę usługi odpow iadając klientowi na otrzym ane od niego komunikaty. W tym modelu serwe* jest często centralnym węzłem. Jeśli klienty m uszą kom unikować się ze sobą, ((j serw er pośredniczy w ich kom unikacji - klient nie może wysiać komunikatu bèzi' pośrednio do innego klienta. M odel klient-serw er przedstawiony jest schematycznie' na ry su n k u 4.1. Jak zobaczym y w dalszej części tego rozdziału, protokoły transí portowe intersieci i interfejs program istyczny biblioteki lwjp tak zaprojektowano!1 aby wspierać ten model kom unikacji. Interfejs program istyczny biblioteki IwIP nić jest tu bynajm niej wyjątkiem. Inne interfejsy program istyczne, w tym powszechnie', stosow any interfejs gniazd, też preferują model klient-serwer. Innym m odelem kom unikacji, często spotykanym w intersieciach, jest model równy z równym (ang. peer-to-peer. M odel ten jest pokazany na ry su n k u 4.2. W tym' modelu wszyscy uczestnicy są rów nopraw ni, nie ma w yróżnionego żadnego cen-1' tralnego węzła. Dzięki temu model ten jest bardziej odporny na aw arie niż model.' klient-serwer. W modelu klient-serw er w yłączenie centralnego serwera uniemożliw wia jakąkolw iek dalszą kom unikację. Pierwotnie Internet miał być siecią wojskową;? a potem akadem icką, odporną na aw arie pojedynczych komputerów i byl projekto- Rys. 4.1. Wlodel klient-serwer Rys. 4.2. Model równy z równym wany jako sieć rów nopraw nych węzłów. Jednak postępująca kom ercjalizacja sieci sprawia, że obecnie większość usług oferow anych w Internecie opiera się na m o delu klient-serwer. Z punktu widzenia program isty model klient-serw er jest bardzo ważny również dlatego, że aplikacje działające w modelu równy z równym używają w warstwie transportowej modelu klient-serwer. Transport kom unikatów między dw om a instancjami aplikacji odbyw a się zawsze w modelu klient-serwer. W danym mom encie jedna instancja pełni rolę klienta, a druga serwera. Różnica w stosunku do czystego m o delu klient-serw er jest taka, że aplikacja może dow olnie często zmieniać pełnioną funkcję. Ponadto ta sam a instancja aplikacji m oże w stosunku do jednej instancji odgrywać rolę klienta, a w stosunku do innej rolę serwera. Taka aplikacja, pełniąca w sieci obie funkcje, jest po angielsku określana term inem servent. Termin ten powstał z połączenia dwóch angielskich terminów - początkowej części słowa server z końcow ą częścią słowa client. Jak w przypadku serwera i klienta, servent może też. oznaczać węzeł, na którym działa taka aplikacja. Należy przypom nieć, że rzeczow nik klient w znaczeniu inform atycznym przyjmuje w m ianowniku i bierniku liczby mnogiej formę klienty. W tym znaczeniu odmienia się tak sam o jak rzeczow nik pilot, oznaczający urządzenie do zdalnego sterowania. Numer portu Adres IP identyfikuje jednoznacznie interfejs sieciowy, a dzięki temu również w ę zeł sieci. N atom iast w rzeczyw istości to nie węzły sieci komunikują się ze sobą, a aplikacje uruchom ione w tych węzłach. D latego potrzebny jest dodatkowy poziom adresowania, um ożliwiający w skazanie aplikacji, która wysyła dane i do której te dane są skierowane. Protokoły transportowe intersieci, czyli TCP i UDP, używ ają w tym celu portów. Port jest abstrakcyjnym końcem komunikacji. Porty są num erowane liczbami całkow itym i od 1 do 65 535. Num er portu można więc zapisać jako liczbę 16-bitową. Klient, który inicjuje kom unikację z serwerem, może wybrać dowolny num er portu. Serwer, aby można się było z nim skom unikować, musi nasłuchiwać na porcie o pow szechnie znanym numerze. Dlatego num er portu, na którym nasłuchuje serwer, identyfikuje też usługę św iadczoną przez ten serwer i używany przez niego protokół. A ktualna lista przydzielonych numerów portów znajduje się pod adresem http://www.iana.org/assignm ents/port-num bers. W ta b e li 4.1 zam ieszczone są najpopularniejsze usługi internetowe wraz z przydzielonym im numerem portu i używ anym protokołem transportowym. Krótki opis usługi wymaga kilku wyjaśnień. W FTP (ang. File Transfer Protocol serwer nasłuchuje na porcie 21, ale to połączenie jest używ ane tylko do przesyłania poleceń. W celu przesiania pliku budowane jest oddzielne połączenie między klientem a serwerem. W trybie aktywnym serw er inicjuje takie połączenie z portu 20 na uzgodniony wcześniej port klienta. W trybie pasyw nym połączenie, którym będzie przesyłany plik, inicjuje klient. W niektórych protokołach, np. DHCP, również klient musi nasłuchiwać odpowiedzi od serwera na porcie, którego numer jest pow szechnie znany. W pozostałych przypadkach (patrz tabela 4.1 podany jest numer portu, na którym serwer odbiera komunikaty od klienta.
146 4. Programowanie w modelu klient-seąm, 4.2. TCP 147 Tab. 4.1. Niektóre powszechnie znane numery portów i przyporządkowane im usługi Numer portu 4.2. TCP Protokół transportowy Protokół warstwy aplikacji Królkl opis usługi 20 TCP FTP Przesyłanie plików. 21 TCP FTP Przesyłanie plików 22 TCP SSH Zdalny terminal, transmisja szyfrowana 7$ 23 TCP TELNET Zdalny terminal, transmisja nieszyfrowana "71 25 TCP SMTP Wysyłanie poczty elektronicznej 53 UDP DNS Tłumaczenie adresów domenowych na adresy IP i er: u wrotnie 67 UDP DHCP Konfigurowanie ustawień sieciowych, port serwera iąi 68 UDP DHCP Konfigurowanie ustawień sieciowych, port klienta ^ 69 UDP TFTP Prosty protokół przesyłania plików T T 80 TCP HTTP Przesyłanie stron www } '< 110 TCP POP3 Odbieranie poczty elektronicznej ' ą 123 UDP NTR SNTP Synchronizowanie czasu Komunikaty w ym ieniane między klientem a serwerem są identyfikowane za mocą czterech wartości: adres IP klienta, numer portu klienta, adres IP serwcraj numer portu serwera. Dzięki temu wiele klientów działających w jednym węźle s ci może kom unikować się rów nocześnie z tym sam ym serwerem. M im o że wtcciyj wszystkie klienty używ ają tego sam ego adresu IP, można je rozróżnić za pomocy numeru portu. U żywanie w intersieciach jako adresu źródłowego lub docełowegój łącznie adresu IP i numeru portu zaciera nieco granicę między w arstw ą sieciow^ a transportową. T C P (ang. Transmission Control Protocol jest głów nym protokołem transporto wym inlersieci, co wyraża się w pow szechnie stosowanej nazwie stosu protokołów intersieci, czyli TCP/IP. Podstaw ow a wersja TCP jest opisana w RFC 793. TCP1; przyjęto jako standard intersieci STD 7. 4.2.1. Podstawowe własności, / 1.'. TCP jest protokołem strum ieniow ym. Przesyłany strumień danych nie ma żadnej»i struktury. Oznacza to, że jeśli aplikacja przekazuje dane do w ysiania w jakichś por-'1 cjach, to nie ma gwarancji, że aplikacja, która te dane odbiera, odczytu je w takich;.! sam ych porcjach. TCP przesyła sw oje dane w datagram ach IP. IP jest protokoienhí pakietowym, więc TCP musi dzielić przesyłany strumień danych na porcje, nazyv^ wane segm entam i, m ieszczące się w polu danych datagramów IP. TCP optymalizuje#! rozm iar segm entów danych, aby m aksym alnie wykorzystać przepustowość sieciajg TCP stara się najlepiej w ykorzystać MTU sieci i dzieli wysyłany strumień danych:'^ na segm enty o największym m ożliw ym rozm iarze, który nie spow oduje jeszc/e fragmentacji datagram ów IP. W tym celu TCP buforuje przesyłane dane. Dane.ii przekazyw ane do wysiania przez aplikację mogą być łączone w w iększe porcje lub.; dzielone na m niejsze kawałki. 4.2.2. TCP, w przeciwieństwie do IP, jest protokołem połączeniowym. Zanim rozpocznie się przesyłanie danych, trzeba zbudow ać połączenie. Budowanie połączenia jest asym etryczne. Jedna strona (zw ykle nazyw ana serwerem w ykonuje otwarcie pasywne, czyli nasłuchuje na wybranym porcie. Druga strona (zw ykle nazywana klientem wykonuje otw arcie aktywne, czyli inicjuje połączenie z aplikacją, która wykonała wcześniej otw arcie pasyw ne. D la każdego zestawionego połączenia oprogram ow anie stosu TCP/IP utrzym uje w pam ięci pew ne struktury danych, m.in. bufor nadawczy i odbiorczy. Zw olnienie tych struktur następuje po zamknięciu połączenia. TCP korzysta z zaw odnego dostarczania oferow anego przez IP. Segmenty TCP są kapsulkowane w datagram ach IP. Żeby zapewnić, że wszystkie dane dotrą do odbiorcy, TCP potw ierdza otrzym anie danych i retransm ituje te segmenty, które nie zostały potw ierdzone przez określony czas. W celu w yznaczenia czasu, po którym ma nastąpić retransm isja (RTO, ang. retransmission timeout, TCP estym uje średni czas od wysiania segm entu do otrzym ania potw ierdzenia jego odbioru (ang. SRTT - sm oothed round trip time i w ariancję tego czasu (RTTVAR, ang. round trip time variation. Do pomiaru brane są tylko czasy dla segmentów, które nie były retransm itowane, gdyż dla retransm itowanego segm entu nie można stwierdzić, której z wysianych jego kopii dotyczy odebrane potwierdzenie. Czas, po którego upływie następuje retransm isja, jest obliczany ze wzoru RTO = SRTT + max(g, 4 RTTVAR, gdzie G (ang. granularity jest rozdzielczością zegara używ anego do pomiaru czasu. Jako początkową wartość RTO, zanim wyznaczy się estymaty SRTT i RTTVAR, przyjm uje się 3 sekundy. Szczegóły są opisane w RFC 2988. Dzięki dynam icznemu w yznaczaniu czasu retransm isji T C P skutecznie adaptuje się do sieci o różnych opóźnieniach transm isji. TCP um ożliw ia kom unikację dw ukierunkow ą. Dane mogą być transm itow ane jed nocześnie w obu kierunkach połączenia. Segm ent przesyłany w jednym kierunku zaw iera potw ierdzenie danych w ysłanych w przeciw nym kierunku. Nagłówek Segm ent TCP składa się z nagłów ka i następującego po nim pola danych, przy czym pole danych jest opcjonalne - m oże go nie być, jeśli nie ma żadnych danych do wysiania. Cały segm ent TCP jest przesyłany w polu danych datagram u IP. Form at podstawowej wersji nagłówka jest przedstaw iony na ry su n k u 4.3. Pierwsze dwa pola, zgodnie z ich nazwami, zawierają num er portu nadawcy i odbiorcy. Pole DL. NAGŁ. zaw iera długość nagłów ka w stówach 32-bilowych. Specyfikacja określa, że pole to zawiera przesunięcie części z danym i w ewnątrz segm entu (ang. data offset. N agłów ek może być rozszerzony o pola zaw ierające dodatkow e opcje, ale zaw sze musi mieć długość, która jest w ielokrotnością 4 oktetów. Podstawow a wersja nagłów ka ma 20 oktetów, więc wtedy pole DL. NAGŁ. ma wartość 5. Bity, które zarezerwow ano do przyszłego wykorzystania, powinny być równe zeru. Pole SUM A KONTROLNA służy zapew nieniu integralności segmentu. Jest obliczana na podstawie nagłówka TCP, pola danych TC P i pseudonagłówka utworzo-
148 4. Programowanie w modelu klient-serwejf- NR PORTU NADAWCY [0:15] NUMER PORZĄDKOWY [0:311 NUMER POTWIERDZENIA [0:31] NR PORTU ODBIORCY [16:31] DL. NAGL. zarezerwowane ZNACZNIKI ROZMIAR OKNA [0:4] [5:7] [8:15] [16:31] SUMA KONTROLNA WSKAŹNIK DANYCH PILNYCH [0:15] [16:31] Rys. 4.3. Format nagłówka TCP bez dodatkowych opcji nego na podstawie nagłów ka IP. Pseudonagłów ek składa się z adresów IP nadawcys i odbiorcy, pola PROTO KÓ Ł z nagłów ka dalagram u IP oraz pola zawierającego'! obliczoną długość całkow itą segm entu TCP. Po szczegóły dotyczące dodatkowych ; opcji nagłówka TCP i sposobu liczenia sumy kontrolnej odsyłam do RFC 793.it Zauważm y, że w nagłówku nie ma informacji o dlugos'ci pola danych. Ta długość ;! musi być obliczona. Od całkow itej długości datagramu IP trzeba odjąć długości' nagłówków IP i TCP. Sposób liczenia długości pola danych segm entu TCP i sumyf kontrolnej TCP są kolejnym i przejawam i ścisłego związku protokołów IP i TCP,1, oraz braku w intersieciach ostrej granicy między warstwą sieciow ą a transportową,1? IP nie gwarantuje, że datagram y dotrą do odbiorcy w tej samej kolejności, w kuuej zostały wysłane. TCP, aby zapew nić, że dane dotrą do odbiorcy we właściwej kolej-;; ności, numeruje przesyłane oktety m odulo 232. Num eracja jest prow adzona osobno?;? dla każdego kierunku transmisji. Pole N UM ER PO RZĄ DKOW Y (ang. sequence: j num ber zaw iera num er pierw szego oktetu w ysyłanego w bieżącym segmencie ^ Pole N UM ER PO TW IERDZEN IA (ang. acknow ledgm ent num ber zaw iera numery następnego oczekiwanego oktetu. Innym i słowy, potw ierdza popraw ne odebranie.? oktetów o poprzednich numerach. Pole to jest ważne tylko wtedy, gdy ustawio-, ny jest znacznik ACK (ang. acknow ledgm ent, patrz tabela 4.2. Pole ROZMIAR?/ OKNA określa ilość miejsca w buforze odbiorczym, służy do kontroli przepływu,:n inform uje drugą stronę kom unikacji, ile oktetów danych może być odebranych.':;' T C P dopasow uje rozm iar okna do szybkości przetwarzania danych przez aplikację??; Jeśli dane napływ ają szybciej, niż aplikacja jest w stanieje czytać, bufor odbiorc/y zapełnia się i TCP wstrzymuje drugą stronę kom unikacji, wysyłając zerowy rozm iar okna. W znow ienie przesyłania, czyli zaproponow anie niezerowego rozmia-' ru okna nastąpi wtedy, gdy w buforze odbiorczym zrobi się miejsce na segment? o maksym alnym rozm iarze. W ten sposób T C P unika nieefektyw nego przesyłaniu? małych porcji danych. Podsum ow ując, odebranie potw ierdzenia i niezerowego rozmiaru okna oznacza, że druga strona kom unikacji oczekuje na oktety o numerach..?! od N UM ER PO TW IERD ZEN IA do (NUM ER PO TW IERDZEN IA + R O ZM IA R '' OKNA - 1 mod 2n. C zasem zachodzi potrzeba przesłania danych poza głów nym strum ieniem transjj m isyjnym, na przykład gdy trzeba przerw ać działanie aplikacji, z którą się komuą, nikujenty. W T C P przew idziano w tym celu m ożliw ość przesiania danych pilny Tab. 4.2. Znaczniki TCP Znacznik Bit Opis CWR 8 Okno przeciążeniowe zosiato zredukowane (ang. congestion window reduced ECE 9 Potwierdza informację o przeciążeniu (ang. explicit congestion echo URG 10 Przesyłany segment zawiera dane pilne (ang. urgent ACK 11 Pole zawierające numer potwierdzenia jest Istotne PSH 12 Ten segment jest prośbą o wypchnięcie (ang. push danych. Poinformuj aplikację, że powinna przeczytać dane z bufora odbiorczego RST 13 Natychmiast skasuj połączenie w obu kierunkach SYN 14 FIN 15 Synchronizuj numer porządkowy. Tylko pierwszy komunikat wystany w danym kierunku może mieć ustawiony ten bit Oznacza koniec strumienia danych u nadawcy i początek procedury zamykania połączenia. Każdy kierunek transmisji jest zamykany osobno (ang. urgent. Pole W SK A ŹN IK DANYCH PILNYCH określa numer oktetu, od którego w przesyłanym segm encie rozpoczynają się dane pilne. Pole to jest ważne tylko wtedy, gdy ustawiony jest znacznik URG, patrz tabela 4.2. TC P nie określa dokładnie, jak aplikacja ma być poinform ow ana o odebraniu danych pilnych. Szczegóły pozostaw iono im plem entatorom stosu. Biblioteka lwip nie obsługuje danych pilnych. Znaczniki przesyłane w nagłówku TCP są opisane w tabeli 4.2. Pierwotnie w RFC 793 bity numer 8 i 9 w polu znaczników były zarezerwowane do przyszłego wykorzystania i pow inny mieć wartość 0. W RFC 3168 do obsługi zatorów zdefiniowano nowe znaczniki CW R i ECE, które um ieszczono w tych bitach. Nie opisuję ich, gdyż w bibliotece lwip nie zaim plem entow ano ich obsługi. W yzerowanie tych bitów zapew nia poprawną w spółpracę z oprogram ow aniem, które je obsługuje. Użycie pozostałych znaczników w yjaśniam w kolejnych podrozdziałach. 4.2.3. Otwieranie połączenia Diagram sekwencji otw ierania połączenia TCP jest przedstawiony na ry su n k u 4.4. W idzimy na nim zaznaczone schem atycznie dw ie instancje biblioteki lwip. Po lewej stronie um ieszczone są funkcje klienta, a po prawej funkcje serwera. Strzałka skierowana od funkcji do biblioteki oznacza, że jest to funkcja biblioteki lwip wywołana przez klienta lub serwer. Strzałka skierow ana od biblioteki do funkcji oznacza, że jest to funkcja zwrotna, w ywołana przez bibliotekę lwip. Strzałka z gwiazdką oznacza, że to wywołanie jest opcjonalne. Pośrodku, między instancjami biblioteki lwip, narysowane są segm enty TCP w ym ieniane między nimi. Dla przejrzystości diagram nie uw zględnia obsługi błędów, których jednak nie wolno ignorować. Należy bezwzględnie sprawdzać wartości zw racane przez funkcje i właściwie reagować, jeśli jakaś funkcja zakończy się niepow odzeniem. Przykłady klientów i serwerów prezentow ane w następnych rozdziałach obsługują błędy. Biblioteka lwip przechow uje inform acje o stanie połączenia TCP w strukturze typu tcp_pcb (ang. TCP protocol control błock. K lient rozpoczyna swoje działanie od wywołania funkcji tcp_new, która przydziela nową strukturę typu tcp_pcb i zwraca w skaźnik do niej. W skaźnik ten jest następnie przekazyw any w szystkim funkcjom obsługującym TCP. Przydzieloną strukturę typu tcp_pcb nazywam dalej deskryptorem TCP.
150 4. Programowanie w modelu klient-serweł 4.2. TCP 151 Klient pcb «tcp_new{ --> tcp_bind(pcb, addr, port *-> tcp_arg(pcb, arg *-> tcp_err(pcb, conn_err_callback > tcp_rgcv(pcb, recv_callback *-> tcp_sent(pcb, sent_calłback *-> tcp_poll(pcb, poll_callback, time *-> tcp_connect(pcb, addr, port, connect_callback > connect_callback(arg, pcb, err < tcp_recv(pcb, recv_callback *-> tcp_sent(pcb, 3ent_calłback *-> tcp_poll(pcb, polł_callback, time ł-> Rys. 4.4. Otwieranie połączenia TCP Iw/P SYN(/?k > SYN(ns, ACK(nk + 1 A CK(ns + 1 IwIP < pcb = tcp_new( Serw er <-- tcp_bind(pcb, addr, port < li3ten_pcb» tcp_listen(pcb <-* tcp_arg(listen_pcb, arg <-- tcp_accept (Xisten_pcb, accept_c.i iibackjf > accept_callback(arg, conn_pcb, err <-* tcp_arg(conn_pcb, conn_arg <-- tcp_err(conn_pcb, conn_err_callback <-- tcp_recv (conn_pcb, recv cailback <-* tcp_sent(conn_pcb, sent callback < - tcp_poll (conn_pcb, połl jjallback, tim< i Klient wywołuje funkcję tcp _bind, aby wybrać adres IP i port, których będzie używał. Oczywiście można wybrać tylko adres IP, który został skonfigurowany. Adres, 1P jest zawsze związany z określonym interfejsem sieciowym. Zatem w ybór adresu determ inuje też interfejs, przez który będzie odbyw ać się komunikacja. Jeśli jest tylko jeden adres IP, to w ywołanie tej funkcji można pominąć. K lient m oże wybrać dowolny port. Jeśli nie w ywołam y funkcji tcp _ b in d, biblioteka przydzieli klientowi jakiś wolny port. Za pomocą funkcji tcp_ arg m ożna ustawić w deskryptorze TC P dodatkow y argum ent arg, który będzie przekazyw any wszystkim funkcjom obsługującym TCP. Argum ent ten najczęściej jest w skaźnikiem do struktury przechowującej prywatne dane klienta. Dla każdego połączenia TC P przydzielam y nową instancję tej struktury. Dzięki temu za pom ocą wspólnej funkcji zwrotnej m ożna obsługiw ać symultanicznie wiele klientów. Funkcja tcp err rejestruje funkcję zw rotną c o n n _ e rr_ c a łlb a c k, która będzie wywołana przez bibliotekę IwIP, gdy w ystąpi jakiś błąd, np. próba otw arcia połączenia zakończy się niepow odzeniem lub połączenie zostanie skasowane. W tej funkcji zwrotnej trzeba zwolnić całą pam ięć przydzieloną klientowi, a w szczególności. strukturę przechow ującą pryw atne dane klienta. Funkcje tc p _ re c v i tc p _ s e n t rejestrują funkcje zwrotne re c v _ c a llb a c k i sent c a llb a c k, które będą wołane odpow iednio, gdy zostaną odebrane dane lub zostanie odebrane potwierdzenie dostarczenia danych. Funkcja tc p p o ll rejestruje funkcję zwrotną p o ll_ c a łlb a c k, która będzie wywołana cyklicznie w ustalonych odstępach czasu tim e, co um ożliwia zaim plem entow anie budzika w aplikacji klienta. Funkcje. te są opcjonalne i trzeba ich użyć tylko wtedy, gdy potrzebna je st odpow iednia funkcja zwrotna. Jeśli klient chce odbierać dane od serwera, to oczywiście musi zarejestrować funkcję re c v _ c a llb a c k. Te funkcje zw rotne można też zarejestrować później, gdy ju ż zostanie otw arte połączenie. W łaściw e otwieranie połączenia przez klienta rozpoczyna się od wywołania funkcji te p connect. Jako jej argum enty podaje się adres IP i numer portu serwera oraz adres funkcji zwrotnej co n n e c t_ c a llb a c k, która zostanie w ywołana, gdy serwer odpowie pozytyw nie na prośbę otw arcia połączenia. Funkcja tc p co nnect tylko inicjuje otw ieranie połączenia i nie czeka na zakończenie procedury otwierania. Otwarcie połączenia TCP wym aga zsynchronizow ania num erów porządkowych. W tym celu w ym ieniane są trzy segmenty. Rozpoczyna biblioteka IwIP po stronie klienta, w y syłając pierwszy segm ent zaw ierający sam nagłów ek TCP, bez pola danych, z ustawionym znacznikiem SYN i początkowym num erem porządkow ym nk, nazywanym w skrócie segm entem SYN. Biblioteka IwIP po stronie serwera odpow iada drugim segm entem, który zaw iera sam nagłówek T C P z ustawionymi znacznikam i SYN i ACK. Ten drugi segm ent jest w skrócie nazywany segm entem SYN+ACK. Za pom ocą tego segm entu strona serw era proponuje swój początkowy num er porządkowy ns i potw ierdza, że oczekuje pierw szego oktetu o num erze nk + 1, um ieszczając tę wartość jako num er potw ierdzenia. G dy strona klienta odbierze ten segm ent, w ywołana jest funkcja zwrotna co n n e c t_ c a llb a c k. Na koniec, po zakończeniu tej funkcji biblioteka IwIP po stronie klienta za pom ocą trzeciego segm entu potw ierdza, że oczekuje pierwszego oktetu o num erze ns + 1, umieszczając w nagłówku TCP tę wartość jako num er potw ierdzenia i ustawiając znacznik ACK. Ten trzeci segm ent jest nazywany w skrócie segm entem ACK. Od tego mom entu połączenie jest otw arte i można przesyłać dane w obu kierunkach. W łaściwie to klient może ju ż w ysiać dane w segm encie ACK, kończącym procedurę otw ierania połączenia. W funkcji c o n n e c t_ c a lib a c k m ożna opcjonalnie wywołać funkcje tcp recv, tcp s e n t i tc p _ p o ll, jeśli odpow iednie funkcje zw rotne nie zostały dotychczas zarejestrowane. Jeśli funkcja tcp _ co n n e ct zwróci wartość ERR_0K i zbudow anie połączenia nie pow iedzie się, czyli nie dotrze praw idłowy segm ent SYN+ACK, to zamiast funkcji c o n n e c t_ c a llb a c k zostanie w yw ołana funkcja c o n n _ err_ callb ack. Ze względów bezpieczeństw a początkowe numery porządkow e powinny być wybierane pseudolosow o, aby utrudnić ataki na TCP, czyli np. nieuprawnione w strzelenie segm entów w strumień danych (ang. TCP session hijackitig lub nieuprawnione zestawienie połączenia (ang. TCP spoofing. Serwer, podobnie jak klient, rozpoczyna działanie od wywołania funkcji tcp_new, która przydziela nowy deskryptor TCP. Następnie za pom ocą funkcji tcp _ b in d serwer w ybiera adres IP i port, na których będzie nasłuchiwał połączeń od klientów. Funkcja tc p _ lis te n uaktywnia kolejkę klientów oczekujących na zaakceptowanie połączenia patrz opis stałej MEMP_NUM_TCP_PCB_LISTEN w pliku Iwiopts.h. Należy zwrócić uwagę, że funkcja tc p _ lis te n przydziela nowy deskryptor TCP. Uwaga, to wskaźnik zwrócony przez funkcję te p l i s t e n, a nie w skaźnik zwrócony przez funkcję te p new, musi być przekazany jako argum ent dwóm następnym wołanym funkcjom. Funkcja tcp accept rejestruje funkcję zwrotną a ecep ted _ callb ack, która będzie w ywołana po zaakceptowaniu połączenia od klienta. Funkcja te p arg
152 4. Programowanie w modelu klient-serwer ustawia w deskryptorze TCP argum ent arg, który zwykle jest w skaźnikiem do prywalnych danych serwera i który będzie przekazywany funkcji accep ted _ callb ack. i co um ożliwia obsługę wielu serwerów za pom ocą wspólnej funkcji zwrotnej. Każdy 'i serw er może mieć sw oją pryw atną strukturę danych. Jeśli biblioteka lw lp po stronie serwera zaakceptuje nowe połączenie od klienta' tworzy dla tego połączenia nowy deskryptor TCP i wywołuje funkcję zwrotną ac- a c e p t_ c a llb a c k, której drugim argum entem jest w skaźnik do tego nowego deskryp-j! tora. Każde połączenie TCP ma swój unikalny deskryptor. W funkcji accep t ca' Iback trzeba zarejestrować funkcje zwrotne, które będą wołane dla tego nowego.!* połączenia. Najpierw jednak funkcja tc p _ a rg ustawia w deskryptorze TCP argu- f rnent conn_arg, który będzie przekazyw any funkcjom zwrotnym. Argum ent ten jest zw ykle wskaźnikiem do alokowanej w ewnątrz funkcji a c c e p t_ c a llb a c k struktury: zawierającej pryw atne dane zw iązane z połączeniem, co um ożliw ia obsługę wielu połączeń za pomocą wspólnych funkcji zwrotnych. Podobnie jak w przypadku i klienta, funkcja tc p _ e r r rejestruje funkcję zwrotną co n n _ e rr_ c a llb a c k wołaną, gdy wystąpi błąd. Funkcje tc p _ re c v i tc p _ s e n t rejestrują funkcje zwrotne recv c a llb a c k i s e n t_ c a llb a c k wołane odpowiednio, gdy zostaną odebrane dane lub' zostanie odebrane potw ierdzenie dostarczenia danych. Funkcja tc p p o ll rejestruje cyklicznie wołaną funkcję zw rotną p o ll_ c a llb a c k, co um ożliw ia zaimplementowanie budzika w aplikacji serwera. 4.2.4. Przesyłanie danych Sekw encja zdarzeń tow arzysząca przesyłaniu danych za pom ocą TCP przedstawio-" na jest na ry su n k u 4.5. Poniew aż wym iana danych jest sym etryczna, komunikujące, się strony są oznaczone literami A i B. W konkretnym przypadku A jest klientem, a B serwerem albo odw rotnie - A jest serwerem, a B klientem. Strona A przekazuje dane do w ysiania za pom ocą funkcji tc p _ w rlte. TCP tyl-lk ko pierwszą porcję danych w nowo otwartym połączeniu w ysyła natychmiast, i: N atom iast z wysianiem kolejnych porcji czeka, aż uzbiera się dostatecznie dużo : danych, aby wypełnić segm ent o m aksym alnym rozm iarze. Ponadto TCP wysyła zgrom adzone w buforze nadaw czym dane, niezależnie od ich rozm iaru, równie/ wtedy, gdy otrzym a potw ierdzenie otrzym ania poprzednio wysianych danych. Taka" strategia zm niejsza liczbę przesyłanych segm entów i narzut zw iązany z wysyłaniem., m ałych porcji danych, unikając jednocześnie sytuacji, w której dane czekałyby zbyt,: długo w buforze nadawczym. A plikacja może nigdy nie wstawić do wysiania w y-. starczająco dużej porcji danych, aby zapełnić segm ent o m aksym alnym rozmiarze TC P dobrze w spółpracuje zarówno z aplikacją, która w ysyła dane w dużych por1:: i 1: A lwlp lwlp B tcp_write(pcb, data, len, flags > tcp_write(pcb, data, len, flags > tcp_outpiłt (pcb *-> sent_callback(arg, pcb, len <-* Rys. 4.5. Przesyłanie danych TCP [A C K ] data...> ACK [data] <... > recv_całlback(arg, pcb, pbuf, erii 1 < tcp_recved(pcb, len cjach, jak i aplikacją, która wysyła dane znak po znaku. N atychmiastowe wysianie danych można wymusić za pom ocą funkcji tc p _ o u tp u t. Biblioteka lw lp wywołuje tę funkcję przy okazji obsługi różnych zdarzeń. Jeśli funkcja tc p _ w rite jest w y wołana w ewnątrz jakiejś funkcji zwrotnej, to zwykle nie trzeba wywoływać funkcji tcp _ o u tp u t. N atom iast jej wywołanie może okazać się konieczne, gdy tc p _ w rite jest w ywołana spoza biblioteki lwlp. Strona B odbiera dane za pom ocą funkcji zwrotnej re c v _ c a lłb a c k. W ewnątrz tej funkcji po przetworzeniu odebranych danych trzeba poinformować o tym bibliotekę lw lp za pom ocą funkcji tcp_recved, która oblicza nowy rozm iar okna odbiorczego po stronie B. Jeśli strona A zarejestrowała funkcję zwrotną se n t_ c a llb a c k, to za jej pom ocą zostanie poinform ow ana o otrzym aniu od strony B potwierdzenia odebrania danych. TCP przesyła dane łącznie z potw ierdzeniem otrzym ania danych przesianych w drugim kierunku. Strona A, wysyłając segm ent danych do B, równocześnie informuje B o numerze oktetu, którego oczekuje od B, ustawiając w nagłówku TCP znacznik ACK i odpowiedni numeru potwierdzenia. Strona B po otrzymaniu od A segmentu danych potw ierdza go i jednocześnie w tym samym segm encie wysyła do A dane, które czekają na w ysianie w buforze nadaw czym B. Nie każdy segm ent musi być potwierdzony. Specyfikcja TCP nakazuje potw ierdzanie przynajmniej co drugiego segmentu. Zauważmy, że potw ierdzenie danych otrzym anych w jakim ś segmencie oznacza też polw ierdzęnie w szystkich danych z poprzednich segmentów. 4.2.5. Zamykanie połączenia Sekw encja zdarzeń podczas zwykłego zam ykania połączenia TCP jest pokazana na ry su n k u 4.6. N atom iast na ry su n k u 4.7 przedstaw ione jest awaryjne zamykanie (kasowanie połączenia TCP. Każda ze stron może zainicjować zamknięcie połączenia. D latego na tych diagram ach strony komunikacji są oznaczone, jak poprzednio, literami A i B. Strona A inicjuje zw ykle zam knięcie połączenia, wywołując funkcję tc p _ c lo se. Po wywołaniu tej funkcji do B jest wysyłany segm ent FIN, czyli segm ent, w którego nagłówku TC P jest ustawiony znacznik FIN, co oznacza, że rozpoczyna się zamykanie połączenia w kierunku od A do B i strona A nie będzie już wysyłać do B żadnych danych. Natom iast strona A nadal m oże odbierać dane od B i po wywołaniu przez A funkcji tc p _ c lo se biblioteka lw lp może wywołać funkcję zwrotną recv_ c a llb a c k, jeśli pojawią się jakieś nowe dane od B. Jeśli nie chcemy otrzymywać A lwlp lwlp B R N tcp_close(pcb --> --------- > > recv_callback(arg ACK callback(arg, pcb, pbuf, err <-* < --------- Rys. 4.6. Zamykanie połączenia TCP FIN < --------- < tcp_close(pcb ACK --------- >
154 4. Programowanie w modelu klient-seńw A IwIP IwIP B RST tcp abort{pcb > > conn_err_callback(arg, conn_err_callback(arg, err < Rys. 4.7. Awaryjne zamykanie połączenia TCP danych po wywołaniu tc p _ c lo se, trzeba wcześniej w yrejestrow ać funkcję zwr< za pom ocą funkcji tcp_recv, podając NULL jako adres nowej funkcji zwrotnej. Strona B, otrzymawszy znacznik FIN, odsyła segm ent ze znacznikiem A'' " Aplikacja po stronie B o zam knięciu połączenia jest inform ow ana dopiero po czytaniu wszystkicli danych z bufora odbiorczego. O dbywa się to za pomocą koś lejnego wywołania funkcji zwrotnej re c v _ c a llb a c k, w której w skaźnik do buforai z odebranym i danymi ma wartość NULL. W ewnątrz tej funkcji zwrotnej strona 13 zwykle zam yka połączenie w kierunku od B do A, wywołując funkcję tc p clor c, co inicjuje wysłanie do A segm entu ze znacznikiem FIN. Strona A, po otrzyma^ niu takiego segm entu, odsyła segm ent ze znacznikiem ACK. To kończy procedurę; zwykłego zamykania połączenia. O program ow anie TCP po obu stronach zwalnia zasoby biblioteki IwIP zw iązane z tym połączeniem, czyli strukturę typu tc p buf; oraz bufor nadawczy i odbiorczy. Połączenie można też zamknąć aw aryjnie (skasować za pom ocą funkcji tcp_aboi t. W tedy w szelkie dane nieodczytane z buforów odbiorczych i czekające na wysłanie w buforach nadawczycli zostają utracone. Strona inicjująca aw aryjne zamknięcie w ysyła segm ent RST (ang. reset, czyli segm ent z ustawionym w nagłówku znaczki nikiem RST. Segment len nie w ym aga potwierdzenia. Funkcja tc p _ a b o rt zwalnia; zasoby biblioteki IwIP zw iązane z połączeniem. Aby rów nież aplikacja mogła zwolnić sw oje zasoby, biblioteka IwIP w yw ołuje bo obu stronach skasowanego połączenia funkcję zwrotną c o n n _ err_ callb ack. 4.2.6. Funkcje biblioteczne Teraz opiszę bardziej szczegółow o i w kolejności alfabetycznej poszczególne funkcje biblioteki iwip w ykorzystywane przy pisaniu aplikacji kom unikujących się z.i pom ocą TCP. void tcp_abandon(struct tcp_pcb *pcb, int reset; Funkcja tcp_abandon kasuje połączenie i zw alnia deskryptor TCP. Pierwszym argumentem tej funkcji jest w skaźnik pcb do deskryptora TCP opisującego kasowane połączenie. Jeśli drugi argum ent r e s e t jest różny od zera, to w ysyłany jest segment RST. W ywołanie funkcji tcp_abandon z argum entem r e s e t równym zeru nic pow oduje żadnej komunikacji sieciow ej. Jest to przydatne przy obsłudze błędów, gdy trzeba zwolnić deskryptor TCP, a połączenie nie zostało jeszcze otwarte. Przed zakończeniem tej funkcji w ywołana je st funkcja zw rotna co n n _ err_ callb ack, w której należy zwolnić przydzielone przez aplikację zasoby zw iązane z kasowanym połączeniem. void tcp_abort(struct tcp_pcb *pcb; Funkcja tc p a b o rt kasuje połączenie i zw alnia deskryptor TCP. Argum ent pcb jest wskaźnikiem do deskryptora TC P opisującego kasow ane połączenie. Aktualna im plem entacja biblioteki IwIP definiuje tę funkcję jako makro: idefine tcp_abort(pcb tcp_abandon((pcb, 2 void tcp accept(struct tcp pcb *pcb, err t (* accept callback(void *arg, struct tcp pcb *conn_pcb, err t err; Funkcja tc p _ a c c e p t rejestruje funkcję zwrotną, którą biblioteka IwIP woła, gdy w stanie pasyw nego nasłuchiw ania zostanie odebrane nowe połączenie. Pierwszym argum entem tej funkcji jest w skaźnik pcb do deskryptora TCP przydzielonego za pom ocą funkcji tc p _ lis te n. Drugi argum ent accept callback jest adresem rejestrowanej funkcji zwrotnej. void tcp_accepted(struct tcp_pcb *pcb ; Funkcja tc p _ a c c e p te d inform uje bibliotekę IwIP, że należy zaakceptować odebrane połączenie. Funkcja ta powinna być w ywołana w ewnątrz funkcji zwrotnej accep t_ c a llb a c k. Argum ent pcb jest w skaźnikiem do deskryptora TCP zwróconego przez funkcję tc p li s t e n, a nie wskaźnikiem do deskryptora TCP nowego połączenia, przekazanym jako drugi argum ent funkcji a c c e p t_ c a lłb a c k, co bardzo kom plikuje logikę program u. Na szczęście funkcja ta musi być w ywołana tylko wtedy, gdy do nasłuchiw ania przychodzących połączeń używam y kolejki o zm iennym rozm iarze, czyli gdy w pliku Iwipopts.h została zdefiniow ana stała TCP_LISTEN_BACKLOG o w artości 1. W małych aplikacjach m ikrokontrolerowych nie wydaje się to potrzebne. D latego proponuję używ anie kolejki o stałym rozm iarze zdefiniow anym w pliku Iwipopts.h za pom ocą stałej MEMP_NUM_TCP_PCB_LISTEN. Aktualna im plem entacja biblioteki IwIP definiuje tę funkcję jako makro, które jest puste, jeśli w pliku Iwipopts.h nie zdefiniujem y stałej TCP_LISTEN_BACKLOG. void tcp_arg(struct tcp_pcb *pcb, void *arg; Funkcja tc p arg przypisuje do deskryptora TCP wskaźnik, który będzie przekazywany funkcjom zwrotnym, wołanym z tym deskryptorem. Pierwszym argum entem tej funkcji jest wskaźnik pcb do deskryptora TCP. Drugim argum entem jest w skaźnik arg, który może być w dowolny sposób wykorzystywany przez aplikację. N ajczęściej jest to w skaźnik do struktury przechow ującej stan komunikacji. Za przydzielenie i zw olnienie pamięci adresowanej tym w skaźnikiem odpowiedzialna jest aplikacja.
156 4. Programowanie w modelu klient-serwem 4.2. TCP 157 err_t tcp_bind(struct tcp_pcb struct ip_addr u!6 t port; pcb, addr, Funkcja tcp_bin d wiąże deskryptor TCP z lokalnym adresem IP i portem. P ierw v\ mi argum entem tej funkcji jest w skaźnik pcb do deskryptora TCP. Drugim argumenteijtff jest wskaźnik addr do struktury zawierającej adres IP. Jeśli argum ent ten ma wartości IP_ADDR_ANY, deskryptor jest w iązany ze wszystkimi skonfigurow anym i w węźla adresami IP. Jest to szczególnie przydatne, gdy węzeł ma wiele interfejsów siecio-i wych (co oznacza, że ma też wiele adresów IP i chcem y uruchom ić serwer, któryś będzie odbierał połączenia na w szystkich interfejsach. Trzeci argum ent p o rt jest'f numerem portu, który ma być użyty. Jeśli argum ent ten ma wartość zero, biblioteka" lwip przydzieli dowolny niezajęty port. Ten argum ent ma zwykle wartość zero, gdyj urucham iam y klienta, gdyż wtedy num er portu jest nieistotny. Dla serwera zwykle* podaje się wartość niezerową, gdyż serw er musi nasłuchiwać na pow szechnie zna-' nym porcie. Funkcja zw raca ERR_OK, gdy zakończyła się sukcesem, lub ERR USEji gdy nie można użyć podanych w artości, np. żądany port jest zajęty. err_t tcp_close(struct tcp^pcb 'pcb; Funkcja tc p _ c lo se inicjuje zam ykanie połączenia i zw alnianie deskryptora TCP; Argum ent pcb jest w skaźnikiem do deskryptora TC P opisującego zamykane połączenie. D eskryptor jest utrzym yw any przez bibliotekę lwip jeszcze przez pewien czas i jest zwalniany dopiero po zakończeniu procedury zam ykania połączenia, ale po wywołaniu tej funkcji aplikacja pow inna przestać go używać. Jedynym wyjął, kiem jest sytuacja, gdy w tym połączeniu są jeszcze jakieś dane do przeczytania i biblioteka lwip przekaże aplikacji w skaźnik do tego deskryptora w funkcji zwrotnej re c v _ c a llb a c k. Funkcja zw raca ERR_OK przy poprawnym zakończeniu lub kod f błędu, gdy wystąpił problem, np. brakuje pamięci, aby wysiać segm ent FIN. Jeśli funkcja ta kończy się niepow odzeniem, deskryptor TCP nie jest zwalniany. err t tcp connect(struct tcp pcb *pcb, struct ip addr *addr, u!6 t port, err t {* connect callback(void *arg, struct tcpjpcb *pcb, err t err; Funkcja tcp _ co n n ect inicjuje aktyw ne otw arcie połączenia i rejestruje funkcję zwrotną, którą biblioteka lwip w ywoła, gdy połączenie zostanie otwarte. Pierwszym argum entem tej funkcji jest w skaźnik pcb do deskryptora TCP, który ma być użyty do otwarcia połączenia. Drugim argum entem jest wskaźnik addr do struktury zawierającej adres IP, z którym chcem y się połączyć. Trzeci argum ent p o rt jest numerem portu, z którym chcem y się połączyć. Czwarty argum ent connect_callbac;i jest adresem funkcji zwrotnej, która będzie w ywołana, gdy zostanie odebrany segment SYN+ACK. Funkcja tcp _ co n n ect nie czeka na otwarcie połączenia i natych-. 1 m #1 > : '{'r mm miast kończy działanie, zw racając ERR_OK, gdy tylko uda się wysłać segment SYN rozpoczynający otwieranie połączenia. Jeśli wysłanie segmentu SYN nie powiodło się, funkcja zwraca kod błędu inform ujący o przyczynie niepowodzenia. void tcp err(struct tcp pcb *pcb, void (* conn err collback(void *arg, err t err; Funkcja tc p _ e r r rejestruje funkcję zw rotną w ołaną przez bibliotekę lwip, gdy połączenie zostanie skasowane. Powodem skasowania połączenia może być na przykład otrzym anie segm entu RST, wywołanie funkcji tcp_abandon, tc p _ a b o rt łub brak pamięci. Pierwszym argum entem tej funkcji jest wskaźnik pcb do deskryptora TCP. Drugi argum ent co n n _ err_ co llb ack jest adresem rejestrowanej funkcji zwrotnej. struct tcp_pcb * tcp^listen(struct tcp_pcb *pcb; Funkcja tc p _ lis te n inicjuje pasyw ne otw arcie, czyli nasłuchiwanie połączeń TCP. Argum ent pcb jest w skaźnikiem do deskryptora TCP, który ma być użyty. Funkcja zwraca nowy deskryptor TCP i zwalnia deskryptor przekazany jako argument. Takie dziw ne zachow anie biblioteki lwip wynika z tego, że deskryptor potrzebny do nasłuchiw ania połączeń zajm uje mniej pamięci. D eskryptor przeznaczony do pasywnego nasłuchiw ania połączeń może być używany przez serwer, ale nie może być używany przez klienta - nie można za jego pom ocą zainicjować aktywnego otw arcia połączenia. Jeśli nie udało się utw orzyć nowego deskryptora, funkcja zwraca NULL. Aktualna im plem entacja biblioteki lwip definiuje tę funkcję jako makro: Idef ine tcp_listen (pcb tcp_listen_with_backlog (pcb, 255 struct tcp pcb * tcp listen_with_backiog (struct tcp_pcb *pcb, u8_t backlog; Funkcja tcp_listen_w ith backlog działa analogicznie jak funkcja tc p _ łis te n, ale ogranicza rozm iar kolejki oczekujących połączeń do wartości podanej jako drugi argum ent backlog. Aby móc używać tej funkcjonalności, trzeba w pliku Iwipopts.h zdefiniować stałą TCP_LISTEN_BACKLOG o wartości 1. struct tcp_pcb * tcp new(void; Funkcja tcp_new przydziela nowy deskryptor TCP. Zwraca wskaźnik do nowego deskryptora lub NULL, gdy nie udało się przydzielić nowego deskryptora. err t tcp output (struct tcp_pcb *pcb; Funkcja tc p _ o u tp u t w ym usza wysłanie segm entów czekających w buforze nadawczym. Argum ent pcb jest w skaźnikiem do deskryptora TCP opisującego połączenie, przez które dane mają być wysiane. Funkcja zwraca ERR_OK, gdy wysianie powiod-
158 4. Program owanie w modelu klient-seię 4.2. TCP 159 io się lub nie ma nic do wysiania. W przeciwnym przypadku zw raca właściwy ko błędu. Funkcja ta jest używana w ew nątrz biblioteki IwlP. A plikacja nie m usi. wywoływać, gdy funkcja tc p _ w rite jest wywołana w ewnątrz obsługi jakiegoś zd'i' rżenia biblioteki IwlP, czyli np. w ew nątrz jakiejś funkcji zwrotnej. i void top_poll(struct tcp_pcb *pcb, err_t {* poll_callback(void *arg, struct tcp_pcb u8_t time; 'pcb, Funkcja tc p _ p o ll rejestruje w ołaną cyklicznie funkcję zwrotną. Pierwszym argijf m entem tej funkcji jest w skaźnik pcb do deskryptora TCP. Drugi argument p o l$ c a llb a c k jest adresem funkcji zw rotnej. Trzeci argum ent tim e określa interwał cząii sowy wywołania funkcji zwrotnej. Interwal ten wynosi 2 time TCP_TMR_INTERVAL, gdzie TCP_TMR_INTERVAL jest interw alem czasowym wywołania budzika TCP w i lisekundach. Standardowo budzik ten jest wołany co 250 ms. Zatem funkcja zv na p o łl_ c a llb a c k jest w ywołana co tim e/2 s. void tcp_recv(struct tcp_pcb *pcb, err_t {* recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err t err; i. Funkcja tcp recv rejestruje funkcję zwrotną, którą biblioteka IwlP wola, aby pi zakazać aplikacji dane odebrane w połączeniu TCP. Pierwszym argum entem tej funkcji jest wskaźnik pcb do deskryptora TCP. Drugi argum ent re c v _ c a llb a c k jest adv resem rejestrowanej funkcji zwrotnej. void tcp_recved(struct tcp_pcb ui6 t len; rpcb, Funkcja tc p recved informuje bibliotekę IwlP, że aplikacja przetworzyła otrzymane dane i jest gotow a na otrzym anie kolejnej porcji danych. Pierwszym argumentem iii tej funkcji jest w skaźnik pcb do deskryptora TCP opisującego połączenie, z kt< otrzymano dane. Drugi argum ent len zawiera liczbę bajtów przeczytanych przez i aplikację. Po wywołaniu tej funkcji biblioteka oblicza nowy rozm iar okna odbii czego i jeśli rozm iar okna zm ienił się, informuje o tym drugą stroną komuniki void tcp_sent(struct tcp_pcb *pcb, err_t (* sent_callback(void *arg, struct tcp_pcb *pcb, u!6 t len; Funkcja tc p _ s e n t rejestruje funkcję zwrotną, którą biblioteka Iw lp wola, aby informować aplikację, że druga strona potw ierdziła odebranie danych. Pierwsz; argum entem tej funkcji jest w skaźnik pcb do deskryptora TCP. Drugi argument s e n t_ c a llb a c k jest adresem rejestrowanej funkcji zwrotnej. ul6_t tcp sndbuf (struct tcp pcb *pcb; Funkcja tcp_sndbuf zw raca liczbę bajtów, które mogą być wstaw ione do bufora nadawczego. Próba w ysłania większej porcji danych za pom ocą funkcji tcp write zakończy się niepowodzeniem. A rgum ent pcb jest w skaźnikiem do deskryptora TC P opisującego połączenie, przez które dane m ają być wysiane. Aktualna im plementacja biblioteki IwlP definiuje tę funkcję jako makro. err_t tcp_write(struct tcp_pcb *pcb, const void *data, ul6_t len, u8_t flags; Funkcja tcp_w rite w staw ia dane do bufora nadaw czego. Pierw szym argum entem tej funkcji jest w skaźnik pcb do deskryptora T C P opisującego połączenie, przez które dane m ają być w ysiane. D rugim argum entem jest w skaźnik data do w ysyłanych danych. Trzeci argum ent len określa ilość danych do w ysiania w bajtach. C zw arty argum ent flags m oże być zerem lub alternatyw ą następujących w artości: - TCP_WRITE_FLAG_COPY - skopiuj dane do pamięci stosu; - TCP_WRITE_FLAG MORE - będą następne dane. Jeśli nie użyjem y opcji TCP_WRITE_FLAG_COPY, biblioteka IwlP zakłada, że dane w skazywane przez d a ta nie ulegną m odyfikacji, aż zostaną wysiane i potw ierdzone. O dpowiada za to aplikacja. Jest to szczególnie użyteczne, gdy d a ta wskazuje na dane w pamięci stałej. U nika się wtedy zbędnego kopiowania. Jeśli ustawimy opcję TCP_WRITE_FLAG_COPY, dane w skazywane przez d a ta zostaną skopiowane do pamięci zarządzanej przez bibliotekę IwlP. W tym przypadku, jeśli funkcja tc p w rite zakończy się poprawnie, aplikacja m oże natychm iast zw olnić pam ięć wskazywaną przez d a ta lub użyć jej ponownie. Jeśli używ am y sterow nika util_eth zc.c i d a ta wskazuje na obszar pamięci Flash, należy koniecznie ustawić opcję TCP_WRITE_ FLAG_C0PY. Biblioteka IwlP dzieli dane otrzym ane do wysłania na segm enty i w staw ia te segmenty do kolejki nadawczej. W ostatnim w staw ionym segm encie ustawia znacznik PSH, chyba że podana została opcja TCP_WRITE_FLAG_MORE. W tedy znacznik PSH nie jest ustawiany. O pcja ta jest przydatna, jeśli wysyłam y dane za pom ocą wielu kolejnych wywołań funkcji tcp _ w ite. W tedy ustawiamy tę opcję we wszystkich w ywołaniach tej funkcji z w yjątkiem ostatniego. Funkcja tcp_write zw raca ERR OK, jeśli w staw ienie danych do bufora nadawczego pow iodło się, a w przeciwnym przypadku zwraca kod błędu inform ujący o przyczynie niepowodzenia.
160 4. Programowanie w modelu klient-scrwek 4.2. TCP 161 4.2.7. Funkcje zwrotne W tym podrozdziale opisuję bardziej szczegółowo, w kolejności alfabetycznej'* funkcje zwrotne używane przez bibliotekę lwip. Te funkcje zwrotne, które w danej1' aplikacji są nam potrzebne, musimy zaim plem entow ać sami. err_ł accept_callback(void *arg, struct tcp_pcb *conn_pcb, err t err; Funkcja zwrotna a c c e p t_ c a llb a c k jest w ywołana po stronie wykonującej otwarcie? pasywne (zwykle jest to serwer. Biblioteka lwip wywołuje ją po odebraniu m-umentu ACK kończącego otw ieranie połączenia. Pierwszym argum entem tej funk-;'i cji jest wskaźnik arg do danych aplikacji, przypisany za pom ocą funkcji tc p ar :.. Drugim argum entem jest wskaźnik conn pcb do nowego deskryptora TCP, utwórz, i nego dla właśnie otw artego połączenia. W szelkie dalsze operacje zw iązane z tym ^ połączeniem powinny używać tego now ego deskryptora, a nie deskryptora zwró-kconego przez funkcję tc p _ lis te n ani tym bardziej deskryptora zwróconego przez funkcję tcp new. Trzeci argum ent e r r jest kodem błędu. Analiza tekstu źródłowego >' biblioteki lwip pokazuje, że ma on zaw sze wartość ERR_0K. Funkcja accep t_ cal] - back powinna zwrócić ERR_0K, jeśli połączenie ma być kontynuowane. Jeśli zwróci'^ wartość różną od ERR_0K, połączenie zostanie skasowane. err_t connect_callback(void *arg, struct tcp_pcb *pcb, err t err; Funkcja zwrotna connect callback jest w ywołana po stronie wykonującej otwar-1 cie aktywne (zwykle jest to klient. B iblioteka lw IP wywołuje ją po odebraniu segmentu SYN+ACIC. Pierwszy argum ent arg jest w skaźnikiem przypisanym za po-; 1łwBBB f l i mocą funkcji tcp _ arg. Drugi argum ent pcb jest w skaźnikiem do deskryptora TCP opisującego otwarte połączenie. Trzeci argum ent e r r zaw iera kod błędu. Analiza tekstu źródłowego biblioteki lwip pokazuje, że ten argument ma zawsze wartość' ERR_0K. Po zakończeniu tej funkcji zw rotnej biblioteka lwip w ysyła segment ACK. kończący otwieranie połączenia. A naliza tekstu źródłowego pokazuje też, że biblioteka lwip nie sprawdza wartości zwracanej przez ąędiinkcję. ; void conn_err_coilback(void *arg, err t err; Funkcja zwrotna co n n _ err_ co llb ack jest w ywołana przez bibliotekę lwip w celu poinform owania aplikacji o aw aryjnym zam knięciu połączenia. Może to być skut-j kiem otrzym ania segmpntu RST, w yw ołani^ funkcji tcp_abandon, wywołania funk-j, cji tc p _ a b o rt lub wystąpienia błędu, np. braku pamięci. Pierwszym argumentem; tej funkcji jest wskaźnik arg do danych aplikacji, przypisany za pom ocą funkcji; tcp_arg. Aplikacja powinna zw olnić zasoby wskazywane przez ten wskaźnik,*; Drugi argum ent e r r jest kodem błędu, który spow odował skasowanie połączenia,v\.«si i iftw&s m n i Zauważmy, że funkcja ta nie dostaje w skaźnika do deskryptora TCP, gdyż w momencie jej wywołania połączenie może już nie istnieć. err_t poll callback(void *arg, struct tcp_pcb *pcb; Funkcja zw rotna p o ll_ c a łlb a c k jest w ywołana cyklicznie przez bibliotekę lwip. Pierwszym argum entem tej funkcji jest wskaźnik arg ustawiony dla tego deskryptora za pom ocą funkcji tcp_arg. Drugim argum entem jest wskaźnik pcb do deskryptora TCP. Jeśli funkcja p o ll_ c a llb a c k zwróci wartość ERR_0K, to po jej zakończeniu zostanie w ywołana funkcja tcp _ o u tp u t. A naliza tekstu źródłowego biblioteki lwip pokazuje, że inne zwracane wartości są ignorowane. err_t recv_callback(void *arg, struct tcp_pcb pcb, struct pbuf *p, err_t err; Funkcja zw rotna re c v _ c a llb a c k jest w ywołana przez bibliotekę lwip w celu poinform ow ania aplikacji o otrzym aniu danych, które aplikacja powinna przeczytać. Pierwszym argum entem tej funkcji jest w skaźnik arg do danych aplikacji, przypisany za pom ocą funkcji tcp _ arg. Drugim argum entem jest wskaźnik pcb do deskryptora TCP opisującego połączenie, z którego zostały odebrane dane. Trzecim argum entem jest wskaźnik p do łańcucha buforów zawierającego odebrane dane. Czwarty argum ent e r r jest kodem błędu. A naliza tekstu źródłowego biblioteki lwip pokazuje, że argum ent ten ma zaw sze wartość ERR_0K. Funkcja re c v _ c a llb a c k służy też do poinform ow ania aplikacji, że połączenie zostało zamknięte przez drugą stronę kom unikacji i nie ma ju ż żadnych danych do przeczytania. W tedy wskaźnik p ma wartość NULL. Aplikacja pow inna zam knąć połączenie w przeciwnym kierunku za pom ocą funkcji tc p _ c lo se. Jeśli otrzym ane dane zostały przetworzone, to aplikacja pow inna wewnątrz funkcji recv callback poinform ować o tym bibliotekę lwip za pomocą funkcji tc p _ re - cved, zw olnić łańcuch buforów w skazywany przez p za pom ocą funkcji p b u f_ free i zwrócić wartość ERR_0K. Jeśli aplikacja z jakiegoś powodu nie może przetworzyć otrzym anych danych, to funkcja recv calłback może zwrócić kod błędu różny od EPf^OK.i nie wolno jej wtedy zwalniać łańcucha buforów. W tym przypadku biblioteka lwip spróbuje wywołać funkcję zw rotną ponow nie z tymi sam ymi danymi. Stanie się to przy najbliższej okazji, czyli po w ystąpieniu następnego zdarzenia wyw ołującego bibliotekę lwip. Ciągle odrzucanie danych przez aplikację może powodow ać odrzucanie przez bibliotekę lwip kolejnych napływających segm entów TCP z powodu przepełnienia bufora odbiorczego. Nie powoduje to utraty danych, bo TCP radzi sobie z taką sytuacją i retransm ituje niepotwierdzone segmenty. W pływa to jednak niekorzystnie na w ydajność aplikacji. err t sent callback(void arg, *0zt aj*i struct tcp_pcb *pcb,. ~ ' i-ul6 t len; ----------- 1 (LI Z-----------------
162 4. Programowanie u* modelu klient-serweą 4.3. UDP 163 4.3. UDP Funkcja zwrotna sen t_ ca llb a ck jest wywołana przez bibliotekę lwip, gdy otrzyji! mano potwierdzenie odebrania danych. Pierwszym argumentem tej funkcji jecp wskaźnik arg do danych aplikacji, przypisany za pomocą funkcji tcp_arg. Drugi argumentem jest wskaźnik pcb do deskryptora TCP opisującego połączenie, z kti rego odebrano potwierdzenie. Trzeci argument len to liczba oktetów, które zosta potwierdzone od poprzedniego potwierdzenia. Analiza tekstu źródłowego pokazuje '3 że biblioteka lwip ignoruje wartość zwracaną przez tę funkcję. UDP (ang, User Datagram Protocol jest pakietowym protokołem transportowym'! intersieci. Jest opisany w RFC 768 i został przyjęty jako standard intersieci STD 6. '4 4.3.1. Podstawowe własności UDP rozszerza 1P o numery portów, zachowując wszystkie pozostałe własnos;ci II* ' czyli przesyłanie bezpołączeniowe i zawodne. Jednak w praktyce datagramy giną; bardzo rzadko. UDP bardzo dobrze sprawdza się w protokołach typu pylanie-odpo-.' wiedź, gdzie cała komunikacja sprowadza się do wymiany dwóch pakietów: klient1 zadaje serwerowi pytanie, a serwer odpowiada klientowi. Według takiej zasady * działa na przykład protokół D NS. Przy tego rodzaju komunikacji zgubienie data-, graniu nie jest groźnie. Klient nie otrzymawszy odpowiedzi w ustalonym czasie, zadaje pytanie ponownie. Zaletą UDP, w stosunku do TCP, jest mniejszy narzut: komunikacyjny. Nie trzeba otwierać i zamykać połączenia. Nagłówek UDP jest. krótszy niż nagłówek TCP. *. 4.3.2. Nagłówek Pakiety UDP nazywane są datagramam i. Datagram UDP składa się z przedsta- ' wionego na rysunku 4.8 nagłówka i następującego po nagłówku pola danych,, Datagram UDP jest przesyłany w polu danych datagramu IP. UDP rozszerza IP : o mechanizm portów, więc dwa pierwsze pola nagłówka zawierają odpowiednio numer portu nadawcy i odbiorcy. Pole DŁUGOŚĆ DATAGRAMU UDP zawiera, całkowitą długość w oktetach datagramu, łącznie z nagłówkiem. Wartość w tym. polu jest zawsze większa lub równa 8. Projektanci protokołów intersieci postąpili niekonsekwentnie. Długość datagramu UDP daje się wyliczyć na podstawie długości datagramu IP, podobnie jak w TCP, gdzie nic.,,przcwidziano w nagłówku pola 1* zawierającego długość segmentu TCP.. W UDP suma kontrolna jest opcjonalna. Jeśli pole SUMA KONTROLNA zawiera zero, to nie obliczono sumy kontrolnej. Pojawia się problem, co zrobić, gdy wyli-; '.' ' A czona suma kontrolna jest równa zeru. Suma kontrolna UDP jest obliczana w aryt- 1 I b 'W 0 31. NR PORTJJ NADAWCY NR PORTU ODBIORCY '/!? [0:15] [16:31] DŁUGOŚĆ DATAGRAMU UDP [0:151 Rys. 4.8. Format nagłówka UDP SUMA KONTROLNA [16:31] 1 metyce uzupełnieniowej do jedynek, gdzie zero ma dwie reprezentacje: wszystkie bity równe zeru oraz wszystkie bity równe jedności. Gdy wyliczona suma kontrolna jest równa zeru, używa się reprezentacji zawierającej jedynki. Suma kontrolna UDP obejmuje nagłówek UDP, pole danych UDP i pseudonagłówek utworzony na podstawie nagłówka IP, co jest kolejnym przejawem zacierania w intersieciach granicy między warstwą sieciową a transportową. Sposób obliczania sumy kontrolnej UDP jest opisany szczegółowo w RFC 768. 4.3.3. Inicjowanie klienta i serwera Procedury inicjowania klienta i serwera UDP przedstawione są schematycznie na rysunku 4.9. UDP nie tworzy połączenia, więc podczas inicjowania nie są w y mieniane żadne datagramy między klientem i serwerem, nie jest generowany żaden ruch w sieci. Funkcja udp_new przydziela strukturę typu udp_pcb (ang. UDP protocol control błock, w której biblioteka lwip przechowuje kontekst komunikacji UDP. Strukturę tę nazywam deskryptorem UDP. Wskaźnik do tego deskryptora jest przekazywany jako argument pozostałym funkcjom obsługującym komunikację UDP. Za pomocą funkcji udp_recv klient i serwer rejestrują funkcję zwrotną recv _ ca l- lback, która będzie wywołana przez bibliotekę lwip, gdy pojawią się dane. Serwer wywołuje funkcję udp_bind, aby uaktywnić nasłuchiwanie na wybranym adresie IP i porcie. Również klient może użyć tej funkcji. Funkcja udp connect ustawia w lokalnej strukturze typu udp pcb adres IP i numer portu serwera, co powoduje, że będą odbierane tylko datagramy UDP od tego serwera. Jeśli nie wywołuje się tej funkcji, będą odbierane wszystkie datagramy kierowane pod adresem IP i na port klienta. Wołanie funkcji udp_bind i udp_connect po stronie klienta jest opcjonalne. Klient pcb» udp_new( > udp_bind{pcb, c_addr, c_port *-> udp_connect(pcb, s_addr, sjport *-> ucp_recv(pcb, recv_callback, arg > Rys. 4.9. Inicjowanie klienta i serwera UDP 4.3.4. Przesyłanie danych lwip lwip Serwer < pcb = udp_new( < udp_recv(pcb, recv_cailback, arg < udp_bind(pcb, s_addr, s_port Diagram przesyłania danych za pomocą UDP przedstawiony jest na rysunku 4.10. Biblioteka lwip udostępnia dwie funkcje wysyłające datagram UDP. Można go Klient lwip udp_send(pcb, pbuf --> udp_sendto (pcb, pbuf, s addr, s_port > % recv jallback(arg, pcb, pbuf, i # m idr' s_p r t i Rys. 4.10. Przesyłanie danyijh UDP lwip Serw er > recv_callback(arg, pcb, pbuf, c_addr, c_port > recv_callback(arg, pcb, pbuf, c_addr, c port < udp_sendto(pcb, pbuf, c_addr, c_port
164 4. Programowanie w modelu klient-serwefc 4.3. UDP 165 4.3.5. :»n wysiać za pom ocą funkcji udp_sendto, podając adres 1P i num er portu odbiorcy.- Jeśli klient skonfigurow ał adresu IP i port serw era za pom ocą funkcji udp_oc:',j n e c t, to nie musi ich podaw ać za każdym razem i może w ysiać datagram za poi* mocą funkcji udp send. A plikacja jest inform ow ana o odebraniu datagramu /. p pom ocą funkcji zw rotnej re c v _ c a llb a c k. U DP nie potw ierdza odebrania danycl{$ Jeśli takie potw ierdzenie jest w ym agane, musi być zaim plem entow ane w warstwi^; aplikacji. Funkcje biblioteczne. 'Si W tym podrozdziale opisuję bardziej szczegółowo i w kolejności alfabetycznej funkcje biblioteki lwip obsługujące kom unikację za pom ocą UDP. err t udp bind (struct udp_pcb *pcb, struct ip addr *addr, u!6_t port; Funkcja udp bind wiąże deskryptor U DP z lokalnym adresem IP i portem, któ re mają być używane do odbierania datagramów. W ybrany adres i port będą też'15 um ieszczane jako adres i port nadaw cy w wysyłanych za pom ocą tego deskryptofąf datagram ach UDP. Pierwszym argum entem tej funkcji jest w skaźnik pcb do de-, skryptora UDP. Drugim argum entem jest w skaźnik addr do struktury zawierającej? adres IP. Jeśli argum ent ten ma wartość IP_ADDR_ANY, deskryptor wiązany jest n w szystkim i lokalnymi adresami IP, co jest przydatne, gdy jest tylko jeden interfejs1 sieciow y lub gdy chcemy, aby serw er odbierał datagram y UDP ze wszystkich in-' terfejsów sieciowych. Trzeci argum ent p o rt jest numerem portu. Jeśli ma w artość zero, to biblioteka lwip przydziela dowolny niezajęty port. Ten argum ent ma zwyt* kle wartość zero, gdy urucham iam y klienta, gdyż wtedy num er portu jest nieistotny,. Dla serwera zwykle podaje się wartość niezerową, gdyż serw er musi nasłuchiwać.; na pow szechnie znanym porcie. Funkcja zw raca ERR_OK, gdy zakończyła się sukcesem, a ERR_USE, gdy nie m ożna użyć podanych wartości, np. żądany port jesti' zajęty. err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *addr, ul6_t port/ Funkcja udp_connect przypisuje do deskryptora UDP adres IP i num er portu zdah; nego końca komunikacji. Ustawia dom yślny adres IP i port, na które będą wysy-' lane datagram y U DP za pom ocą funkcji udp send. Ponadto powoduje, że będą odbierane i przetwarzane tylko datagramy, które zostały wysłane z podanego źrór dla. Pierwszym argum entem jest w skaźpjk pcb do deskryptora UDP. Drugim atr gum entem jest wskaźnik addr jlo.struktufy zawierającej adres IP. Trzeci argumenj? p o rt jest numerem portu. Podany adres IP i num er portu są tylko zapisywane w ItA, kalnym deskryptorze UDP. Nie odbyw a się żadna kom unikacja sieciowa. Jeśli me; został przypisany lokalny adres IP i num er portu, najpierw jest w ywołana funkcją * udp_bind. Dlatego funkcja udp_connect może zwrócić te same wartości co funkcja udp_bind. void udp_disconnect(struct udp pcb *pcb; Funkcja u dp_disconnect usuwa z deskryptora UDP adres IP i num er portu zdalnego końca komunikacji. O dwraca działanie funkcji udp_connect. Nie generuje żadnego ruchu w sieci. Argument pcb jest w skaźnikiem do deskryptora UDP. struct udp_pcb * udp jiew(void; Funkcja udp_new przydziela nowy deskryptor UDP. Zwraca wskaźnik do przydzielonego deskryptora lub N U LL, gdy nie udało się go przydzielić. void udp recv (struct udp_pcb *pcb, void (* recv callback (void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u!6_t port, void *arg; Funkcja udp_ recv rejestruje funkcję zwrotną, która będzie wywołana przez bibliotekę lwip po odebraniu datagram u UDP. Pierwszym argum entem tej funkcji jest w skaźnik pcb do deskryptora UDP, w którego kontekście będą odbierane datagramy. Drugi argum ent recv callback jest adresem rejestrowanej funkcji zwrotnej. Trzecim argum entem jest w skaźnik arg do danych aplikacji. W skaźnik ten będzie przekazyw any jttko pierwszy argum ent funkcji zwrotnej, co umożliwia obsługę wielu klientów i serwerów za pomocą wspólnej funkcji zwrotnej. void udp_remove(struct udp pcb *pcb; Funkcja udp_remove zw alnia deskryptor UDP. Argument pcb jest wskaźnikiem do deskryptora, który ma być zwolniony. Funkcja ta powinna być wywołana, gdy klient lub serw er kończą działanie. w--------- -------------------------------- - ----------------------- --- ------------------------------------ err_t udp_send(struct udp_pcb *pcb, struct pbuf *p Funkcja udp_send wysyła datagram U DP na adres IP i port, które zostały skonfigurowane za pom ocą funkcji udp_connect. Pierwszym argumentem tej funkcji jest wskaźnik pcb do deskryptora UDP, w którego kontekście mają być wysyłane datagram y UDP. Drugim argum entem jest wskaźnik p do łańcucha buforów z danymi, które mają być um ieszczone w polu danych datagram u UDP. Ten łańcuch bufonów przydzielam y za pom ocą funkcji p b u f_ a llo c, której pierwszy argument powłmen htuć wartość P B U F _T R A N S P O R T. Aktualna im plem entacja funkcji udp_send w ywołuje >pó prostu funkcję udp_ sendto z adresem IP i numerem portu pobranymi
166 4. Programowanie w modelu klicnt-serwem 4.4. Uwagi końcowe 167 4.3.6. z przekazanego jej deskryptora UDP. Funkcja udp_send zwraca te same wartości Gęjl funkcja udp_sendto. err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, ul6_t port; Funkcja udp_sendto w ysyła datagram UDP na podany adres IP i port. Pierwszymi! argum entem tej funkcji jest w skaźnik pcb do deskryptora UDP, w którego kotwi tekście mają być w ysyłane datagram y UDP. Drugim argum entem jest wskaźnik pi do łańcucha buforów z danymi, które mają być um ieszczone w polu danych dau,.^ gramu UDP. Łańcuch buforów przydzielam y za pom ocą funkcji p b u f_ a llo c, któi-j rej pierwszy argum ent ma wartość PBUF_TRANSPORT. Trzecim argum entem funkcji' udp_sendto jest w skaźnik addr do struktury zawierającej adres IP, na który ma by<$ w ysłany datagram UDP. Czwarty argum ent p o rt jest numerem portu, na który ma] być wysłany ten datagram. Jeśli nie został przypisany lokalny adres IP i numer por-i^ tu, najpierw wywołana jest funkcja udp bind. Funkcja udp_sendto zwraca ERR_0K,:i gdy udało się wysłać datagram UDP. Jeśli wystąpił problem, funkcja ta zwraca właściwy kod błędu. Funkcja zwrotna O program owanie U DP używa tylko jednej funkcji,zwrotnej, która służy do odbieg rania danych. M usimy ją sami zaim plem entow ać. void recv_caliback(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u!6_t port; Funkcja zwrotna re c v _ c a llb a c k jest w ywołana przez bibliotekę łwip, gdy zosta-' nie odebrany datagram UDP. Pierwszym argum entem tej funkcji jest wskaźnik arg; podany przy rejestrowaniu lej funkcji zwrotnej. D rugim argum entem jest wskaźnik pcb do deskryptora UDP, w którego kontekście odebrano datagram UDP. Trzecim argum entem jest w skaźnik p do łańcucha buforów^żawierającego pole danych datagranut UDP. Czwartym argum entem jest w skaźnik addr do struktury zawierającej, adres IP nadawcy datagram u U D P Piąty argum ent p o rt jest numerem portu nadaw-- cy datagram u UDP. A rgum enty addr i p o rt są potrzebne, aby aplikacja mogła odr powiedzieć na otrzym any kom unikat. Argumenty te mogą być przekazane wprost funkcji udp_sendto. 4.4. Uwagi końcdwe O pisane w tym rozdziale struktury danych i funkcje biblioteki łwip, z wyjątkiem funkcji zwrotnych, są zadeklarow ane w plikach tcp.h i udp.h, a ich implementację l l l 5^ znajdują się w plikach tcp.c, tcp_in.c, tcp_oul.c, udp.c. Funkcje zw rotne trzeba z.i- im plem entow ać sam emu. Oczywiście zaproponow ane nazwy funkcji zw rotnych są tylko przykładow e i można je wybrać dowolnie. W tym miejscu książki kończy się opis dolnych warstw inter,sieci. Stąd jest to dobre m iejsce na male podsum owanie. Przedstaw iony dotychczas material wystarcza, aby zacząć pisać aplikacje sieciowe na m ikrokontroler STM 32F107 z wykorzystaniem biblioteki lwip, co jest tem atem następnych rozdziałów, które zawierają przykłady serwerów i klientów korzystających z opisanego w tym rozdziale interfejsu program istycznego. W kolejnych rozdziałach pojaw ią się rów nież dodatkow e informacje 0 protokołach intersieci. Przykłady będą też okazją do zaprezentowania niektórych peryferii mikrokontrolerów STM 32. C zytelnikom, którzy chcieliby bliżej poznać zasady działania intersieci i nie chcą czytać dokum entów RFC, polecam wydaną przed w ielom a laty, ale wciąż aktualną i bardzo przystępnie napisaną książkę D ouglasa C om era [1]. D otychczas kilka razy w spom niałem o bezpieczeństw ie. Ten tem at wymaga pośw ięcenia mu nieco uwagi. Bezpieczeństw o w inform atyce należy postrzegać jako odporność na celow e działanie zm ierzające do zm uszenia systemu inform atycznego, aby działał niezgodnie z jego pierw otnym przeznaczeniem, niezgodnie ze specyfikacją lub wręcz, aby w ogóle przestał działać. Na bezpieczeństw o m ożna patrzeć z różnych punktów widzenia. Jednym z fundam entalnych elem entów bezpieczeństw a aplikacji jest jej popraw ność. Jeśli aplikacja działająca w sieci komputerow ej ma jakiś błąd lub lukę, to kiedyś zostanie to wykorzystane. Pisząc aplikacje sieciowe, należy zakładać, że druga strona kom unikacji m oże zachow ać się złośliwie. W szczególności nie w olno zakładać, że otrzym yw ane kom unikaty zaw sze będą zgodne z ich specyfikacją 1 należy przeprowadzać ich skrupulatną walidację. Jeśli na przykład program ista piszący serw er założy, że komunikaty od klienta są zawsze popraw ne i zaniecha ich sprawdzania, to zawsze znajdzie się inny program ista, który napisze klienta w ysyłającego niepoprawny kom unikat, co w najlepszym przypadku doprowadzi do unieruchom ienia serwera. W mniej optym istycznym w ariancie błąd w im plem entacji może doprow adzić do nadużycia usługi. N ikt nigdy nie zwolni nas z obowiązku pisania popraw nych programów, a program y sieciow e wym agają szczególnej staranności. Należy przyjąć założenie, że druga strona kom unikacji może zachowywać się zupełnie dow olnie lub wręcz złośliwie i należy m aksym alnie uodparniać pisaną aplikację, na takie zachowania. Takie podejście staram się prezentować w przykładach. Innym i, nie mniej istotnymi, elem entam i bezpieczeństw a są: - poufność (ang. confidentiality, - integralność (ang. integrity, - dostępność (ang. availability. Nieprzypadkow o pierwsze litery angielskich nazw tych elem entów bezpieczeństwa tw orzą skrót CIA. Poufność oznacza ochronę informacji przed nieautoryzowanym ujawnieniem. Realizuje się ją, stosując kontrolę dostępu do usługi np. za pomocą hasei' szy-ffijłjąc przesyłane dane. Integralność oznacza ochronę informacji przed ich nieuprawnionym zmodyfikow aniem. Zwykle integralność chroni się przez sku-
168 4. Programowanie w modelu klient-serwer teczne wykrywanie każdej próby modyfikacji. Protokoły sieciowe stosują w tym1'' celu sumy kontrolne. Jednak zw ykle sum y kontrolne są łatwe do podrobienia i z.! pew niają ochronę tylko przed losowym i przekłamaniami. Nie zapewniają jed n ak ; żadnej ochrony przed celowo w prowadzanym i zmianami. W celu zapewnienia integralności stosuje się kryptograficzne sumy kontrolne i podpisy elektroniczne. Podpis elektroniczny służy też do zapew nienia autentyczności (ang. authenticity informacji, czyli pewności co do pochodzenia danych i ich autorstwa. Dostępność jest to zdolność do św iadczenia usługi bez przerw, 24 godziny na dobę, 7 dni w ty- godniu itd. Najczęstszą przyczyną braku dostępności w sieci są awarie i ataki typu ; odm ow a usługi (DoS. Dostępność zapew nia się, stosując rozw iązania programowe i sprzętow e, np. przez dublow anie krytycznych elem entów systemu. Należy wy- ' raźnie zaznaczyć, że pełna ochrona nie jest m ożliwa, a stosowane środki zawsze wynikają z pewnego komprom isu, często o podłożu ekonom icznym. Powyższy akapit ma tylko uczulić Czytelnika na wspom niane problemy. Ich uw zględnienie w ykracza daleko poza ramy tej książki. W wielu zastosowaniach nie jest to też konieczne. W dalszym ciągu zakładam, że przykłady będą uruchamiane ' w zaufanej sieci lokalnej, chronionej od niebezpieczeństw czyhających w Internecie', co najmniej za pom ocą ściany ogniowej. Serwer TCP
170 5. Sen 5.1. Protokół warstwy aplikacji 171 Tematem tego rozdziału jest serw er używający protokołu transportowego T C p M ateriał podzieliłem na dwa przykłady. W pierwszym z nich pokazuję, jak zaii plem enlow ać kom unikację sieciow ą. Przykładow a aplikacja um ożliw ia zdalne w łąi czanie i w yłączanie diod św iecących podłączonych do mikrokontrolera oraz odczyt! czasu, jaki upłynął od w łączenia układu, ale m ożna ją łatwo rozbudow ać i dodać ste-tl row anie innymi peryferiam i. Drugi przykład jest rozszerzeniem pierwszego. Serwera pow inien działać bezobsiugowo i radzić sobie z sytuacjam i awaryjnym i. W tyłgl celu w drugim przykładzie dodaję obsługę układu nadzorcy (ang. watchclog, k tó lf ry zeruje mikrokontroler, gdy oprogram ow anie sieciow e nie w ykazuje aktywno przez określony czas. N adzorca w ym usza też ponow ienie próby uruchom ienia : wera, gdy wystąpi problem podczas konfigurow ania ustawień sieciowych. 5.1. Protokół warstwy aplikacji Przystępując do napisania aplikacji sieciow ej, musimy wybrać protokół warsty/y* aplikacji. M ożemy użyć jakiegoś istniejącego protokołu lub zaprojektow ać własr ' Przykłady wykorzystujące istniejące standardow e protokoły aplikacyjne intersiem pojaw iają się w rozdziale 8 i rozdziale 10. W tym rozdziale oraz w rozdziale 6 : i rozdziale 7 pokazuję, jak zaprojektow ać własny protokół. 5.1.1. Projekt protokołu Projekt protokołu powinien zaw ierać specyfikację wym ienianych komunikatów i opis interakcji między klientem a serwerem. Istnieją zasadniczo dw a podejścia do projektowania formatu komunikatów : komunikaty mogą być opisane za pomocą struktur binarnych albo mogą to być kom unikaty tekstowe, czytelne dla człowieka, i K omunikatom tekstowym też m ożna nadać strukturę - zw ykle za pom ocą X M L -a,, W szystkie dotychczas opisane w tej książce protokoły intersieci mają komunikaty,-' zdefiniow ane jako struktury binarne. N atom iast wiele protokołów aplikacyjnych in -. tersieci używa kom unikatów tekstowych. O ba podejścia mają sw oje zalety i w ady., K omunikaty binarne są zw ykle krótsze od odpow iednich kom unikatów tekstowych niosących tę sam ą treść i łatwiej jest zaim plem entow ać analizę komunikatów binarnych niż tekstowych, dlatego kom unikaty binarne są preferowane w dolnych warstwach intersieci. Jednak człow iekowi zdecydow anie łatwiej czytać komunikaty i tekstowe. W przypadku kom unikatów tekstow ych ułatwiona jest analiza śladu (ang,. tracę przebiegu protokołu i..łatwiej jest napisać-skrypty testujące protokół, dlate-. #r:. «go komunikaty tekstowe tak często są używane' w^-protokołach warstwy aplikacji,:; Do testowania komunikacji z serw erem używ ającym protokołu tekstow ego możnaw ykorzystać program telnet, który jest standardow o dostępny zarów no w systemie Linux, jak i W indows. Program telnet jest klientem protokołu Telnet, cłioć sam ter; stowany serwer nie musi w pełni obsługiw ać tego protokołu. W tym rozdziale oraz w rozdziałach 6 i 10 pokazuję protokoły tekstowe. Protokoły aplikacyjne, których komunikaty mają posłać binarną, pojaw iają się jeszcze w rozdziałach 7 i 8. Interakcję między klientem a serw erem iribżna opisać słownie lub wyrazić za pomocą diagramu autom atu stanowego. Opis słowny bywa preferowany w przypadku ; prostych protokołów bezslanowych. Bezstanowość oznacza, że reakcja protokołu., zależy tylko od aktualnie odebranego komunikatu i nie zależy od komunikatów Tab. 5.1. Polecenia obsługiwane przez serwer Polecenie Opis h Wyświetl tekst pomocy zawierający opis obsługiwanych poleceń? Wyświetl tekst pomocy zawierający opis obsługiwanych poleceń G g R r t X Włącz zieloną diodę świecącą Wyłącz zieloną diodę świecącą Włącz czerwoną diodę świecącą Wyłącz czerwoną diodę świecącą Podaj, ile czasu upłynęło od włączenia mikrokontrolera Zakończ komunikację w ym ienionych poprzednio. Opis za pom ocą autom atu bywa bardziej precyzyjny. W dokum entach RFC przeplatają się oba podejścia. Zaprojektow any na potrzeby przykładów zam ieszczonych w tym rozdziale protokół jest bardzo prosty i bezstanowy, dlatego w zupełności wystarczy jego opis słowny. Należy zwrócić uwagę, że projekt protokołu jest czym ś innym niż projekt im plem entacji protokołu. Projekt protokołu opisuje głów nie kom unikację sieciową, czyli przede wszystkim dane, które m ożna podejrzeć w kanale kom unikacyjnym, w pinając się analizatorem sieci. Projekt protokołu opisuje też inform acje, które można wywnioskować na podstawie obserwacji tej komunikacji. Protokół musi dać się zaim plem entow ać w różnych środowiskach i nie może zależeć od architektury sprzętu, języka program owania czy interfejsu program istycznego udostępnianego przez jakieś biblioteki. N atom iast projekt im plem entacji musi uwzględniać specyficzne własności sprzętu, języka program owania i wykorzystywanych bibliotek. Po tym krótkim w stępie przejdźm y do opisu protokołu, za pom ocą którego będziemy kom unikow ać się z naszym serwerem. W tabeli 5.1 zaw arto polecenia, które obsługuje serwer. W tabeli 5.2 podano odpow iedzi udzielane przez serwer. Działanie protokołu jest następujące. K lient inicjuje połączenie TCP z serwerem. Serwer po zaakceptowaniu połączenia wysyła klientowi tekst pow itania i znak zachęty do w pisania polecenia. K lient w ysyła jednoliterow e polecenie zakończone znakiem końca wiersza. Jeśli klient poprosi o tekst pomocy, serw er w ysyła go i ponow nie wysyła Tab. 5.2. Odpowiedzi udzielane przez serwer Odpowiedź This is TCP server running on top of the IwIP-st^ek.. Press h or? to get help. Tekst powitania > Znak zachęty db wpisania polecenia h or? print this help G turn on the green led g turn off the green led R turn on the red led r turn off the red led t get microcontroller running time x exit d gg:mm:ss.xxx Tekst pomocy Opis Czas w formacie: liczba dni, godzin, minut, sekund, milisekund Przekroczony czas oczekiwania na polecenie ERROR B *3 1; Błędne polecenie
5.1. Protokół warstwy aplikacji 173 znak zachęty. Jeśli klient zażąda w ykonania operacji na diodzie św iecącej, serwer : wykonuje tę operację i wysyła kolejny znak zachęty. Jeśli klient zażąda informacji cji o czasie, serw er odsyła czas, który upłynął od uruchom ienia mikrokontrolera^ w formacie: liczba dni, godzin, m inut, sekund i milisekund, a następnie wysyła znak zachęty do wpisania kolejnego plecenia. Jeśli klient wyśle polecenie zakoń,*; czenia połączenia, serwer nie odpow iada i natychm iast zam yka połączenie TCP,, Jeśli klient wyśle puste polecenie, sam znak końca wiersza, serw er odsyła znaki1' zachęty. Jeśli klient przez 30 sekund nie w ykazuje żadnej aktywności, nie przysyła żadnego polecenia, serwer w ysyła kom unikat o przekroczeniu czasu i zamyka pot' łączenie TCP. Jeśli serw er odbierze polecenie, którego nie potrafi zinterpretować/^* to w ysyła kom unikat informujący, że odebrał błędne polecenie i ponownie w y s y ła ć znak zachęty. Przesyłane teksty są kodowane za pom ocą ASCII. Klient i serw er jako znak końca'1 w iersza w ysyłają kolejno znak pow rotu karetki CR (ang. carriage return i znakżprzejścia do nowego wiersza LF (ang. line feed. Konwencja ta jest pow szechnie. stosow ana w protokołach intersieci, np. w protokołach Telnet i HTTP. Program tel-'x net po wciśnięci klaw isza Enter w ysyła właśnie znaki CR i LF. Taką sam ą kon- ;$ w encję stosuje się w system ie W indows. W ASCII znak CR ma kod 0x0D, a znak LF kod 0x0A. Serw er pow inien pomijać białe znaki w poleceniach odbieranych od1, klienta. Białymi znakam i, oprócz C R i LF, są np. spacja (znak SP, kod ASCII 0x20 i tabulacja (znak HT, kod ASCII 0x09. Projekt implementacji protokołu Gdy mamy ju ż gotowy projekt protokołu, możem y przejść do im plem entow ania"'^, serw era obsługującego ten protokół. W ymaga to najpierw zaprojektow ania analiza- -'4\ tora (ang. parser odbieranych komunikatów. W tym celu najwygodniej jest u/ \ i autom atu stanowego. N a ry su n k u 5.1 um ieszczone są symbole, których będę u; wał do rysowania takich automatów. A utom at ma pewną skończoną liczbę stanów. Jest to pamięć automatu. M ożna sobie ' w yobrażać, że stan to zm ienna w yliczeniow a przyjm ująca tyle różnych wartości, ile autom at ma stanów. Podczas pracy autom at zm ienia stan. Zm iana stanu może być w ynikiem odebrania komunikatu lub zajścia zdarzenia. K omunikaty są zdefiniowane przez protokół i pochodzą z sieci. Natom iast zdarzenia są generow ane lokalnie. Zdarzenia mogą być generow ane przez sprzętjub mogą być efektem działania użytkownika. Specjalnym rodzajem zdarzenia jest.budzik informujący o upłynięciu określonego czasu. Symbol budzika przypom ina klepsydrę. Podczas zmiany stanu. j.yij. autom at może wysłać kom unikat do sieci lub w ygenerować zdarzenie, które może. być też inform acją dla użytkownika. K omunikaty i zdarzenia reprezentowane są"' przez pięciokąty w kształcie strzałek z opisem w środku. Strzałka po prawej oznacza komunikaty, a strzałka po lewej - zdarzenia. Strzałka skierow ana do wewnątrz oznacza odebranie komunikatu lub zajście zdarzenia, a strzałka skierow ana na ze wnątrz oznacza wysłanie komunikatu lub m y generow anie zdarzenia. A utom at rozpoczyna pracę w stanie początkowym. Podczas pracy autom at pr/.ei dzi pomiędzy stanami w ewnętrznym i, które można ponum erować. Jednak wygodniej jest nadać stanom nazwy, co istotnie popraw ia czytelność rysunku i ułatwia zro-1 if. O debranie kom unikatu Z W ysianie kom unikatu Zajście zdarzenia W ygenerowanie zdarzenia M odyfikacja zmiennej wewnętrznej! Stan początkowy Stan końcowy Stan wewnętrzny Budzik Alternatyw a Przepływ sterowania Rys. 5.1. Symbole używane w automatach stanowych opisujących protokół zum ienie algorytm u, yżedług którego działa autom at. Stan w ewnętrzny reprezentowany jest przez owal z nazwą tego stanu. A utom at m oże zatrzymać się i zakończyć pracę w stanie końcowym. Stanów końcow ych może być wiele. Stanu końcowego m oże też nie być, gdy nie przewidujemy zatrzym ania automatu. Przepływ sterowania w autom acie zaznaczamy, łącząc poszczególne sym bole strzałkami. Z danego stanu autom at może zostać w yprowadzony przez wiele różnych komunikatów lub zdarzeń, co rysujemy za pomocą rozgałęziających się strzałek. Aby nie tworzyć zbyt wielu stanów, czasem wygodnie jest w prowadzić do automatu zm ienne wewnętrzne. Zm iany wartości zmiennych w ewnętrznych rysuje się w prostokątach. Jeśli autom at zaw iera zm ienne wewnętrzne, to przepływ sterow a nia m ożna uzależnić od ich wartości. Rysuje się to przez umieszczenie właściwego warunku w rombie, do którego prow adzi jedna strzałka, a z którego w ychodzą dwie strzałki. Jedna, ta oznaczona etykietą tak, pokazuje przepływ sterowania, gdy waru- "ifek jeśf spełniony, a druga - oznaczona etykietą nie - gdy nie jest spełniony. Form alnie wszystkie zmienne w ewnętrzne tworzą pam ięć automatu, czyli są częścią jego stanu. W szystkie zmienne w ewnętrzne można usunąć, wprowadzając dodatkowe stany. Przykładowo, jeśli zm ienna może przyjm ow ać 30 wartości, to każdy stan należy zastąpić trzydziestom a stanami, po jednym dla każdej wartości usuwanej zm iennej. W idać, że prowadzi to do law inow ego wzrostu liczby stanów i zm niejsza czytelność diagramu opisującego autom at. Z drugiej strony, można zredukować liczbę nazwanych stanów wewnętrznych do jednego, um ieszczając całą pamięć automatu w zmiennych. Takie skrajne podejście też zm niejsza czytelność diagramu. 0.'W bq^ e, co ma być stanem, a co zmienną, decyduje więc przede wszystkim czytelnosśćji(izyskanego projektu automatu.
-K.v: 174 5. Serwer TCP.i* 5.1. Protokół warstwy aplikacji 175 Na kolejnych rysunkach pokazano projekt im plem entacji serw era realizującego protokół opisany w poprzednim podrozdziale. Dla każdego połączenia serwer uru-ą cham ia nową kopię autom atu. Na ry su n k u 5.2 przedstaw iony jest początkowyfragm ent działania autom atu. Po odebraniu nowego połączenia serw er wysyła do/ klienta tekst pow itania oraz znak zachęty do w pisania polecenia i przechodzi do"' stanu InilState, w którym czeka na polecenie. Poniew aż TCP jest protokołem stru-,;'/ m ieniow ym, odbierane dane trzeba analizow ać znak po znaku. W stanie InitStale.: serw er ignoruje znaki spacji SP, tabulacji IIT i powrotu karetki CR, a po każdym i znaku now ego wiersza w ysyła kolejny znak zachęty. Ze specyfikacji protokołu wi,j.' dać, że końcem wiersza jest para znaków CR i LF. Form alnie należałoby traktować \ sam znak LF jako błąd. Jednak w system ach uniksow ych jako znak końca w iers/a stosuje się sam znak LF. Zatem bardziej rozsądnym podejściem jest uznanie samego znaku LF jako popraw nego zakończenia w iersza. U praszcza to implementację ' - nie trzeba w prowadzać dodatkow ego stanu oczekiw ania po znaku CR na znak LF. Ponadto taka im plem entacja serw era toleruje klienta, który nie działa w pełni '< zgodnie ze specyfikacją, ale odstępstw o od niej jest na tyle drobne, że polece- nia mogą być popraw nie zinterpretow ane. A lternatyw nie należałoby potraktować brak znaku CR przed LF jako błąd, ale takie rygorystyczne traktow anie błędów nie zaw sze jest dobrą strategią. O debranie w stanie InitStale jednego ze znaków wym ienionych w tabeli 5.1 pow oduje przejście do stanu, w którym serw er będzie oczekiwał na zakończenie polecenia, czyli na odebranie znaku LF. Serwer ma " współpracować z klientem protokołu Telnet, więc dodatkow o w stanie InilState rozpoznaje znak IAC (ang. interpret cis com m and o kodzie OxFF, o czym dalej. SP, HT, CR <( LF In n y znak <( 1 Tekst pomocy \ 1znak zachęty / Rys. 5.3. Projekt implementacji serwera - stan HelpState Do im plem entacji serw era w prowadzam y celow y błąd. O debranie znaku dolara zaw iesza działanie serwera - na rysunku nie ma strzałki w ychodzącej z sym bolu oznaczającego odebranie tego znaku. Ten błąd posłuży w następnym przykładzie do przetestow ania działania nadzorcy. O debranie każdego innego znaku w stanie InilState w prow adza autom at serw era w stan ErrorState. N a ry su n k u 5.3 zilustrow ane jest działanie serw era w stanie HelpState. Białe znaki są ignorowane. Po odebraniu znaku LF serw er w ysyła tekst pom ocy i znak zachęty, po czym wraca do stanu InitState, w którym będzie oczekiwał na kolejne polecenie. O debranie niebiałego znaku w stanie H elpstate pow oduje przejście do stanu ErrorState. Na ry su n k u 5.4 w idoczne jest działanie serw era w stanach dotyczących obsługi diod św iecących. Jak poprzednio w tych stanach białe znaki są ignorowane. Po odebraniu znaku LF serw er zależnie od stanu, w którym się znajduje, w łącza lub w yłącza odpow iednią diodę, a następnie w ysyła znak zachęty i w raca do stanu InitState. O debranie niebiałego znaku traktow ane jest jako błąd i pow oduje przejście do stanu ErrorState. N a ry su n k u 5.5 przedstaw iony jest fragment autom atu dotyczący stanu G etlocaltim estate. Schem at działania jest analo- Rys. 5.2. Projekt implementacji serwera - stan początkowy lim 1 I I I Rys. 5.4. Proijśkt implementacji serwera - stany LedState
5. /. Protokół warstwy aplikacji 177 Rys. 5.5. Projekt implementacji serwera - stan GetLocalTimeState giczny jak na poprzednich rysunkach, z tą tylko różnicą, że po odebraniu znaku j LF serw er w ysyła klientow i inform ację zaw ierającą czas, który upłynął od urucho-l m ienia m ikrokontrolera. N a ry su n k u 5.6 pokazano fragm ent autom atu dotyczący;;; stanu ExitState. Po odebraniu znaku L F serw er zam yka połączenie i kończy dzia- łanie autom atu. Rys. 5.8. Projekt implementacji serwera - stan ErrorState Na ry su n k u 5.7 pokazano działanie serwera w stanie lacstate. Serwer znajdzie się w tym stanie po odebraniu znaku IAC, który oznacza początek polecenia protokołu Telnet. Powodem w prowadzenie tego stanu jest um ożliwienie współpracy serwera z program em telnet. W tym celu wystarczy, aby serwer ignorował polecenia DO (kod OxFD, D ONT (kod OxFE, W ILL (kod OxFB i W O N T (kod OxFC. Po każdym z tych poleceń w ystępuje jednooktetow y kod opcji, na którą serwer czeka w stanie O ptionstate i którą ignoruje, wracając po jego odebraniu do stanu InitState. Inne polecenje nie powinno się pojawić i jest traktowane przez serwer jako błąd. Więcej na teh tem at można przeczytać w [1, rozdz. 23]. Na ry su n k u 5.8 objaśniono działanie serw era w stanie ErrorState. W tym stanie serw er pow inien starać się wyjść z błędu i rozpoznać początek poprawnego polecenia. D latego serw er ignoruje wszelkie znaki z w yjątkiem LF, po którego odebraniu w ysyła do klienta informację o błędzie i znak zachęty do wpisania poprawnego polecenia, po czym wraca do stanu InitState. Rys. 5.6. Projekt implementacji serwera - stan ExitState I, Nowe połączenie <T tłmeout = 30 Tekst powitania \ i znak zachęty / Rys. 5.7. Projekt implementacji serwera - stan lacstate lii s Rys. 5.9. Projdikt implementacji serwera - przekroczenie czasu
178 5. Serwer 7 5.2. 5.2.1. Specyfikacja protokołu nakazuje serwerow i zaniknięcie poleczenia TCP, gdy kii nie odzywa się, nie przysyła żadnego polecenia przez 30 sekund. Aby zrealizowa to wymaganie, dodajem y budzik, który generuje zdarzenie upływu czasu co sekuąl dę. Ponadto dodajem y zm ienną timeout, która zlicza upływ czasu - jest zr szana o jeden po odebraniu zdarzenia budzika. O dpowiednie modyfikacje automatu! przedstaw ione są na ry su n k u 5.9. Po odebraniu nowego połączenia serwer inicji zmienną timeout w artością 30. D odatkow o w każdym stanie (na rysunku zaznaczo-^ nym jako owal z w ielokropkiem po odebraniu dow olnego komunikatu (na rysunkui kom unikat oznaczony w ielokropkiem trzeba ponow nie zainicjow ać tę zmienną,i' W każdym stanie należy też dodać obsługę zdarzenia budzika: serw er zmniejsza! wartość zmiennej timeout i jeśli osiągnie ona wartość zero, to inform uje klientki! o upłynięciu czasu oczekiwania na polecenie i zam yka połączenie TCP. Przykład 5a - pierwsza wersja serwera TCP Nazwy plików przykładu 5a zam ieszczone są w tabeli 5.3. W iększość z nich została ju ż opisana w poprzednich rozdziałach. Dalej opisuję tylko nowe pliki. Pliki tcp_server.h i tcp_server.c Plik tcp_server.h zaw iera sygnaturę funkcji TCPserverStart, która urucham ia ser-:i'ij w er TCP..lej im plem entacja znajduje się w pliku lcp_servenc. Argum entem funkcji'-,? TCPserverStart jest numer portu, na którym serwer ma nasłuchiwać. Funkcja ta jfjj zwraca zero, gdy uruchom ienie pow iodło się, a wartość ujemną, gdy nie udało się-żłj uruchom ić serwera. Funkcja TCPserverStart działa zgodnie z diagram em serwera,śj przedstaw ionym w poprzednim rozdziale na rysunku 4.4, a dodatkow o sprawdza: wartości zwracane przez poszczególne funkcje z biblioteki lwip i obsługuje błędy. Funkcja ta jest przeznaczona do w ywołania z program u głównego, a więc spoza biblioteki lwip, dlatego w ywołania funkcji bibliotecznych odbyw ają się przy zablokowanych przerwaniach biblioteki. Funkcja tcp_listen wykonuje pasyw ne otwar-: cie - aktyw uje nasłuchiwanie połączeń od klientów. Funkcja tcp_accept rejestruje funkcję zwrotną obsługującą faktyczne otw ieranie połączenia. M iędzy wywołaniem. tcp_listen a tcp_accept stos lw IP nie może odebrać połączenia, ale ponieważ przerwania są zablokowane, to ew entualne odebrane w tym czasie pakiety sieciov Tab. 5.3. Pliki przykładu 5a e x jc p d.c startup_stm 32j:ld. c tcp_server.c tcp_server.li Źródłowe i biblioteczne board_conl.c boardjnil.c boardj e d.c util_delay.c util_eth_nc.c uli/jed.c u tiljw ip.c ulil time.c Nagłówkowe. board_conf.h board_de!.h board_de!s.h boardjn it.h board led.h util_delay.h * util_eth.li u tilje d.li u tiljw ip.h util time.ti tibiwip4.a iibstm32f10x.a CC./ cortex-m3, h lwipopts.li stm32!10x_conf.h R. f i 1 i i V is 1111 ; JjV 7 5'i : T 5.2. Przykład 5a - pierwsza wersja serwera TCP 179 poczekają w kolejce odbiorczej DM A i zostaną przetworzone przez stos dopiero po ponow nym uaktywnieniu przerwań - nic złego się nie stanie. int TCPserverStart(uintl6 t port { struct tcp_pcb *pcb, *listen_pcb; err_t err; IRQ~DECL_PROT CT(x ; IRQ^PR0TECT(x, LWIP_IRQ_PRI0 ; pcb = tcp_new(; IRQ UNPROTECT(X; i f Tpcb *== NULL return -1; IRQ^PROTECT(x, LWIP_IRQ_PRI0; err = tcp_bind(pcb, IP_ADDR_ANY, port; IRQ_UNPROT CT(x; i f (err!= ERR_0K ( IRQ_PROTECT(x, LWIP_IRQ_PRI0; tcp_abandon(pcb, 0; IRQ_UNPROTECT(x; return -1; } IRQ_PR0TECT(x, LWIP_IRQ_PRIO; listen_pcb = tcp_listen(pcb; if (listen_pcb tcp_accept(listen_pcb, accept_callback; IRQJJNPROTECT(x ; if (listen pcb == NULL { IRQ PROTECT(x, LWIP_IRQ_PRI0; tcp_abandon(pcb, 0; IRQJJNPROTECT(x; return -1; } return 0; Stan autom atu serwera przechow ujem y w strukturze typu s t a te. Składow a timeout tej struktury zawiera liczbę sekund, przez które serw er ma jeszcze czekać na polecenie od klienta, patrz rysunek 5.9. Składow a function tej struktury jest adresem funkcji, która ma być w ywołana w celu obsłużenia kolejnego odebranego znaku. struct state { err t (* function (struct state *, struct tcp_pcb *,..uint8 t; int timeout; 1; Dla każdego stanu definiujem y osobną funkcję, której nazwa jest taka sama jak nazwa tego stanu. Zmiana stanu odbyw a się przez zm ianę wartości składowej function. W szystkie funkcje mają tę sam ą sygnaturę. Pierwszym argum entem jest w skaźnik do struktury typu state, co um ożliw ia obsługę wielu klientów (połączeń TCP za pom ocą tych sam ych funkcji - funkcja dostaje w skaźnik do struktury związanej z klientem (połączeniem TCP, którego ma obsłużyć. D rugim argumenlem ijast y k aźnik do deskryptora TCP opisującego połączenie, z którego odebrano dane. Trzccjin argum entem jest odebrany znak. Funkcje obsługujące stany zwracają
5.2. Przykład 5a - pierwsza wersja serwera 'PCP 181 wartości typu e r r _ t zgodnie z konw encją przyjętą w bibliotece lwlp, czyli ERR_; y przy popraw nym zakończeniu lub w łaściwy kod błędu. D odatkow o m ogą zwrócier wartość ERRJLXIT, która oznacza, że funkcja zakończyła się popraw nie, ale nalen zamknąć połączenie TCP. łdefine ERRJ1XIT 100 Funkcje obsługujące poszczególne stany automatu serwera są następujące: static err t InitState (struct state *, struct tcp_pcb *, uint8_t; static err_t lacstate(struct state *, struct tcp_pcb *, uint8_t; static err t OptionState(struct state *, struct tcp_pcb *, uint8_t; static err_t HelpState(struct state *, struct tcp_pcb *, uint8_t; static err_t OnGreenLedState(struct state * struct tcp_pcb *, uint8_t; static err_t OffGreenLedState(struct state struct tcp_pcb * uint8_t; static err_t OnRedLedState(struct state * struct tcpjpcb *, uint8_t; static err_t OffRedLedState(struct state, struct tcp_pcb *, uint8_t; static err t GetLocalTimeState(struct state struct tcp pcb uint8_t; static err_t ExitState(struct state *, struct tcp_pcb *, uint8_t; static err_t ErrorState(struct state *, struct tcp_pcb *, uint8_t; W iększość odpow iedzi udzielanych przez serwer, zam ieszczonych w tabeli 5.2,. to stale napisy, które znajdują się w pamięci tylko do odczytu (ROM lub racz o i Flash. Aby skrócić tekst program u, do ich wysyłania definiujem y makro tc p ^ te_from_rora. Argum ent p jest wskaźnikiem do deskryptora TCP opisującego polą-i^ czenie, przez które ma być wysłana odpow iedź. Argument x jest nazw ą tablicy typu.fjj ch ar zawierającej napis do wysiania zakończony zerowym bajtem, który nie jest;1 wysyłany. M akro nie działa poprawnie, jeśli x jest wskaźnikiem typu char*. Należy też zwrócić uwagę, że jeśli używamy sterow nika util_eth_zc.c, to dane do wysłania'! trzeba skopiować do pamięci biblioteki - ostatni argum ent funkcji tc p _ w rite musi$ mieć wtedy wartość TCP_WRITEJTLAG_COPY. jjdefine tcp_write from_rom(p, x tcp_write(p, x, sizeof(x - 1, 0 Funkcja zwrotna accept_callback jest wywołana, gdy zostanie odebrane nowći^jf połączenie. Funkcja ta realizuje pozostałą część diagram u serwera z rysunku 4.4.''- ; N ajpierw alokujemy now ą strukturę typu s t a t e i inicjujemy jej składowe. Struktura; s t a t e jest zw iązana z nowym połączeniem (klientem, a jej adres będzie przeka-jd zywany funkcjom zw rotnym obsługującym to połączenie. Na koniec wysyłamy doljij klienta tekst powitania i znak zachęty. static const char invitation!}» "\r\n" "This is TCP server running on top of the lwlp stack.\r\n" "Press h or? to get help.\r\n" "\r\n" idefine SERVER_TIMEOUT '30 idefine POLL PER SECOND 2 static errjt accept_cauback(void *arg, struct tcp_pcb *pcb, err_t err { struct state *state; state = mein malloc (sizeof (struct state; if (state == NULL return ERR_MEM; state->function = InitState; state->timeout = SERVER_TIMEOUT; tcp_arg(pcb, state; tcp_err (pcb, conn err_callback ; tcp_recv (pcb, recv callback ; tcp_poli (pcb, pou callback, POLL_PER SECOND ; return tcp_write_from_rom(pcb, invitation; } Gdy połączenie zostanie zam knięte awaryjnie lub z powodu wystąpienia błędu, biblioteka lw lp wywołuje funkcję zw rotną co n n _ err_ callb ack. Zadaniem tej funkcji jest zw olnienie pamięci przydzielonej strukturze typu s t a t e w funkcji a c c e p t_ c a l- lback. A dres struktury typu s t a te, która ma być usunięta z pamięci, jest pierwszym argum entem funkcji c o n n _ err_ callb ack. static void conn_err_callback(void *arg, err_t err ( mem_free(arg; i W kilku miejscach w program ie serwera w ystępuje potrzeba zam knięcia połączenia TCR Trzeba wtedy zwolnić pamięć przydzieloną strukturze typu s t a t e oraz wywołać funkcję tc p _ c lo se z biblioteki lwlp. Aby nie powtarzać tych samych sekwencji instrukcji, im plem entujem y funkcję tc p _ e x it zam ykającą połączenie TCP. Pamiętajmy, że po wywołaniu tc p _ c lo se mogą jeszcze przyjść dane, ale nie będziem y mogli ich obsłużyć, gdy wcześniej zwolnim y pamięć przechowującą stan połączenia. Z kolei, jeśli nie zwolnim y tej pamięci, to po zakończeniu funkcji tcp_ e x it nie będzie ju ż okazji do jej zwolnienia. D latego na początku funkcji tc p _ e x it wyrejestrow ujem y funkcję zw rotną służącą do odbierania danych, wywołując funkcję tcp _ recv. static void tcp_exit(struct state state, struct tcp_pcb *pcb ( tcp_recv(pcb, NULL; if (state ( mesi_free (state ; tcp_arg(pcb, NULL; I tcp_close(pcb; I Przetwarzanie odebranych danych i w ysyłanie odpowiedzi odbywa się w funkcji zwrotnej recv_callback. Najpierw sprawdzamy, czy w ywołanie tej funkcji jest skutkiem odebrania nowych danych, czy też zostało zainicjowane zamykanie połączenia TCP. Jeśli wskaźnik p jest niezerowy, to ożhiicza, że odebrane zostały dane. Najpierw wywołujemy funkcję tcp_recved, która informuje stos lwlp, że aplikacja odebrała dane. W łaściwe przetwarzanie delegujem y do funkcji StateAutomaton. Jeśli funkcja StateAutomaton zakończy się sukcesem i funkcja zwrotna recv_cal- Iback ma zwrócić wartość ERR_OK, to należy zw olnić łańcuch buforów wskazywany przez p. Jeśli funkcja StateAutomaton zwróci wartość ERR_EXIT lub wskaźnik p jest równy zeru (druga strona zam knęła połączenie od swojej strony, to należy zamknąć połączenie TCP za pom ocą funkcji tcp_exit. errjjt recv_callback(void *arg, struct tcpjpcb *pcb, * I struct pbuf *p, err_t err ( if (p t 'i
5.2. Przykład 5a ~ pierwsza wersja serwera TCP 183 tcp_recved(pcb, p->łot_len; err = StateAutomaton(arg, pcb, p; if {err == ERR_OK j err == ERR_EXIT pbuf_free(p; if (err == ERR_EXIT { tcp_exit(arg, pcb; err * ERR_OK; 1 else { tcp^exit(arg, pcb; err"~= ERR OK; return err; i Funkcja StateAutomaton obsługuje autom at stanowy serwera. Najpierw ponownie' inicjujemy składową timeout - klienent jest aktywny, więc czas połączenia wydłużamy o kolejne 30 sekund. Następnie przeglądamy wskazywany przez p łańcuch buforów zawierający odebrane dane i dla każdego znaku w ywołujem y funkcję wskazywaną przez składową function struktury state. Pamiętajmy, że ta funkcja mo/e zmienić wartość składowej function - automat może przejść do innego stanu. static err_t StateAutomaton(struct state *s, struct tcp_pcb *pcb, struct pbuf *p I s->timeouł = SERVER_TIMEOUT; for {;; ( uint8_t *c = (uint8_t *p->payload; uint!6_t i; err t err; j for (i = 0; i < p->len; ++i if (ERR_OK!= (err = s->function(s, pcb, c (ij return err; if (p~>len == p->tot_len break; else p = p->next; } return ERR_OK; } Przetwarzaniem w stanie In its ta te zajm uje się funkcja o tej samej nazwie. Funkcja ta działa zgodnie z diagram em z rysunku 5.2. static const char promptf] = "\r\n" "> "; ' ' *"*' static err_t InitState(struct state *state, struct tcp_pcb *pcb, uint8_t c ( switch (c { case IAC: state->function = IACState; return ERR_0K; case ' ': " / case *\t1: t case 1\r': return ERR_OK; case '\n *: return tcp write from rom(pcb, prompt;! case 'h': case '?': state->function» HelpState; return ERRJ3K; case 1G ': state->function = OnGreenLedState; return ERR_0K; case 'g': state->function» OffGreenLedState; return ERR_OK; case *R * : state->function = OnRedLedState; return ERR_0K; case 'r1: state->function = OffRedLedState; return ERR_0K; case 11': state->function = GetLocalTimeState; return ERR_0K; case 1x ' : state->function - ExitState; return ERR OK; case for {;;; default: state->function = ErrorState; return ERR_0K; } Funkcje obsługujące kolejne stany są bardzo podobne. D latego prezentuję tylko niektóre z nich. N ajpierw jedna z funkcji zajm ujących się obsługą diod świecących. Działa ona zgodnie z diagram em z rysunku 5.4. static err_t OnGreenLedState(struct State *state, struct tcp_pcb *pcb, uint8_t c { switch (c ( case ' ': case '\t': case '\r': return ERR_0K; case '\nł: state->function» InitState; GreenLEDon{ ; return tcp write from_rom{pcb, prompt; % default: state->function = ErrorState; return ERR OK; ~ * W ysłaniem informacji z czasem, który upłynął od w łączenia m ikrokontrolera, zajmuje się funkcja GetLocalTimeState. Funkcja ta działa zgodnie z diagramem z rysunku 5.5. Napis można sform atować za pom ocą funkcji snprintf, która należy do standardowej biblioteki C, ale nie jest w spółużyw alna. Zam iast niej lepiej jest użyć J-fir funkcji sn p rin tf r, która jest w spółużyw alna i jest dostarczana przez bibliotekę Newj-ib. O bie funkcje tworzą napis, który zgodnie z konw encją obow iązującą w języrirfc zm dńczony jest zerowym bajtem. Im plem entow any protokół, jak większość protokołów fintersieci, nie w ysyła w napisach tego term inalnego zera.
184 5. Serwer T( p; static const char timefrml = "\r\n" "%u ^02u:ł02u:%02u.%03u\r\n" "\r\n" "> static err_t GetLocalTimeState{struct state state, struct tcp_pcb *pcb, uint8_t c { switch (c { case ' : case \t'; case *\r': return ERR_OK; case *\n *: { char buf[time_buf_size]; unsigned d, h, m, s, ms; int size; struct _reent reent; I state->function = InitState; GetLocalTime{&d, &h, &m, &s, &ms; size = _snprintf_r{6reent, buf, TIME_BUF SIZE, timefrm, d, h, m, s, ms; if (size <- 0 return tcp_write_from_rom{pcb, error; if {size >= T IME BUF_SIZE size = TIME_BU?_SIZE - 1; return tcp_write(pcb, buf, size, TCP_WRITE_E,LAG_COPY; default: state->function ErrorState; return ERR OK; Polecenie, które klient w ysyła do serwera, chcąc zakończyć z nim połączenie, < - sługuje funkcja Exitstate, działająca zgodnie z diagram em z rysunku 5.6. Aby -f zakończyć działanie autom atu i zam knąć połączenie z klientem, funkcja ta zwraca 1 w artość ERR_EXIT. static err_t Exitstate{struct state state, struct tcp_pcb pcb, uint8_t c ( switch {c { case ': case *\t *: case *\ r 1:, 'fir ' return ERR OK; case '\n': t r-ł-łtr return ERR_EXIT; default: state->function = ErrorState; return ERR OK; 1 P - - 1 Poinform ow aniem klienta o błędnym poląceniu zajm uje się funkcja ErrorSt działająca zgodnie z diagram em pokazanyńi na rysunku 5.8. "' i. static const char error[] = "\r\n" a "ERROR\r\n" "\r\n" g P P ta j l l, 5.2. Przykład 5a - pierwsza wersja serwera TCP 185 "> static err t ErrorState{struct state state, struct tcp_pcb *pcb, uint8_t c { switch {c { case '\n': state->function = InitState; return tcp_write_from_rom{pcb, error; default: return ERR OK; i Funkcja zwrotna p o ll_ c a llb a c k im plem entuje fragment diagramu z rysunku 5.9 obsługujący zdarzenie budzika. Pozostałe fragmenty tego diagramu, inicjujące zm ienną tim eout, są zaim plem entow ane w funkcjach a c c e p t_ c a llb a c k i StateA utom aton. Jeśli w skaźnik s t a t e ma wartość NULL, może to oznaczać, że wystąpił problem z zam knięciem połączenia - struktura s t a t e została zwolniona, a w ywołanie tc p _ c lo se nie pow iodło się i połączenie nadal jest otwarte. Trzeba spróbować zam knąć je ponownie. static const char timeout U = "\r\n" "TIMEOUT\r\n"; static err_t poll_callback{void *arg, struct tcp_pcb *pcb { struct state state = arg; if (State == NULL ' tcp_exit{arg, pcb; else if { (state->timeout <= 0 { tcp_write_from_rom(pcb, timeout; tcp_exit(arg, pcb; return ERR_OK; 2. Plik e x jc p d.c Plik e x jc p d.c zaw iera funkcję main przykładu. W funkcji tej uruchamiamy kolejne potrzebne układy m ikrokontrolera i stos lwip oraz konfigurujem y ustawienia sieciowe, analogicznie jak w opisanym wcześniej przykładzie 3a. Na zakończenie urucham iam y serwer TCP. Serw er nasłuchuje na standardowym porcie usługi Telnet, czyli na porcie 23. M ożna wybrać dow olny num er portu z przedziału od 1 do 65 535. Nie pow inno się jednak używać portów pow szechnie znanych usług wbrew ich przeznaczeniu. Podczas urucham iania-św ieci się czerwona dioda. Jej zgaśnięcie sygnalizuje zakończenie urucham iania serwera. M iganie czerwonej diody w rytmie zdefiniowanym drugim argum entem makra e rro r_ ch eck oznacza, że w ywołanie funkcji, um ieszczone jako pierwszy argum ent tego makra, zakończyło się niepowodzeniem. int main{ { static struct netif netif; static struct ethnetif ethnetif = {PHYJVDDRESS}; uiqt8_t confbit; Deii^dSjdoO; confbit,= ffetconfbit{;
186 5. Serwer TCl\ 5.2. Przykład 5a - pierwsza wersja serwera TCP 187 AllPinsDisable{; LEDconfigureO ; RedLEDonf; SET IRG_PROTECTION { ; error_check(clkconfigure(, 1; error_check(localtimeconfigure(, 2; error_check(ethconfiguremii{, 4; netif.hwaddr(o - 2; netif.hwaddr[l] = (BOARDJTYPE» 8 & Oxff; netif.hwaddr[2] = BOARD_TYPE & Oxff; netif.hwaddr[3j «= (ETHJ30ARD» 8 & Oxff; netif.hwaddr[4] = ETH_BOARD & Oxff; netif.hwaddr[5] = 1 + confbit; if (iconfbit IP4_ADDR(Snetif.ip_addr, 192, 168, 51, 84}; IP4 ADDR(&netif.netmask, 255, 255, 255, 240; IP4_ADDRf&netif.gw, 192, 168, 51, 81; } else { IP4_ADDR(&netif.ip^addr, 0, 0, 0, 0 ; IP4_ADDR(finetif.netmask, 0, 0, 0, 0; IP4_ADDR(&neti f.gw, 0, 0, 0, 0; 1 error_check(lwipinterfacelnit(snetif, Sethnetif, 5; LWIPtimersStart(; error check(tcpserversłart(23, 7; RedLEDoff{; return 0;! Zwróćm y uwagę, że funkcja main kończy się instrukcją re tu rn. Jest to dopuszczalne, gdy używamy pliku startup_stm 32_cld.c. Procedura startow a zaim plem entowana w tym pliku wchodzi w nieskończoną pętlę, gdy funkcja main zakończy działanie. Jeśli używamy innej procedury startow ej, bezpieczniejszym rozw iązaniem jest zastąpienie instrukcji re tu rn nieskończoną pętlą: for 5.2.3. Testowanie przykładu U ruchamiając przykład w zestawie ZL29A RM, w ybieram y za pom ocą zworki BOOT1 sposób skonfigurow ania ustawień sieciowych. Jeśli zw orka jest w pozycji 0, użyte zostaną ustaw ienia zdefiniow ane statycznie w funkcji main. Jeśli jest w pozycji 1, to zostanie uruchom iony klient DHCR "Serwer przystosow any jest do współpracy z program em telnet. W najprostszym przypadku urucham iam y go z linii poleceń systemu W indows lub konsoli Linuksa, pisząc telnet host port Jako h o st podajemy adres IP przydzielony zestawowi uruchom ieniowem u, używając notacji z kropkami. Jako p o rt podajem y num er portu, na którym nasłuchuje serwer. Jeśli nie podamy numeru portu, Jo zostanie użyty dom yślny port usługi Telnet, czyli port 23. Zatem zakładając, że zestaw uruchom ieniowy ma adres 192.168.5U89, możemy połączyć się z nim za pom ocą polecenia telnet 192.168.51.89 Jako h o st można też podać nazwę węzła. W Linuksie w pliku /etc/hosts możemy zdefiniow ać nazwy w ęzłów w naszej sieci lokalnej. Jeśli umieścimy w tym pliku wpis 192.168.51.89 local-89.localdomain local-89 to będziem y mogli połączyć się z serwerem za pom ocą polecenia telnet local-89 lub telnet local-89.localdomain Przykładową sesję, w której w ypróbowano wszystkie polecenia, przedstawiono w tabeli 5.4. Podczas tej sesji powinniśm y zaobserwow ać włączanie i wyłączanie diod św iecących. W trakcie połączenia możem y za pom ocą kombinacji klawiszy Ctrl+] przełączyć program telnet w tryb w ydaw ania lokalnych poleceń. Opis pole- Tab. 5.4. Test poleceń serwera Linux Windows $ telnet 192.168.51.89 Trying 192.168.51.89... C:\>telnet 192.168.51.89 Connected to local-89.localdomain (192.168.51.89. This is TCP server running on top of the Escape character is,a]'. lwlp stack. Press h or? to get help. This is TCP server running on top of the lwlp stack. > h Press h or? to get help. h or? print this help > h G g turn on the green led turn off the green led h or? print this help R turn on the red led g ' turn on the green led r turn off the red led g turn off the green led t get microcontroller running time R turn on the red led X exit r turn off the red led t get microcontroller running time > G X exit > g > G > R > g > r > R > t > r 0 00:05:01.990 > t > a 0 00:07 ERROR > a ERROR CO «sr > xæ Connect io^fei-osed by foreign host. $. Ù > X Connection to host lost. C:\>
5.3. Przykład 5b - serwer TCP z nadz,orcą 189 Tab. 5.5. Test zamknięcia połączenia Linux W indows lf: S $ telnet 192.168.51.89 C:\>telnet 192.168.51.89 Trying 192.168.51.89... Connected to local-89.localdomain This is TCP server running on top of the (192.168.51.89. lwip stack. Escape character is Press h or? to get help. This is TCP server running on top of the > t : lwip stack. Press h or? to get help. 0 00:06:22.210 > t > A3 r 0 00:08:57.230 Welcome to Microsoft Telnet Client > A1 Escape Character is 'CTRL+}* telnet> quit Connection closed. Microsoft Telnet> quit e 9 C:\> Tab. 5.6. Test przekroczenia czasu Linux $ telnet 192.168.51.89 Trying 192.168.51.89... Connected to local-89.localdomain (192.168.51.89. Escape character is,a3'. This is TCP server running on top of the lwip stack. Press h or? to get help. > t 0 00:11:59.220 TIMEOUT Connection closed by foreign host. $ Windows C:\>telnet 192.168.51.89 This is TCP server running on lwip stack. Press h or? to get help. > t 0 00:08:45 200 > TIMEOUT Connection to host lost. C:\> top of the ceń dostępnych w tym trybie m ożna uzyskać, w ydając polecenie help. Połączenie można zamknąć poleceniem c lo s e. Połączenie można też zamknąć, kończąc jednocześnie działanie programu telnet, poleceniem q u i fa w Linuksie dodatkowo kombinacją klawiszy Ctrl-D. Odpowiedni przykład pokazano w tabeli 5.5. Znaczki ' ' oznaczają w ciśnięcie klawiszy Ctrl-]. W tabeli 5.6 zam ieszczono test zamknięcia połączenia TC P w wyniku przekroczenia czasu oczekiwania na polecenie. Przykład 5b - serwer TCP z nadzorcą * Serw er z poprzedniego przykładu ma pew ną wadę. Jeśli wystąpi problem podczas jego urucham iania, to program kręci się w nieskończonej pętli w ewnątrz makra ei - ror_ check. Niezłym pom ysłem jest w tej sytuacji wyzerowanie mikrokontrolera Tab. 5.7. Pliki przykładu 5b e xjcpd_w dg.c startup_stm32_cld. c tc p je rv e r.c tc p je r m.h Źródłowe i biblioteczne b o a rd jo n l.c boardjnit.c boardjed. c b o a rd jo n f.h board_dei.li board_dels.li b o a rd jn ith b o a rd jed.li Nagłówkowe util_delay.c u t iljth jic.c ulujedtc utiljw ip.c utiljim e.c u lfj/d g.c util_delay.h u tiljth.il u tiljed.h utiljw ip.li uliljim e.h util wdg.h Iiblwip4.a Iibstm32t10x.a CC.Il cortex-m3.li lwipopts.li stm 32t10xjont.h i ponow ienie próby uruchom ienia serwera. Ponadto sam serw er jest dość skom plikow aną aplikacją i trudno wykluczyć w ystąpienie w jego im plem entacji jakiegoś błędu, który doprowadzi do zawieszenia. W im plem entacji serwera w prowadziliśmy celow o taki błąd. A plikacje urucham iane na m ikrokontrolerach mają działać bezobsługow o i aby radzić sobie z takimi problem am i, mikrokontrolery wyposażane są w układ nadzorcy (ang. watchclog. W tym przykładzie dodajem y do serwera TCP obsługę układu nadzorcy. Nazwy plików potrzebnych do jego skom pilowania zam ieszczone są w tabeli 5.7. Są to w większości te same pliki co w poprzednim przykładzie. W dalszyńt ciągu opisuję tylko różnice między tymi przykładami. Pliki util_wdg.h i utiljw dg.c M ikrokontrolery STM 32 wyposażone są w dw a układy nadzorców: IWDG (ang. independent watchdog i W W DG (ang. witulow wcitchdog. Układ IW DG jest typowym układem nadzorcy, jaki znajduje się w większości mikrokontrolerów i dlatego w ykorzystam y właśnie ten układ. Układ IW DG zaw iera 12-bitowy licznik zliczający od zadanej wartości początkowej do zera. Taktowany jest przez dzielnik wstępny (ang. prescaler w łasnym generatorem LSI (ang. Iow speed internal RC 0 nom inalnej częstotliw ości 40 khz. G enerator ten ma spory rozrzut produkcyjny 1 według danych katalogowych [8, 9] jego rzeczyw ista częstotliwość waha się między 30 a 60 khz. W pliku util wgd.h deklarujem y makro WatchdogReset, które restartuje licznik nadzorcy. W tym celu wywołuje funkcję IWDG_ReloadCounter z biblioteki STM32. łdefine WatchdogReset IWDG_ReloadCounter Funkcja IWDG_ReloadCounter jest bezparam etrowa, ustawia początkową wartość licznika układu nadzorcy i nie zw raca żadnej wartości. Aby zapobiec wyzerowaniu mikrokontrolera przez układ nadzorcy, funkcja ta pow inna być wywołana w regularnych odstępach czasu. Do inicjowania układu nadzorcy deklarujem y w pliku util_wgd.h funkcję WatchdogStart. Jej im plem entacja znajduje się w pliku util_ wgd.c. Na początku tego pliku definiujem y tablicę prescalertbl dopuszczalnych wartości dzielnika wstępnego. 't liii li I static conirc'jgint8 t prescalertbl [] = { IWDG_Presciiłer_4,
190 5. Seerwer 5.3. Przykład 5b - serwer TCP z nadzorcą 191 5.3.2. IWDG_Prescaler_8, IWDG_Prescaler_16, IWDG_Prescaler_32, IWDG_Prescaler_64, IWDG_Prescaier 128, IWDG_Prescaler 256 i; static const int maxldx = sizeof(prescalertbl / sizeof(prescalertbl[0] - 1; Argum entem funkcji W atchdogstart jest czas, po którym licznik układu nai ina osiągnąć wartość zero i w yzerow ać mikrokontroler. Wartość tego czasu podaj my w milisekundach. Dokładnej w artości nie uda się uzyskać, choćby ze wz na niedokładność częstotliw ości taktowania. W ybieramy wartość dzielnika nego i początkową wartość licznika, aby jakoś przybliżyć ten czas. Do obliczeprzyjmujemy nom inalną częstotliw ość taktowania 40 khz. Układ IW DG konfjgu'. rujemy za pomocą funkcji z biblioteki STM 32. Nazwy tych funkcji rozpoczynaj się prefiksem IWDG. Funkcja IWDG_WriteAccessCmd uaktywnia zapis do rejestrókonfiguracyjnych układu nadzorcy. Funkcja IW DG_SetPrescaler ustawia dzielni wstępny układu nadzorcy. Funkcja lwdg SetReload ustawia wartość, którą jest Ini cjowany licznik nadzorcy po w ywołaniu funkcji IWDG_ReloadCounter. Na koni układ nadzorcy uaktywniam y za pom ocą funkcji IWDG_Enable, która włącza tążgenerator LSI. void WatchdogStart(unsigned timeout { int idx; timeout *= 10U; for (idx = 0; timeout > OxlOOOU && idx <= maxldx; timeout» = 1, ++idx; if (idx > maxldx { idx = maxldx; timeout = OxlOOOU; IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable; IWDG_SetPrescaler(prescalerTbl(idx; IWDG_SetReload{timeout - 1; IWDG_ReloadCounter(; IWDG Enable(; Plik ex_tcpd_wdg.c Plik ex_tcpd_wdg.c zaw iera funkcję main tego przykładu. Funkcja ta jest bardzo podobna do funkcji main z poprzedniego przykładu. Różnice są następujące. Na samym początku funkcji main urucham iam y układ nadzorcy: WatchdogStart (WDG_TIMEOUT MS ; Po uruchom ieniu serwera konfigurujem y regularne wywołanie funkcji restartującej licznik nadzorcy: ( ^ LWIPsetAppTimer(WatchdogReset, WDG_RESET_IN'#ERVAL_MS; Na początku pliku ex_tcpd_jvdg.c m usim y jeszcze zdefiniować stale czasowe: łdefine WDG_TIMEOUT_MS 26000 idefine WDG RESET INTERVAL MS 13000 ' 'i Zakładając nom inalną częstotliw ość LSI, m aksym alny czas możliwy do uzyskania za pom ocą układu IW DG wynosi ok. 26 sekund. Interwal restartowania licznika nadzorcy pow inien być odpow iednio mniejszy - musi uw zględniać rozrzut częstotliwości taktowania. Testowanie przykładu Ten przykład powinien pozytyw nie przejść te sam e testy, które przeszedł poprzedni przykład. Dodatkow o należy przetestować nadzorcę. Przykładow e wydruki z term i nalu pokazane są w tabeli 5.8. W celu zaw ieszenia serwera wysyłam y znak dolara. Poniew aż w wyniku zaw ieszenia oprogram ow anie stosu lwip nie działa, wysłany do serw era segm ent TCP ze znakiem dolara nie zostanie nigdy potwierdzony i oprogram owanie TCP po stronie klienta retransm ituje ten segment. Po restarcie serwera stos lwip dostaje taki relransm itowany segm ent, który nie należy do żadnego aktywnego połączenia, więc odsyła segm ent z ustawionym znacznikiem RST, aby poinform ow ać o tym drugą stronę kom unikacji. W ten sposób klient m oże dowiedzieć się, że połączenia ju ż nie ma. Niestety program telnet z systemu W indows nie roz- Tab. 5.8. Test nadzorcy Linux $ telnet 192.168.51.89 Trying 192.168.51.89... Connected to local-89.localdomain (192.168.51.89. Escape character is,a. This is TCP server running on top of the IwIP stack. Press h or? to get help. Windows C:\>telnet 192.168.51.89 This is TCP server running lwip stack. Press h or? to get help. > t > t > $ 0 00:17:59.980 > $ Connection closed by foreign host. $ telnet 192.168.51.89 Trying 192.168.51.89... Connected to local-89.localdomain (192.168.51.89.* Escape character is,aj'. This is TCP server running on top of the IwIP stack. Press h or? to get help. 0 00:11:08.800 Welcome to Microsoft Telnet Escape Character is 'CTRL+J Microsoft Telnet> quit C:\>telnet 192.168.51.89 This is TCP server running IwIP stack. Press h or? to get help. > t > t 0 00:00:53.550 0 00:00:48.350 > X Con^^ftioi^losed by foreign host. $ Connection to host lost. C:\> on top of the Client on top of the
poznaje popraw nie tej sytuacji. Trzeba go zakończyć poleceniem q u it. O poprav.. nym działaniu, czyli zrestartowaniu układu przez nadzorcę, m ożna się przekonać1, obserwując diody świecące. Świadczy o tym rów nież to, że m ikrokontroler zaczyna' odm ierzać czas od początku. W idzimy to, wysyłając polecenie t po ponownym po.-: łączeniu się z serwerem. R estartow anie serwera zaobserwujem y również, gdy nie pow iedzie sie jego uruchom ienie, np. gdy odłączymy kabel ethernetowy. Klient TCP
194 6. Klient Tl /> 6.1, Projekt protokołu 195 6.1. Tematem tego rozdziału jest klient używ ający protokołu transportowego TCl1 Rozdział zawiera dw a przykłady. W pierwszym z nich pokazuję klienta, którego',! zadaniem jest okresowe łączenie się z serwerem w celu przesłania danych pochoij dzących na przykład z czujników. W przerwach między połączeniam i kłient p rz ę śl chodzi w tryb czuw ania (ang. standby, który zapew nia najm niejszy pobór prądu'-; przez mikrokontroler. Czas przebyw ania w trybie czuw ania odm ierza zegar czastiw rzeczyw istego (RTC, ang. real time clock. Po wejściu w ten tryb utracona zostaje;* zaw artość pamięci operacyjnej (RA M i większości rejestrów. Zachowyw ana jest-jj jedynie zawartość rejestrów zapasow ych (ang. backup regisłers, w których można przechow ać niezbędne ustaw ienia programu. Drugi przykład rozszerza klientah 0 obsługę rejestrów zapasowych. Podobnie jak serw er z poprzedniego rozdziału;-, klient prezentowany w tym rozdziale też pow inien działać bezobslugowo i radzićfl sobie z sytuacjam i awaryjnym i. Serw er z poprzedniego rozdziału używał w typ celu układu nadzorcy. W tym rozdziale pokazuję inne metody zerow ania mikrokontrolera i restartowania programu. Przedstaw iony klient jest w zasadzie tylko szkieletem aplikacji, nie obsługuje żadnych czujników, ale łatwo można go rozbudować ^ 1 zaim plem entować obsługę potrzebnych peryferii, co pozostawiam Czytelnikowi - jako ćwiczenie. Projekt protokołu Komunikaty w ym ieniane między klientem a serwerem opisane są w tabeli 6.1. K omunikaty są kodow ane jako teksty ASCII. K om unikat składa się ze słowa kluczowego, po którym mogą w ystępow ać parametry! oddzielone spacją. Parametry W mogą być opcjonalne. Każdy kom unikat kończy się znakiem końca wiersza, czyli iists sekwencją znaków CR i LF. i.py'' Protokół jest stanowy. Stanow ość tego protokołu została niejako wym uszona prze/ to, że interpretacja komunikatu SET zależy od typu komunikatu, na który jest od- -y powiedzią. A utom at dla klienta jest przedstaw iony na ry su n k u 6.1. Nie będziemy ' im plem entow ać serwera, ale dla pełności opisu potrzebny jest rów nież jego aui mat. Jedna z wielu m ożliwych wersji autom atu serwera jest przedstaw iona na rysu n k u 6.2. Jak widać, autom aty te różnią się, w szczególności mają różne zl stanów. Po otwarciu połączenia TC P klient w ysyła kom unikat HELLO i przechodzi do stanu HelloState, w którym oczekuje na odpow iedzieć serwera. Jeśli w tym stanie klient odbierze komunikat OK, czyli pozytyw ne-potwierdzenie, to w ysyła komunikat PARAM i przechodzi do stanu Param State, w którym oczekuje na odpowiedź.- od serwera. Jeśli w stanie H ellostate klient odbierze kom unikat SET, czyli prośbę i 0 zsynchronizow anie zegara czasu rzeczyw istego, to próbuje go ustawić. Jeśli pr; siane data i czas mają popraw ny form at i prośba pow iedzie się, to klient odsyła kom unikat OK ze zm ienionym czasem, a po nim komunikat PARAM i przechud/: do stanu ParamState. W przeciw nym przypadku klient wysyła kom unikat ERROK 1 zam yka połączenie TCP. Jeśli w stanie Param State klient odbierze komunikat OK. to w ysyła kom unikat BYE i zam yka połączenie TCP. Jeśli w tym stanie odb rze komuńikat SET i przesłane param etry mają popraw ne wartości, to ustawia je, co potw ierdza kom unikatem OK z nowymi w artościam i parametrów, a nasti Rys. 6.1. Automat stanowy protokołu po stronie klienta wysyła kom unikat BYE i zam yka połączenie TCP. Jeśli param etry w odebranym kom unikacie SET mają niepoprawne wartości, to klient wysyła kom unikat ERROR i zam yka połączenie TCP. H.. Przejście do stanu końcow ego oznacza zam knięcie połączenia TCP. Klient zam y ka 'rlw n ip j (połączenie TCP zawsze po odebraniu komunikatu ERRO R lub BYE. Zam knięcierpołączenia TC P przez drugą stronę należy traktować jak odebranie ko-,!.
6.2. Przykhul 6a - pierwsza wersja klienta TCP 197 Tab. 6.1. Komunikaty Komunikat HELLO PARAM SET OK ERROR BYE Opis Jest to pierwszy komunikat wysyłany przez klienta do serwera po zestawieniu połączenia TCP w celu zainicjowania komunikacji. Parametrami są data i czas, odczytane z zegara czasu rzeczywistego klienta, w formacie RRRR.MM.DD gg:mm:ss (rok, miesiąc, dzień, godzina, minuta, sekunda. Serwer powinien odpowiedzieć komunikatem OK lub SET Komunikat ten jest wysyłany przez klienta do serwera w celu przesłania swojej konfiguracji. Ma dwa parametry. Parametr C0NNECTI0NJIM E0UT jest czasem, po którym klient, nie doczekawszy się na odpowiedź od serwera, zamyka połączenie TCP Parametr STANDBY_TIME jest czasem przebywania klienta w uśpieniu. Oba parametry są podawane w sekundach jako liczby przy podstawie dziesięć. Serwer powinien odpowiedzieć komunikatem OK lub SET Komunikat ten jest wysyłany przez serwer do klienta w celu zmiany ustawień przesłanych za pomocą komunikatu HELLO lub PARAM. Parametry tego komunikatu mają taki sam format jak parametry komunikatów HELLO i PARAM. Na ten komunikat klient odpowiada komunikatem OK, jeśli dokonał zmiany ustawień zgodnie z intencją serwera. W przeciwnym przypadku klient odpowiada komunikatem ERROR Komunikat ten jest wysyłany przez klienta lub serwer w celu potwierdzenia odebrania komunikatu. Jeśli jest odpowiedzią na komunikat SET, to zawiera parametry, które zostały ustawione. W innych przypadkach parametry po tym komunikacie są opcjonalne i mogą być ignorowane. Na ten komunikat się nie odpowiada Komunikat ten jest wysyłany przez klienta lub serwer w celu poinformowania, że odebrany komunikat nie może być zinterpretowany. Parametry po tym komunikacie są opcjonalne i mogą być ignorowane. Na ten komunikat nie odpowiada się, a strona, która go wysiała, natychmiast po jego wysianiu zamyka połączenie TCP Komunikat ten jest wysyłany przez klienta lub serwer w celu zakończenia komunikacji. Parametry po tym komunikacie są opcjonalne i mogą być ignorowane. Na ten komunikat nie odpowiadajsię, a strona, która go wysiała, natychmiast po jego wysianiu zamyka połączenie TCP tent SET. W ybór m iędzy tymi dw iem a m ożliwościami zależy od strategii działania serwera i nie musi być częścią specyfikacji, która tylko opisuje możliwe działania. 2. Przykład 6a - pierwsza wersja klienta TCP Nazwy plików przykładu 6a zam ieszczone są w tabeli 6.2. W iększość z nich opisałem już w poprzednich rozdziałach. Poniżej om aw iam nowe pliki, ale ponieważ przykład korzysta z trybu czuwania, zaczynam od krótkiego przedstawienia trybów obniżonego poboru mocy w mikrokontrolerach STM32. Rys. 6.2. Automat stanowy protokołu po stronie serwera munikatu BYE. Jeśli klient nie otrzym a żadnego komunikaty przez określony c2.is lub odbierze błędny kom unikat, to w ysyła kom unikat ERRO R i zam yka połączę-, a l l nie. Domyślny czas oczekiwania na odpow iedź w ynosi 30 sekund. Jego wartość BIS nie m oże być mniejsza niż 5 sekund, aby komunikaty zdążyły dotrzeć do odbiorcy i serw er miał dostatecznie dużo czasu na wysianie odpowiedzi. Po zamknięciu połą-, czenia klient przechq/jzi w stan uśpienia, pom yślny czas przebyw ania w tym stanie w ynosi 20 sekund. Takie początkowe wartości tych czasów mają ułatwić testowanie, programu. W autom acie serwera nie jest określone, kiedy na kom unikat HELLO lub PARAM serwer ma odpow iedzieć kom unikatem OK, a kiedy zmienić ustawienia komunika- Tab. 6.2. Pliki przykładu 6a exjcp_client.c startup_stm32_cld.c tcp_client.c tcp_client.h «l i _... iii... Źródłowe i biblioteczne boardjtonl.c b o a rd jn iic b o ardjed.c board_conf.h b o a rd Jel.ti board_dels.il boardjnit.h boardjed. h util_delay.c util_elh_nc.c util Jed. c u tiljw ip.c u tiljtc.c utiljim e.c Nagfri wkowe util_delay.li util_error.h u tije th.h u tiljed.h utiljw ip.h u tiijtc.h util lime.h Iiblwip4.a libstm32m0x.a cc.h cortex-m 3.li Impopts.h stm 32l10x_conl.h
6.2. Przykład 6a - pierwsza wersja klienta TCP 199 6.2.1. 6.2.2. Tryby o obniżonym poborze mocy M ikrokontrolery STM 32 w yposażono w kilka trybów o obniżonym poborze W trybie uśpienia (ang. sleep m ode w strzym yw ane jest taktowanie rdzenia j4 mięci, wszystkie układy peryferyjne pozostają taktowane. W trybach głębokie uśpienia (ang. deep sleep m ode w strzym yw ane jest taktowanie rdzenia, pamji i większości peryferii. Są dw a tryby głębokiego uśpienia: tryb zatrzymania (ąt stop mode i tryb czuw ania (ang. standby. Rdzeń mikrokontrolera zasilany jest napięciem 1,8 V. W trybie zatrzymania wyj" czone są wszystkie sygnały taktujące w dom enie 1,8 V. W strzym ane są oscy łat RC HS1, oscylator kw arcowy H SE i wszystkie pętle fazowe - patrz rysunek ż.jf W ewnętrzny regulator napięcia 1,8 V może pozostać w stanie normalnej pracy al zostać przełączony w stan niskiego poboru mocy (ang. low pow er mode. W trybf zatrzym ania zawartość rejestrów i pamięci pozostaje niezm ieniona. Mikrokontrotó można w ybudzić z tego trybu za pom ocą jednego z 16 przerwań zewnętrznyc EXTI (ang. external interrupt, przerw ania w ygenerowanego przez programową!' ny detektor napięcia zasilania PV D (ang. program m able voltage detector, alarm'' zegara czasu rzeczyw istego lub sygnału budzenia (ang. wake-up w ygenerow ane/ przez interfejs USB albo Ethernet. ^ W trybie czuw ania w ewnętrzny regulator napięcia 1,8 V jest wyłączony. Oscylatory HSI, H SE i wszystkie pętle fazow e są rów nież w yłączone. Zawartość pamięć i większości rejestrów zostaje utracona. Zachowyw ana jest jedynie zawartość rejestrów zapasowych. M ikrokontroler z trybu czuw ania m ożna w ybudzić na kilk sposobów: zerując go za pom ocą w yprowadzenia RESET, zerując go za pomo4; cą nadzorcy, podając narastające zbocze na wyprowadzenie WKUP-PAO lub zgla' szając alarm zegara czasu rzeczyw istego. Tryb czuw ania różni się od pozostałych jeszcze tym, że po wybudzeniu z niego wykonywanie program u rozpoczyna się od1 początku. W pozostałych trybach w ykonywanie programu jest wznawiane od miej, sca, gdzie nastąpiło uśnięcie. Jeśli odłączym y główne zasilanie mikrokontrolera, tc zegar czasu rzeczyw istego i rejestry zapasow e są zasilane przez wyprowadzenl, V bat* c* którego można podłączyć baterię podtrzym ującą zasilanie tych układów, - Program wykorzystujący tryb czuw ania pow inien sprawdzić, czy przyczyną rozpoczęcia w ykonywania jest wybudzenie, czy włączenie zasilania. We wszystkicli trybach o obniżonym poborze mocy mogą pozostawać włączone:.;;f - w ewnętrzny oscylator RC LSI (ang. Iow speodr internal o częstotliwości nomi nalnej 40 khz, zw ykle taktujący układ nadzorcy IW DG; - zewnętrzny oscylator kw arcowy LSE (ang. low speed external, którego częstot- i. liwość wynosi zwykle 32 768 Hz i który taktuje zegar czasu rzeczywistego. Pliki u tiljtc.h i u tiljtc.c Pliki util_rtc.h i util_rtc.c zaw ierają im plem entację obsługi zegara czasu rzeczy- - w istego oraz trybu ęzuwania. U m aw iam y się, że zegar odlicza sekundy od począt-. ku dnia 1 stycznia 1970 roku, czyli zgodnie z konw encją obow iązującą w systc-. mach uniksowych. Funkcja RTCconfigure konfiguruje zegar czasu rzeczywistego, Funkcja'ta zwraca zero, gdy konfigurow anie pow iodło się, a w przeciwnym przy- ; padku wartość ujemną. int RTCconfigure(void 1 XTI_InitTypeDef EXTI_InitStructure; NVIC_Ini.tTypeDef NVIC_InitStructure; RCC_APBlPeriphClockCmd(RCC_APBiPeriph_PWR RCC_APBlPeriph_BKP, ENABLE ; PWR_BackupAccessCmd(ENABLE ; if (PNR_GetFlagStatus(PWR_FLAG_SB!= RESET { PWR_ClearFlag(PWR_FLAG_SB; RTC_WaitForSynchro ( ; else ( BKP_DeInit(; RCC_LSEConf ig (RCC_LSE_0N ; active_check(rcc_getflagstatus(rcc_flag_lserdy, 10000000; RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE; RCC_RTCCLKCmd(ENABLE; RTC_WaitForSynchro(; RTC_SetPrescaier(32767; RTC_WaitForLasŁTask(;! EXTI_StructInit(6EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Linel7; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCrad = ENABLE; EXTI_InitStructure.EXTI_Mode» EXTI_Mode_Interrupt; EXTI_Init(SEXTI_InitStructure; NVXC_InitStructure.NVIC_IRQChannelPreemptionPrior.ity = L0W_IRQ_PRI0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_Init(SNVIC_InitStructure; NVIC InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn; NVIC~Init(SNVIC_InitStructure; return 0; 1 Poniew aż zegar czasu rzeczyw istego jest sprzętow o związany z układem zarządzającym trybami obniżonego poboru mocy (PW R, a jego rejestry znajdują się w tej samej dom enie zasilania i taktowania co rejestry zapasow e (BKP, w funkcji RTCconfigure najpierw uaktywniam y taktowanie tych układów i dostęp do rejestrów zapasowych. Następnie sprawdzam y za pom ocą funkcji PWR GetFlagStatus, czy m ikrokontroler obudził się ze stanu czuw ania - czy ustawiony jest bit PWR_ FLAG_SB. Jeśli się obudził, to zegar czasu rzeczyw istego działa i nie trzeba go konfigurować. Trzeba tylko w yzerować znacznik PWR_FLAG_SB i poczekać za pomocą funkcji RTC_WaitForSynchro na zsynchronizow anie zegarów. Jest konieczne, gdyż układy zegara czasu rzeczyw istego są taktowane z dw óch źródeł: z oscylatora kw arcow ego LSE i sygnałem PCLK1 z w yjścia dzielnika A B P I. Synchronizację należy przeprowadzić po każdym włączeniu któregoś z tych sygnałów, np. za pomocą funkcji RCC_APBlPeriphClockCmd. Jeśli ^przyczyną rozpoczęcia w ykonyw ania programu nie jest obudzenie ze stanu c z u w h ia j Ji włączenie zasilania m ikrokontrolera, to trzeba skonfigurować zegar czasu rzeczywistego. N ajpierw jednak za pom ocą funkcji BKP_DeInit zerujemy
6.2. Przykład 6a - pierwsza wersja klienta TCP 201 układ sterujący rejestrami zapasowymi. Za pom ocą funkcji RCC_LSEConfig* tyw niam y oscylator LSE, który używ a zewnętrznego kwarcu 32 768 Hz. Fm RCC_GetFlagStatus sprawdza, czy oscylator ten wystartował. Funkcja ta jesllfj w olyw ana maksym alnie 10 milionów razy przez makro activ e_ch eck. Makro jest zdefiniow ane w pliku board_init.h i powoduje, że jeśli w tym czasie osćfl tor się nie uruchom ił, to funkcja RTCconfigure kończy działanie i zwraca warfe1 ujemną. Funkcje RCC_RTCCLKConfig i RCC_RTCCLKCmd włączają taktowanie zeg' czasu rzeczyw istego oscylatorem LSE. Funkcja R T C _SetP rescaler ustawia dzie nik wstępny, tak aby licznik zegara czasu rzeczyw istego byl zwiększany z częst' tliw ością 1 Hz. W spółczynnik podziału jest o jeden większy od wartości argumeii tej funkcji. Funkcje biblioteki STM 32 m odyfikujące rejestry konfiguracyjne zeg czasu rzeczyw istego tylko inicjują tę operację. Zapis do rejestrów RTC trwa śt* sunkow o długo (kilka taktów zegara LSE i przed kolejnym zapisem lub odcz tern, aby upewnić się, że poprzedni zapis się zakończył, trzeba wywołać funkii RTC_WaitForLastTask. Na koniec w funkcji RTCconfigure konfigurujem y przerwania zegara czasu rzec>/? wistego. Przerwanie RTC_IRQn jest zgłaszane po każdym zwiększeniu licznika /.'' gara czasu rzeczywistego, czyli w naszym przypadku co jedną sekundę. Przerwanie alarmu RTCAlarm_IRQn jest zgłaszane przez licznik zegara czasu rzeczywistego, po osiągnięciu zadanej wartości. Przerw ania konfigurujem y z niskim prioryteteis! wywłaszczania, niższym niż biblioteka lwip. Na razie nie aktywujem y przerwali Zostaną uaktyw nione przez kolejne funkcje. '. Funkcja R TC setc allback ustawia funkcję zwrotną, która ma być w ywołana po ka^i dym w ystąpieniu przerwania RTC_IRQn. Jej jedynym argum entem jest adres funkęjlij zwrotnej, która jest bezparam etrow a i nie zwraca żadnej wartości. Jeśli argument ten ma wartość NULL, to blokujem y przerwanie RTC_IRQn za pom ocą funkcji RTCji.' ITC onfig - zerowany jest bil RTC IT_SEC. W przeciwnym przypadku adres funkcji;' zwrotnej zapisujem y w zmiennej globalnej c a llb a c k, a następnie aktywujemy prze* rw anie RTC_IRQn - ustawiany jest bit RTC_IT_SEC., static void (*callback(void = NULL; void RTCsetCallback(void (*f (void ( if (f == NULL ( RTC_WaitForLastTask(; RTC_ITConfig(RTC_IT_SEC, DISABLE; callback = ;,,... if (f I RTC_WaitForLastTask(; RTC_ITCon ig(rtc_it_sec, ENABLE; 1 Obsługą przerwania RTC_IRQn zajm uje się funkcja RTC_IRQHandler. Zeruje ona' znacznik przerwania (bit RTC_IT_SEC i wywołuje funkcję zwrotną, jeśli została ona skonfigurowana. " void RTC_IRQHandler(void ( if (RTCLGetITStatus(RTC_IT_SEC!= RESET ( RTC_WaitForLastTask(; RTC ClearITPendingBit(RTC IT SEC; } if (callback callback (; Licznik zegara czasu rzeczyw istego odczytujem y i modyfikujemy odpowiednio za pom ocą funkcji G etrealtim eclock i SetRealTim ecłock. uint32_t GetRealTimeClock(void ( RTC_WaitForLastTask(; return RTC_GetCounter(; } void SetRealTimeCłock(uint32_t t ( RTC_WaitForLastTask(}; RTC_SetCounter(t; } Funkcja Standby w prowadza m ikrokontroler w tryb czuwania. Argumentem tej funkcji jest czas, po którym m ikrokontroler ma się obudzić. Czas podajem y w sekundach. M ikrokontroler wybudzi się, gdy wystąpi zdarzenie alarmu, czyli gdy licznik zegara czasu rzeczyw istego osiągnie wartość ustawioną za pom ocą funkcji RTC_SetAlarm. Nie ma potrzeby aktyw ow ania przerwania zw iązanego z alarmem. Podanie jako argumentu funkcji Standby wartości 0 spowoduje uśpienie na 232 sekund, czyli w praktyce na czas nieograniczony. M ikrokontroler w tryb czuwania w prowadza funkcja PWR_EnterSTANDBYMode. Przedtem jednak za pomocą funkcji ETHpowerDown w stan; obniżonego poboru mocy przełączamy rów nież interfejs ethernetowy. void Standby(uint32_t time ( RTC_Wa i t ForLas tta s k (; RTC_SetAłarm(RTC_GetCounter{ + time; RTC_WaitForLastTask{; ETHpowerDown(; PWR_EnterSTANDBYMode(; } Alarm zegara czasu rzeczy wistego jest zgłaszany w ostatnim cyklu oscylatora LSE, taktującego RTC, odpow iadającego sekundzie, w której alarm należy zgłosić. Zatem rzeczyw isty czas do zgłoszenia alarmu może być nieco dłuższy od zadanego, w najgorszym przypadku o niecałą sekundę. Czasem pożądane może być przejście w tryb czuw ania z pewnym opóźnieniem. Zajm uje się tym funkcja D elayedstandby. Pierwszym jej argumentem jest żądane opóźnienie. Drugim argum entem jest czas, po którym mikrokontroler ma się obudzić. O ba czasy podajem y w sekundach. Czas uśpienia zapam iętujemy w zmiennej globalnej standbytim e. O późnienie realizujem y za pom ocą alarmu zegara czasu rzeczywistego. Alarm w ywołuje przerwanie RTCAlarm_IRQn, które aktywujem y za pom ocą funkcji RTC_ITConfig - ustawiamy bit RTC_IT_ALR. Aby zabezpieczyć się przed fałszyw ym alarmem - rejestr alarmu ma zawsze jakąś wartość - w zmiennej globalnej magicnumber ustawiamy pew ną ustaloną wartość MAGIC_NUMBER. Jest bardzo mało prawdopodobne, że taka wartość znajdzie się tam przypadkowo, idefine MAGIC_NUMBER 1836547290 stat^jl uint3 _t magicnumber; stat isaurtl$fe t standbytime; void Delayódiftandby (uint32_t delay, uint32_t time 1
202 6. Klient TY.'/" 6.2. Przykład 6a ~ pierw sza wersja klienta TCP 203 6.2.3. if (delay > 0 ( magicnumber = MAGIC_NUMBER; standbytime = time; RTC_WaitForLastTask(; RTC SetAlarm(RTC_GetCounter( + delay; RTC_WaitForLastTask(; RTC~ITConfig(RTC_IT ALR, ENABLE; else Standby(time; V, Przerwanie RTCAlarm_IRQn jest obsługiw ane przez funkcję RTCAlarm_IRQHandle:t;;ji która w prowadza m ikrokontroler w tryb czuwania, gdy wartość zmiennej magic-ijj' Number zgadza się z w artością ustaw ianą przez funkcję D elayedstandby. void RTCAlarmJRQHandler (void ( if (RTC_GetITStatus(RTC_IT_ALR!= RESET ( EXTI_ClearITPendingBit(EXTI_Linel7; RTC_WaitForLastTask(; RTC_ClearITPendingBit(RTC_IT_ALR; if TraagicNumber == MAGICJtUMBER 1 magicnumber = 0; SŁandby(standbyTime; Pliki tcp_client.h i tcpjclient.c < W pliku tcp_client.c znajduje się im plem entacja klienta realizującego protokół < sany w rozdziale 6.1. Zaczynam y od zdefiniow ania stałych reprezentujących ko- - munikaty, które odbiera klient. D efiniujem y też formaty komunikatów, które kiieni wysyła. Ponadto definiujem y form aty param etrów komunikatu SET.»define TIME_FRM "%Y.%m.%d %H:%M:%S" Idefine PARAM_FRM "%"U32_F" %"U32_F static const char ok_msg(] static const char s e tjn s g f] static const char b yejn sgu static const char error_msg(] static const char heilo_msg_frm{ static const char param_msg_frm(] static const char ok_time_msg frm( ] static const char ok_param_msg_frm(] static const char bye_msg_frm(] static const char error_msg_frm[] static const char set_time_frm[] static const char setj?aram_frmu Stan klienta przechow ujem y w strukturze typu s t a te. łdefine MSG_SIZE 32»define CONNECTIONJTIMEOUT 0 ;»define STANDBYJTIME 1 p *»define PARAM NUMBER 2 "OK"; "SET"; "BYE"; "ERROR"; "HELLO TIME_FRM \r\n"; "PARAM "PARAM_FRM"\r\n"; "OK "TIME_FRM"-YFYn"; "OK "PARAM_FRM»U\n"; "BYE "TIME_FRM"\r\n"; "ERROR "TIME_FRM"\r\n"; TIME_FRM; PARAM FRM; struct state { err_t (* function(struct state struct tcp_pcb *; uint32_t param(param_number; uint32_t timer; unsigned idx; char msg[msg_size]; 1; Składow a fu n c tio n struktury typu s t a t e jest adresem funkcji, która ma być w yw o łana w celu obsłużenia odebranego kom unikatu. Pierwszym argum entem tej funkcji jest w skaźnik do struktury typu s t a te. Drugim argum entem jest w skaźnik do deskryptora TCP opisującego połączenie, z którego odbieram y komunikaty. Składowa param struktury typu s t a t e jest tablicą param etrów klienta. W tej tablicy pod indeksem CONNECTIONJTIMEOUT przechow ujem y czas oczekiwania na komunikat. Po przekroczeniu tego czasu klient, nie otrzym aw szy żadnego kom unikatu, zamyka połączenie TCP. W tablicy param pod indeksem STANDBYJTIME przechowujem y czas przebyw ania klienta w trybie czuw ania. Składow a tim e r odlicza czas, przez który klient będzie jeszcze czekał na kolejny kom unikat i po którego upłynięciu, nie otrzym aw szy kom unikatu, zam knie połączenie. W artości czasów są w sekundach. Składow a msg służy do zgrom adzenia odbieranego kom unikat. Składow a idx jest indeksem pierwszego wolnego znaku w tablicy msg, czyli jest to liczba dotychczas odebranych znaków komunikatu. Stała MSG SIZE określa maksym alną długość kom unikatu pow iększoną o jeden, tak żeby w buforze zm ieściło się jeszcze terminalne zero, zw ykle kończące napisy w języku C. Klienta urucham ia się za pom ocą funkcji T C P c lie n ts ta rt. Jej sygnatura znajduje się w pliku tcp_client.h. Ta funkcja jest przeznaczona do w ywołania z program u głównego, dlatego w ołane w niej funkcje z biblioteki lwip otoczone są makrami chroniącym i przed współbieżnym w ykonyw aniem kodu tej biblioteki. Pierwszy argum ent addr funkcji T C P c lie n ts ta rt jest adresem IP serwera, z którym ma być naw iązane połączenie TCP. Drugi argum ent p o rt jest num erem portu, na którym nasłuchuje serwer. Funkcja ta działa według diagram u klienta przedstaw ionego na rysunku 4.4. Zwraca zero, gdy zostało zainicjow ane urucham ianie klienta, czyli gdy udało się w ysiać segm ent SYN, a w artość ujem ną w przeciwnym przypadku. Na początku alokujem y i inicjujemy strukturę typu s t a te. Na razie param etry inicjujemy w artościam i dom yślnym i. Im plem entację czytania tych param etrów z rejestrów zapasow ych odkładam y do następnego przykładu. idefine DEFAULT_CONNECTION_TIMEOUT 30 Idefine DEFAULT_STANDBY_TIME 20 w idefine POLL_PER_SECOND 2 int TCPclientStart (struct ip_addr *addr, uint!6_t port] ( struct tcp pcb *pcb; struct state *state; err_t err; IRQ_DECL_PROTECT(x; state = mem_malloc(sizeof(struct state; if (state == NULL return -1; state->function = HelloState; f.«i /SlfcpO: Jj&zeczytać ustawienia z rejestrów zapasowych. */ staif->piifirltconkęction_timeout = DEFAULT_CONNECTION_TIM OUT; state->paraflli[standby TIME] = DEFAULT STANDBY TIME;
6.2. Przykład 6a - pierwsza wersja klienta TCP 205 state->timer = state->param(connection_txmeout]; state->idx = 0; IRQ_PROTECT(x, LWIP_IRQ_PRIO ; pcb = tcp_new{; IRQJJNPROTECT(x; if {pcb «NULL { mem_free(state; return -1; i IRQ_PROTECT(x, LWIP_IRQ_PRIO; tcp_arg(pcb, state; tcp_err(pcb, conn_err_callback; tcp_recv(pcb, recv_całlback; tcp_poii(pcb, poll_callback, POLL_P R_SECOND; IRQ UNPROTECT(x; IRQ_PRO?ECT(x, LWIP_IRQ_PRIO; err = tcp_connect{pcb, addr, port, IRQ_UNPROTECT(x; if (err!= ERR_OK { mem_free(state; IRQ_PROTECT(x, LWIP_IRQ_PRIO ; tcp_abandon(pcb, 0; IRQ_UNPROTECT(x; return -1; return 0; connect callback; Jeśli połączenie TCP do serw era zostanie ustanowione, to biblioteka lwip wywołuję:'1 funkcję zw rotną c o n n e c t_ c a łlb a c k, w której klient w ysyła do serwera komunią: kat HELLO. Funkcję tcp_w rite_tim e_rasg, w ysyłającą do serwera komunikaty znacznikiem czasu, opisuję nieco dalej. static err_t connect_callback(void *arg, struct tcp_pcb *pcb, err_t err ( return tcp_write_time msg(pcb, hello_msg_frm; i Jeśli połączenie TCP nie zostanie otw arte lub zostanie zam knięte awaryjnie (sl.i-owane, to biblioteka lwip wywołuje funkcję zw rotną co n n _ err_ callb ack. W funltr cji tej powinniśm y zapisać do rejestrów zapasow ych parametry klienta, co zaimplem entujem y w następnym przykładzie. R eakcją ritr skasowanie połączenia TCP jest w prowadzenie m ikrokontrolera w tryb czuw ania na czas określony przez parametr/ param [STANDBY_TIME]. Zaśnięcie opóźniam y o 2 sekundy, aby dać bibliotece Iv.lP i interfejsowi Ethernet szansę w ysiania komunikatu kasującego połączenie TCP. Je to potrzebne w sytuacji, gdy kasow anie połączenia jest inicjow ane przez bibliotekę lw IP po stronie klienta, co może być skutkiem w ystąpienia jakiegoś błędu. static void conn_errjsallback(void struct state *statę =,arg; arg, erf t err /* TODG: Zapisać ustawienia do rejestrów zapasowych. DelayedStandby(2, state->param[standby_time; m Jeśli zostaną odebrane dane lub połączenie zostanie norm alnie zamknięte przez serwer, to biblioteka lwip wywołuje funkcję zw rotną recv_callback. Działanie tej funkcji jest analogiczne jak w przykładach 5a i 5b. #define ERR_ XIT 100 static err_t recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf p, err_t err ( if ip ( tcp_recved(pcb, p->tot_len; err = StateAutomaton(arg, pcb, p; if (err == ERR_OK! err == ERR_EXIT pbuf_free(p; if (err == ERR_EXIT { tcp_ex.it(arg, pcb; err~= ERR_OK; } else ( tcp_exit(arg, pcb; err = ERR_0K; return err; Połączenie TCP zam yka funkcja tcp_exit. Nie musimy w niej zwalniać pamięci przydzielonej strukturze typu state, gdyż po zam knięciu połączenia m ikrokontroler jest w prowadzany w tryb czuw ania, a po obudzeniu rozpocznie wykonywanie program u od początku, jakby został wyzerowany. Jeśli nie pow iodło się wywołanie funkcji tcp_close, to w ywołujem y funkcję tcp_abort, która z kolei wywołuje funkcję conn_err_callback. Jeśli wywołanie funkcji tcp_close zakończy się sukcesem, to w prowadzam y m ikrokontroler w tryb czuwania na czas określony przez param etr param[standby_time]. Jak poprzednio zaśnięcie opóźniam y o 2 sekundy, aby dać bibliotece lwip szansę dokończenia procedury zamykania połączenia TCP. Przed zaśnięciem powinniśm y zapisać do rejestrów zapasowych parametry klienta, co zaim plem entujem y w następnym przykładzie. static void tcp_exit(struct state *state, struct tcp pcb *pcb ( if (tcp_close(pcb!= ERR_OK ( tcp abort(pcb; 1 else { /* TODO: Zapisać ustawienia do rejestrów zapasowych. */ DelayedStandby(2, state->paramistandby_time]; I i Przekroczenie czasu oczekiwania na kom unikat obsługuje funkcja zwrotna p o ll_ c a llb a c k. Jeśli czas upłynie, czyli licznik tim e r zmniejszy się do zera, to w ysyłamy kom unikat o błędzie i zamykamy połączenie TCP. static err_t polł_callback(void *arg, struct tcp_pcb *pcb { struct state *state = arg; if ( (state->tirner <= 0 { tcp_write_time_jnsg(pcb/ error_msg_frm; tcp_exit(arg, pcb;
6.2. Przykład 6a - pierwsza wersja klienta TCP 207 Funkcja tcp_w rite_tim e_m sg w ysyła kom unikat zawierający znacznik czasu ( i czas wysłania komunikatu. Protokół nakazuje wysyłać znacznik czasu tylko w m unikacie HELLO, ale nie zabrania w ysyłania go w innych komunikatach, Dlateg w szystkie komunikaty wysyłane przez klienta, jeśli to tylko możliwe, zawieraj znacznik czasu, co ułatwia testow anie programu. Pierwszy argum ent pcb funkcj tcp_w rite_tim e_m sgjest wskaźnikiem do deskryptora TCP opisującego połączenie przez które ma być wysłany kom unikat. Drugi argum ent form at jest wskaźnikiem* do stałego napisu zaw ierającego form at kom unikatu do wysłania. Funkcja zwraoa kod zakończenia typu e r r _ t zgodnie z konw encją stosow aną w bibliotece IwIP, static err_t tcp write_time_msg (struct tcp_pcb *pcb, const char format { char msg(msg_size]; time_t rtime; struct tm tm; size_t size; SYS_ARCH_DECL_PROTECT(x; rtime = GetRealTimeClock(; if (localtime_rf&rtime, &tm == NULL return ERR OK;. -'i SYS_ARCH_PROTECT(x; \ size = strftime(msg, MSG_SIZE, format, &tm; SYS_ARCH_UNPROTECT(x; return tcp write (pcb, msg, size, TCP_WRITE_FLAG_COPY; ; } ~ 4 W funkcji tcp_w rite_tim e_m sg najpierw za pom ocą funkcji GetRealTimeClc.-.' odczytujem y liczbę sekund, które upłynęły od pdczątku epoki Uniksa, czyli odj 1 stycznia 1970 roku, a następnie konw ertujem y ją na datę i czas za pom ocą funk, cji lo c a ltim e r z biblioteki standardow ej C. Jeśli konw ersja się nie powiedzie;'1, to nie można nic rozsądnego zrobić i funkcja kończy działanie, zwracając ERR_'d, ; Funkcja s tr f tim e z biblioteki standardow ej C form atuje kom unikat i umieszcza;, go w buforze msg, ale kopiuje do tego bufora nie więcej niż MSG_SIZE bajtów, wliczając terminalne zero. Natom iast zw raca liczbę bajtów um ieszczonych w bufoi/.e, nie w liczając w to term inalnego zera. Poniew aż funkcja ta nie jest współużywalna, jej wywołanie otoczone jest m akram i blokującym i przerwania. Funkcja tcp_w rite w ysyła kom unikat, zgodnie ze specyfikacją protokołu, bez term inalnego zeia. Struktura tm jest zdefiniow ana w bibliotece standardowej C. struct tm { int tm_sec; int tmjnin; int tm_hour; int tmjnday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm isdst; /* sekunda */ minuta */ godzina */ dzień miesiąca */ miesiąc */ rok */ dzień tygodnia */ dzień roku */ znącznik czasu letniego 7 ; Funkcja tcp_write_j>aram_msg w ysyła kom unikat PARAM z param etram i klienta. Pierwszy argum ent pcb jest w skaźnikiem do deskryptora TC P opisującego połączenie, przez które ma być wysłany kom unikat. Drugi argum ent s jest wskaźnikiem do S m 'J? '% i, fi struktury typu s t a te, w której są przechow ywane param etry klienta. Trzeci argum ent form at jest w skaźnikiem do stałego napisu zaw ierającego form at komunikatu do wysłania. Funkcja zwraca kod zakończenia typu e r r t zgodnie z konwencją stosow aną w bibliotece IwIP. static err_t tcp_write_param_msg(struct tcp_pcb *pcb, struct State *s, const char formatl ( char msg(msg_sizej; size_t size; SYS_ARCH_DECL_PROTECT(x; SYS_ARCH_PROTECT(x; size = snprintf(msg, MSG_SIZB, format, s->param(connection_timeout], s->param(standby_time; SYS_ARCH_UNPROTECT(x; if Tsize >= MSG_SIZE size = MSG_SIZE - 1; return tcp_write (pcb, msg, size, TCP_WRITE_F1,AG_C0PY ; Funkcja tcp_w rite_param _m sg korzysta z funkcji s n p r in tf, która nie jest w spółużyw alna i dlatego jej w ywołanie otoczone jest makrami blokującym i przerwania. Funkcja s n p r in tf um ieszcza w buforze msg m aksym alnie MSG_SIZE znaków, w łączając term inalne zero. Natom iast zw raca liczbę bajtów, które zostałyby um ieszczone w buforze, gdyby miał on dostateczny rozmiar, ale nie włączając term inalnego zera. D latego jeśli wartość zmiennej s iz e jest większa lub równa MSG_SIZE, to oznacza, że napis nie zm ieścił się w buforze msg i został obcięty. Komunikat w ysyłam y za pom ocą funkcji tc p _ w rite, naw et jeśli nie zm ieścił się w buforze. Nie w ysyłam y term inalnego zera. Funkcja StateA utom aton obsługuje autom at stanowy klienta. Jej pierwszy argument s jest wskaźnikieni do struktury zaw ierającej stan automatu. Drugi argum ent pcb jest wskaźnikiem do deskryptora TC P opisującego połączenie, z którego odebrano komunikat. Trzeci argum ent p jest wskaźnikiem do łańcucha buforów, które zaw ierają odebrane dane. Funkcja zw raca kod zakończenia typu e r r _ t zgodnie z konw encją stosow aną w bibliotece IwIP lub wartość ERR_EXIT, gdy połączenie TC P ma zostać zamknięte. static err_t StateAutomaton(struct state *s, struct tcp_pcb *pcb, struct pbuf *p { s->timer» s->param[c0nnecti0n_time0ut]; for {;; { uint8_t *c = (uint8_t *p->payload; uintl6_t i; err_t err; for (i = 0; i < p->łen; ++i ( if (c [i ] ==,\n/ { s->msg(s->idx++ = cji]; s->msg[s->idx = '\0'; s->idx = 0; Ht ff,. err = s->function (s, pcb; 'iw iim v 'c!= ERR_0K return err; ;. iń
208 6. Klient le p ' 6.2. Przykład 6a - pierwsza wersja klietita TCP 20 9 else if (s->idx < MSG_SIZE - 2 s->msg[s->idxt+j = c [i }; } if (p->len == p->tot_len break; else p = p->next; return ERR_OK;! W funkcji StateA utoraaton przeglądam y odebrany kom unikat znak po znakuj Dopóki nie napotkam y znaku LF (w języku C zapisywanego jako ' \ n ', kopiujemyj kolejne znaki komunikatu do bufora msg, który jest składową struktury typu sta te r Kopiujem y maksym alnie MSG SIZE - 2 znaków, aby w buforze pozostało jeszcze miejsce na znak LF i term inalne zero. Jeśli napotkam y znak LF, wstawiamy go'; wraz z term inalnym zerem do bufora msg i w ywołujem y funkcję w skazywaną pi/oz składow ą fu n c tio n struktury typu s t a te. Dla każdego stanu jest zdefiniowaną, funkcja, której nazwa jest taka sam a jak nazwa stanu i która obsługuje komunikaty' odebrane w tym stanie. A utom at klienta ma dw a stany, więc są dw ie takie funkcje, H e llo S ta te i Param State. Pierwszy argum ent s t a t e tych funkcji jest wskaźnikier/. do struktury typu s t a te. Drugi argum ent pcb jest wskaźnikiem do deskryplora T( 'P opisującego połączenie. Funkcje te zw racają kod zakończenia typu e r r _ t zgodnie: z konw encją stosow aną przez bibliotekę lwip lub wartość ERR_EXIT, gdy połączenie TC P ma zostać zamknięte. fdefine msglen(s (sizeof(s - 1 Idefine msgncmp(sl, s2, n \ (strncmp(sł, s2, n == 0 && isspace((intsl[nj static err_t HelloState{struct state state, struct tcp_pcb *pcb { if {msgncmp(state->msg, okjnsg, msglen(okjnsg { state->function = ParamState/ return tcp write paramjnsg (pcb, state, paramjnsg_frm / } else if {msgncmp{state->msg, setjnsg, msglen(setjnsg { err_t err; time_t t; struct tm tm; if (!strptime(state->msg + msglen{setjnsg, set_time_frm, &tm { tcp_write_timejnsg(pcb, error_msg_frm; return ERR _ EXIT; ".,v I t = mktime(&tm; if {t == (time_j:-l ( tcp_write_time_msg(pcb, error jnsg_frm; return ERR_EXIT;! SetRealTimeClock(t; err = tcp_write_timejnsg(pcb, ok_timejnsgjfrm; if (err!= ERR_OK > return err; state->jfunction = ParamState; return tcp write_param_msg (pcb, state, paramjnsg_f rm; } else if {msgncmp(state->msg, bye_msg, msglen(byejnsg ii msgncmp{state->msg, errorjnsg, msglen(errorjnsg return ERR_EXIT; else { tcp_write_time msg (pcb, error_msg frm; return ERR EXIT; } Funkcja H e llo S ta te realizuje górną część diagram u z rysunku 6.1. Funkcja s t r p tim e z biblioteki standardowej C konwertuje datę i czas reprezentow ane za pomocą napisu do postaci struktury typu tm. Zwraca zero, gdy konwersja się nie powiodła. Funkcja mktime też z biblioteki standardowej C konw ertuje datę i czas reprezentowane za pom ocą struktury tm na liczbę sekund, które upłynęły od początku 1 stycznia 1970 roku. Zwraca wartość -1 zrzutowaną na typ tim e_ t, gdy konwersja się nie powiodła. łdefine MXN_C0NNECTI0N_TIME0UT 5 idefine MAX_CONNECTION_TIMEOUT 600 łdefine MIN_STANDBY_TIME 1 łdefine MAX_STANDBY_TIME 1800 static err_t ParamState(struct state *state, struct tcp_pcb *pcb { if (msgncmp(state->msg, okjnsg, msglen(ok_msg} 1 tcp_write time_msg(pcb, byejnsg_frm; 1 ~ j else if (msgncmp(stateu >msg, setjnsg, msglen(setjnsg { uint32_t timeout, sleep time; int ret; SYS_ARCH_DECL_PROTECT(x; SYS_ARCHJ?HOTECT(x; ret = sscanf(state->msg + msglen(setjnsg, set_param_frm, &timeout, &sleep time; SYS_ARCHJJNPROTECT(x ; if (ret!= 2 H timeout < MIN_CONNECTIOK_TIMEOOT timeout > MAX_CONNECTIONJfIMEOUT [ sleep_time < MIH_STANDBY_TIME 1 sleep_time > MAX_STANDBY_TIME { tcp_write_time_msg(pcb, error_msg_frm; 1 else ( state->param[connection_timeout] = timeout; state->param(standby_time3 = sleep_time; tcp_write param_msg (pcb, state, ok_param_msg_f rm; tcp_write_time_msg (pcb, bye_msg_frm; i else if ( msgncmp(state->msg, byejnsg, msglen(byejnsg &&!msgncmp(state->msg, errorjnsg, msglen(errorjnsg tcp_write_time_msg(pcb, errorjnsg_frm; return ERR_EXIT; Fuńfeja#sf6:an>State realizuje dolną część diagramu zam ieszczonego na rysunku 6.1. Najiparametry czasow e nakładamy pewne ograniczenia. Dolne ograniczę-
210 6- Klient / q J 6.2. Przykład 6a - pierwsza wersja klienta TCP 211 6.2.4. nie na czas oczekiwania na kom unikat zabezpiecza klienta przed ustawienie krótkiego czasu, który uniem ożliwiłby odebranie komunikatu. D olne ogranie żenili na czas uśpienia zabezpiecza przed zerow ą w artością tego czasu, co oznaczałoby? w praktyce uśpienie na zaw sze - patrz opis funkcji Standby. G órne ograniczenia ngj param etry klienta mają ułatwić jego testowanie. O dczyt param etrów z komunikatij SET w ykonujem y za pom ocą funkcji ss c a n f z biblioteki standardowej C. Funkćjai ta nie jest współużywalna. «'i Plik utiljerror.h V -j Dotychczas na w szystkie błędy podczas urucham iania program u reagow aliśm yn jego zaw ieszeniem w nieskończonej pętli - m akro erro r_ c h e c k. W poprzeddjfajfl rozdziale w celu w yprow adzenia m ikrokontrolera z tego stanu używaliśm y nàd»';;f zorcy. W tym podrozdziale pokazuję inne rozw iązanie tego problem u. W pliku'ï*! util_error.h zdefiniow ane są m akra służące do obsługi błędów. Pierwszym argu-*$l m entem każdego z tych m akr jest w yw ołanie funkcji, która zw raca wartość zeró, przy popraw nym zakończeniu, a w artość ujemną, gdy wystąpi! błąd. Rodzaj błędcf sygnalizuje funkcja E rro r, która m iga czerw oną diodą świecącą. Drugi argu. makra określa liczbę m ignięć. Do obsługi błędów, których przyczyna wydaje mc nieusuwalna, przeznaczone jest m akro e rro rjo e rm a n e n t. M akro to zawiesza pro- ;V. gram i tak napraw dę jest przem ianow anym m akrem e rro r_ c h e c k zdefiniowanym'3 w pliku util_led.h. fdefine error_permanent(expr, err error_check(expr, err łdefine error_check(expr, errl \ if ((expr < 0 { \ for (;; { \ Error(err; \ f \ I i l Błędy, których przyczyna może ustąpić po wyzerowaniu mikrokontrolera, obsługuje ' makro e r r o r _ r e s e ta b le. Za pom ocą tego makra najpierw inform ujem y o rodzaju błędu, trzykrotnie wywołując funkcję E rro r, a następnie zerujem y mikrokontrolci za pom ocą funkcji NVIC_SystemReset z biblioteki STM 32. M ikrokontroler STM32 podczas zerow ania wym usza stan niski na wyprowadzeniu RESET, dzięki czemu cale urządzenie jest restartowane. fdefine error_resetable(expr, err if {(expr < 0 i int i; for (i = 0 ; i < 3; ++i ( Error(err; 1 NVIC_SystemReset(; Do obsługi błędów, których przyczyna może ustąpić po pewnym czasie, przeznaczone jest makro errq r_ tem p o rary. N ajpiąrw inform ujem y o rodzaju błędu, trzykrotnie wywołując funkcję E rro r, a następnie w prowadzam y m ikrokontroler w stan czuw ania na 10 sekund. Czas czuw ania m ożna oczyw iście wydłużyć lub uczynić go param etrem makra. Po wybudzeniu m ikrokontroler rozpoczyna wykonywanie programu od początku.. t y łdefine error_temporary(expr, err \ if ((expr <01 \ int i; \ for (i = 0; i < 3; ++i ( \ Error(err; \ I \ Standby(10; \ 1 6.2.5. Plik ex_tcp_client.c Plik ex_tcp_client.c zaw iera funkcję main przykładu. Funkcja ta jest w swojej strukturze bardzo podobna do funkcji main z poprzednich przykładów. G łówna różnica polega na tym, że używ am y m akr z pliku util_error.h. Błędy zgłaszane przez funkcje urucham iające układy sprzętow e zaklasyfikowano jako takie, które mogą ustąpić po wyzerowaniu mikrokontrolera i restarcie urządzenia. D latego funkcje urucham iające układy sprzętow e wołane są za pom ocą makra e rro r _ r e s e ta b le, które zeruje mikrokontroler, gdy funkcja zasygnalizuje błąd. W yjątkiem jest funkcja L ocalt im ec onfigurę, gdyż analiza jej tekstu źródłowego wskazuje, że jedynym pow odem jej błędnego działania może być funkcja SysTick_C onfig, która zakończy się niepow odzeniem tylko wtedy, gdy otrzym a niepoprawną wartość param etru. Taki błąd nie ustąpi po wyzerowaniu m ikrokontrolera. Dlatego funkcję L ocalt im ec onfigure wywołujem y za pom ocą makra erro r_ p erm an en t. Błędy zgłaszane przez funkcje urucham iające protokoły sieciowe zaklasyfikowano jako chw ilow e problem y z siecią, które najprawdopodobniej ustąpią po pewnym czasie. D latego te funkcje wywołujem y za pom ocą m akra e r r o r tem porary. int main{ { static struct netif netif; static struct ethnetif ethnetif = {PHY_ADDRESS}; static struct ip_addr addr; uint8_t confbit; Delay(1000000; confbit = GetConfBit{; AllPinsDisableO; LEDconfigure(; RedLEDonO; SET_IRQ_PROTECTION{; error_resetable(clkconfigure{, 1; error_permanent(localtimeconfigure{, 2; error_resetable(rtcconfigure{, 3; error_resetable (ETHconfigureMII (, 4; - netif.hwaddr[0] = 2; netif.hwaddrdj = (BOARDJTYPE» 8 6 0xff; netif.hwaddr[2] = BOARD TYPE & Oxff; netif,hwaddr(3] - {ETH_BOARD» 0 & 0xff; netif.hwaddr 4 = ETH_BOARD & Oxff; netif.hwaddr(5] * 1 + confbit; if ( confbit { IP4_ADDR(&netif.ip_addr, 192, 168, 51, 84; SijJ IP4 ADDR(&netif.netmask, 255, 255, 255, 240;.jli ^_ADD^(Snetif,gw, 192, 168, 51, 81; else i, jil
212 6. Klient T( p 3 6.2. Przykład 6a - pierwsza wersja klienta TCP 213 6.2.6. IP4_ADDR(&netif.ip_addr, O, O, O, 0; IP4_ADDR(6netif.netmask, O, O, O, 0; IP4_ADDR(&netif.gw, O, O, O, OJ; error_temporary(lwipinterfacelnit(&netif, Łethnetif, 5; LWIPtimersStart(}; error_temporary(dhcpwait(&netif, 10, 4, 6; IP4_ADDR{&addr, 192, 168, 51, 88; error_temporary{tcpclientstart Uaddr, 22222, 7; RedLEDoff ( ; for W zestawie ZL29ARM sposób uzyskania ustawień sieciowych wybieram y jak poprzednio za pom ocą zworki BOOT1. Po uruchom ieniu biblioteki lwip i skonfipu- J rowaniu ustawień sieciow ych urucham iam y klienta. Łączy się on z serwerem, który Ć nasłuchuje pod adresem 192.168.51.88 na porcie 22222. Podczas całego procesu,»3 konfigurow ania świeci się czerw ona dioda. Jej zgaśnięcie oznacza koniec urucha- ; miania, a m iganie sygnalizuje w ystąpienie problemu. Testowanie przykładu Do przetestow ania przykładu jest potrzebny serw er TCP. M ożna w tym celu uz program u netcat, który jest standardow o um ieszczany w dystrybucjach Lim iku i urucham iany poleceniem nc. Jest też dostępna w Internecie jego darm owa w sja przeznaczona dla system ów W indow s. Program netcat, którego używałem _ do testow ania przykładów w środow isku W indows, znajduje się w pliku n c lh zip w archiw um z przykładam i w katalogu./m ain/netcat. N etcat jest narzędziem używ anym z linii poleceń (ang. com m and linę. C zyta ze standardow ego wejść i pisze na standardow e wyjście. Łatw o daje się go użyć w skrypcie. W tabeli a * zam ieszczone są przykładow e sposoby jego uruchom ienia. W pierw szym wii jest polecenie urucham iające serw er tylko na jedno połączenie - netcat końc. działanie po zam knięciu połączenia. W drugim wierszu jest polecenie umożliw jące odbieranie wielu kolejnych połączeń od klienta. Jeśli nie będzie ono dzii popraw nie, to m ożna użyć, zam ieszczonych w ostatnim wierszu tabeli, poleceń. -/i urucham iających serw er w nieskończonej pętli..,wr przypadku Linuksa całą sekw encję poleceń można po prostu w pisać w linii poleceń. W przypadku Window trzeba utw orzyć plik w sadowy (z rozszerzeniem bat z podaną w tabeli sekwen«poleceń. Tab. 6.3. Uruchamienie serwera Linux nc -1 22222 nc -1 -p 22222 nc -lk 22222 nc -L -p 2^222 Secho off :forever while truei do nc -1 22222; done nc -i -p 22222 goto forever Windows Pierwszy test polega na ogólnym sprawdzeniu działania klienta oraz działania zegara czasu rzeczywistego i trybu czuwania. N a w szystkich poniższych wydrukach komunikaty bez parametrów i kom unikaty SET są wysyłane przez serwer, czyli są wpisyw ane z konsoli, a pozostałe komunikaty pochodzą od klienta. HELLO 1970.01.01 00:00:03 OK PARAM 30 20 OK BYE 1970.01.01 00:00:11 HELLO 1970.01.01 00:00:38 SET 2010.08.05 12:00:00 OK 2010.08.05 12:00:00 PARAM 30 20 OK BYE 2010.08.05 12:00:01 Po w łączeniu zasilania zegar czasu rzeczyw istego zaczyna odliczanie od północy 1 stycznia 1970 roku. M iędzy kom unikatem BYE a HELLO upłynęło 27 sekund. M ikrokontroler spal przez 20 sekund, a pozostałe 7 sekund zajęło ponowne uruchom ienie klienta. Przekroczenie czasu można przetestować, nie odpowiadając na kom unikat HELLO. HELLO 2010.08.05 12:00:28 ERROR 2010.08,05 12:00:58 HELLO 2010.08.05 12:01:25 ERROR 2010.08.05 12:01:5Ś M iędzy kom unikatem HELLO a ERRO R upłynęło zgodnie z oczekiwaniem 30 sekund. M iędzy kom unikatem ERRO R a HELLO upłynęło jak poprzednio 27 sekund. Kolejny test polega na próbie zmiany parametrów. HELLO 2010.08.05 12:10:51 OK PARAM 30 20 SET 10 10 OK 10 10 BYE 2010.08.05 12:10:57 HELLO 2010.08.05 12:11:14 OK PARAM 30 20 OK BYE 2010.08.05 12:11:20 Oba param etry czasow e klienta zostały zm ienione na 10 sekund. Tym razem między kom unikatem BYE a HELLO upłynęło 17 sekund, czyli klient spał zadane 10 sekund i urucham iał się ponownie przez następne 7 sekund. Po wybudzeniu przyw rócono dom yślne wartości parametrów. Na koniec należałoby też przetestować, czy klient popraw nie reaguje na błędne komunikaty. HELLO 2010.08.05 12:16:28 KO ERROR 2010.08.05 12:16:30 HELLO 2010.08.05 12:16:57 I ERROR 2010-.0$j05 12:17:03
Przykład 6b - klient TCP z obsługą rejestrów zapasowych Nazwy plików przykładu 6b zam ieszczone są w tabeli <5.4. W dalszym ciągu opisu^ ję tylko różnice w stosunku do przykładu 6a. Pliki util_bkp.h i util_bkp.c Pliki ulil_bkp.li i util_bkp.c zaw ierają im plem entację funkcji obsługujących reje-, stry zapasowe. Poszczególne modele mikrokontrolerów z rodziny STM 32 mają! różną liczbę rejestrów zapasowych. N iektóre mają ich tylko 10. Mikrokontroler STM 32F107 wyposażono w 42 dw ubajtow e rejestry, których zaw artość jest pod-"' trzy mywana przez zasilanie podłączone do w yprowadzenia V BAT. Rejestry te nieste-:' ty nie zajm ują ciągłego obszaru pam ięci w przestrzeni adresowej m ikrokontroler. Żeby łatwiej było się nimi posługiw ać, w bibliotece STM 32 zdefiniowano stale BKP_DR1 do BKP_DR42. Każdej z tych stałych przypisano wartość przesunięcia (ang,1 offset, pod którym znajduje się odpow iedni rejestr zapasowy. Przesunięcia są wy-a znaczane w zględem adresu 0x40006C00. Żeby można było traktow ać rejestry za- 1 pasow e jako ciągły obszar pam ięci o rozm iarze 84 bajtów, będziemy je adresować-; pośrednio za pom ocą tablicy BKPdataReg. static const uintl6_t BKPdataReg ] = { BKP_DR1, BKP_DR2, BKPJDR3, BKP DR4, BKP_DR5, BKP_DR6, BKP_DR7, BKP_DR8, BKP_DR9, BKPJJR10, BKP_DRll, 8KPJR12, BKP_DR13, BKP_DR14, BKP_DR15, BKP_DR16, BKP_DR17, BKP_1R1B, BKP_DR19, BKP_DR20, BKP_DR21, BKP_DR22, BKP_DR23, BKPJ3R24, 1; BKP_DR25, BKP_DR26, BKP_DR31, BKP_DR32, BKP DR37, BKP DR38, BKP_DR27, BKP_DR28, BKP_DR29, BKP_DR30, BKP_DR33, BKP_DR34, BKP_DR35, BKP_DR36, BKP_DR39, BKP_DR40, BKP_DR41, BKP_DR42 Poniew aż m ikrokontrolery STM 32 są 32-bitowe, wygodnie i efektyw nie jest pr/e sylać dane w porcjach czterobajtowych. D latego będziemy traktow ać pamięć reje- T ab. 6.4. Pliki przykładu 6b Źródłowe i biblioteczne ex tcp clnt bkp.c board conf.c util bkp.c Iiblwip4.a startup stm 32 cld.c board init.c u til delay, c Iibsten32i10x.a u tiljw ip.c u tiljtc.c u til time.c tcp_client_bkp.c boardjed.c util_etli_nc.c u til Je d.c - V Nagłówkowe tcp client, h board cont.h u til bkp.h cc.h board def.h u til delay.h cortex-m3.h boarfi dets.h util error.h Iwipopts.h board init.h util eth.h t stm32t10x cont.h b oardjed.h u tilje d.li u tiljw ip.li - u tiljtc.li u til time.h i m at v 6.3. Przykład 6b - klient TCP z. obsługą rejestrów zapasowych 215 strów zapasow ych jako pamięć 21 słów czterobajtow ych (ang. words. Jedno z tych słów rezerwujem y na sum ę kontrolną (CRC, ang. cyclic redundancy check, o czym poniżej. Zatem w STM 32F107 do dyspozycji pozostaje nam 20 słów pamięci zapasowej. Aby uczynić oprogram ow anie m ożliw ie przenośnym, definiujem y stałą BKPwords, która oznacza liczbę stów m ożliwych do zapisania w pamięci zapasowej. static const int BKPwords = (sizeof(bkpdatareg / (2 * sizeof(bkpdatareg[0] - 1; Podukład m ikrokontrolera odpow iedzialny za obsługę rejestrów zapasow ych konfigurujem y za pom ocą funkcji B K Pconfigure, która w łącza potrzebne sygnały taktujące i uaktyw nia dostęp do tych rejestrów. U aktyw nia też układ liczący sprzętow o sum ę kontrolną. Do jej obliczania jest używ any ten sam w ielom ian, który jest stosow any do w yliczania sekw encji kontrolnej ram ki ethernetow ej. Zawartość rejestrów zapasow ych m ożna skasow ać za pom ocą w yprow adzenia PC 13 m ikrokontrolera (ang. tamper. W zestaw ie ZL29A RM do tego w yprow adzenia podłączony jest przycisk SW 4, który po w ciśnięciu w ym usza na tym w yprowadzeniu stan niski. void BKPconfigure(void ( RCC_APBlPeriphClockCmd(RCC_APBlPeriph_PWR RCC_APBlPeriph_BKP, ENABLE ; RCC_AHBPeriphClockCmd(RCC AHBPeriph_CRC, ENABLE; PWR BackupAccessCmd(ENABLE; BKP_ClearFlag(; BKP TamperPinLevelConfig(BKP_TamperPinLevel_Low; BKPJTamperPinCmd(ENABLE; Stała BKPwords jest w idoczna tylko w ew nątrz jednostki translacji util_bkp.c. Aby uzyskać do niej dostęp z innych jednostek translacji, definiujem y funkcję BKPnumberWords. int BKPnumberWords(void { return BKPwords; Funkcja BKPwrite zapisuje dane do pomięci zapasowej. Pierwszy argum ent d ata jest w skaźnikiem do tablicy słów, które m ają być zapisane. Drugi argum ent count jest liczbą słów do zapisania. Funkcja zw raca liczbę faktycznie zapisanych słów. Każde słow o zapisujem y w dw óch kolejnych rejestrach zapasowych. Na końcu zapisujem y sum ę kontrolną, która pozw ala zw eryfikować poprawność późniejszego odczytu i co być m oże ważniejsze, pozw ala leż stwierdzić, czy odczytane zerowe dane są wynikiem wyzerowania zawartości rejestrów zapasowych, czy rzeczywiście takie dane w nich zapisano. int BKPwrite (const uint32 t data, int count { int idx; uint32_t crc; * if,county > BKPwords ^unt^*p &KPwords; CRC ResetP${};
6.3. Przykład 6h - klient TCP z obsługą rejestrów zapasowych 217 for (idx = 0; idx < count; ł+idx { BKP WriteBackupRegister(BKPdataReg(2 * idx], data[idx] & 0xffff; BKP_WriteBackupRegister(BKPdataReg[2 * idx +1], data{idx] >> 16; CRC^CalcCRC{data[idx]; } crc = CRC GetCRC ( ; BKP_WriteBackupRegister(BKPdataReg[2 * idx], crc Qxffff; BKP_WriteBackupRegister(BKPdataReg(2 * idx + ł], crc» 16; return idx; Funkcja BKPread odczytuje dane z pam ięci zapasowej. Pierwszy argum ent data jest w skaźnikiem do tablicy słów, gdzie m ają być um ieszczone odczytane dane. Drugi1 argum ent count jest liczbą słów do odczytania. Funkcja zw raca liczbę odczytanych słów albo wartość ujemną, gdy nie zgadza się sum a kontrolna. int BKPread(uint32_t *data, int count 1 int idx; uint32_j: crc; if (count > BKPwords count - BKPwords; for (idx = 0; idx < count; ++idx data(idx] = BKP_ReadBackupRegister(BKPdataReg(2 * idxj f (BKP_ReadBackupRegister(BKPdataReg[2 * idx +1] «16; crc = BKP_ReadBackupRegister(BKPdataReg[2 * idx} (BKP^ReadBackupRegister(BKPdataReg[2 * idx + 1] «16; CRC_ResetDR{; if (crc!- CRC_CalcBłockCRC(data, idx return -1; return idx; Plik tcp_client_bkp.c Plik tcp_client_bkp.c zaw iera im plem entację klienta realizującego rozważany w tym rozdziale protokół i dodatkow o w yposażonego W.flbsługę rejestrów zapasowych. W stosunku do im plem entacji klienta z pliku tc p ^ lie n t.c są tylko trzy różnice. Pierwsza różnica występuje w funkcji T C P c łie n ts ta rt. Sprawdzamy, czy rejestry zapasow e zaw ierają param etry klienta i jeśli zaw ierają, to je czytamy. Jeśli nie uda się odczytać parametrów, to są one inicjow ane w artościam i dom yślnym i. int TCPcłientStart(struct ip_addr *addr, uintl6_t port ( if (BKPread(state->param, PARAMJ1UMBER!= P^RAM_NUMBER { state->param(connection_timeout = DEFAUL%CONNECTION_TIMEOUT; I state->param[standby_time] = DEFAULT_STANDBY_TIME; Pozostałe różnice polegają na zapam iętaniu param etrów klienta w rejestrach zapasow ych przed uśpieniem m ikrokontrolera w funkcjach, które kończą działanie klienta, czyli conn err_callback i tc p _ e x it. static void conn_err_callback(void *arg, err_t err ( struct state state = arg; BKPwrite(state->param, PARAM_NUMBER; DelayedStandby(2, state->param(standby_time; static void tcp_exit(struct state state, struct tcp_pcb *pcb { if (tcp_close(pcb!= ERR_OK tcp_abort(pcb; else { BKPwrite(state->param, PARAM_NUMBER; DelayedStandby(2, state->param(standby_time]; 1 6.3.3. Plik ex_tcp_clnt_bkp.c Plik ex_tcp_client_bkp.c zawiera funkcję main przykładu. Funkcja ta jest prawie identyczna jak w poprzednim przykładzie. Jedyna różnica polega na wywołaniu na jej początku funkcji BKPconfigure konfigurującej rejestry zapasowe. BKPconfigure{; 6.3.4. Testowanie przykładu i Przykład powinien najpierw przejść pozytywnie w szystkie testy, którymi byl testowany poprzedni przykład. Następnie można przystąpić do testowania rejestrów zapasowych. Najpierw próbujemy zm ienić wartości param etrów klienta. HELLO 2010.08.05 12:25:29 OK PARAM 30 20 SET 10 10 OK 10 10 BYE 2010.08.05 12:25:35 Sprawdzamy, czy rzeczyw iście parametry zapisano. HELLO 2010.08.05 12:25:51 OK PARAM 10 10 OK BYE 2010.08.05 12:25:54 HELLO 2010.08.05 12:26:10 ERROR 2010.08.05 12:26:20 Między kom unikatem BYE a HELLO upłynęło 16 sekund, a m iędzy komunikatem HELLO a ERROR minęło 10 sekund, co świadczy o tym, że parametry zmienio- H. no, zapisano w rejestrach zapasowych i odczytano z nich po wybudzeniu mikrok o n tfo lerąn astęp n ie, gdy m ikrokontroler śpi, w ciskam y przycisk tam per (SW4 w ZL29ARNii.
218 6. Klient TCI' HELLO 2010.08.05 12:28:46 OK PARAM 30 20 OK BYE 2010.08.05 12:20:49 HELLO 2010.08.05 12:29:15 ERROR 2010.08.05 12:29:45 W idać, że przywrócono dom yślne w artości parametrów. M iędzy kom unikatem BYH ' a HELLO upłynęło 26 sekund, a m iędzy kom unikatem HELLO a ERRO R minęło'.< 30 sekund. Serwer UDP
220 7. Serwer U lf ibsl W tym rozdziale przedstawiam prosty serw er używający protokołu transportowego UDP. Serwer ten um ożliw ia zdalne m anipulow anie rejestrami w ejścia-w yjścia1rrilv krokontrolera (przedstawionym i na rysunku 1.3 i opisanym i w rozdziale 1.5, alg można go łatwo rozbudować, aby umożliwia! sterow anie dow olnym i peryferiam i mikrokontrolera, co pozostawiam Czytelnikow i jako ćwiczenie. Przykład bardziej; skom plikowanego serwera korzystającego z protokołu transportowego UDP moi na' znaleźć w nocie aplikacyjnej [5]. Jest lam opisany serw er TFTP (ang. Trivial File Transfer Protocol, czyli serw er prostego protokołu do przesyłania plików.! 7.1. Projekt protokołu Tym razem zaprojektujem y protokół bezstanowy, w którym kom unikaty będą mied postać binarną. Form at kom unikatu jest widoczny na ry su n k u 7.1. Wszystkie kom unikaty mają 32 bity. Początkow e 4 bity zawierają num er wersji protokolu;j' W prow adzenie w kom unikatach pola inform ującego o wersji protokołu jest bafi$ dzo pow szechną praktyką. U łatwia rozw ijanie protokołu i w prowadzanie jego noi? wych wersji z zachow aniem kompatybilności z wersjami poprzednimi. Opisywaną^ tu wersja protokołu m a w polu W ERSJA wartość 1. K olejne czterobitowe pole';6 O PERA CJA w kom unikatach w ysyłanych przez klienta zaw iera polecenie do wy * konania, a w kom unikatach w ysyłanych przez serw er zaw iera odpowiedź. Poleceniair mają wartości od 0 do 7, a odpow iedzi od 8 do 15. Aktualnie zdefiniow ane operacje opisane są w tabeli 7.1. W artości nieuwzględnienie w tabeli są zarezerwowane dof przyszłego wykorzystania. O śm iobitow e pole PORT zaw iera num er portu wejścia-!.--- -w yjścia m ikrokontrolera. N um er 1 oznacza port A, num er 2 port B itd. Numeryi,^. które nie odpow iadają żadnem u fizycznem u portowi m ikrokontrolera, są zarezer- j wowane do przyszłego wykorzystania. Ostatnie 16 bilów komunikatu zajmuje pole';! DANE. Jego interpretacja zależy od operacji i jest opisana w tabeli 7.1. Wartośoil Tab. 7.1. Pole OPERACJA Operacja READJNPUTJ3ITS Wartość READ_OUTPUT_BITS 2 RESET_BITS 3 SET_BITS 4 WRITE_BITS 5 1 opis,. 2 i 2 B Polecenie - odczytaj rejestr wejściowy i d r.zawartość pola DANE jest nieistotna. Serwer-- odpowiada komunikatem z operacją INPUT BITS Polecenie - odczytaj rejestr wyjściowy o d r.zawartość pola DANE jest nieistotna. Serwer. odpowiada komunikatem z operacją OUTPUT BITS Polecenie - wyzeruj w rejestrze wyjściowym o d r bity, które mają wartość 1 w polu DANE. Serwer odpowiada komunikatem z operacją OUTPUT BITS Polecenie - ustaw w rejestrze wyjściowym ODtyBity, które mają wartość 1 w polu DANE. Serwer odpowiada komunikatem z operacją OUTPUT BITS Polecenie - zapisz do rejestru wyjściowego od r wartość pola DANE. Serwer odpowiada _ komunikatem z operacją OUTPUT BITS PDU ERROR 8 Odpowiedź serwera informująca o błędnym poleceniu. Pola PORT i DANE zawierają zera INPUT BITS 9 Odpowiedź serwera - pole DANE zawiera wartość odczytaną z rejestru wejściowego lim OUTPUT BITS 10 Odpowiedź serwera - pole DANE zawiera wartość odczytaną z rejestru wyjściowego opi«. WERSJA OPERACJA ' ' PORT DANE [0:3] - [4:7] [8:15] [16:31] ^ Rys. 7.1. Format komunikatu >.:! 7.2. Przykład 7 - prosty serwer UDP 221 w tym polu są przesyłane w porządku sieciow ym (ang. big-endian, czyli najbardziej znaczący bit pola D ANE ma num er 16, a najmniej znaczący jest bit 31. Protokół działa według zasady żądanie-odpowiedź. Klient wysyła polecenie, serwer je wykonuje i odsyła właściwą odpowiedź. Jeśli serw er nie może poprawnie zinterpretować polecenia, to odpow iada kom unikatem, w którym pole OPERACJA ma wartość PD U_ERRO R (ang. protocol data unit error. 7.2. Przykład 7 - prosty serwer UDP Nazwy plików przykładu 7 zam ieszczone są w tabeli 7.2. W iększość z nich opisałem ju ż w poprzednich rozdziałach. Poniżej omawiam tylko nowe pliki. T ab. 7.2. Pliki przykładu 7 e xjid p d.c startup_stm32_cld.c udpjserver.c udp_server.h Źródłowe i biblioteczne board_con!.c boardjn it.c board Je d.c board_conui boardjjef.h b o a rd je ts.h board jn it.h boardje d.h Nagłów kowe utiljtelay.c util_eth_nc.c util Je d.c u liljw ip.c util time, c uliljdelay.h util_error.h util_eth.h utiljed.h utiljw ip.b U tiljlc.il util time.h 7.2.1. Pliki udp_server.h i udp_server.c Iiblwip4.a Iibstm32t10x.a cc.h cortex-m3.h Iwipopts.h stm32i10x_conf.h Pliki udp_server.h i udp_server.c zaw ierają im plem entację serwera, który realizuje opisany wyżej protokół. Serwer urucham ia się za pom ocą funkcji UDPserverStart. Jej argum entem jest num er portu, na którym serwer ma nasłuchiwać. Funkcja ta zwraca zero, gdy uruchom ienie serw era pow iodło się, a wartość ujem ną w przeciw nym przypadku. Funkcja UDPserverStart realizuje diagram serwera przedstawiony na rysunku 4.9. Dodatkowo sprawdzam y wartości zwracane przez funkcje biblioteki lwip i stosow nie reagujemy, jeśli któraś funkcja zwróci błąd. Funkcja UDPserverStart jest przeznaczona do w ywołania z program u głównego, dlatego w- w ywołania funkcji z biblioteki lwip otoczone są makiam i, które zabezpieczają przed ich w spółbieżnym w ykonywaniem. Za pomocą-funkcji udp_recv ustawiamy funkcję zw rotną recv_callback, która będzie w ywołana po odebraniu przez serwer danych. Ostatni argum ent w wywołaniu funkcji udp_recv ma wartość NULL, gdyż nasz serw er jest bezstanowy i nie ma potrzeby przechow ywania żadnych informacji między kolejnym i jego wywołaniami. int UDPserverStart(uint!6_t port { struct udp_pcb *pcb; err t err; *>1, IH0~ ECL_PH0TECT (x; IR Q _ re O T llć l( (, LW IP_IR Q _PR IO ; pcb = udp<_n(j/w{;
222 7. Serwer U ft 7.2. Przykład 7 - prosty serwer U DP 223 IRQ_UNPROTECT(x; if (pcb == HÜLL return -1; IRQ PROTECT (x, LWIP_IRQ_PRIO; udp recv (pcb, recv_callback, HULL; IRQ_ÜNPROTECT(x; IRQ_PROTECT(x, LWIP_IRQ_PRIO ; err = udp_bind(pcb, IP_ADDR_ANY, port; IRQJJNPROTECT(x; if (err!= ERR_OK { IRQ_PROTECT(x, LWXP_IRQ_PRIO; udp_remove(pcb; IRQ_ÜNPROTECT(x; return -1; return 0; Form at komunikatu, przedstaw iony na rysunku 7.1, definiujem y jako strukturę I pdu t (ang. protocol data unit type, Pola komunikatu W ERSJA i OPERACJA definiujem y jako wspólną składową v e rs io n _ o p e ra tio n. A lternatyw nie można je zdefiniować jako dostępne w języku C pola bitowe. Jednak pola bitowe nie są przenośne - sposób ich realizacji zależy od konkretnej im plem entacji języka C. Dlatego;, należy ich unikać w definicjach struktur, których postać binarna jest ustalona. PACK_STRUCT_BEGIN : struct pdu_t { 1 PACK_STRUCT_FIELD(u8_t version_operation; * PACK_STRUCT_FIELD(u8~t port; PACK_STRUCT_FIELD{ul6_t pins; PACK_STRUCT_STRUCT; PACK STRUCT_END K om pilator nie ma obowiązku um ieszczania składowych struktury w ciągłym obszarze pamięci. Ze względu na szybkość realizacji operacji na składowych struktury, kom pilator może zoptym alizow ać ich rozm ieszczenie w pam ięci, zostawiając między kolejnymi składowym i puste, niew ykorzystane miejsce. Aby w yłączyć takie optym alizacje, w bibliotece IwIP zdefiniow ano makra, których nazwa zaczyna się od przedrostka PACK. W iększość kom pilatorów pozw ala program iście decydować o rozm ieszczeniu składowych struktury w pamięci, ale nie jest to ujęte w standardzie języka C. Poszczególne kom pilatory oferują w tym celu różne rozszerzenia. Poniżej przedstawiam realizację m akr PACK dla GCC. Te definicje oraz definicje dla innych kom pilatorów znajdują się w pliku cc.h. łdefine PACK STRUCT BEGIN łdefine PACK_STRUCT_STRUCT _ a t t r i b u t e _ (( _ p a c k e d _ łdefine PACK_STRUCT_END łdefine PACK_STRUCT_FIELD(x x Numer wersji protokołu oraz operacje i odpowiedzi protokołu definiujemy jako stale. łdefine PDU_VERSION * 1 I łdefine READ_INPUT_BITS 1, * łdefine READ_OUTPUT_BITS ' 2 łdefine RESET_BITS 3 łdefine SET_BITS 4 łdefine WRITE BITS 5 łdefine PDU ERROR 8 łdefine INPUT_BITS 9 łdefine OUTPUT_BITS 10 Aby tekst źródłowy byl przejrzysty, definiujem y makra odczytujące i zapisujące poszczególne pola w strukturze typu pdu_t. A rgum ent pdu jest wskaźnikiem do struktury tego typu. Zauważmy, że dostęp do pola DATA jest zrealizowany za pomocą funkcji n to h s i htons. Jest to potrzebne, gdyż pole DATA jest w porządku sieciowym. łdefine pdu_version(pdu ((pdu->version_operation >> 4 łdefine pdu_operation(pdu ((pdu->version_operation & 0xf łdefine pdu_port(pdu ((pdu->port łdefine pdu_data(pdu ntohs((pdu->data łdefine set_pdu_version_operation(pdu, v, c \ ((pdu->version_operation = ((v << 4 i ((c & 0xf łdefine set_pdu_port(pdu, x ((pdu->port = (x łdefine set_pdu_data(pdu, x ((pdu->data - htons(x Zasadniczą obsługę protokołu im plem entujem y w funkcji zwrotnej re c v c a llb a c k. Na początku za pom ocą funkcji p b u f_ a llo c przydzielam y bufor, w którym serwer um ieści sw oją odpowiedź. Poniew aż potrzebny jest bufor o bardzo małym rozm iarze, alokujem y go na stercie (param etr PBUF_RAM, a nie z puli buforów. Faktyczny rozm iar przydzielonego bufora będzie większy niż rozm iar struktury typu pdu t, w yspecyfikowany jako drugi argum ent funkcji pbuf a llo c. Na początku bufora zostanie pozostaw ione miejsce na w staw ienie nagłów ków protokołów warstwy dostępu do sieci, sieciowej i transportowej (param etr PBUF_TRANSPORT. Jednak, dla ułatwienia posługiw ania się buforem, składowa p ayload w przydzielonej strukturze typu pbuf wskazuje na początek miejsca w buforze, gdzie mają być umieszczone dane warstwy transportowej. void tecv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, uint!6_t port { struct pbuf *q; struct pdu_t *src, *dst; q = pbuf alloc (PBUF TRANSPORT, sizeof (struct pdu_t, PBUF RAM; if (q =»~NULL { pbuf_free(p; return; sre = p->payload; dst * q->payload; if (p->len < sizeof(struct pdu_t II pdu_version(sre!= PDU_VERSION II pdu_port(sre < 1 II pdu_port(sre > gpiocount { set_pdu version_operation(dst, PDU_VERSION, PDU_ERR0R; set_pdu_port(dst, 0; set_pdu data(dst, 0; I else { GPIO_TypeDef *io * gpio pdu_port(src - 1}; ^^tchi(^du_operation (sre ( case JiE^_IKPUT_BITS:
224 7. Serwer (Jlp 7.2. Przykład 7 - prosty serwer UDP 225 set_pdu_version_operation (dst, PDU VERSION, INPUT_BITS ; set_pdu_port(dst, pdu_port(src/ set_pdu_data(dst, GPIO_ReadInputData(io; break; case READ OUTPUT BITS: set_pdu_version_operation(dst, PDU VERSION, OUTPUT_BITS; set_pdu_port(dst, pdu_port(src; set_pdu_data(dst, GPIO_ReadOutputData(io; break; case RESET_BITS: GPIOJlesetBits (io, pdu_data (src; set_pdu version operation (dst, PDIM/ERSION, OUTPUT_BITS ; set_pdu port (dst, pdu_port (src; set_pdu_data(dst, GPIO_ReadOutputData(io; break; case SET_BITS: GPIO_SetBits(io, pdu_data(src; setj?du versionjdperation (dst, PDU_VERSION, OUTPUT_BITS; set_pdu port (dst, pdu_port(src; set_pdu_data(dst, GPIO_ReadOutputData(io; break; case WRITE_BITS: GPIO_Write(io, pdu_data(src; set_pdu_version_operation(dst, PDUJflSRSION, OUTPUT_BXTS; set_pdu_port(dst, pdu_port(src; set_pdu_data(dst, GPIO_ReadOutputData(io; break; default: set_pdu_version_operation(dst, PDU_VERSION, PDU_ERROR; set_pdu_port(dst, 0; set_pdu_data(dst, 0;! 1 pbuf_free(p; udp_sendto(pcb, q, addr, port; pbuf_free(q; Jeśli bufor zostanie przydzielony, to dalsza część funkcji recv callback jest dość typowa. Najpierw sprawdzam y popraw ność odebranego komunikatu. Jeśli komunikat jest poprawny, to wykonujem y zaw arte w nim polecenie i odsyłam y odpowiedź. Na koniec trzeba też zw olnić przydzielone bufory. 7.2.2. Plik ex_udpd.c Plik ex_udpd.c zaw iera funkcję main przykładu. Zawiera też funkcję PORTCconf igure, która konfiguruje niektóre w yprow adzenia portu C jako wejścia, żebyśm y mieli co testować. W zestawie ZL29A RM do wyprowadzeń P C 5...P C 9 jest podłączony dżojstik (m g. joystick, do w yprowadzenia P C I2 - przycisk SW 3, a do wyprowadzenia PC13 - przycisk SW4. Jako wyjść użyjem y diod świecących podłączonych w zestawie ZL29ARM do w yprowadzeń PE14 i PE15. static void PORTCconf icjure (void { GPIO_InitTypeDef GPIO_InitStruct; * RCC_APB2EeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE; GPIO_StructInit(SGPIO_InitStruct; GPIO InitStruct.GPIO Pin = GPIO Pin 5 I GPIO Pin 6 I GPIO Pin 7 I t 7.2.3. '.w' GPIO_Pin_8 GPIO_Pin_9 GPIO_Pin_12 GPI0_Pin_13; GPIO_InitStruct,GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO Init (GPIOC, SGPIO_InitStruct ; W funkcji main inicjujemy poszczególne potrzebne w tym przykładzie układy mikrokontrolera, moduły biblioteki IwlP, a następnie konfigurujem y ustawienia sieciowe, podobnie jak w poprzednich przykładach. Na końcu za pomocą funkcji UDPserverStart urucham iam y serw er U DP na porcie 33333, a za pom ocą funkcji PORTCconf igure konfigurujem y port C. int main{ ( static struct netif netif; static struct ethnetif ethnetif = (PHY_ADDRESS; uint8_t confbit; Delay(1000000; confbit GetConfBit(; AllPinsDisable(; LEDconfigure {; RedLEDon{; SET_IRQ_PROTECTION(; error_resetable(clkconfigure(, 1; error_permanent(localtimeconfigure{, 2; error_resetable (ETHconf iguremii (, 4; netif.hwaddr(0] = 2 ;? netif.hwaddrflj = (BOARDJFYPE» 8 S 0xff; netif.hwaddr[2] = BOARDJFYPE fi 0xff; netif.hwaddr[3 = (ETHJ30ARD» 8 & 0xff; netif,hwaddr[4 = ETHJ30ARD & 0xff; netif.hwaddr 5 = 1 + confbit; if ( confbit { IP4_ADDR(finetif.ip addr, 192, 168, 51, 84; IP4_ADDR i&netif.netmask, 255, 255, 255, 240; IP4_ADDR(&netif.gw, 192, 168, 51, 81; S else { IP4_ADDR(inetif.ip_addr, 0, 0, 0, 0; IP4 ADDR(Łnetif.netmask, 0, 0, 0, 0; IP4_ADDR (Snetif.gw, 0, 0, 0, 0; error_resetable(lwipinterfacelnit(snetif, Sethnetif, 5; LWIPtimersStart(; error_resetabie(dhcpwait(snetif, 10, 4, 6; error resetabłe(udpserverstart(33333, 7; RedLEDoff{; PORTCconfigure(; for {;;}; } Testowanie przykładu Do przetestowania przykładu wykorzystamy program netcat. Komunikat do serwera można wysłać za pomocą poleceń z tabeli 7.3. W dalszym ciągu zakładam, że serwer ijjfcywa adresu 192.168.51.89. Najpierw rozważmy polecenie dla systemu Linux. Za pćimodppjolecenia echo można wysiać napis na standardowe wyjście. Parametry -ne powoduj'4, że po wysianiu tego napisu nie jest wysyłany znak końca wiersza,
7. Serwer U D p ~ ' Tab. 7.3. Uruchamianie klienta echo -ne i nc -u -wł 192,168.51.89 33333 I hexdump -C "nc" -u -wl 192.168.51.09 33333 < infile > outfile Tab. 7.4. Przykładowe testy w zestawie ZL29ARM Test Poleconie Odpowiedź Wciśnięty przycisk SW3 ii 03 00 00 19 03 23 eo Wciśnięty przycisk SW4 ii 03 00 00 19 03 13 e8 Oba przyciski wciśnięte ii 03 00 00 19 03 03 e8 Sprawdzenie stanu diod 12 05 00 00 la 05 co 00 Włączenie zielonej diody 13 05 40 00 la 05 80 00 Włączenie czerwonej diody 13 05 80 00 la 05 00 00 Wyłączenie zielonej diody 14 05 40 00 la 05 40 00 Wyłączenie czerwonej diody 14 05 80 00 la 05 co 00 Włączenie obu diod 15 05 00 00 la 05 00 00 Błędne polecenie 20 00 00 00 18 00 00 00 a znak lewego ukośnika (ang, backslash jest traktowany jako znak rozpoczynający sekwencję specjalną, co umożliwia wysyłanie napisów, które zawierają dane binarne. Aby wysiać znak (bajt o wartości szesnastkowej hh, należy wpisać \xhh. Dane do. wysiania umieszczamy w cudzysłowach. Wyjście polecenia echo przekierowujemy na wejście programu netcat (polecenie nc. Parametr -u oznacza wysyłanie danych za pomocą UDP. Parametr -wl nakazuje programowi neicat czekać maksymalnie jedną sekundę na odpowiedź. Kolejnymi argumentami polecenia nc są adres IP i numer portu serwera. Program netcat po odebraniu odpowiedzi od serwera wypisuje ją na standardowym wyjściu, które przekierowujemy na wejście polecenia hexdump. Program hexdump umożliwia wypisywanie danych w różnych formatach. Param etr -C oznacza wyświetlanie szesnastkowo każdego bajta oddzielnie. Polecenie echo w systemie Windows nie jest tak elastyczne jak w Linuksie. Najprostszym rozwiązaniem jest przygotowanie polecenia, które chcem y wysiać, w pliku i n f i l e za pomocą edytora binarnego. Zawartość pliku i n f i l e wysyłamy na wejście polecenia nc, a odpowiedz zapisujemy w pliku o u tf il e. Dobrym pomysłem jest przygotowanie sobie całego zestawu plików z poszczególnymi potrzebnymi poleceniami. W następującym przykładzie odczytujem y stan wejść.portu C w zestawie ZL29ARM. czyli wysyłamy kom unikat zapisany szesnastko\yo. U 030000. Klient UDP 5 echo -ne "\xll\x03\x00\x00" nc -u -wl 192.168.51.89 33333 hexdump -C 00000000 19 03 33 eo 00000004 Otrzymaliśmy odpowiedź 190333e0, co oznacza, że wejścia PC 13, PC 12, PC9, PC8, PC7, PC6 i PC5 są w stanie wysokim, a pozostałe w stanie niskim, czyli oba przyciski i dżojstik nie są- wciśnięte. Kolejne przykłady zamieszczone są w tabeli 7.4. Zauważmy, że w niektórych odpowiedziach Wejście PC3 ma poziom wysoki. Do wejścia tego jest podłączony sygnał zegara M1I_TX_CLK z modułu ethernetowego, dlatego możemy się spodziewać odczytu dość przypadkowych wartości na tym wejściu.
8.2. SNTP 229 Tematem tego rozdziału są klienty protokołów DNS i SNTP. Oba używają protoko'^ łu transportowego UDP. Przykładem ich użycia będzie program, który komunikuje' się z internetowym serwerem czasu i synchronizuje zegar czasu rzeczywistego rfuijt krokontrolera. DNS DNS (ang. D omain Narne System, czyli system nazw dom enow ych, jest jed ri l z podstawow ych usług w intersieciach. Zapewnia tłum aczenie nazw domenowych? węzłów sieci, znanych użytkow nikom sieci, czyli nazw takich jak www.niema.pl :nat, adresy IP. Umożliwia też tłum aczenie odwrotne, czyli sprawdzenie, jaka nazwa do'y nienow a odpow iada danem u adresowi IP. DNS składa się z protokołu komunikacyj-' nego i hierarchicznego systemu serwerów. DNS używ a protokołu transportowego? UDP, choć serwery m iędzy sobą m ogą kom unikować się rów nież za pomocą TCP.!; Serw er nasłuchuje na porcie 53. Zasadnicze elem enty DNS są opisane w dokumem; tach RFC 1034 i RFC 1035, które razem tworzą standard intersieci STD 13. W bibliotece lw lp zaim plem entow ano prostego klienta DNS - pliki clns.h i dns.c,' Konfiguruje się go w pliku Iwipopts.h, co opisałem w rozdziale 3.2.2. Do obsługi, klienta protokołu DNS biblioteka lw lp dostarcza kilku funkcji. errj: dns gethostbyname (const char *name, struct ip_addr *addr, dns_found_callback found, void *arg;» Funkcja dns_gethostbynam e odw zorow uje nazwę dom enow ą na adres IP. Pierw-,/> jej argum ent name jest wskaźnikiem do napisu zaw ierającego nazwę, która ma być. przetłum aczona. Drugi argum ent addr jest w skaźnikiem do struktury, w której m a ; być um ieszczony przetłum aczony adres IP, jeśli został on ju ż pozyskany wcześniej przez klienta DNS i znajduje się w jego lokalnej pamięci. W tym przypadku funk-, cja zwraca wartość ERR_0K. Liczbę pam iętanych lokalnie odw zorowań określa stała DNS_TABLE_SIZE zdefiniow ana w pliku Iwipopts.h. Jeśli adres IP nie jest jeszcze /n.iny, to funkcja w ysyła prośbę do serw era DNS i kończy działanie, zwracając wartość. ERR_INPROGRESS. W tym przypadku aplikacja zostanie pow iadom iona o odebraniu,, odpowiedzi od serwera za pom ocą funkcji zw rotnej^której adres found jest trzecim, argum entem. Czwarty argum ent arg jest wskaź.nijdem, który zostanie przekazany funkcji zwrotnej, co um ożliw ia obsługę w szystkich odw zorowań za pom ocą wspók, nej funkcji zwrotnej. W przypadku w ystąpienia problemu funkcja może też zwrócić inny kod błędu typu e r r _ t, zgodnie z konw encją stosow aną w bibliotece lwlp. typedef void (* dns_found_callback(const char *name, struct ip_addr *addr, 1 void *arg; ^ Jeśli klient DNS odbierze odpow iedź od serwera, to biblioteka lw lp wywołuje funkcję zwrotną typu d n s_ fo u n d _ callb a ck. Pierwszy jej argum ent name wskazuje nazw ę węzła, którego adres IP chcem y poznać. Drugi jej argum ent addr jest wskaź- I 8.2. SNTP "I nikiern do struktury zawierającej przetłum aczony adres IP lub ma wartość NULL, gdy wystąpił błąd, np. nazwa nie może być przetłum aczona lub nie istnieje. Trzeci argum ent arg jest wskaźnikiem przekazanym jako czwarty argum ent wywołania funkcji dns_gethostbynam e. Funkcja zw rotna jest typu void, czyli nie zwraca żadnej wartości. Opis konfigurow ania adresów serwerów DNS znajduje się rozdziale 3.2.2. Adresami serwerów DNS można też zarządzać za pom ocą dwóch poniższych funkcji. void dns_setserver(u8_t numdns, struct ip_addr *dnsserver; Funkcja d n s _ s e ts e rv e r ustawia adres serwera DNS. Pierwszy argum ent numdns jest indeksem w wewnętrznej tablicy modułu DNS przechowującej adresy serwerów i musi być mniejszy niż stała DNS_MAX_SERVERS zdefiniow ana w pliku Iwipopts.h. Drugi argum ent d n sserv e r jest wskaźnikiem do struktury zawierającej adres IP serwera DNS. struct ip_addr dns_getserver(u8_t numdns; Funkcja d n s_ g e tse rv e r zwraca adres IP serwera DNS o indeksie numdns. Jeśli indeks ma niepoprawną wartość lub pod tym indeksem nie został skonfigurowany żaden serwer, to zwraca adres składający się z sam ych zer. SNTP (ang. Sim ple Network Time Protocol to protokół umożliwiający synchronizację zegarów w intersieciach, jest opisany w RFC 4330. SNTP jest uproszczoną wersją NTP (ang. Netw ork Time Protocol, ale zupełnie wystarcza do precyzyjnej synchronizacji zegara czasu rzeczyw istego w dow olnym urządzeniu, które ma dostęp do Internetu. SNTP używa protokołu transportowego UDP. Serwery czasu nasłuchują na porcie 123. SNTP jest protokołem bezstanowym. K lient wysyła do serwera żądanie (ang. request podania aktualnego czasu, a serwer odsyła odpowiedź (ang. reply z czasem. SNTP jest kom patybilny z N TP w tym sensie, że serwery N TP odpow iadają na żądania SNTP. Czas w N TP i SNTP jest reprezentow any za pom ocą 64-bitowego znacznika czasu (ang. timestamp. Znacznik czasu jest zapisywany w sieciowym porządku oktetów i zaw iera liczbę sekund, które upłynęły od dnia 1 stycznia 1900 roku czasu uniw ersalnego (UTC, ang. Universal Time Clock. Początkowe 32 bity znacznika zawierają część całkowitą, a kolejne 32 bity to część ułamkowa. Taki format zapisu czasu zapewnia rozdzielczość około 233 pikosekundy. Jeśli taka precyzja nie jest potrzebna, to nieużywane, najmniej znaczące bity znacznika czasu wypełnia się zerami. Znacznik czasu równy zeru oznacza nieprawidłowy lub nieosiągalny czas. Znacznik czasu przepełnia się co 232 sekund, czyli co około 136 lat, lecz nie powinno to pow odować w iększych problemów, gdyż jest to na tyle długi okres, że mozrni z ś ^ je g o źródła łatwo określić, którego 136-lelniego okresu czasu dotyczy dany znacznik. Drobnym problem em jest występow anie raz na 136 lat niereprezen-
8.2. SNTP 231 towalnego okresu o długości 233 pikosekund, gdy znacznik czasu musiałby, wartość zero. Żądanie i odpowiedź mają wspólny format, taki sam jak w NTP. Jest on stawiony na ry su n k u 8.1. W szystkie liczby wielooktetow e zapisuje się w ku sieciowym. Dwubitowe pole LI zaw iera ostrzeżenie o sekundach przestępn (ang. leap imlicator. Wartość 0 oznacza brak ostrzeżenia. W artości 1 i 2 oznaczają że ostatnia minuta bieżącego dnia będzie m iała odpow iednio 61 lub 59 sekund! Wartość 3 oznacza, że zegar serw era nie został jeszcze zsynchronizowany. To p- jest istotne tylko w kom unikatach zaw ierających odpow iedź od serwera. Klient'.- wsze wstawia w nim wartość 0. Trzybitow e pole VN zawiera num er wersji pr0; tokołu. O pisywana tu wersja ma num er 4. Trzybitowe pole M ode zawiera rod/ operacji. Klient, wysyłając żądanie, ustawia wartość 3, a serwer w odpowiedzi usta*» wia wartość 4. Serw er pracujący w trybie rozgloszeniowym, wysyłający cyklicznie" w ustalonych odstępach czasu inform ację o aktualnym czasie, um ieszcza w tvn> polu wartość 5. Inne m ożliwe wartości tego pola opisane są szczegółowo w doku-', mencie RFC 4330. II VN Mode Stratum Poll [0:1] [2:4] [5:7] [8:15] [16:23] Rys. 8.1. Format komunikatu NTP Root Delay [32 bity] Root Dispersion [32 bity] Reference Indentifier [32 bity] Reference Timestamp [64 bity] Originate Timestamp [64 bity] Receive Timestamp [64 bity] Transmit Timestamp [64 bity] Key Identifier (opcjonalne [32 bity] Message Digest (opcjonalne [1284>itów] Precision [24:311 Pole Stratum informuje o położeniu (numerze warstwy serwera w hierarchii synchronizacji. Klient zawsze ustawia w nim wartość 0. Jest ono istotne tylko w odpowiedzi udzielonej przez serwer. Wartość 0 w tym polu oznacza komunikat kiss-o -death, który informuje, że serwer nie chce udzielić odpowiedzi, gdyż np. jest przeciążony. Klient, otrzymawszy taki komunikat, powinien przestać wysyłać żądania do tego serwera i zwrócić się do innego serwera. Jeśli żaden alternatywny serwer nie jest dostępny, to klient może ponawiać żądanie do tego serwera, zwiększając wykładniczo odstęp czasu między kolejnymi żądaniami, jeśli serwer nadal nie odpowiada. Wartość 1 w polu Stratum wysyłają serwery pierwotne (pierwszej warstwy, najczęściej synchronizowane własnym zegarem atomowym. Wartości od 2 do 15 wysyłają serwery wtórne, synchronizowane za pomocą NTP do serwerów, które należą do warstwy o jeden mniejszej. Wartości od 16 do 255 są zarezerwowane do przyszłego wykorzystania. Pole Poll zaw iera inform ację o m aksym alnym interw ale między kolejnymi kom unikatami serwera, gdy działa on w trybie okresow ego rozgłaszania. Pole Precision zawiera inform ację o dokładności zegara, którego używ a serwer. Czasy te określa się jako 2X sekund, gdzie x jest w artością um ieszczoną w danym polu, interpretowaną jako ośm iobitow a liczba ze znakiem w kodzie uzupełnieniow ym do dwójki. K lient zaw sze zeruje wszystkie bity w tych polach i może ignorować wartości otrzym ane od serwera, jeśli ich nie potrzebuje. Pole Root Delay zaw iera różnicę czasu w sekundach między tym serwerem a serwerem pierwotnym, z którym się on synchronizuje, wynikającą m.in. z opóźnień przesyłania kom unikatów w sieci. W artość w tym polu interpretuje się jako liczbę ułam kową slalopozycyjną ze znakiem w kodzie uzupełnieniow ym do dwójki, gdzie przecinek jest postawiony między 15. a 16. bitem. W artość w tym polu może być ujem na. Pole to jest istotne tylko w kom unikatach wysyłanych przez serwer. Klient ustawia w nim zawsze zero. Pole Root Dispersion zaw iera m aksym alny błąd czasu w sekundach, wynikający z tolerancji częstotliw ości zastosow anego w serwerze zegara. Wartość w tym polu interpretuje się jako liczbę ułam kową bez znaku, gdzie przecinek jest postawiony między 15. a 16. bitem. Pole to jest istotne tylko w kom unikatach wysyłanych przez serwer. K lient ustawia w nim zaw sze zero. W polu Reference Identifier serw er pierwotny um ieszcza swój identyfikator, zaw ierający m aksym alnie cztery znaki A SCII i dopełniony zerowymi oktetami. Serwery warstw 2. do 15. um ieszczają w tym polu adres IP serwera, do którego się synchronizują. Pole to jest istotne tylko w kom unikatach w ysyłanych przez serwer. Klient ustawia w nim zawsze zero. Kolejne cztery pola zawierają znaczniki czasu. Reference Tim estam p informuje, kiedy zegar serwera byl po raz ostatni synchronizowany. Klient um ieszcza w tym polu zero. W polu Originate Tim estam p klient um ieszcza zero, a serwer kopiuje do niego zawartość pola Transm it Tim estam p z żądania klienta. W polu Receive Tim estam p klient um ieszcza zero, a serw er um ieszcza w nim czas odebrania żądania od klienta według swojego zegara. W polu Transm it Tim estam p klient i serwer um ieszczają czas wysiania kom unikatu, posługując się swoim lokalnym zegarem. WaiTOŚci ttf pozw alają oszacow ać czas podróży komunikatów, co um ożliwia dokładniejszą Synchronizację. O znaczm y O riginate Tim estam p, Receive Tim estam p
232 & Klient Upp 8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego 233 Żądanie \ / O dpowiedź R ys. 8.2. Wymiana komunikatów NTP między klientem a serwerem - Czas klienta - Czas serwera i Transm it Tim estam p z odebranej od serwera odpow iedzi odpow iednio przez 'I I T2 i T3. Przez T4 oznaczm y czas odebrania odpow iedzi przez klienta według jego lokalnego zegara, patrz ry su n ek 8.2. W tedy łączny czas podróży komunikatów w obie strony szacuje się jako T4 - T l - T3 + T2, a korektę czasu klienta, czyli różnicę m iędzy czasem serw era a klienta, jak o (T2 + T3 - T l - T4 / 2. Pola Key Identifier i M essage D igest są opcjonalne i nie są używ ane w uproś/,r czonej wersji protokołu, czyli w SNTP. Podsum owując, w najprostszym przypadki 1 klient w prośbie ustawia w polu VN wartość 4, w polu M ode wartość 3, a w po/o stałych polach wartość 0. M oże też ustawić swój czas w polu Transm it Timestamp W odpow iedzi uzyskanej od serw era klient sprawdza popraw ność pól LI, VN M ode i Stratum. Do korekty sw ojego czasu korzysta z wartości w polu Transmi Tim estam p, ew entualnie też z czasów O riginate Tim estam p i R eceive Timestamp a zaw artość pozostałych pól ignoruje. Klient powinien też sprawdzać, czy otrzyma-! ne znaczniki czasow e-są praw idłowe, czyli niezerowe. 8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego Nazwy plików przykładu 8 zam ieszczone są w tabeli 8.1. W iększość z nich opis; lem ju ż w poprzednich rozdziałach. Poniżej opisuję tylko nowe pliki. 8.3.1. Pliki sntp_clienł.h i sntpjclient.c Pliki sntp_client.h i sntpjclient.c zaw ierają im plem entację klienta protokołu SN'I P Zaczynam y od zdefiniow ania stałych i struktur tego protokołu. Pow inniśm y zdf niować stałą NTP_P0RT reprezentującą num er portu, na którym nasłuchują serwery W N TP czas odm ierza się w sekundach od 1 stycznia 1900 roku, a nasz zegar czast rzeczyw istego liczy sekundy od 1 stycznia 1970-ruku. Do przeliczania deiiniujemj stalą NTP_T0_UNIX, której w artością jest liczba sekund między tym i datami. łdefine N T P J O R T 123 łdefine NTP_TOJJNIX 2208988800U Znacznik czasu reprezentujem y jako strukturę typu ntp_ tim estam p. PACK_STRUCT_BEGIN struct ntp_timestamp (. PACK_STRUCT_FIELD(uint32_t Secondsl; PACK STRUCTJ?IELD(uint32_Ł SecondsFraction/; i PACiTsTRUCTJBTRUCT;., PACK_STRUCT END O bow iązkow ą część kom unikatu NTP, zam ieszczoną na rysunku 8.1, reprezenti my jako strukturę typu n tp jn sg. Z tych sam ych powodów co w poprzednim r<>/-. Tab. 8.1. Pliki przykładu 8 ex_sntp.c iont5x8.c sntp_ciient.c startupjtm 3 2 _ c ld. c font5x8.h sntp_client.h Źródłowe i biblioteczne board_con!.c boardjn it.c boardj c d J s 0 108. c boardje d.c board_cont.li board_del.lt board_dels.h board_init.il board_lcd.li b oardjed.h Natjlówk owe util_delay.c u til_ e llijic.c u iiijc d.c utiljcd_ex.c util Je d.c u tiljw ip.c u tiijtc.c u tiljim e.c util_delay.h util_error.h utit_eth.h u tiljc d.h u tiijc d _ e x.il u tfje d.h utiljw ip.h util_rtc.li util tinie.h Iiblwip4.a Iibstm32tl0x.a cc.h cortex-m3.h lwipopts.li stm32t10x_cont.h dziale, czyli nieprzenośności pól bitowych języka C, trzy pierwsze pola komunikatu definiujem y jako wspólną ośm iobitow ą składową LI_VN_Mode. PACK_STRUCT_BEGIN struct ntpjnsg { PACK_STRUCT FIELD(uint;'8_t LI_VN Mode; PACK_STRUCT_FIELD(uint8_t Stratum; PACK_STRUCT_FIELD(uint8_t Poll; PACK_STRUCT_FIELD(int8_t Precision; PACK_STRUCT_FIELD(uint32_t RoofcDeiayi; PACK_STRUCT_FIELD(uint32_t RootDispersion; PACK_STRUCT_FIELD(uint8_t Referenceldentifier[4}; PACK_STRUCT_FIELD(struct ntp_timestamp ReferenceTimestamp; PACK_STRUCT_FIELD(struct ntp_timestamp OriginateTimestamp; PACK_STRUCT_FIELD(struct ntp_timestamp ReceiveTimestamp; PACK_STRUCT_FIELD(struct ntp_timestamp TransmitTimestamp; l PACK_STRUCT_STRUCT; PACK_STRUCT_END Wartości, które pojawiają się w polach wysyłanych i odbieranych komunikatów, też trzeba zdefiniow ać jako stałe. łdefine NTP_LI_NO_WARNING 0 idefine NTP_LI_ĄLARM_CONDITION 3 łdefine NTP_VERSI0N 4 łdefine NTP_MODE_CLIENT 3 łdefine NTP_HODE_SERVER 4 łdefine NTP_STRATUM_KISS_OF_DEATH 0 łdefine NTP STRATUM MAX 15 Pola LI, VN, M ode komunikatu zdefiniow aliśm y jako składową LI_VN_Mode struktury typu n tp jn sg. Do obsługi tych pól służą poniższe makra. M akro n tp _ le a p _ in - d ic a to r daje wartość pola LI. M akro n tp _ v e rsio n daje wartość pola VN. Makro ntp_raode daje wartość pola M ode. A rgum entem tych m akr jest wskaźnik pdu do struktury typu n tp jn sg. M akro ntp_com pose_li_vnjnode pom aga wypełnić skladow if Li Mm Mode tej struktury. Argumentami są odpow iednio wartości pól LI, VN i M ode.. i,li'
234 8. Klient UDp 8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego 235 fdefine ntp_leap_indicator(pdu ((uint8_t((pdu->li_vn_mode >> 6 Sdefine ntp_version(pdu {(uint8_t{((pdu~>li_vn_mode» 3 & 0x7 idefine ntp_mode (pdu ((uint8_t((pdu->li_vn_mode & 0x7 fdefine ntp_compose_li_vn_mode(li, vn, mode \ ({(li «6} ({(vn «3 & 0x38 {(mode & 0x7} Klienta urucham ia się za pom ocą funkcji S N T P clien ts tart. Jej pierwszy argument servername jest nazwą serwera, do którego ma być wysiane żądanie SNTP. Funkcja ta jest przeznaczona do w ywołania z program u głów nego i tylko inicjuje klienta, który następnie działa w przerwaniu biblioteki lwip, czyli w tle w stosunku do programu głównego. D latego funkcja S N T P clien ts tart nie zw raca żadnej wartości, a drugi jej argum ent s t a tu s jest wskaźnikiem do zmiennej typu in t, w której klient um ieszcza status, czyli inform ację o wyniku sw ojego działania. Wartości, które może przyjm ować status, są opisane w tabeli 8.2. O dpowiednie stale definiujemy za pom ocą dyrektyw id e f in e w pliku sntp_client.h. W ywołania funkcji biblioteki lwip wewnątrz funkcji S N T P c lie n ts ta rt są otoczone makrami wykluczającymi współbieżne w ykonywanie kodu tej biblioteki. void SNTPclientStart(const char servername, int status { struct ip_addr serverip; err_t err; IRQ_DECL_PROTECT(x ; IRQ PROTECT {x, LWIP_IRQ_PRIO; err = dns_gethostbyname(servername, SserverlP, SNTPrequest, (void*status; IRQJJNPROTECT(x;, if (err == ERR_OK { IRQ_PROTECT(x, LWIP_IRQ_PRIO ; SNTPrequest(serverName, &serverip, (void*status; IRQJJNPROTECT(x ; status = SNTP_IN_PROGRESS; else if (err =* ERR_INPROGRESS status = SNTP_IN_PROGRESS; else status = SNTP DNS ERROR; Tab. 8.2. Status klienta SNTP Slata SNTP NOT RUNNING W artość SNTP_SYNC!1R0NIZED 1 2 ' " U p ls Klient nie został jeszcze uruchbrtllóny Klient uzyskał czas od serwera, zsynchronizował zegar czasu rzeczywistego i zakończył działanie SNTP IN PROGRESS 0 Klient wysłał żądanie do serwera DNS lub SNTP i oczekuje na odpowiedź SNTP_DNS_ERROR - 1 Klient zakończył działanie, gdyż nie udało się przetłumaczyć nazwy serwera na adres IP SNTP MEMORY ERROR - 2 Klient zakończył działanie z powodu błędu alokacji pamięci SNTP UDP ERROR - '3 Klient zakończył dzialańie, gdyż nie udało się wysłać żądania do serwera SNTP_NO_RESPONSE SNTP_RESPONSE_ERROR - 5-4 ' Serwer SNTP nie odpowiedział w przewidzianym czasie ł klient zakończył działanie Serwer SNTP odpowiedział, ale odpowiedź jest błędna i nie można na jej podstawie zsynchronizować zegara. Klient zakończył działanie Funkcja S N T P clien ts tart zaczyna od przetłum aczenia nazwy serw era na adres IP. Jeśli funkcja dns_gethostbynam e zwróci wartość ERR_0K, to struktura se rv e rip zawiera adres IP i w ywołana jest funkcja SNTPrequest, której zadaniem jest wysianie żądania SNTP. Jeśli funkcja dns_gethostbynam e zw róci wartość ERR_INPROGRESS, co oznacza, że żądanie DNS zostało wysłane, to po otrzym aniu odpowiedzi zostanie w ywołana funkcja SNTPrequest, której adres jest trzecim argum entem w yw o łania funkcji dns gethostbynarae. Czwarty argum ent s t a tu s funkcji dns_gethostbyname jest w skaźnikiem, który ma być przekazany jako trzeci argum ent funkcji SNTPrequest. Przed zakończeniem funkcja S N T P clien ts tart ustawia status klienta w zmiennej wskazywanej przez w skaźnik s ta tu s. Opisyw ana im plem entacja um ożliw ia urucham ianie wiele (sic! klientów rów nocześnie. D la każdego klienta alokujem y strukturę typu sn tp c li e n t. Struktury klientów tworzą listę. Na pierwszy elem ent listy w skazuje zm ienna globalna f i r s t C l i e n t. Składow a n ex t w skazuje na następny elem ent na liście. Składowa pcb jest w skaźnikiem do deskryptora UDP, używ anego do wysłania żądania i odebrania odpowiedzi. Składow a tim e r jest licznikiem odm ierzającym czas, przez który klient będzie jeszcze czekał na odpow iedź serwera. Gdy wartość tej składowej zmniejszy się do zera, klient zakończy działanie, a jego dane zostaną usunięte z pamięci. Składow a s ta tu s w skazuje na zmienną, w której ma być um ieszczony status klienta. struct sntp_client { struct sntp client next; struct udp_pcb *pcb; int timer; int status; }; static struct sntp_client firstclient = NULL; Po otrzymaniu odpowiedzi od serwera DNS w celu w ysiania żądania do serwera SN TP w ywołujem y funkcję SNTPrequest. Jej pierwszy argum ent wskazuje nazwę serw era SN TP i nie jest używany. A rgum ent ten jest potrzebny, gdyż adres tej funkcji jest przekazyw any do funkcji dns_gethostbynam e jako adres funkcji zwrotnej, co w ym aga zgodności sygnatur funkcji. Drugi argum ent se rv e rip jest w skaźnikiem do struktury zawierającej adres IP serwera. Jeśli w skaźnik ten ma wartość NULL, to wystąpił jakiś błąd podczas kom unikacji z serwerem DNS. Przyczyną może być podanie błędnej nazwy serwera SNTP lub aw aria sieci. Trzeci argum ent s t a tu s jest wskaźnikiem do zmiennej, w której klient ma umieścić swój status. static void SNTPrequest(const char servername, struct ip_addr serverip, void* status { struct sntp client client; if (NULL serverip { * (int*status = SNTP_DNS_ERROR; return; client = mem_malloc(sizeof(struct sntp_client; if (client == NULL f * (int*status - SNTPJ4EMORY_ERROR; return; Ulr j j ciient->pctffi NULL;
8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego 237 client->timer = SNTPJfIMEOUT; client->status = (inł*status i client->next * firstclient; firstclient = client; if (NULL!= (client->pcb = udp_new( & ERR OK == udp bind (client->pcb, IP_ADDR_ANY, 0} fi ERR_OK == udp_connect(client->pcb, serverip, NTP^PORT 1 struct pbuf *p; p = pbuf alloc(pbufjtransport, sizeof{struct ntpjnsg, PBUF_RAM; if tp r struct ntpjnsg *msg = p->payload; memset(msg, 0, sizeof(struct ntpjnsg; msg->u_vn_mode = ntp_compose_li vn inode(ntp_u_no_warning, NTP_VERSION, NTP_MODE CLIENT; udp_recv(client->pcb, SNTPrepiy, client; if (ERR_OK!= udp send(client->pcb, p> { * (intmstatus = SNTP_UDP_ERR0R; client->timer = 0; i pbuf_free(p; } else { * (int*status = SNTP_MEMORY_ERROR; client->timer = 0; i else { * (intmstatus = SNTp_UDP_ERROR; client->timer = 0; } I W funkcji SNTPreguest próbujem y przydzielić klientowi now ą strukturę typu sntp_client i jeśli alokacja się pow iodła, inicjujemy składowe tej struktury ora/ wstaw iam y ją na początek listy klientów. N astępnie próbujem y uzyskać deskryptoi UDP za pom ocą funkcji udp new. Jeśli się powiodło, próbujem y za pom ocą funkcji udp_bind uzyskać port, który posłuży do w ysiania żądania i na którym będziemy oczekiwać odpowiedzi od serwera. Jeśli dostaliśm y port, w ywołujem y funkcję udp_ connect, aby przypisać do deskryptora U DP adres i num er portu serwera. Dzięki temu odebrana zostanie tylko odpow iedź od tego serwera, do którego wysialiśmy żądanie, a wszelkie inne datagram y U D P będą ignóitrrane. Jeśli pow yższe się udało, próbujem y przydzielić za pom ocą funkcji pbu'f J l l o c bufor, w którym zostanie um ieszczone żądanie SNTP. Po przydzieleniu bufora wypełniam y pola żądania; SNTP. Następnie ustawiam y funkcję zw rotną udp_recv, która zostanie wywołana1 po odebraniu odpowiedzi od serwera. Funkcja zw rotna jako argum ent dostanie; wskaźnik client do struktury typu sntp_client opisującej klienta. W reszcie cale: żądanie wysyłam y za pom ocą funkcji udp_send. Na koniec, po wysłaniu żądania zw alniam y bufor z niepotrzebnym ju ż kom unikatem. Jeśli w powyższej procedurze napotkaliśm y jakiś problem, ustawiam y*odpowiedni status klienta i zerujemy składową timer struktury client, aby przyspieszyć usunięcie klienta z pamięci, o czym dalej. Przed usunięciem klienta zostanie też zw olniony przydzielony mu deskryptor UDP. Po otrzymaniu odpowiedzi od serwera SNTP biblioteka wywołuje funkcję zw rotną SNTPrepiy. Pierwszym argum entem tej funkcji jest wskaźnik do struktury typu sntp_client. Drugi argum ent pcb jest deskryptorem UDP. Trzeci argument p jest wskaźnikiem do bufora, który zaw iera odpow iedź serwera. Czwarty argument serverip jest w skaźnikiem do struktury zaw ierającej adres IP serwera. Piąty argument serverport jest num erem portu, którego serw er użył do wysłania odpowiedzi. W dalszym ciągu potrzebne nam są tylko wskaźniki arg i p. Po zweryfikowaniu popraw ności odebranego komunikatu uaktualniam y licznik zegara czasu rzeczywistego za pom ocą funkcji SetRealTimeClock. Zwróćm y uwagę na odwracanie kolejności bajtów za pom ocą funkcji ntohl i konwersję czasu za pomocą stałej NTP_TO_UNIX. Na koniec w funkcji SNTPrepiy ustawiamy odpowiedni status klienta, zw alniam y bufor zawierający odebrany kom unikat i zerujemy składową timer struktury client, aby natychm iast usunąć niepotrzebnego ju ż klienta. static void SNTPrepiy(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *serverip, ul6_t serverport ( struct sntp_client *client = arg; struct ntpjnsg *msg; msg = p->payload; if (p->len >= sizeof(struct ntpjnsg ntp_leap.indicator (msg!~ NTP LI_ALARM_CONDITION ntp_version(msg == NTPJ/ERSION ntpjnode(msg =*= Ni;P_MODE_SERVER msg->stratum!= NTP_STRATUM_KISS_0F_DEAT1I msg->stratum <= NTP_STRATUM_MAX (msg->transmittimestamp.seconds!= 0 j msg->transmit?imestamp.secondsfraction!= 0 { SetRealTimeCiock{ntohl(msg->TransmitTimestarap.Seconds - NTP_TO_UNIX; Mciient->status = SNTP SYNCHRONIZED; } else * (client->status = SNTPJiESPONSEJSRROR; pbuf_free(p; client->timer = 0; i Do obsługi przekroczenia czasu oczekiwania na odpow iedź serwera im plem entujemy budzik. Przyjmijmy, że będzie on wołany co 200 milisekund oraz że klient będzie czekał na odpow iedź maksym alnie 4 sekundy. Zapew ne wystarczyłoby czekać krócej, bo zwykle serwery N TP odpow iadają szybko, a nie ma sensu synchronizować się do serwera, na którego odpow iedź trzeba 'długo czekać, ale - aby ułatwić testow anie - dobrze jest jednak ustawić odpow iednio długi czas oczekiwania. Okres budzika i liczbę okresów, które klient ma czekać na odpow iedź od serwera, definiujem y odpow iednio jako stale SNTP_TIMER_MS i SNTP_TIMEOUT. łdefine SNTP_TIMER_MS 200 Idefine SNTPJTIMEOUT 20 Zdarzenie budzika obsługiw ane jest przez funkcję SNTPtimer, w której przeglądamy liptę klientów. Z listy usuwamy te klienty, które działają już zbyt długo. Jeśli ldieimiwit tiy.ydzielono deskryptor UDP, to zw alniam y ten deskryptor przed usunięciem z pamjięci danych klienta.
238 8. Klient Ul/* 8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego 239 8.3.2. void SNTPtimer(void { struct sntp_client *curr, *prev, *next; curr = firstciient; prev = NULL; while (curr { if ( (curr->timer 0 { if (curr->pcb udp_remove(curr->pcb; if (prev next = prev->next = curr->next; else next = firstciient = curr->next; if {*(curr~>status == SNTP_IN_PROGRESS * (curr->status - SNTP_NO_RESPONSE; mem_free(curr; curr = next; else { prev Plik ex_sntp.c curr; curr->next; Plik ex_siitp.c zawiera im plem entację testów klienta SNTP. Najpierw deklarujemy ' tablicę serv e rt b ł wskaźników do nazw serwerów. W śród tych nazw jest serwer nieoferujący usługi SNTP. Jego nazw a w ystępuje dw ukrotnie, aby przetestować pamięć lokalną DNS. Żeby przetestow ać DNS, na drugiej pozycji jest nazwa, która nie istnieje w Internecie. Dwie ostatnie pozycje w tablicy w skazują na nazwy oficjalnych serwerów czasu, synchronizow anych zegarem atom owym i zainstalowanych w Głównym Urzędzie Miar. Dla każdego serw era urucham iam y osobnego klienta. Statusy klientów przechow ujem y w tablicy s ta tu s. ffdefine COUNT 5 static const char * const servertbl(count] = j "wikipedia.org", "nie.ma.takiego.serwera", "wikipedia.org", "tempusł.gum.gov.pl", "tempus2.gum.gov.pi" -,**. 1; static int status[count] = ( SNTP_NOT_RUNNING, SNTP NOT_RUNNING, SNTP_NOT_RUNNING, SNTP_NOT_RUNNING, SNTP_NOT_RUNNING ; W przerwaniu zegara czasu rzeczyw istego, /zgłaszanym co sekundę, wywołujemy funkcję zw rotną C lockc allback, której zadhniem jest wyświetlenie na LCD aktualnego czasu oraz informacji o działaniu poszczególnych klientów. Na przemian przez dw ie sekundy wyświetlam y nazw y serwerów, a przez kolejne dw ie sekundy - statusy klientów, które uruchomiliśmy. static void ClockCallback(void { static int f = 0; int i; LCDgoto(0, 1; LCDwriteTime(GetRealTimeCiock(; for (i = 0; i < COUNT; ++i i LCDgoto(2 + i, 0; if (f == 0 ( LCDwrite(serverTbl(i]; LCDwritet" "; else if (f == 2 j f 3 f switch (status(i} { case SNTP_N0T_RUNNING: LCDwrite("SNTP not running "; break; case SNTP S YNCHR0NI ZED: LCDwrite("Time synchronized break; case SNTP_IN_PR0GRESS: LCDwrite("SNTP in progress "; break; case SNTP_DNSJERR0R: LCDwrite("DNS error "; break; case SNTP_MEM0RY_ERR0R: LCDwrite("Memory error "; break; case SNTP_UDP_ERR0R: LCDwrite("UDP error "; break; case SNTPJ10_RESP0NSE: LCDwrite("SNTP no response "; break; case SNTP_RESP0NSE_ERR0R: LCDwrite("SNTP response error "; break; } i } f = (f + 1 & 3; } W funkcji main inicjujemy poszczególne układy m ikrokontrolera, ustawiamy funkcję zw rotną C lockc allback w ołaną w przerwaniu zegara czasu rzeczywistego, konfigurujem y ustaw ienia sieciow e, ustawiam y budzik SNTPtimer dla klientów SNTP, a na koniec urucham iam y po kolei, w niew ielkich odstępach czasu, klienty SNTP za pom ocą funkcji S N T P clien ts tart. int main( { static struct netif netif; static struct ethnetif ethnetif = (PHY^ADDRESS}; int i; uint8_t confbit; Dehy (1%Ł&00; confbit.= btconfbit(;
240 8. Klient UDP 8.3. Przykład 8 - automatyczna synchronizacja zegara czasu rzeczywistego 241 AllPinsDisable(; LEDconfigure(; RedLEDonO ; LCDconfigure (}; SET_IRQ_PROTECTION(}; error_resetable(clkconfigure{, 1 ; error_permanent (Locall imeconfigure (, 2; error_resetable(rtcconfigure(, 3; RTCsetCaliback(CiockCallback; error_resetable(ethconfiguremii(, 4; netif.hwaddr[0] = 2; netif.hwaddr[l] = (BOARDJl'YPE» 8 & Oxff; netif.hwaddr[2} = BOARDJTYPE & Oxff; netif.hwaddr[3j = (ETH_BOARD» 8 & Oxff; netif.hwaddr[41 = ETH_BOARD & Oxff; netif.hwaddr[5 = 1 + confbit; if ( confbit { IP4_ADDR(6netif.ip_addr, 192, 168, 51, 84; IP4_ADDR(&netif.netmask, 255, 255, 255, 240; IP4_ADDR(&netif.gw, 192, 168, 51, 81; 1 else { IP4_ADDR(&netif.ip_addr, 0, 0, 0, 0; IP4_ADDR( netif.netmask, 0, Ö, 0, 0; IP4_ADDR(finetif.gw, 0, 0, 0, 0; I error_resetable(lwipinterfacelnit(&netif, Sethnetif, 5; LWIPtimersStart(}; LWIPsetAppTimer(SNTPtimer, SNTP_TIMER_MS; error_resetable(dhcpwait{&netif, 10, 4, 6; RedLEDoff (; for (i = 0; i < COUNT; ++i { Delay (10000000; SNTPclientStart(serverTbl(i, &status[i }; I for {;;; 8.3.3. Testowanie przykładu Po uruchom ieniu programu w zestaw ie ZL29A RM w pierwszym wierszu LCD powinny pojawić się data i czas. Na początku jest to godzina 00:00:00 dnia 1 stycznia 1970 roku. Po zsynchronizow aniu zegara powinny pojawić się aktualna data i aktualny czas UTC. Czas ten, zależnie od pory roku, spóźnia się w stosunku do czasu polskiego o jedną lub dw ie godziny. W w ierszach od trzeciego do siódmego wyświetlane są na przem ian nazwy serw erów i statusy klientów. Oprócz serwerów, których nazwy są um ieszczone w pliku źródłow ym ex_sntp. c, można w ypróbować inne serwery NTP.j N astępujące serwery są wymienione w W ikipedii: - tem puslgum.gov.pl, 212.244.36.227, cezowy zegar atomowy, Główny Urząd Miar, oficjalny serw er dostarczający czas urzędowy w Polsce; - tempus2.gum.gov.pl, 212.244.36.228, cezowy zegar atomowy, Główny Urząd Miar, oficjalny serw er dostarczający czas urzędowy w Polsce; - vega.cbk.poznan.pl, 150.254.183.15, cezowy zegar atomowy, Polska Akademia N auk, Centrum Badań Kosmicznych, Obserw atorium Astrogeodynamiczne w Borowcu k. Poznania; - ntp.itlwaw.pl, 193.110.137.171, cezowy zegar atomowy, Instytut Łączności w W arszawie; - ntp.elproma.cotn.pl, 83.19.137.3, rtibidowy zegar atomowy, laboratorium porów nujące wzorce czasu, Łom ianki k. W arszawy; - Lpl.pool.ntp.org, pula serwerów rozm ieszczonych na terenie Polski; - Leurope.pool.ntp.org, pula serwerów rozm ieszczonych w Europie; - Lpool.ntp.org, ogólnoświatow a pula serwerów. Do testow ania sam ego protokołu SNTP, w archiwum z przykładami w katalogu./m ake/ex8_udp_clnt znajduje się plik linux_sntp_clnt.c, który zawiera im plem entację prostego klienta SNTP dla system u Linux. Program ten wyświetla szczegółowe inform acje zaw arte w odpow iedzi otrzymanej od serwera. U ruchamia się go, podając jako argum ent nazwę serw era SNTP. K lient ten nie obsługuje przekroczenia czasu - jeśli nie dostaniem y odpowiedzi, trzeba usunąć program za pom ocą kom binacji klawiszy Ctrl-C. Uwagi końcowe Niewłaściwie używane i skonfigurowane klienty SNTP mogą niepotrzebnie zwiększyć ruch w sieci i bardzo obciążyć serwery czasu. Należy bezwzględnie stosować się do dobrych praktyk (ang. best practlces podanych w RFC 4330, a w szczególności: - Nie w olno wysyłać żądań do serwera częściej niż co 15 sekund. - Klient powinien zw iększać w ykładniczo odstęp czasu między kolejnymi żądaniami, jeśli serwer nie odpow iada w rozsądnym czasie. - Należy używ ać serwerów, które są w sieci położone możliwie najbliżej klienta. - Jeśli nazwa lub adres IP serwera SN TP są zapisane w pamięci stałej urządzenia, które jest w ytwarzane w celach kom ercyjnych, to musi to być serw er zarządzany przez producenta lub sprzedawcę tego urządzenia. - Jeśli nie jest w ym agana dokładność oferowana przez zegar atomowy, nie należy korzystać z serwerów pierwotnych, a raczej z puli serwerów, które są synchronizow ane do serwerów pierwotnych. Przedstaw iona wyżej im plem entacja jest bardzo uproszczona. N ie uwzględnia opóźnień w transmisji pakietów sieciowych. Nie obsługuje stref czasowych ani zmian czasu na letni i zimowy. Form uły um ożliw iające oszacow anie czasu podróży pakietów i w yznaczenie korekty czasu klienta zam ieściłem w podrozdziale opisującym SNTP. Strefy czasow e oraz czas letni i zim owy są w spierane przez standardową bibliotekę języka C i dość łatwo m ożna dodać ich obsługę. Jeśli potrzebujem y precyzyjnie określić czas zdarzenia w przeszłości lub różnicę czasu między zdarzęniami, trzeba uwzględnić sekundy przestępne, w stawiane od czasu do czasu dla tskoryąjjo>vania zegara z położeniem Ziem i względem Słońca. Ponadto, mając jeszcze świężo w pamięci zam ieszanie z problemem roku 2000, należy zastano-
242 8. Klient U DI' wić się nad problem em roku 2036, kiedy to 7 lutego o godzinie 6:28:16 1 ( ' przepełni się znacznik czasu NTP. Zam ieszczona tu im plem entacja wydaje się navw' ten problem odporna. K olejnego problem u możem y spodziewać się w roku 2038, gdy czas uniksowy w system ach 32-bitow ych stanie się ujemny. Typ time t zwy- n- ; kle definiuje się jako liczbę ze znakiem i po doliczeniu do maksymalnej wartości 231-1 następuje warlos'ć ujem na - 2 31, czyli dnia 19 stycznia 2038 roku po godzinie!'!$ i 3:14:07 nastąpi godzina 20:45:52 dnia 13 grudnia 1901 roku. Tak włas'nie zadziała i biblioteka Newlib, użyta do napisania przykładów prezentowanych w tej książce, v Choć zapewne do tego czasu w szystkie kom putery będą ju ż reprezentow ać czas w arytm etyce co najm niej 64-bitow ej i ich ten problem nie dotknie, to wciąż mogą jeszcze pozostawać w użyciu 32-bitow e mikrokontrolery. Pow yższe uwagi należy uw zględnić, jeśli w ymagamy bardzo dokładnego odm ierzania czasu. Odpowiednie modyfikacje programu pozostawiani Czytelnikow i jako am bitne ćwiczenie. Rozgłaszanie
244 9. Rozgłaszanie UDp 9.1. Przykład 9a - roz.glasz.anie datagramów UDP 245 U DP um ożliw ia rozgłoszenie (ang. broadcast datagramu, czyli wysianie go je d -' noczcsnie do wszystkich odbiorców w danej podsieci. W tym rozdziale prezentuję1 dw a przykładowe programy. Pierwszy rozgłasza datagram y U DP i w zasadzie jest. prostym klientem UDP. Drugi jest prostym serwerem U D P i jest przeznaczony do testowania tego pierw szego - w yświetla na LCD w szystkie odebrane datagramy' UDP. Zasady działania klienta i serw era U DP w yjaśniłem ju ż w poprzednich 10/. działach, więc bez zbędnego wstępu teoretycznego przystępuję od razu do opisuprzykładów. 9.1. Przykład 9a - rozgłaszanie datagramów UDP N azwy plików przykładu 9a zam ieszczone są w tabeli 9.1. W śród nich jest tylko jeden nowy plik, który opisuję w następnym podrozdziale. Tab. 9.1. Pliki przykładu 9a ex_send_bcast.c tonl5x8.c slarlup_stm32_cld. c iont5x8.h 9.1.1. Plik ex_send_bcast.c Źródłowe i biblioteczne board_conl.c boardjn it c boardjcd_ks0108.c board Jed. c board_conl.il board_del.li board_defs.li boardjnit.h b o a rd jcd.h b oardjed.h Nagłówkowe u tiljle la y.c util_eth_nc.c util Jed. c u lilje d.c u liljw ip.c util lime.c util_delay.h util_error.h u iiijih. b u liljc d.li u tilje d.li u tiijw ip.il U iiljtc.il u til lim e.li Iiblwip4.a Iibstm32l10x.a CC.Il cortex-m3.li lwipopts.li slm32f10x_conf.l Plik ex_send_bcast.c zaw iera im plem entację prostego klienta UDP, rozsyłającego cyklicznie datagram y U DP do w szystkich węzłów w swojej podsieci. W opisanych dotychczas klientach pozostawialiśm y bibliotece lwj.p w ybór portu źródłowego i w tym celu podawaliśmy zero jako num er portu w.jrzecim argum encie wywołania funkcji udp_bind. W odróżnieniu od tam tych, w tym kliencie używam y ustalonego portu źródłowego, którego num er definiujem y jako stalą SRC_PORT. N um er portu, na który będą wysyłane datagram y UDP, definiujem y jako stalą DST_PORT. Stała. BCAST_TIMER_MS określa okres pow tarzania transmisji w m ilisekundach. Stała BUF_ SI ZE definiuje rozm iar pom ocniczego bufora. łdefine SRC_PORT 5001 łdefine DST_P0RT 5002 ł łdefine BCAST_TIMER_MS 2666 łdefine BUF ŚI2E 64 R ozgłoszenie datagram u realizuje funkcja SendBroadcast. W ysyła ona pojedynczy datagram na przem ian na adres ukierunkow anego rozgłaszania i adres rozgłaszania w bieżącej podsieci, czyli 255.255.255.255. Podsieć testow a ma przydzieloną pulę adresów 192.168.51.80/28, a zatem adresem ukierunkow anego rozgłaszania w niej jest 192.168.51.95. Funkcje z biblioteki lw lp przechow ują adres IP w strukturze typu ip_addr, a funkcje z biblioteki standardow ej C w strukturze typu in_addr. W ysyłany datagram U DP zaw iera krótki tekst ASCII. Tekst ten zaw iera num er kolejny datagram u, potem adres 1P rozgłaszania i num er portu, na który datagram jest wysyłany. Tekst kończy się znakiem końca w iersza, czyli sekw encją znaków CR i LF. W ysyłany datagram U D P w yświetlam y też na LCD - każdy datagram w osobnym wierszu. U żyty LCD może wyświetlić tylko 8 wierszy tekstu, więc po w yśw ietleniu każdych kolejnych 8 datagram ów czyścim y ekran LCD. Należy pam iętać, że funkcja snprintf i funkcje obsługujące w yśw ietlacz ciekłokrystaliczny nie są w spółużyw alne, co spraw ia, że rów nież funkcja SendBroadcast, w której są one wołane, nie jest w spółużyw alna. W tym przykładzie w ykonanie funkcji SendBroadcast nie zostanie jednak nigdy w yw łaszczone przez przerw anie, w którym m ogłaby zostać ponow nie wywołana, więc nie ma problem u. static void SendBroadcast(void ( static unsigned count = 0; struct udp_pcb *pcb; struct pbuf *p; 1 struct ip_addr broadcast_ip; struct in_addr ip; int size; char buffbuf_sizkj; count++; if (count & 1 IP4_ADDR(&broadcast_ip, 192, 168, 51, 95; else IP4_ADDR(&broadcast_ip, 255, 255, 255, 255; ip.s_addr = broadcast_ip.addr; size - snprintf (buf, BUF_SIZE, "%u %s:%u\r\n", count, inet_ntoa(ip, DST_PORT; if (size <= 0 J! size >= BUFJJIZE return; pcb = udp_new(; * * if (pcb == NULL return; if (ERR_0 K!= udp_bind (pcb, IP_ADDR_ANY, SRC_P0RT j NULL == (p» pbuf_alloc(pbuf_transport, size, PBUF_RAM} { udp_remove(pcb;
\ - * ; 'Ili-'-,Vi i':-'v 246 9. Rozgłaszanie UIP 9.2. Przykład 9b - odbieranie datagramów U DP 247 udp_sendto(pcb, p, &broadcast_ip, DST_PORT; pbuf_free(p; udp_remove(pcb; if {(count & 7> = 1> LCDclear (; LCDwrite(buf; Funkcja raain nie różni się istotnie od analogicznych funkcji z poprzednich przykładów. Zasadnicza różnica polega na tym, że przed wejściem w nieskończoną pętlę ustawiamy funkcję S endb roadcast jako budzik wołany okresow o co BCAST_TIMER_ MS milisekund. int main ( 1 static struct netif netif; static struct ethnetif ethnetif = (PHY_ADDRESS; uint8_t confbit; Delay(1000000; confbit = GetConfBit(; AIlPinsDisabłeO; LEDconfigure(; RedLEDon(; LCDconfigure(; SET_IRQ_PROTECTION(; error_resetable(clkconfigure(, 1; error_permanent(localtimeconfigure(, 2; errorjresetable(ethconfiguremii{, 4; netif.hwaddr[q - 2; netif.hwaddr[l *= (BOARDJFYPE» 8 & 0xff; netif.hwaddr(2 = BOARD TYPE fi 0xff; netif.hwaddr{3} = (ETH_B0ARD» 8 & 0xff; netif.hwaddr{41 = ETH_BOARD & Oxff; netif.hwaddr(5] = 1 + confbit; if {!confbit ( IP4_ADDRfŁnetif.ip_addr, 192, 168, 51, 84; IP4_ADDR(&netif.netmask, 255, 255, 255, 240; IP4_ADDR(&netif.gw, 192, 168, 51, 81; 1 else 1 IP4_ADDR(fi.netif.ip_addr, 0, 0, 0, 0; IP4_ADDR(&netif.netmask, 0, 0, 0, 0; IP4_ADDR(&netif.gw, 0, 0, 0, 0; t ^ - *** 1 error_resetable(lwipinterfaceinit(&netif, fiethnetif, 5; LWIPŁimersStart(; error_resetable{dhcpwait(snetif, 10, 4, 6; RedLEDoffO; > LWIPsetAppTimer(SendBroadcast, BCAST_TIMER_MS; for (;;; } 9.1.2. Testowanie przykładu Do przetestow ania tego przykładu m ożna użyć prostego serwera, który prezentuję w przykładzie 9b. W ymaga to jednak posiadania dw óch zestawów uruchom ieniowych. Innym rozw iązaniem jest uruchom ienie prostego serwera na komputerze, którego używa się do program ow ania m ikrokontrolera. Prosty serwer UDP dla systemu Linux, um ożliwiający testow anie tego przykładu, znajduje się w archiwum z przykładam i w pliku Unux_recv_bcast.c, który jest w katalogu./make/ex9a_send_bcast. Program ten wywołuje się z linii poleceń, podając jako argum ent numer portu, na którym ma nasłuchiwać serwer. Dla każdego odebranego datagramu U DP w yśw ietla on inform ację o adresie IP i num erze portu źródłowego, z którego ten datagram został wysłany. W yświetla też zaw artość otrzym anego datagramu UDP, zakładając, że jest to tekst ASCII. Po skom pilowaniu i uruchom ieniu tego program u, a następnie uruchom ieniu układu z m ikrokontrolerem, powinniśmy zobaczyć w ydruk podobny do poniższego. $./linux recv_bcast.elf 5002 Listening on port 5002 From:192.168.51.89:5001 Content:! 192.168.51.95:5002 From:192.168.51.89:5001 Content:2 255.255.255.255:5002 9.2. Przykład 9b - odbieranie datagramów UDP N azwy plików przykładu 9b zam ieszczone są w tabeli 9.2. Jedyny nowy plik opisuję w następnym podrozdziale. Tab. 9.2. Pliki przykładu 9b Źródłowo i biblioteczna ex recv bcast.c board cont.c util delay. c Iiblwip4.a lont5x8.c board init.c util eth nc.c Ubstm32t10x.a startup_stm32_cld. c boardjcd_ks0108.c boardjed.o u tiljc d.c utiljod_ex.c u tilje d.c u tiljw ip.c util time.c Nagłówkowe... font5x8.h board conf.h util delay.h cc.h board det.h util error.h cortex~m3.h board dets.h util eth.h Iwipopts.h - 4-4 * --------------- b o a rd jn it.h b o a rd jcd.h b oardjed.h uffljcd.h util_lcd_ex.h u tilje d.h utiljw ip.h u tiljtc.h util time.h stm32/10x_conui
248 9. Rozgłaszanie UDl' 9.2. Przykład 9b - odbieranie datagramów UDP 249 9,2.1. Plik ex_recv_bcast.c Plik ex_recv_bcast.c zaw iera im plem entację prostego serwera UDP przeznaczonego do współpracy z klientem z przykładu 9a. Serw er urucham iam y za pom ocą funkcji U D P servers tart. Argum entem tej funkcji jest num er portu, na którym serwer m a nasłuchiwać. Funkcja ta zwraca zero, gdy uruchom ienie serwera pow iodło się. a wartość ujem ną w przeciwnym przypadku. Jest ona identyczna jak funkcja o tej samej nazw ie z przykładu 7. static int UDPserverStart(uintl6_t port; Funkcja U D P servers tart ustawia funkcję zw rotną re c v _ c a llb a c k, która obsługuje odbierane datagramy. W funkcji re c v _ c a llb a c k w yświetlam y w pierw szym wierszu LCD adres IP i num er portu źródłow ego, z którego został wysłany datagram UDP, a w pozostałych w ierszach zaw artość odebranego datagram u, zakładając, że jest to tekst ASCII. Przed w yśw ietleniem kolejnego datagram u czyścim y ekran LCD. static void recv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, uintl6_t port ( struct pbuf *q; uint!6 t len; LCDclear(; LCDwriteIPport(addr, port; LCDputchar(,\n'; for (len = 0, q = p; len < p->łot_len; len += q->łen, q = q->next LCDwriteLenWrap(q->payload, q->len; pbuf_free(p; Aby ten program mógł odbierać datagram y wysyłane przez program z przykładu 9a, serw er musi nasłuchiwać na porcie 5002.»define UDP_P0RT 5002 Funkcja main jest bardzo podobna do analogicznej funkcji z poprzedniego przykładu. Są dw ie istotne różnice. K onfigurujem y inne adresy M AC i IP, aby programy z przykładów 9a i 9b m ogły być uruchom ione rów nocześnie w tej samej sieci testowej. Na końcu zam iast klienta urucham iam y serwer'udp. int main ( { static struct netif netif; static struct ethnetif ethnetif «(PHY_ADDRESS; uint8_t confbit; Delay(1000000; confbit = GetConfBit(; AllPinsDisable(; " * LEDconfigure(}; RedLEDonO ; LCDconfigureO / SET_IRQ_PROTECTION(; error_resetable(clkconfigure(, 1}; error_permanent(localtimeconfigure(, 2; error_resetable (ETHconfigureMII (, *1; netif.hwaddr[0} = 2; netif.hwaddr(l = (BOARD_TYPE» 8 & Oxff; netif.hwaddr[2] = BOARD_TYPE Oxff; netif.hwaddr[3] = (ETH_BOARD» 8 & Oxff; netif,hwaddr[4] = ETH_BOARD & Oxff; netif.hwaddr[5 = 3 + confbit; if ( confbit { IP4_ADDR( netif.ip^addr, 192, 168, 51, 85; IP4 ADDR( netif.netmask, 255, 255, 255, 240; IP4_ADDR( netif.gw, 192, 168, 51, 81; \ else { IP4_ADDR( netif.ip_addr, 0, 0, 0, 0; IP4 ADDRiinetif.netmask, 0, 0, 0, 0; IP4_ADDR( netif.gw, 0, 0, 0, 0; error_resetable(lwipinterfacelnit(snetif, ethnetif, 5; LWIPtimersStart(; error_resetable (DHCPwait (Snetif, 10, *1, 6; error_resetable(udpserverstart(udp_port, 7; RedLEDoff(; for (;;; 2. Testowanie przykładu Przykład ten zasadniczo ma charakter pom ocniczy. A le aby móc go przetestow ać sam odzielnie, w archiw um z przykładam i um ieszczony jest prosty klient UDP dla Linuksa, w ysyłający cyklicznie co 4 sekundy datagram na podany adres 1P i num er portu. Im plem entacja tego klienta znajduje się w pliku linux_send_bcast.c, który znajduje się w katalogu,/m ake/ex9b_recv_bcast w archiw um z przykładami. Program ten wywołujem y, podając dw a argum enty: docelow y adres IP i num er portu. Program w ysyła takie sam e datagram y U DP jak program z przykładu 9a. Zaw artość w ysłanych datagram ów U D P w ypisuje na standardow e wyjście. Za pom ocą tego program u m ożna w ysyłać je na adres rozgłaszania w bieżącej podsieci: $./łinux_send_bcast.elf 255.255.255.255 5002 1 255.255.255.255:5002 2 255.255.255.255:5002 M ożna też wysyłać datagram y UDP na adres ukierunkowanego rozgłaszania: $./linux_send_bcast.elf 192.168.51.95 5002 1 192^.68.51.95:5002 2 19i i68.$1^5:5002
250 9. Rozgłaszanie UDP Ponadto w celach testowych można rów nież wysyłać datagramy UDP po prostu na adres jednostkowy: $,/linux send_bcast.elf 192.168.51.90 5002 1 192.168.51.90:5002 2 192.168.51.90:5002 Serwis WWW
10.1. Komunikacja między klientem a serwerem WWW 253 Jedną z bardzo ważnych usług w Internecie, a być może wręcz usługą najw ażniej-' szą, jest W W W (ang. worlcl wide web. Najprawdopodobniej większość internau-, tów utożsamia Internet z WWW. U sługa W W W działa w modelu klient-serwer. Serwery udostępniają różnego rodzaju witryny i portale, a klientami są przeglądarki1 internetowe, instalowane w każdym urządzeniu mającym dostęp do intersieci, obecnie rów nież w telefonach komórkowych. Wiele urządzeń sieciowych, np. rutery domowe, wyposaża się w serwery WWW, umożliwiające zarządzanie nimi za pomocą przeglądarki internetowej. Popularność W W W wynika z atrakcyjnego i intuicyjnego interfejsu graficznego. Również konstruow ane przez nas urządzenia sterowane mikrokontrolerami chcielibyśmy wyposażać w taki interfejs, dlatego w tym rozdzia-1 le przedstawiam przykład bardzo prostego serwisu WWW, który zajm uje mniej niż 1 80 KiB pamięci Flash. Jak przystało na ostatni rozdział książki, jest to najbardziej skom plikowany z prezentowanych w niej przykładów. 10.1. Komunikacja między klientem a serwerem WWW Komunikację między użytkownikiem oraz reprezentującym go klientem a serwerem W W W można podzielić na trzy fazy: - Użytkownik, posługujący się przeglądarką internetową, wpisuje nazwę serwisu albo klika na odnośnik lub przycisk. W wyniku tego przeglądarka generuje identyfikator zasobu, który ma być wyświetlony użytkownikowi. - Przeglądarka wysyła do serwera żądanie dostarczenia zasobu. - Serwer odsyła żądany zasób lub informację o błędzie. Zwykle wyświetlenie strony internetowej wymaga ściągnięcia wielu kolejnych zasobów zawierających tekst i grafikę. Na podstawie otrzymanych dotychczas informacji przeglądarka autom atycznie generuje kolejne identyfikatory potrzebnych zasobów i dla każdego z nich powtarza dwie ostatnie fezy komunikacji. URI (ang. Uniform Resource Identifier jest ustandaryzowanym sposobem identyfikowania różnego rodzaju zasobów, w tym przede wszystkim zasobów internetowych. Szczególnymi przypadkami URI są URN (ang. Uniform Resource Name i URL (ang. Uniform Resource Locator. URN określa tylko nazwę zasobu, a URL oprócz identyfikacji zasobu wskazuje również spossb'dostępu do niego. Podzbiory URN i URL nie są rozłączne, konkretny identyfikator może być zaklasyfikowany jednocześnie jako URN i URL. Identyfikator URI rozpoczyna się od pola <schemat>, które określa format pozostałej części identyfikatora, protokół komunikacyjny itd. Po tym polu następuje dw ukropek i część specyficzna dla danego schematu. Bardzo często spotykanymi schem atam i są: file, ftp, http, mailto itd. <schem ut> :<czę& specyficr.ua dla schem atu> Wśród wszystkich form atów URI, ze względu na zastosowanie w WWW, najbardziej dla nas interesujący jest schem at hierarchiczny, w którym część specyficzna schematu rozpoczyna się dwoma ukośnikami. 1.1.2. HTTP <schemat> ://<węzei> :<port> /<ścieżka>?<zapytame>if<iyugm ent> Pole <w ęzeł> jest nazwą dom enow ą w ęzła lub adresem IP w notacji dziesiętnej z kropkam i. Pole <port> jest num erem portu usługi sieciow ej, zapisanym jako liczba przy podstaw ie dziesięć. Pole to um ożliw ia określenie niestandardowego num eru portu, innego niż dom yślny dla danego schem atu. O ddziela się go od poprzedzającej części URI dw ukropkiem. Pole <ścieżka> określa zwykle nazwę pliku zaw ierającego zasób. Jest oddzielone od poprzedzającej części URI ukośnikiem. Pole <zapytanie> zaw iera dodatkow e parametry, wysyłane do serwera usługi i m ające wpływ na udzieloną przez niego odpow iedź. M a to na przykład zastosow anie przy dynam icznym generow aniu stron WWW. Każdy param etr składa się z nazwy i wartości oddzielonych znakiem równości. Pole <zapytanie> jest oddzielone od poprzedzającej je części U RI pytajnikiem. K olejne parametry oddziela się od siebie znakiem et (ang. am persaiul, czyli &. Pole <fragm ent> specyfikuje fragm ent zasobu. Jest oddzielone od poprzedzającej je części URI krzyżykiem. Poszczególne pola identyfikatora są opcjonalne i nie m uszą w ystępować. Jeśli jakieś pole nie występuje, to nie w ystępuje też znak oddzielający to pole od poprzedniej części URI, przy czym nie dotyczy to znaków oddzielających schem at od dalszej części URI. Te znaki przynależą do schem atu i muszą się pojaw ić, jeśli podany jest schem at. A plikacja m oże określić schem at domyślny. Dla przeglądarki internetowej dom yślnym schem atem jest http. Każdy schemat ma zw ykle przypisany dom yślny port. Jeśli używ any jest port domyślny, to pole <port> nie musi w ystępować. D la http dom yślnym num erem portu jest 80. Więcej na tem at URI m ożna przeczytać w dokum encie RFC 3986. Poniżej zam ieszczone są przykładow e URI. fi!e:///katalogl/katalog2/p!ik ftp://ftp.is.co.za/ric/rrc3y86.txt http://ww w.ietf.org/rfc/3986.txt http ://w w w,serw er:8080/katalog/index.lum l?param etrl=abc& param etr2=i23#roziizial-l Ulap://[200I:cib8::7]/c-G B7objectClass?one nuiilto:john.d oe@ exam ple.com new.s:com p.infosysienis.www.servers.unix iei:-fl-8 16-555-1212 telnet://! 92.0.2.16:80/ u rn :oasis:nam es:specification:docbook:dtd:xm ł:4.1.2 127.0.0.1:8080 HTTP (ang. Hypertext Transfer Protocol jest protokołem, za pomocą którego przeglądarka internetowa komunikuje się z serwerem WWW. Aktualna wersja tego protokołu, czyli wersja L I, jest opisana w dokum encie RFC 2616. HTTP używa protokołu transportowego TCP. Serwer standardowo nasłuchuje na porcie 80. HTTP jest protqkolem bezstanowym - wymiana komunikatów składa się z żądania wysyłanego [t ez k, tąnta i odpowiedzi odsyłanej przez serwer. W yświetlenie strony WWW wymaga zwykle ściągnięcia wielu zasobów (tekst, obrazki, a dla każdego z nich
10.1. Komunikacja między klientem a serwerem WWW 2 55 musi zostać wysiane osobne żądanie. W wersji 1.0 HTTP każda taka komunikacją odbywa się w osobnym połączeniu TCP. W wersji 1.1 HTTP, w celu zmniejszenia narzutu związanego z budowaniem i rozłączaniem połączenia, w pojedynczym po- : łączeniu TCP można przesłać wiele kolejnych żądań i odpowiedzi. Protokół HTTP jest d o ić rozbudow any i nie ma tu m iejsca, aby przedstaw ić go 4, w całości. Pokażę tylko kilka przykładów. K om unikaty kodow ane są tekstowo.;';!: W niektórych m iejscach w ielkość liter nie ma znaczenia (ang. case insensiti ve, w innych zaś jest istotna (ang. case sensitive - jest to szczegółow o opisi ne w specyfikacji protokołu. K ażdy w iersz kończy się sekw encją znaków CK i LF. Ż ądanie w ysyłane przez klienta składa się z nagłów ka (ang. header, I ry kończy się pustym w ierszem, czyli dodatkow ą sekw encją znaków CR i LI-. ' Po nagłów ku może w ystępow ać opcjonalne ciało (ang. body żądania. Pierw: w iersz nagłów ka zaw iera nazwę metody, URI oraz w ersję protokołu używi przez klienta. Poszczególne składniki pierw szego w iersza oddziela się pojedy czym i spacjam i. N ajczęściej używ a się metody GET, która oznacza żądanie dostarczenia zasobu. URI żądanego zasobu m ożna podać w całości, ale można pom inąć pola <schem at>, <w ęzeł> i <port>, gdyż żądanie jest kierow ane przi połączenie TCP, które w łaśnie zostało otw arte do serw era określonego w polat <w ęzeł> i <port>, a pole <schem at> jest w tym przypadku nadm iarow e, bo wiadom o, że przesyła się kom unikaty protokołu HTTP. K olejne w iersze nagłówka U um ożliw iają przekazanie dodatkow ych param etrów. W iersze z param etram i składają się z nazwy param etru, dw ukropka i w artości param etru. W artość parametru,, m oże być poprzedzona białym i znakam i - preferpw ana jest pojedyncza spacja. ' Białe znaki na końcu w iersza są ignorow ane. Żądanie z m etodą G ET nie zaw ie-1.. ra ciała. Zatem najprostsze m ożliw e żądanie w ygląda następująco (pusty w ierv kończący żądanie jest istotny: G E T / HTTP/1.0 Często zdarza się, że jeden fizyczny serw er pod tym samym adresem IP obsługuje wiele nazw domenowych. Z punktu widzenia klientów wygląda to jak wiele wirtualnych serwerów. Aby to umożliwić, klient umieszcza nazwę serwera jako parametr Host, który jest obowiązkowy od wersji 1.1 HTTP. G E T /pub/index.html H T T P / 1.! *1 Host: www.nazwa.serwera Zwykle przeglądarka internetowa w ysyła w żądaniu więcej informacji. Popatrzmy na poniższy przykład. Param etr User-Agent identyfikuje przeglądarkę, a czasem też system operacyjny zainstalowany na komputerze użytkownika. Kolejne parametry Accept informują serwer o akceptowanych przez przeglądarkę formatach danych, obsługiwanych językach, metodach kom presowania danych, obsługiwanych rodzajach kodowania znaków narodowych ild. Płiramelr Connection informuje, czy połączenie ma być zamknięte (wartość close natychm iast po odesłaniu odpowiedzi, czy utrzymywane (wartość keep-alive przez serwer, gdyż będą w nim przesyłane kolejne żądania. G E T / l e d? L E D = R & L E D = G H T T P / 1.1 Host: localhost User-Agent: Firevox/1.0 (Operating Systcm/3.0 Accept: text/html.text/plain,image/png Accept-Language: pl,en-us,en Accept-Encoding: gzip,deflate Acccpt-Charsct: ISO-8859-2,UTF-8 Connection: close Zauważmy, że w powyższym przykładzie w URI przesyłane są parametry zapytania. Jeśli tych param etrów jest dużo, jeśli pochodzą z rozbudow anego formularza, wygodniej jest użyć w tym celu metody POST. W tedy parametry zapytania zostaną umieszczone w ciele komunikatu, jak w poniższym przykładzie. Param etr Contenl- -Type informuje, że komunikat zaw iera ciało i podaje sposób jego kodowania. N atomiast wartość parametru Content-Length informuje o długości ciała w oktetach. Zwróćmy uwagę, że ciało komunikatu nie kończy się znakiem końca wiersza (czego nie widać na wydruku. Zwróćm y też uw agę na pusty wiersz oddzielający nagłówek od ciała komunikatu. P O S T /led H T T P / 1.1 1-Iost: localhost User-Agent: Firevox/1.0 (Operating System/3.0 Connection: close Cnntent-Typc: application/x-www-fonn-urlencoded Content-Length: 11 L E D = R & L E D = G Odpowiedź serwera składa się z nagłówka, który kończy się pustym wierszem. Po nagłówku może występować ciało zawierające zasób, o który prosił klient. Pierwszy wiersz nagłówka zawiera wersję protokołu używaną przez serwer, kod statusu (ang. status code i jego opis tekstowy (ang. reason phrase. Poszczególne składniki pierwszego w iersza oddziela się pojedynczymi spacjami. Kod statusu informuje o wyniku działania serwera. Jest to trzycyfrow a liczba dziesiętna. Interpretacja poszczególnych zakresów wartości jest zamieszczona w tabeli 10.1. Opis tekstowy nie jest interpretowany przez oprogram ow anie klienta i jest przeznaczony dla Tab. 1 0.1. Niektóre kody statusu HTTP 1 0 0...1 9 9 2 0 0...2 9 9 3 0 0...3 9 9 4 0 0...4 9 9 500'.ł.% 99 Informacje o postąpię przetwarzania żądania, np.: 100 cząść żądania została odebrana i zaakceptowana Żądanie klienta zostało poprawnie odebrane, zrozumiane i zaakceptowane, np.: 200 - odpowiedź zawiera żądany zasób Klient musi podjąć dodatkową akcją w celu uzyskania zasobu, np.: 301 - żądany zasób został przeniesiony na stale i znajduje sią pod innym adresem Wystąpił błąd po stronie klienta, np.: 400 - przestane żądanie ma niepoprawną składnią, 404 - żądany zasób nie istnieje Wystąpił błąd po stronie serwera, np.: i Siffl - nie można obsłużyć żądania z powodu nieoczekiwanego problemu, 50S i- serwer nie obsługuje wersji HTTP podanej w żądaniu
256 10. Serwis VVU II 10.1. Komunikacja między klientem a serwerem WWW 257 użytkownika. Podobnie jak w żądaniu, kolejne wiersze nagłówka odpowiedzi i opcjonalne i umożliwiają przekazanie dodatkowych parametrów. W iersze z pari metrami składają się z nazwy param etru, dw ukropka i wartos'ci parametru. Warto: parametru może być poprzedzona białymi znakami - preferowana jest pojedyn< spacja. Białe znaki na końcu wiersza są ignorowane. W poniższym przykłai param etr Date informuje o dacie i czasie wysłania odpowiedzi. Param etr Server identyfikuje serwer i system operacyjny. Param etr Last-M odified informuje o da< i czasie ostatniej modyfikacji przesyłanego zasobu. Parametr Connection o w tości close informuje, że serwer zam knie połączenie po wysłaniu tej odpowied/i Wartość parametru Content-Length określa długość ciała wiadomości w oktetach,,, czyli rozm iar przesyłanego zasobu. Param etr Content-Type określa rodzaj zasobi i sposób jego kodowania. Często spotykanym i wartościami tego parametru są: lex html, text/plain, text/xml, text/css, application/octet-stream, application/pdf, image' gif, image/jpeg, image/png, im age/svg+xml, image/tiff, image/vnd.microsoft.ici itd. Nazwy te jednoznacznie identyfikują rodzaj zasobu i nie wymagają objaśnienia. D odatkowe pole charset specyfikuje sposób kodowania zasobu, informuje o sposi bie kodowania znaków. W poniższym przykładzie ciało odpowiedzi zawiera teksi zapisany w HTM L z użyciem kodowania UTF-8. Zasadniczą część ciała odpowiedzi tu pominięto i zastąpiona wielokropkiem. H T T P / 1.1 200 O K Dale: Fri, 20 A u g 2010 10:38:34 G M T Server: Apacz/i.O (Operating Sys(em/3.0 Last-Modified: Fri, 20 A u g 2010 10:11:55 G M T Connection: close Content-Length: 937 Content-Type: text/html; charset=utf-8 <htm l> 10.1.3. HTML HTM L (ang. Hypertext M arkup Language jest podstawowym językiem opisu stron WWW. Opisuje strukturę dokum entu W W W za pomocą znaczników (ang. tags. Nazwa znacznika jest otoczona nawiasam i ostrokątpymi. W iększość znaczników występuje w parach: znacznik otw ierający i z.naczni^zamykający. W znaczniku zamykającym jego nazwa poprzedzona jest ukośnikiem. Na przykład cały dokument rozpoczyna się znacznikiem <html>, a kończy znacznikiem </html>. Znaczniki <head> i </head> otaczają nagłówek dokumentu. Znaczniki <title> i </title> identyfikują tekst, który przeglądarka pow inna wyświetlić na pasku tytułowym swojego okna. Znaczniki <body> i </body> otaczają opis strony, która ma być wyświetlona wewnątrz okna przeglądarki. Znaczniki < h ł>, < /h l>, <h2>, </h2> itd. służą do w skazania nagłówków poszczególnych rozdziałów i sekcji w tekście. HTM L umożliwia też zdefiniowanie sposobu prezentacji dokumentu w przeglądarce internetowej. Znaczniki <center> i </center> nakazują wycentrowanie treści, którą otaczają. Znacznik <p> rozpoczyna nowy paragraf, a znacznik </p> kończy go. Niektóre znaczniki nie mają pary, w ystępują samodzielnie, np. znacznik <hr /> wyświetla poziomą linię. Takie znaczniki, według najnowszej specyfikacji, mają po nazwie spację i ukośnik. Znacznik może też mieć parametry, które umieszcza się w znaczniku otwierającym po nazwie i oddziela białymi znakami. Parametry mają zwykle postać: nazwa parametru, znak równości, wartość parametru w cudzysłowach. Przykładowo znacznik <font size= 6 > nakazuje zmianę rozmiaru czcionki. Odpowiadający mu znacznik domykający </font> przywraca poprzedni rozm iar czcionki. Nowoczesne serwisy W W W nie korzystają z tej możliwości HTM L-u i opisują wygląd strony za pomocą arkuszy stylów CSS (ang. Cascading Style Sheets. HTM L pozwala na osadzanie w tekście rysunków, filmów, odnośników do innych dokumentów itp. Na przykład znacznik <img src=... alt=... /> nakazuje umieszczenie obrazka. Param etr sre wskazuje URI zasobu, który zawiera ten obrazek i który trzeba ściągnąć. Param etr alt określa tekst, który ma być wyświetlony, gdy tego zasobu nie udało się ściągnąć lub gdy przeglądarka pracuje w trybie tekstowym i nie umożliwia wyświetlenia grafiki. Znaczniki <a href=... > i </a> otaczają tekst odnośnika, zwykle podkreślony i wyświetlany w kolorze niebieskim. Parametr href określa URI zasobu, którego przeglądarka ma zażądać po kliknięciu na ten tekst. HTTP pozwala też na osadzanie w dokumentach ciągów instrukcji języków skryptowych. HTM L umożliwia tworzenie interaktywnych formularzy. Znacznik <form action=... method=... > rozpoczyna definicję formularza. Param etr action definiuje URI, który ma być wysłany do serwera. Param etr method określa metodę (zwykle POST, która ma być użyta. Znacznik </lbrm > kończy definicję formularza. W ewnątrz formularza można zdefiniować kontrolki za pom ocą znacznika <input />. Parametr type określa typ kontrolki. Przykładowo wartość checkbox oznacza pole wyboru, a wartość submit - przycisk, na który trzeba kliknąć, aby przeglądarka wykonała akcję opisaną w parametrach action i method znacznika <form>. Interpretacja parametrów znacznika kontrolki zależy od typu kontrolki. Parametr value w kontrolce typu subm it określa tekst, który ma być wyświetlany na przycisku. Parametry name i value kontrolki typu checkbox oznaczają odpowiednio nazwę i wartość parametru wysyłanego w zapytaniu, które dla metody G ET jest umieszczane w polu <zapytanie> URI, a dla metody POST w ciele żądania, jak to przedstawiono w przykładach w rozdziale 10.1.2. Nie ma tu miejsca na szczegółowy opis składni i \yszystkich elementów HTML-u. Czytelnik zainteresowany poznaniem tego języka bez problemu znajdzie w Internecie jakiś samouczek. Zamiast kontynuować jego opis, obejrzyjmy następujący przykład strony wygenerowanej przez serwer z prezentowanego w tym rozdziale przykładu 10. <himl> <headxtitle>przykladowy Serwer HTTP</litlex/head> < b o dyxcenler> < p x i m g src= slm32_logo.gif alt= S T M 3 2 L O G O " / x / p > < p x f i i t siy^f 6 > 0 0 0 :0 9 :0 2.4 7 0 < /fo n tx /p x h r /> <form action= l/led melhod="post >
<p>czerwona dioda świecąca <inpul type= chcckbox n a m e = L E D value= R /></p> <p>ziclona dioda świecąca cinput type="chcckbox n a m e = " L E D value= G checked /></p> < pxinput type= submit valuc= Zmień /></p> < / formxhr /> <p>iwip/i.3.2 CMSiS/i.30 S T M 3 2 F 1 0 x Standard Periplicrals Library/3.3.0 ncwlib/i.!8.0</p> </ccntcrx/body> </html> Na podstawie powyższego opisu przeglądarka internetowa wyświetla stronę pokazaną na ry sunku 10.1. Na górze jest wyświetlany obrazek, a poniżej niego czas, który upłyną! od uruchom ienia mikrokontrolera. Pomiędzy poziomymi liniami znajduj; się pola wyboru i przycisk Zm ień umożliwiające sterowanie diodami świecący-; mi w zestawie ZL29ARM. Na dole wyświetlane są informacje o wersjach bibliotek użytych do skom pilowania przykładu. ; <3 Przykładowy Serwer HTTP - Microsoft Intel net rnplm er M B File Edit View Favorites Tools Help g B a c k - - j j j. x ' yp s reh Favorites [ f, j j -, ; v @ Address ÿ h ttp ://192. l 68.51.B9/. - ij k j Go l Unl^ r " s -Tv' -S T M. ' $..." A U33 OfG. niiam al, duel CAN. ;u';'.c i. 'ss Is! 0 00:09:02.470 f 10.2. 10.2.1. 10.2. Przykład 10 - prosty serwis WWW 259 Przykład 10 - prosty serwis WWW Prezentowany w tym podrozdziale przykład serwisu W W W składa się z serwera HTTP i aplikacji WWW. Serwer jest bardzo prosty, obsługuje tylko fragment protokołu HTTP i nie jest w pełni zgodny ze specyfikacją zawartą w RFC 2616, ale jest w ystarczająco funkcjonalny, aby można było oprzeć na nim aplikację komunikującą się z mikrokontrolerem za pom ocą przeglądarki internetowej. Serw er ten może też być podstawą do własnej, pełniejszej im plem entacji HTTP. Nazwy plików tego przykładu zebrano w tabeli 10.2. W iększość z nich opisałem ju ż w poprzednich rozdziałach. W kolejnych podrozdziałach om aw iam szczegółowo pliki specyficzne dla tego przykładu. Pliki http_parser.h i http_parser.c HTTP jest protokołem tekstowym, więc serwer musi przetłum aczyć odebrany komunikat na postać lepiej nadającą się do dalszego przetwarzania, zwykle na postać binarną. Proces ten nazywa się parsowanie i obejm uje analizę leksykalną oraz składniową tekstu. Nie wdając się w szczegóły, chodzi w skrócie o pominięcie nieistotnych fragmentów tekstu (białych znaków, komentarzy, wyszukanie słów kluczowych, specjalnych sekwencji znaków (operatorów, nazw (identyfikatorów, stałych (literałów, a następnie sprawdzenie poprawności składni i stworzenie struktury danych, którą będzie można dalej wygodnie przetwarzać. Komunikaty HTTP są dość skom plikowane i napisanie pcinego parsera HTTP jest zadaniem czasochłonnym. Zwykle nie pisze się parserów od podstaw. Istnieją dobre darm owe narzędzia, takie jak na przykład Flex i Bison, umożliwiające przyspieszenie i zautom atyzowanie procesu im plem entowania parsera. Poniew aż jednak przykładowy serwer ma być bardzo prosty, napiszemy też prosty parser. Jego interfejs definiujemy w pliku httpjm rser.h, a im plem entację umieszczam y w pliku http_jmrser.c. Zaczynamy od zdefiniowania typu wyliczeniowego m ethod_t dla metod, które parser ma rozpoznawać. typedef enum (GET_METHOD = 1, POST METHOD = 2 method_t; Tab. 10.2. Pliki przykładu 10 Czerwona dioda świecąca JH_. Zielona dioda świecąca * Zmień lwip/1.3.2 CMSIS/1.30 S T M 3 2 F 1 0 x Standard Peripherals Library/3.3.0 newlib/1.18.0 [ j Done $ Internet Rys. 10.1. Główna strona serwisu WWW z przykładu 10. J ex_httpd.c http_application.c http_parser.c htlp_server.c startup_stm32_cld.c http.h http_applicalion.li http_parser.h http_serm.h stm32_logo.h -..------------------------------------- Źródłowe 1biblioteczne board_conl.c boardj n il.c boardje d.c board_conf.h board_del.h board_dels.h boardjnit.h boardjed.h Nagłówkowe util_delay.c util_eth_nc.c u tilje d.c u liljw ip.c util time.c ulil_delay.h util_error.h ulil_elh.li u lilje d.h utiljw ip.h u tiljtc.h util time.li Iiblwip4.a Iibstm32l10x.a cc.h cortex-m3.h Iwipopts.h stm32t10x_conf.h
260 10. Serwis WWW 10.2. Przykład 10 - prosty serwis W W W ' 261 Definiujem y też typ wyliczeniowy s t a tu s _ t dla kodów statusu odsyłanych klientowi przez serwer. Są to tylko niektóre, wybrane kody statusu, spośród zdefinio-f wanych w RFC 2616. Nasz serwer jest bardzo uproszczony i większość błędów : w żądaniu otrzymanym od klienta kw ituje statusem 400 Bad Request. Naton napotkawszy problem z udzieleniem odpowiedzi, wysyła klientowi status 51 Internal Server Error. Dodatkowo serwer rozpoznaje błędną nazwę zasobu i nieoh- ' sługiwaną wersję HTTP, wysyłając odpowiednio status 404 Not Found lub 51 '. HTTP Version Not Supported. Status 100 Continue jest używany wewnętrznie, >.; przez serwer i oznacza, że analiza żądania jeszcze się nie zakończyła. Jeśli żądl klienta jest poprawne, to serwer odsyła żądany przez niego zasób i w odpowied um ieszcza status 200 O K. typedef enum (Continue_X00 = 100, OK_200 ~ = 200, Bad_Request_400 = 100, Not_Found_404 = 404, Internal_Server_Error_500 = 500, HTfP_Version_Not_Supported_505 = 505 status_t; W ewnętrzny stan parsera przechowujem y w strukturze typu h ttp _ p a rs e r. łdefine BUFFER_SIZE 32 struct http_parser { state_t state; method t method; char buffer[bueter_size]; int buffer idx; char const * const * resource_tbl; int resource len; int resource idx; int content len; char const * const * param tbl; int param len; char param flag 11j; Parser implementujemy jako autom at stanowy. Stan parsera przechowujemy w składowej s ta te. Definicja typu s t a t e _ t znajduje się w dalszej części tego podrozdziału, przy opisie automatu. W składowej raethod parser zapisuje identyfikator rozpoznanej metody. W składowej b u ffe r parser przechowuje fragment analizowanego komunikatu. Składowa b u ffe r_ id x zawiera liezfię,-znaków zapisanych aktualnie w tym buforze. Składowa re s o u rc e _ tb ł jest wskaźnikiem do tablicy wskaźników do nazw zasobów, które parser ma rozpoznawać. Składowa re so u rc e _ łe n zawiera, rozm iar tej tablicy, czyli liczbę zasobów, które mają być analizowane. W składowe resource idx parser um ieszcza indeks znalezionego zasobu. Składowa contc:v len zawiera liczbę znaków, które pozostały jeszcze do przeanalizowania w ciele żądania. Składowa param _tbl jest wskaźnikiem do tablicy wskaźników do napisów zawierających paramęjry, które parser ma rozpoznawać w ciele żądania. Każdy napis składa się z nazwy parametru, znaku równości i wartości parametru. Składowa param _len zawiera rozm iar tej tablicy wskaźników. Składowa param _ flag jest tablicą, w kfórej parser umieszcza informacje o znalezionych parametrach. Tablica ta jest alokowana dynam icznie i inicjowana zerami, a jej rzeczywisty rozm iar wynosi param _len. Jeśli param etr zostanie znaleziony, to pod odpowiednim indeksem w tej tablicy parser um ieszcza wartość 1. struct http_parser * http_parser_new(char const * const * const res_tbl, int res_len, char const * const * const prm_tbl, int prm_len { struct http_parser *s; s = memjnailoc(sizeof(struct http_parser + prm_len - 1; if (s ( s->state «METHOD_STATE; s->method = 0; s->buffer_idx = 0; s->resource_tbl = res tbl; s->resource len = res_len; s->resource_idx = -ł; s->content_len = 0; s->param_tbl = prm_tbl; s->param_len = prm len; memset(s->param_flag, 0, prm_len; } return s; i Funkcja parser_new tworzy nowy parser, czyli alokuje nową strukturę typu h ttp _ p a rs e r i inicjuje jej składowe. Jej pierwszy argument re s _ tb l jest wskaźnikiem do tablicy wskaźników do nazw zasobów, które parser ma rozpoznawać. Drugi argument re s _ le n zawiera rozm iar tablicy re s _ tb l. Trzeci argument prm _tbl jest w skaźnikiem do tablicy wskaźników do napisów z parametrami, które parser ma rozpoznawać. Czwarty argum ent prm _len zawiera rozm iar tablicy prra_tbl. Funkcja ta zwraca wskaźnik do struktury typu h ttp _ p a rs e r, która identyfikuje parser. W skaźnik ten jest przekazywany jako pierwszy argument pozostałych funkcji modułu parsera. Dzięki temu można utworzyć wiele parserów - każde odebrane przez serwer połączenie TCP ma przydzielony własny parser. Funkcja zwraca N U L L, gdy nie udało się przydzielić pamięci. void http_parser_delete(struct http_parser *s { mem free(s; i Funkcja p a rs e r_ d e le te usuwa parser i zwalnia zajmowaną przez niego pamięć. Jej argumentem jest wskaźnik do struktury opisującej parser, który ma być usunięty. Kolejne trzy funkcje umożliwiają odczytanie wyniku parsowania. method_t http_parser_method(const struct http_parser return s? s->method : -1; 1 Funkcja http_parser_ m eth o d zwraca identyfikator rozpoznanej w żądaniu metody. Jej argumentem jest wskaźnik do struktury opisującej parser. Poprawny identyfikator metody nigdy nie jest liczbą ujemną. int http_parser_resource(const struct http_parser *s ( return s? s->resource_idx : -1/ FunlJlja h tp_ parser resource zwraca indeks rozpoznanego zasobu. Jej argumentem jestji'wskaznik do struktury opisującej parser. Zwrócenie wartości ujemnej
262 10. Serwis WWW1 0.2. Przykład 10 - prosty serwis WWW 263 oznacza, że nie znaleziono zasobu - składowa re so u rc e _ id x jest inicjow ana w ar-, tością -1. int http_parser_param(const struct http_parser *s, int idx { return s SS idx >= 0 ss idx < s->param_len? s->param_ laq[idx : 0; I Funkcja h ttp _ p arser_ p aram zwraca wartość 1, gdy parser znalazł param etr o in ; deksie idx, a wartość 0 w przeciwnym przypadku. Jej pierwszym argumentem jest wskaźnik do struktury opisującej parser. Drugi argument idx jest indeksem sprawdzanego parametru. status_t http_parser_do(struct http_parser *s, char x 1 if (! s return Internal_Server_Error_500; else if {s->bu fer_idx < BUFFER_SIZE s->buffer[s->buffer_idx+t-] = x; switch (s->state { } W łaściwym parsowaniem żądania zajm uje się funkcja h ttp _ p a rse r_ d o. Jej pierwszym argumentem jest wskaźnik do struktury opisującej parser. Funkcja ta jest wywoływana po kolei dla każdego znaku analizowanego komunikatu. Jej drugi argument x zawiera kolejny analizowany znak. Funkcja zwraca status procesu parsowania. Jeśli zwróci wartość Continue_100, oznacza to, że parsowanie nie zostało jeszcze zakończone, a funkcja w ywołująca parser powinna kontynuować odbieranie znaków ze strumienia TCP i przekazyw anie ich parserowi. Jeśli parser zwróci inny status, to oznacza, że parsowanie zostało zakończone i funkcja w ywołująca parser powinna podjąć akcję stosowną do otrzym anego statusu oraz przestać wywoływać parser. Analizowane znaki grom adzim y w pomocniczym buforze b u ffe r. Zakładamy przy tym, że żaden poszukiwany URI ani param etr (wraz ze znakiem końca wiersza nie jest dłuższy niż BUFFER_SIZE znaków. Liczbę dotychczas zgromadzonych znaków przechowujem y w składowej b u ffe r_ id x. Parser im plem entujemy jako automat stanowy. Instrukcja sw itch rozdziela sterowanie zależnie od aktualnego sta-, nu s ta te. Dla każdego stanu im plem entujemy sekcję case. Identyfikatory stanów automatu definiujemy jako typ wyliczeniowy s ta te _ t. typedef enum {METHOD_STATE, URI_STATE, VERSION_STATE,,,p*RAM_STATE, CONTENT_STATE, END_STATE, ERROR STATE state_t; Parser zaczyna od stanu METHOD_STATE (patrz funkcja h ttp_parser_new i jeśli, zmienia stan, to zawsze na stan o wyższym numerze. Stan ERROR_STATE oznacza wystąpienie błędu i parser już nigdy nie wychodzi z tego stanu. Poniżej opisuję kolejne sekcje case, czyli działanie parsera w poszczególnych stanach. W stanie METHOD_STATE parser próbuje rozpoznać metodę żądania. Aktualnie parser rozpoznaje tylko dwie metody, ale dodanie kolejnych nie powinno sprawić kłopotu. case METHOD_STATE: > if {s->buffer_idx == 4 strncmp(s->buffer, "GET ", 4 == 0 { s~>method = GET_METHOD; s->buffer idx = 0; s->state = URI_STATE; i else if {s->buffer_idx == 5 && strncmp(s~>buffer, "POST ", 5 == 0} { s->method = P0ST_METH0D; s->buf fer idx = 0; s->state * URI_STATE; else if (s->buffer_idx >= 5 { s~>state = ERROR STATE; return Bad_Request 400; } return Continue_100; Po rozpoznaniu metody przechodzimy do stanu URI STATE. W tym stanie analizujem y URI żądania. Parsowanie URI jest bardzo uproszczone. Zaimplementowanie pełnego parsera URI jest trudne, gdyż ten sam identyfikator może być zapisany na wiele sposobów, dopuszczalne jest szesnastkowe kodowanie znaków (sekwencja rozpoczynająca się od znaku procentu itd. Spacja nie jest poprawnym znakiem w URI - jest zapisywana jako sekwencja %20. Dlatego koniec URI rozpoznajemy po otrzymaniu spacji oddzielającej URI od pola zawierającego wersję HTTP. Ponieważ spacja nie jest częścią URI, przed dalszymi sprawdzeniami trzeba ją usunąć z bufora. Otrzymany URI porównujemy po kolei ze wszystkimi URI umieszczonymi w tablicy re s o u rc e _ tb l. Po wykryciu zgodności zapamiętujemy indeks znalezionego URI w składowej reso u rce_ id x i przechodzimy do stanu VERSION_STATE. case URI_STATE: if (x = =''! int i; if (s->bufferis->buffer_idx - 1 ] == 1 '} s->buffer_idx ; for (i = 0; i < s->resource_len; ++i ( if {strncmp(s->buffer, s->resource_tbifi], s->bu fer_idx =«0 { s->resource_idx = i; s->buffer_idx = 0; s->state = VERSION_STAT ; return Continue_100; } s->state = ERROR STATE; return Not_Found_404; else if {s->buffer_idx >= BUFFER_SIZE { s->state = ERROR STATE; return Bad_Request_400; } return Continue_100; W stanie VERSION_STATE sprawdzamy, czy n u m e r wersji w otrzymanym żądaniu zgadza się z obsługiwanymi wersjami HTTP. Do p usz cza my obie istniejące aktualnie wersje protokołu. Po p o p r a w n y m zweryfikowaniu wersji przechodzimy do stanuj,param STATE. * l t case VESS^ON_STATE: if (S 2ttfuffer idx == 10 &&
10.2. Przykład 10 - prosty serwis WWW 265 (strncmp(s->bu fer, "HTTR/l.0\r\n", 10 ==011 strncmp(3->buffer, "HTTP/l.l\r\n", 10 == 0 ( s->buf fer_.idx = 0; s->state = PARAM_STATE; i ełse if {s->buffer_idx >= 10 { s->state = ERROR_STATE; return HTTP_Version_Not_Supported_505; return Continue_100; W stanie PARAM_STATE analizujemy pozostałą część nagłówka HTTP, zawierającą parametry żądania. W ykrywamy dw ie sytuacje: koniec nagłówka (bufor zawiera ' tylko znaki CR i LF i wystąpienie parametru Content-Length. Wartość tego parametru oznacza długość ciała wiadomości. Jeśli komunikat nie ma ciała, to przechodzimy do stanu końcowego END_STATE, a jeśli ciało występuje (parametr Conte:.- - Length ma dodatnią wartość, to przechodzimy do stanu CONTENT_STATE. case PARAM_STATE: if (x == <\n' t if (s->buffer idx == 2 66 s->bu fer(0 == *\r* 66 s->bufferilj == '\n'} ( if (s->content len > 0 s~>state = CONTENT_STATE; else { 5->state o END_STATE; return OK_20Q; i } else if (s->buffer_idx >= 19 66 strncmp(s->buffer, "Content-Length: ", 16 == 0 66 s->buffer[s->bu fer_idx - 2 == '\r' 66 s->buffer{s->buffer_idx - 1] == *\n1 { int i; for (i = 16; i < s->buffer_idx - 2; ił-i { if (isdigit{(ints->bufferti] { s->content_len *= 10; s->content_len += s->buffer(ij - *0'; else 1 s->5tate = ERROR_STATE; return Bad_Request 400;, i i s->buffer_idx = 0; } return Continue_100; W stanie CONTENT_STATE analizujem y zawartość ciała komunikatu i poszukujemy pa-; ram etrów z formularza HTM L. Parsowanie jest bardzo uproszczone. Przyjmujemy, że parametry rozdzielane są znakiem &. Koniec parametru rozpoznajemy po otrzymaniu tego znaku lub gdy nie ma ju ż więcej znaków do przetworzenia. Znak & nie jest częścią parametru, dlatego przed dalszymi sprawdzeniami usuwamy go z bufora. Otrzymany param etr porównujem y po kolei ze wszystkimi parametrami umiesz- czonymi w tablicy parara_tbl. Informację o wykryciu zgodności zapamiętujemy w tablicy param _ flag. Jeśli cala zawartość ciała komunikatu została ju ż przetworzona, to przechodzimy do stanu końcowego END_STATE. case CONTENT_STATE: s->content_len ; if (x == *6' j s->content_len == 0 { int i; if (s->buffer{s->bu fer_idx - 1] == '6' s->buffer_idx ; for (i = 0 ; i < s->param_len; ++i if (strncmp(s->buffer, s->param_tbl(i, s->buffer_idx == 0 s->param flag(i] = 1; s->buffer_idx = 0; if (s->content_len == 0 { s->state = END_STATE; return OK_200; } return Continue_100; Jeśli coś poszło źle, np, funkcja http parser_do została ponownie wywołana, mimo że wcześniej zwróciła status różny od Continue_100, to wykonujemy sekcję d e fa u lt. Jedyne co można zrobić w tej sytuacji, to zwrócić informację o błędzie. default: s->state = ERROR_STATE; return Internal_Server_Error_500; Pliki http_server.h, http.h i http_application.h Plik http_server.h zawiera definicję dom yślnego numeru portu HTTP oraz deklarację funkcji H TTPserverStart, która urucham ia serwer HTTP. Funkcja ta jest zaim plem entow ana w pliku http_server.c i jest identyczna jak funkcja T C P servers tart z przykładów 5a i 5b, zaim plem entowana w pliku tcp_servei:c. Argumentem tych funkcji jest numer portu, na którym serwer ma nasłuchiwać. Funkcje te zwracają zero, gdy uruchom ienie serwera powiedzie się, a wartość ujemną w przeciwnym przypadku. idefine HTTP_RORT 80 int HTTPserverStart (uint!6 t; W pliku http.h zadeklarowana jest struktura typu h ttp. Definicja tej struktury znajduje się w pliku http_server.c. Struktura h ttp przechowuje wszystkie niezbędne informacje związane z obsługą żądania i wysyłaniem odpowiedzi HTTP. struct http; Ponadto w pliku http.h zadeklarowane są poniższe funkcje operujące na strukturze typu h ttp i używane w pliku http_application.c\ który opisuję w jednym z następnych podrozdziałów. Implementacja tych funkcji znajduje się w pliku http server,c. charjj phttpj data_new{struct http *, size_ł; void http_dat^_len(struct http *, size_t; void http_dat!a_rom(struct http *, const char *, size t;
266 10. Serwis WFiy 10.2. Przykład 10 - prosty serwis VkWW 267 W pliku http_application.h znajdują się deklaracje pozwalające w sposób ogólny zdefiniować algorytm działania prostego serwisu WWW. W tablicy re s o u rc e _ tb l należy umieścić wskaźniki do napisów reprezentujących URI, które mają być obsługiwane przez serwis. Zmienna re s o u rc e _ tb l_ le n musi zawierać rozm iar tej tablicy, czyli liczbę obsługiwanych URI. W tablicy p a ra ra e te r_ tb l należy umieścić wskaźniki do napisów zawierających parametry, które są generowane przez formularze generowane przez nasz serwis. Zmienna p a ra m e te r_ tb l_ le n musi zawierać rozm iar tej tablicy, czyli liczbę obsługiwanych parametrów. Powyższe tablice i zmienne definiujemy w pliku http_applicałion.c. extern const char * const resource_tbi(j; extern const int resource_tbl_len; extern const char * const parameter_tbl(j; extern const int parameter_tbl_len; status_t make http_answer (struct http *, const struct http_parser *; Samą logikę działania serwisu im plem entujemy też w pliku http_application.c w funkcji m ake_http_answ er. Argumentami tej funkcji są wskaźniki do struktur opisujących stan obsługi żądania H TTP oraz parser. Funkcja ta na podstawie informacji uzyskanych od parsera musi wygenerować odpowiedź HTTP i zwrócić status HTTP. Zwrócenie wartości OK_200 oznacza, że wygenerowanie odpowiedzi powiodło się i należy ją wysiać klientow i. Inny status oznacza wystąpienie problemu. W tedy funkcja, która wywołała funkcję m ake_http_answ er, musi obsłużyć ten problem - w tej sytuacji najlepiej je st wysiać klientowi informację zawierającą ten status. 10.2.3. Plik http_server.c - obsługa HTTP Plik http_server.c zawiera im plem entację serwera HTTP. Najpierw zajmijmy się obsługą żądania HTTP. Aby serwer nie został zawieszony przez złośliwego klienta, ustalamy, że połączenie z klientem może trwać maksymalnie 10 sekund. Jest to czas zupełnie wystarczający, aby odebrać i przeanalizować żądanie oraz wysłać odpowiedź. łdefine SERVER_TIMEOUT 10 Serwer przechowuje dane niezbędne do obsłużenia klienta w strukturze typu h ttp. Składowa p a rs e r jest wskaźnikiem do struktury opisującej parser przypisany temu klientowi. Składowa s t a tu s zaw iera aktualny statuę»absługi żądania HTTP zgodnie z tabelą 10.1. Składowa tim eout zaw iera liczbę sekund, które pozostały jeszcze do zakończenia obsługi klienta. Składowa d a ta jest wskaźnikiem do bufora z odpowiedzią, która ma być wysłana klientowi. Odpowiedź często jest zbyt duża, aby mogła być wysiana w jednym segm encie TCP. D latego musimy zaimplementować wysyłanie odpowiedzi w kawałkach. Składow a d ata_ id x wskazuje pozycję pierwszego jeszcze niewysłanego bajtu w buforze wskazywanym przez d ata. Składowa d a ta l e f t zawiera liczbę bajtów, które pozostają jeszcze do wysiania. struct http { * struct http_parser *parser; status_t * status; int timeout; char *data; size_t size_t uint32 t ; data_idx; data_left; data_flags; Składowa d a ta _ fla g s struktury typu h ttp przechowuje znaczniki potrzebne do obsługi bufora danych. Aktualnie zdefiniowany jest tylko jeden wskaźnik F L A G _ NEEDS FREE. Musi być on ustawiony, gdy bufor d a ta został przydzielony na stercie za pom ocą funkcji m em jnalloc i przed usunięciem z pamięci struktury typu h ttp musi zostać zwolniony za pom ocą funkcji mem_f ree. Jeśli składowa d a ta wskazuje na dane w pamięci stałej (w naszym przypadku Flash, to nie zwalnia się pamięci zajmowanej przez te dane i znacznika F L A G _ N E E D S _ F R E E nie ustawiamy. Idefine FLAG_NEEDS_FREE 1 Nie należy bezpośrednio m anipulować składowym i struktury typu h ttp. Do obsługi tej struktury im plem entujemy szereg funkcji. Funkcja h ttp new przydziela nową strukturę typu h ttp i inicjuje jej składowe. Zwraca wskaźnik do tej struktury lub wartość NULL, gdy wystąpił problem z alokacją pamięci. Funkcja ta tworzy też nową instancję parsera. static struct http * http_new(void ( struct http parser *parser; struct http *bttp; parser = http_parser_new(resource_tbl, resource_tbl_len, parameter_tbl, parameter tbl_len; if (parser == NULL return NULL; http = memjnałloc(sizeof(struct http; if (http { http->parser = parser; http->data = NULL; http->data_flags = 0; http->data_idx = 0; hfctp->data_left = 0; http~>status = Continue_100; http->timeout * SERVERJFIMEOUT; 1 else http_parser_delete(parser; return http; Funkcja h ttp _ d e le te usuwa z pamięci strukturę typu h ttp. Najpierw jednak usuwamy parser i ewentualnie zwalniamy bufor wskazywany przez składową d ata. Argumentem tej funkcji jest wskaźnik do zwalnianej struktury typu h ttp. static void http_delete(struct http *http ( http_parser_delete(http->parser; if (http->data_flags S FLAG_NEEDS_FREE meą_free(http->data; mem free(httpl; 1 Funkcja h ttp d a ta new alokuje bufor, w którym ma być umieszczona odpowiedź udłfclanu$p rzez serwer. Pierwszym argum entem tej funkcji jest wskaźnik do struktury typu h& p, do której ma być przypisany alokowany bufor. Drugi argument s iz e
268 10. Serwis WWW 10.2. Przykład 10 - prosty serwis WWW' 269 zawiera żądany rozm iar tego bufora. Funkcja ta zwraca wskaźnik do przydzieli go bufora lub wartość NULŁ, gdy przydzielenie pamięci się nie powiodło. Funkcja http_data_new jest przewidziana do wywołania przez aplikację W W W zaim m entowaną w pliku http_application.c. char * http_data_new(struct http *http, size_t size { http->data = memjnalloc(size; if (http->data { http->data_idx = 0; http->data_left = size; http~>data_flags = FLAG_NEEDS_FREE; return http->data; Funkcja h ttp _ d a ta _ d e łe te zwalnia bufor przypisany do składowej d a ta struktury typu h ttp. Jej argumentem jest wskaźnik do struktury typu h ttp, do której przypisany jest zwalniany bufor. static void http_data_delete(struct http *http ( if (http->data_flags & FLAG_NEEDS_FREE { mem_free(http->data; http->data = NULL; http->data idx = 0; http~>data_left - 0; http->data_flags» 0; 1 Funkcja h ttp _ d a ta _ łe n modyfikuje rozm iar danych, które pozostały jeszcze do wysiania. Jej pierwszym argum entem jest wskaźnik do struktury typu h ttp. Drugi argum ent s iz e zawiera nowy rozm iar danych. Funkcja h ttp _ d a ta _ le n jest przew i dziana do wywołania przez aplikację W W W zaim plem entowaną w pliku http_applicalion.c. void http_data_len(struct http *http, size_t size { http->data_ieft = size; Funkcja http_ data_ rora przypisuje do struktury typu h ttp odpowiedź serwera znajdującą się w pamięci stałej. Jej pierwszym argumentem jest wskaźnik do struktury typu h ttp. Drugi argum ent d a ta jest wskaźnikiem do miejsca w pamięci, gdzie znajduje się odpowiedź serwera. Trzeci argum ent s ia e zawiera rozm iar tej odpowiedzi. Funkcja http_data_rom jest przewidziana de wywołania przez aplikację W W W zaim plem entow aną w pliku http_application.c. void http_data_rom(struct http *http, const char *data, size_t size ( http->data = (char *data; http->data_idx = 0; http->data_left = size; http->data_flags = 0; * i > Funkcja h ttp _ s e n t sprawdza, czy wszystkie dane zostały ju ż wysiane. Zwraca zero, gdy jeszcze jakieś' dane pozostały do wysłania, a jeden w przeciwnym przypadku. Jej pierwszym argumentem jest w skaźnik do struktury typu h ttp. static int http_sent(struct http *http { return http->data_left == 0;! Funkcja h ttp _ tim e o u t zmniejsza licznik czasu, który pozostał jeszcze do zakończenia obsługi klienta. Zwraca zero, gdy obsługa klienta ma być kontynuowana, a jeden w przeciwnym przypadku, czyli gdy należy zakończyć obsługę klienta. Jej pierwszym argumentem jest wskaźnik do struktury typu h ttp. static int http_timeout(struct http *http ( return (http->timeout <= 0; 1 Funkcja h ttp _ se n d wysyła porcję danych z odpowiedzią serwera. Jej pierwszym argumentem jest wskaźnik do struktury typu h ttp. Drugi argument pcb jest wskaźnikiem do deskryptora TCP opisującego połączenie TCP, przez które dane mają zostać wysiane. Funkcja ta najpierw sprawdza za pomocą funkcji tcp_sndbuf ilość wolnego miejsca w buforze nadawczym TCP. Jeśli jest w nim dostatecznie dużo miejsca, aby wysiać wszystkie dane, to wysyłamy je w całości. Jeśli bufor nadawczy jest zbyt mały, to wysyłamy tylko fragment danych i ustawiamy znacznik TCP_ WRITE_FLAG_MORE oznaczający, że będą jeszcze wysiane kolejne fragmenty. static void http_send(struct http http, struct tcp_pcb *pcb uintl6_t len; uint8 t flag; len = Łcp_sndbuf (pcb ;* if (len > 0 SS http~>data && http->data_ieft > 0 ( if (len >= http->data_left ( len = http->data left; flag => 0; else flag * TCP_WRITE_FLAG MORE; if (ERR_OK == tcp_write(pcb, http->data + http->data_idx, len, flag { http->data_idx i= len; http->data_jleft -= len; } I Funkcja h ttp _ e n g in e obsługuje żądanie klienta. Jej pierwszym argumentem jest wskaźnik do struktury typu h ttp. Drugi argument pcb jest wskaźnikiem do deskryptora TCP opisującego połączenie TCP, z którego odebrano żądanie. Trzeci argument p jest wskaźnikiem do łańcucha buforów, które zawierają odebrane dane. Dopóki status HTTP ma wartość Continue_100, czyli serwer oczekuje na kolejne porcje żądania, to funkcja ta dla każdego odebranego znaku wywołuje funkcję parsera h ttp _ p arser_ d o. Jeśli status HTTP zmieni się na OK_200, co oznacza, że zostało odebrane poprawne żądanie, to wywołujemy funkcję make_http_answer, której zadaniem jest przygotowanie odpowiedzi dla klienta. W trakcie przygotowywania odpowiedzi status HTTP może się zmienić. Jeśli się zmienił, to usuwamy ewentualnąjprzygotowaną przez funkcję m ake_http_answ er odpowiedź za pomocą funkcji h tt ^ d a t# J d e le t e. Następnie, jeśli status HTTP jest różny od OK_200, wywołujemy funkcjęi lh ttp _ e rro r_ re sp o n s e, której zadaniem jest przygotowanie odpowiedzi
270 10. Serwis lvw\y 10.2. Przykład 0 - prosty serwis 271 informującej o błędzie. Na koniec wywołujemy funkcję h ttp _ send, która inicjuje wysłanie odpowiedzi. static void http_engine{struct http *http, struct tcp_pcb *pcb, struct pbuf *p { struct pbuf *q; if {http->status == Continue_100 { for (q = p;; q = q->next { uint8_t *c = (uint8_t *q->payload; uint!6 t i; for (i = 0 ; i < q->len; ++i { http->status = http_parser_do{http->parser, c[i]; if (http->status == Continue_100 continue; else if {http->status *= OK_200 { http->status = make_http_answer{http, http->parser; if {http->status!= OK_200 http_data delete{http; 1 if {http->status!= OK 200 http_error_response{http; http_send{http, pcb; return;! if {q->len == q->tot_len break; } Funkcja h ttp _ e rro r_ re sp o n s e przygotowuje odpowiedź informującą o wystąpieniu błędu. Jej pierwszym argum entem jest w skaźnik do struktury typu h ttp. Kompletne odpowiedzi serwera dla poszczególnych kodów statusu HTTP definiujemy jako stałe napisy - kompilator umieści je w pamięci Flash. W ydaje się to marnotrawstwem pamięci, bo wszystkie te odpowiedzi mają wspólny wzorzec i można by je generować dynamicznie. Pamiętajmy jednak, że fragmenty kodu obsługujące błędy powinny być możliwie odporne na wystąpienie kolejnych błędów, np. związanych z alokacją pamięci. W ystąpienie problemu podczas obsługi sytuacji, która jest wynikiem innego problemu, może doprow adzić do załamania się aplikacji, a pamięci Flash mamy pod dostatkiem. D om yślną odpow iedzią jes.t^,5.00 Internal Server Error, co jest dość rozsądnym rozwiązaniem.. static void http_error_response(struct http *http ( static const char bad_request_400_html(] = "HTTP/1.1 400 Bad Request\r\n" "Connection: ciose\r\n" "Content-Type: Łext/html\r\n" "\r\n" "<html>" "<headxtitle>error</titłex/head>" "<bodyxhl>400 Bad..Request</hlx/body>" * "</html>"; static conkt char not_found_404_html( = "HTTP/1.1 404 Not Found\r\n" "Connection: close\r\n" 10.2.4. "Content-Type: text/html\r\n" "\r\n" "<html>" "<headxtitie> rror</titlex/head>" "<bodyxhl>404 Not Found</hlx/body>" "</html>"; static const char internai_server_error_500_htmł{ = "HTTP/1.1 500 Internal Server Error\r\n" "Connection: close\r\n" "Content-Type: text/htmł\r\n" "\r\n" "<html>" "<headxtitłe>error</titlex/head>" "<bodyxhl>500 Internal Server Error</hlx/body>" "</html>"; static const char version_not_supported_505_html[] = "HTTP/1.1 505 HTTP Version Not Supported\r\n" "Connection: close\r\n" "Content-Type: text/html\r\n" "\r\n" "<html>" "<headxtitle>error</title></head>" "<body><hl>505 HTTP Version Not Supported</hlx/body>" "</html>"; switch{http->status { case Bad_Request_400: http data_rom(http, bad_request 400_html, sizeof(bad_request_400_html - 1; return; case Not_Found_404: http_data_rom{http, not_found_404_html, sizeof{not_found_404_html - ł; return; case HTTP Version_Not_Supported_505: http_data_rom(http, version_not_supported_505_html, sizeof{version_not_supported_505_html - 1; return; default: http data_rom{http, internal_server_error_500_html, sizeof{internal_server_error_500_html - 1; return; i } Plik httpjserver.c - obsługa połączenia TCP' ' W pliku http_server.c znajduje się im plem entacja funkcji HTTPserverStart, która urucham ia serwer HTTP. Pisałem ju ż o niej w jednym z poprzednich podrozdziałów. Pozostało omówienie funkcji zwrotnych obsługujących połączenie TCP. Ich im plem entacja bazuje na funkcjach serwera TCP, który opisałem w przykładach 5a i 5b. Funkcja zwrotna accept_callback jest wywołana przez bibliotekę lwip po odebraniu połączenia TCP. Jej zadaniem jest przydzielenie do tego połączenia nowej struktury typu http i ustawienie pozostałych funkcji zwrotnych. Wartość stałej P O M W p ę r $ECOND dobieramy tak, aby funkcja zwrotna tcp_poll była wywołana co sekundę. i*i
272 10. Serwis WWW 10.2. Przykład 10 - prosty serwis WWW 273 Sdefine POLL_PER_SECOND 2 static err_t accept_callback(void *arg, struct tcp_pcb *pcb, err_t err { struct http *http; http = http new(; if (http ==~NULL return ERR_MEM; tcp_arg(pcb, http; tcp_err(pcb, conn_err_callback/ tcp_recv(pcb, recv_callback; tcp_sent(pcb, sent_callback; tcpj>oll(pcb, poll_callbaek, POLL_PER_SECOND; return ERR_OK; } Funkcja zwrotna re c v _ c a lib a c k jest w ywołana przez bibliotekę lw lp po odebraniu danych z połączenia TCP lub po zam knięciu tego połączenia. W celu przetworzenia odebranych danych wywołujemy funkcję h ttp _ engin e. W reakcji na zamknięcie połączenia TCP przez klienta wywołujemy funkcję clo se_ co n n ectio n, którą opisuję na końcu tego podrozdziału. static err_t recv_callback(void *http, struct tcp_pcb *pcb, struct pbuf *p, err_t err ( if (p I tcp_recved(pcb, p->tot len; http_engine(http, pcb, p; pbuf_free(p; 1 else close connection(http, pcb; return ERR_OK; i Funkcja zwrotna se n t c a llb a c k jest wywołana przez bibliotekę lw lp po odebraniu potwierdzenia dostarczenia danych. Za pomocą funkcji h ttp _ s e n t sprawdzamy, czy cała odpowiedź została już wysłana. Jeśli wszystkie dane zostały wysłane, kończymy połączenie TCP, wywołując funkcję clo se_ co n n ectio n. Jeśli pozostały jakieś dane do wysłania, wywołujemy funkcję h ttp _ sen d, która w ysyła kolejną ich porcję. static err_t sent_callback(void *http, struct tcp_pcb *pcb, ul6_t len { if (http_sent(http, close_connection(http, pcb; else http_send(http, pcb; return ERR_OK; Funkcja zwrotna p o ll_ c a llb a c k jest w ywołana cyklicznie przez bibliotekę lwlp. Za pom ocą funkcji h ttp _ tim e o u t zmniejszamy licznik czasu, który pozostał do zakończenia obsługi klienta i jeśli czas ten ^p ły n ął, zamykamy połączenie TCP, wywołując funkcję c lo s e jc o n n e c tio n. W funkcji p o ll_ c a llb a c k możemy też wywołać funkcję h ttp _ send, ale nie w ydaje się to potrzebne. static err_t poll_callback(void *http, struct tcp_pcb *pcb ( if (http_timeout(http close_connection(http, pcb; /* Można też wysyłać tu. else http_send(http, pcb; */ return ERR OK; i Funkcja zwrotna c o n n _ err_ callb ack jest wywołana przez bibliotekę lwlp, gdy połączenie TCP zostanie zamknięte awaryjnie. Jej zadaniem jest zwolnienie pamięci przydzielonej przez serwer do obsługi klienta. W tym celu wywołujemy funkcję h ttp _ d e le te. static void conn_err_callback(void *http, err_t err ( http_delete (http; } Pomocnicza funkcja c lo se_connectio n zwalnia wszystkie zasoby przydzielone przez serwer do obsługi klienta i zamyka połączenie TCP. static void close_connection(struct http *http, struct tcp_pcb *pcb { tcp_recv(pcb, NULL; tcp_sent(pcb, NULL; tcp_polł(pcb, NULL, 0; http_delete(http; tcp_arg(pcb, NULL;, tcp_close(pcb;.2.5. Plik http_application.c Plik http_application.c zawiera im plem entację logiki działania aplikacji WWW. Przerobienie prezentowanego w tym rozdziale przykładu na potrzeby innego serwisu W W W wymaga tylko modyfikacji tego pliku. Zaczynamy od zdefiniowania tablicy wskaźników re s o u rc e _ tb l do napisów zawierających URI, które serwer ma rozpoznawać. M usimy również zdefiniować rozm iar re s o u rc e _ tb l_ le n tej tablicy. Aby kod źródłowy był przejrzysty, definiujemy też za pomocą dyrektyw S define identyfikatory indeksów w tej tablicy.»define MAIN_PAGE 0»define STM32_LOGO 1»define DONE_PAGE 2 const char * const resource_tbl[] = { *7", "/stm32_1ogo.gi f, "/led" i; const int resource_tbl_ien = sizeof(resource_tbl / sizeof(resource_tbl[0]; Jeśli używamy formularzy HTM L, musimy zdefiniować tablicę wskaźników p a ra - m etep_tbl do napisów zawierających parametry formularzy oraz rozmiar pararaeteiw bl* l<ąn tej tablicy. Aby zwiększyć czytelność kodu źródłowego, definiujemy też za pom pcą dyrektyw lldefine identyfikatory indeksów w tej tablicy.
274 10. Serwis WWW 10.2. Przykład 10 - prosty serwis WWW 275 ((define LED G O Idefine LED_R ł const char * const parameter_tbl{3 = { "LE0=G", "LED-R" I; const int parameter tbl_len = sizeof (parameter tbl / sizeof (parameter_tbl(oj; Serwer w celu wygenerowania odpowiedzi wywołuje funkcję raake_http_answer. W funkcji tej najpierw wywołujemy funkcje parsera: http_parser_ m eth o d i h ttp p a rs e r reso u rce, aby poznać metodę i URI, które znajdowały się w żądaniu przysłanym przez klienta. Następnie, zależnie od URI i metody, wygenerowanie odpowiedzi delegujemy do właściwej funkcji lub zwracamy status 404 Not Found. status_t make_http_answer(struct http *http, const struct http_parser *parser { method_t method; int idx; method = http_parser_method(parser; idx = http_parser resource(parser; if (idx == MAIN_PAGE &6 method == GET_METHOD return http_main_page(http; else if (idx == STM32_LOGO && method == GET METHOD return http_stm32_logo(http; else if (idx == D0NE_PAGE method =»* P0ST_METH0D return http_done_page(http, parser;, else return Not_Foundj304; i O dpowiedź zawierającą główną stronę serwisu, przedstawioną na rysunku 10.1, generujemy za pomocą funkcji http_m ain_page. Opis strony tworzymy dynamicznie. Stały wzorzec całej odpowiedzi znajduje się w statycznej tablicy main_page_html. Stała raain_page_len zawiera rozm iar bufora, w którym ma być um ieszczona odpowiedź i który alokujemy za pom ocą funkcji http_data_new. Rozm iar bufora jest nieco większy niż długość napisu raain_page_htral, gdyż trzeba zapewnić miejsce na wstawiane wartości. Serw er może sym ultanicznie obsługiwać wiele klientów. Dlatego do formatowania odpowiedzi używamy funkcji s n p r in tf r, która jest w spółużywalna. Za pomocą funkcji RedLEDstate i G reenledstate odczytujemy stan diod świecących i ustawiamy odpow iednio stan pól wyboru w formularzu. Jeśli nie powiedzie się przydział bufora lub funkcja _ s n p r in tf _ r zwróci błąd, to zwracamy status 500 Internal Server Error. Po poprawnym sformatowaniu odpowiedzi ustawiamy za pomocą funkcji h ttp _ d a ta _ le n właściwą długość odpowiedzi, bez terminalnego zera, którego nie należy wysyłać. Jeśli cala odpow iedź nie mieści się w buforze (choć nie widać do lego realnego powodu, to wysyłamy część, która się zmieściła, w nadziei żc jest to lepsze rozwiązanie niż niewysłanie żadnej odpowiedzi. Na koniec, jeśli wsźystko poszło dobrze,^zwracamy status 200 O K. static status_t httpjnairtpa'ge (struct http *http ( static const char main_page_html{] = "HTTP/1.1 200 0K\r\n" "Connection: close\r\n" H "Content-Type: text/html; charset=utf-8\r\n "\r\n" "<html>" "<headxtitle>przykładowy Serwer HTTP</titlex/head>" "<body>" "<center>" "<pximg src=\"stm32 logo.gif\" altä\nstm32 L0G0\" /x/p>" "<pxfont size=\"6\">%u %02u:%02u:%02u.%03u</fontx/p>" f'<hr />" "<form acfcion=v71ed\" method=\"post\">" "<p>" "Czerwona dioda świecąca " "<input type=\"checkbox\" name=v'led\" value=\"r\" %s/>" "</p>" "<p>" "Zielona dioda świecąca " "<input type^'checkboxx" name=\"led\" value=\"g\" %s/>" "</p>" "<pxinput type=\"submit\" value=x"zmień\" /></p>" "</form>" "<hr />" "<P>" "lwip/%u.%u.%u CMSIS/%x.%x " "STM32F10x Standard Peripherals ŁibraryAu.%u.%u" łifdef NEWLIB_VERSION " newlib/" _NEWLIB_VERSION lendif "</p>" "</center>" "</body>" "</html>"; static const size_t main_page_2en = sizeof (main page_html + 12; int data_size; unsigned d, h, m, s, ms; char *data; struct _reent reent; data = http_data_new(http, main_page_len; if (data == NULLT return Internal_Server_Error_500; GetLocalTime(&d, &h, &m, &s, &ms; data_size = _snprintf_r(&reent, data, main page_len, main_page_html, d, h, m, s, ms, RedLEDstate(? "checked " GreenLEDstate(? "checked " : LWI PJfERS I0NJ4A JOR, LWI P_VERSI0N_MINOR, LWIP~VERSION_REVISION, CM3_CMS I S_VERSION_MAIN, _CM3_CMSIS_VERSI0N_SUB, _STM32F10X_STDPERIPH_VERSION_MAIN, STM32F1OX_STDPERIPh"v ERSION_SÜ01, STM32 FI 0X STDPERI PH_VERSION_SUB2}; if (data_size <= 0 return Internal_Server_Error_500; if Jl{size_tdata_size < main_page_len http_wtl_len (http, (size_tdata_size; else '
276 10. Serwis WWW 10.2. Przykład 10 - prosty serwis WWW 277 http_data_len(http, main_page_len - 1; return OK_200; I Na głównej stronie serwisu W W W osadzony jest obrazek z logo m ikrokontrolerów STM 32 - zasób stm 32Jogo.gif. Gdy klient zażąda tego zasobu, wywołujemy funkcję http_stm 32_logo. Sam obrazek oraz jego rozm iar zdefiniowane są w pliku stm 32_logo.h, który opisuję w następnym podrozdziale. Funkcja http stm32 logo jest bardzo prosta, umieszcza w łaściwe dane w strukturze typu h ttp za pomocą funkcji http_data rom i kończy działanie, zwracając status 200 O K. atatic status_t http_stm32_logo(struct http *http ( łinclude <stm32_logo,h> http_data_rom(http, stra32_iogo_gif, atffl32_logo_gif_len ; return OK_200; 1 Głów na strona serwisu zawiera też form ularz służący do sterowania diodami świecącym i. Po kliknięciu na przycisk Zm ień tego formularza przeglądarka wysyła żą- I ' 5 P rzy k ła d o w y S erw er HTTP - M icrosoft In te r n e t Enplorer File Edit View Favorites Tools Help Q ü a c k -r ( J - (W S (;,' J>.'> Search S ^ - ' Favorites V - m - Address j.jÿ http ://192. 168.51.89/led J Go I Unta'» Wykonano Powrôt '»i1 danie z metodą POST. Obsługą tego żądania zajm uje się funkcja http_done_page, w której najpierw sprawdzamy za pom ocą funkcji http_parser_param, jakie parametry zawierało to żądanie i stosownie do tego włączamy lub wyłączamy odpowiednią diodę świecącą. Następnie odsyłamy odpowiedź zawartą w tablicy done_page_html. Długość odpowiedzi określa stała done page_len. Po otrzymaniu odpowiedzi przeglądarka wyświetla, przedstawioną na ry sunku 10.2, stronę informującą o wykonaniu zmiany. Po kliknięciu na tej stronie w odnośnik Powrót wracamy do głównej strony serwisu WWW. Funkcja http_done_page zawsze zwraca status 200 O K. static status_t http_done_page(struct http *http, const struct http_parser *parser { static const char done_page html [] = "HTTP/1.1 200 0K\r\n" "Connection: close\r\n" "Content-Type: text/html; charset=utf-8\r\n" "\r\n" "<html>" "<headxtitle>przykładowy Serwer HTTP</titlex/head>" "<body>" "<center>" "<font size=\m\">" "<p>wykonano</p>" " <pxa href=\"/\">powrót</ax/p>" </font>" "</center>" </body>" "</html>"; static const size_t done_page_len = sizeof(done_page_html - 1; if (http_parser_param(parser, LED_G GreenLEDon ( ; else GreenLEDoff(; if (http_parser_param(parser, LED_R} RedLEDon (; else RedLEDoff (; http_data_rom(http, done_page_html, done_page_len; return OK 200/ -,Ti 10.2.6. Plik stm 32Jogo.h Plik stm32_logo.h zawiera kom pletną odpow iedź serwera na żądanie przesiania obrazka, który ma się wyświetlić na głównej stronie serwisu. Odpowiedź znajduje się w tablicy stm 32_ logo_ gif. Stała stm 32_łogo_gif len zawiera długość odpow iedzi. Definicje te umieściłem w osobnym pliku, a nie bezpośrednio w pliku http_application.c, gdyż zajm ują dużo miejsca (poniżej jest wydrukowany tylko mały fragment pliku stm32_logo.li, co bardzo zmniejszyłoby czytelność tekstu źródłowego. Rys. 10.2. Strona WWW informująca o zmianie stanu diod świecących > : I internet :y, ' U static const char stm32_logo_gif(] = "HTTP/ł.l 200 0K\r\n" "(JcsuiectaJn: close\r\n" "Conten^łype: image/gif\r\n" "\r\n" t i
278 JO. Serwis WWW 10.2. Przykład 10 prosty serwis WWW 279 "GIF89a\xfa\x00\xa0\xG0\xf7\xQ0\x00\xf4\xfb\xfe" "\xe7\xf7\xf c:\xb6\xdl\x78\x6c\xb3\x91\xf c:\xc5\x31\x00\xa3\xba\xb6" "\x5b\x03\x00\x0b\x78\xc0\x40\xl6\xf2\x90\xl7\xcc\xa6\x06\xd7\xl3" "\x70\x01\x01\x00\x3b"; static const size_t stm32_logo_gil_len = sizeof(stm32_logo gif - 1; Konwersji pliku binarnego z obrazkiem do postaci akceptowanej przez kompilator C wykonałem za pom ocą prościutkiego programu, którego tekst źródłowy jest w archiwum z przykładami w pliku im age2c.c w katalogu,/make/exlo_httpd. 10.2.7. Plik ex_htłpd.c Plik ex_httpd.c zawiera funkcję main przykładu. Jest ona bardzo podobna do funkcji main z przykładu 5a. int main{ ( static struct netif netif;, static struct ethnetif ethnetif = {PHY_ADDRESS; uint8_t confbit/ Delay(1000000; confbit = GetConfBit(; AllPinsDisableO; LEDconfigure(; RedLEDon(; SET_IRQ_PROTECTION{; error_resetable(clkconfigure(, ł; error_permanent(localtimeconfigure(, 2; error_resetable(ethconfiguremii{, 4; netif.hwaddr[0] = 2; netif.hwaddr[l] = (BOARDJTYPE» 8 & 0xff; netif-hwaddr[2] = BOARDJTYPE & 0xff; netif.hwaddr 31 = (ETH BOARD» 8 6 0xff; netif.hwaddr[4j = ETH_BOARD & 0xff; netif.hwaddr[5j = 1 + confbit; if (iconfbit { IP4_ADDR(finetif.ip_addr, 192, 168, 51, 84; IP4_ADDR(finetif.netmask, 255, 255, 255, 240; IP4 ADDR (finetif.gw, 192, 168, 51, 81; } else { IP4_ADDR(&netif,ip_addr, 0, 0, 0, 0; IP4_ADDR(&netif.netmask, 0, 0, 0, 0; IP4_ADDRffinetif.gw, 0, 0, 0, 0; -,» *' error_resetable(lwipinterfacelnit(finetif, fiethnetif, 5; LWIPtimersStart(; error_resetable(dhcpwait(finetif, 10, 4, 6; error_resetable (HTTPserverStart (HTTP PORT, 7 ; RedLEDoff (; for(;;; 5 > 10.2.8. Testowanie przykładu Prezentowany w tym rozdziale przykład powinien współpracować z dowolną przeglądarką internetową. Jednak nie w szystko da się za pom ocą przeglądarki przetesto- wad. W szczególności trudno jest w ten sposób wysiać błędne żądanie. Odporność serwera na różnego rodzaju błędy najlepiej testuje się za pomocą programu netcat lub telnet. Poniżej zam ieszczam kilka przykładowych testów wykonanych z konsoli Linuksa. Podczas tych testów serwer używał adresu IP 192.168.51.89 uzyskanego za pom ocą DHCP. Ściągnięcie głównej strony serwisu. Ze zwróconej odpowiedzi wynika, że czerwona dioda świecąca jest włączona, a zielona wyłączona. $ echo -en "GET / HTTP/1.l\r\n\r\n" nc 192.168.51.89 80 HTTP/1.1 200 OK Connection: close Content-Type: text/html; charset=utf-8 <html><headxtitle>przykładowy Serwer HTTP</title></headxbody><cente r X p X i m g src="stm32_logo.gif" alt="stm32 LOGO" / x / p x p x f o n t size=" 6">0 01:37:22.610</fontx/p><hr / x f o r m action="/led" method="post"x p>czerwona dioda świecąca <input type="checkbox" name="led" value="r" checked /x/pxp>zielona dioda świecąca cinput type="checkbox" name= "LED" value="g" /x/p><pxinput type="subn\it" value="zmień" / x / p x / f o r m X h r /Xp>lwIP/1.3.2 CMSIS/1.30 STM32F10x Standard Peripherals Lib rary/3.3.0 newlib/l.18.0</px/center></bodyx/html> W yłączenie czerwonej diody świecącej i włączenie zielonej. S echo -en "POST /led HTTP/1.l\r\nContent-Length: 5\r\n\r\nLED=G" \ i nc 192.168.51.89 80 HTTP/1.1 200 OK Connection: close Content-Type: text/html; charset=utf~8 <html><headxtitle>przykladowy Serwer HTTPC/titleX/headxbodyXcente rxfont size-"4"xp>wykonano</p><p><a href="/">powrót</ax/px/fontx /centerx/body></html> W ysłanie błędnego żądania. $ echo -en "PET / HTTP/1.l\r\n\r\n" I nc 192.168.51.89 80 HTTP/1.1 400 Bad Request Connection: close Content-Type: text/html <html><head><title>error</titlex/headxbodyxhl>400 Bad Request</hl> </bodyx/html> Prośba o nieistniejący zasób. $ echo -en "GET /abc HTTP/1.l\r\n\r\n" 1 nc 192.168.5-1.89 80 HTTP/1.1 404 Not Found Connection: close Content-Type: text/html <htmlxbeadxtitle>error</titlex/headxbodyxhl>404 Not Found</hl></ bodyx/html> W ysłanie błędnej wersji HTTP. $ echo -en "GET / HTTP/2.0\r\n\r\n nc 192.168.51.89 80 H T ijldu.i 595 HTTP Version Not Supported ConnectiJm (close Content-Typfii text/html
280 10. Serwis WWW <htmlxheadxtitie>error</titlex/headxbodyxhl>5g5 HTTP Version Not Supported</hlx/body></htmi> Tesr przekroczenia czasu. Połączenie pow inno zostać zamknięte przez serwer po 10 sekundach. $ telnet 192.168.51.89 80 Trying 192.168.51.89... Connected to local-89.localdomain (192,168.51.89}. Escape character is,aj*. Connection closed by foreign host. Narzędzia GNU
282 Dodatek. Narzędzia GNU Dodatek. Narzędzia GNU 283 Przykłady umieszczone w lej książce kompilowałem w środowisku systemu operacyjnego Linux za pomocą darm owego zestawu narzędzi (ang. toolchain GNU z wykorzystaniem darmowej biblioteki Newlib. W tym dodatku dokum entuję proces instalowania środowiska program istycznego i kompilowania przykładów. Opis ten jest przeznaczony dla Czytelników, którzy ju ż mają pewne doświadczenie w używaniu narzędzi GNU. Instalowanie środowiska program istycznego należy rozpocząć od pakietu Binutils. Jego najnowszą wersję można znaleźć na stronie http://ftp.gnu.org/gnu/binutils. W trakcie przygotowywania książki najnowsza w ersja tego pakietu miała numer 2.20.1. Jej instalowanie przebiega następująco: tar -zxf binutils-2.20.1.tar.gz cd binutils-2.20.1 mkdir build cd build../configure --target=arm-elf prefix=/usr/iocal/arm make su make install exit cd../.. Instalujemy kompilator skrośny (ang. cross com piler - kom pilacja jest wykonywana na komputerze o innej architekturze niż architektura procesora, na której ma być wykonywany skom pilowany program. Param etr ta r g e t określa docelową architekturę, na którą będą generowane pliki wynikowe. Param etr - - p r e f i x określa miejsce, gdzie będą umieszczane pliki w ykonywalnę zestawu narzędzi. Aby mieć do nich dostęp, trzeba ustawić ścieżkę poszukiwania plików. Najprościej można to zrobić w pliku.bash_profile (w niektórych dystrybujcach.profile, który znajduje się w katalogu domowym użytkownika. Przykładowy wpis w tym pliku wygląda następująco: PATI!=$PATH:$H0ME/bin:/usr/local/arm/bin:/usr/local/msptSO/bin export PATH Na czas instalowania kolejnych składników środowiska program istycznego wystarczy wykonać polecenie: export PATH=$PATH:/usr/local/arm/bin Pamiętajmy o dodaniu we wszystkich ścieżkach podkatalogu /bin. Polecenie make i n s t a l l powinno wykonywać się z uprawnienianii^ifdministratora, dlatego przed jego wykonaniem przełączamy się w tryb administratora za pomocą polecenia su, a po jego wykonaniu wracamy do trybu użytkownika za pomocą polecenia e x it. Kolejnym pakietem, który trzeba zainstalować, jest GCC. Adresy serwerów oferujących GCC dostępne są na stronie http://gcc.gnu.org/mirrors.html. Ze względu na występujący w rdzeniach Cortex-M 3 drobny błąd w niektórych wariantach instrukcji ld rd, należy użyć wersji co najmniej 4.4.0. Od tej wersji dla Cortex-M 3 domyślnie jest ustawiona opcja -m fix -co rtex -m 3 -ld rd, która zapobiega używaniu przez kompilator wadliwie działających instrukcji. W trakcie przygotowywania książki najnowsza wersja GCC miała numer 4.4.3. Po ściągnięciu pakietu należy go rozpakować: tar -zxf gcc-4.4.3.fcar.gz cd gcc-4.4.3 M ikroprocesory ARM używają trzech zestawów instrukcji: ARM, Thumb i Thumb-2. Dla każdego z tych zestawów trzeba mieć oddzielny zestaw bibliotek. Umożliwia to opcja e n a b le -m u ltilib. Opcji tej jednak nie trzeba podawać podczas konfigurowania zestawu narzędzi, gdyż jest domyślna. Natomiast w pliku Jgcc/config/arm /t-arm -elf musimy zdefiniować, jakie wersje bibliotek mają zostać skom pilowane. Należy wyedytować w nim następujące wiersze: MULTILIB_OPTIONS = mthumb mcpu=cortex-m3 MULTILIBJJIRNAMES = thumb coi:tex-m3 MULTILIB_EXCEPTIONS = mcpu=cortex-m3 MULTILIB_MATCHES Powyższe parametry oznaczają, że w domyślnym dla bibliotek katalogu znajdą się wersje bibliotek używające zestawu instrukcji ARM, w jego podkatalogu thumb - wersje używające zestawu Thum b, a w podkatalogu thumb/cortex-m3 - wersje używające zestawu Thumb-2. Konsolidator będzie dołączał właściwe biblioteki na podstawie argumentów podanych w linii poleceń (konsolidatora lub kompilatora. Domyślnie dołączane będą biblioteki używające zestawu instrukcji ARM. Opcja -mthumb będzie wymuszać dołączanie bibliotek używających zestawu instrukcji Thumb. Zastosowanie łącznie opcji -mthumb oraz -mcpu=cortex-m3 będzie pow o dować dołączanie bibliotek używających zestawu instrukcji Thumb-2. Nazwy opcji kompilatora (z pominięciem początkowego myślnika specyfikuje się za pomocą parametru M0LTILIB_OPTIONS. Nazwy podkatalogów, w których mają być um ieszczane odpowiadające im w ersje bibliotek, wymienia się w parametrze MULTILIB_ DIRNAMES. Param etr MULTILIB_EXCEPTIONS służy do opisania zabronionych kom binacji opcji kompilatora. GCC nie dopuszcza użycia samej opcji -m cpu=cortex- -m3, bez opcji -mthumb. Po skonfigurowaniu wersji bibliotek możemy przystąpić do skom pilowania kompilatora skrośnego: mkdir build cd build../configure target=arm-elf prefix=/usr/local/arm \ enabie-languages=c,c++ with-newlib without-headers \ disable-shared with-mpfr=/usr/local Opcja en ab łe -lan g u ag es określa języki programowania, dla których mają zostać zbudowane kompilatory. Ustawienie łącznie opcji w ith -n ew lib i w ith o u t-h e ad ers sprawia, że biblioteka LIBGCC zostanie zbudowana bez wsparcia jakichkolwiek plików nagłówkowych, czyli nie będzie ona korzystać z żadnej innej biblioteki, natom iast pozostałe biblioteki będą mogły korzystać z biblioteki Newlib i jej plików nagłówkowych. Takie ustawienia są typowe; gdy buduje się kompilator dla systemów wbudowanych. Opcja d is a b le -s h a re d wyłącza używanie bibliotek dzielonych, ładowanych dynamicznie, co w przypadku mikrokontrolerów nie m iałoby po prostu sensu. Opcja w ith -m p fr= /u s r/lo c a l okazała się konieczna, gdyż konfigurator nie znalazł położenia biblioteki MPFR, potrzebnej do skompilowania GCC. Za pomocą tej opcji w skazuje się ścieżkę, gdzie ta biblioteka jest zainstalowana. W konkretnym środowisku opcja ta może być zbędna, ale może okazać się potrzebne użycie innych opcji. Sam proces kompilowania przebiega następująco: make instaljlrgcc