ISIX-RTOS – przykład 4 – wątkowa obsługa portu szeregowego RS232

 

Opis systemu ISIX-RTOS i jego funkcji opublikowaliśmy w artykule „Mini system operacyjny dla STM32 – wprowadzenie”, który można przeczytać tu.

W przykładzie czwartym pokażemy, w jaki sposób napisać uniwersalną klasę/sterownik obsługi portu szeregowego. Obsługa portu szeregowego przyda się z pewnością w większości aplikacji, chociażby do tworzenia komunikatów diagnostycznych na etapie uruchamiania projektu. Sterownik dla portu szeregowego napiszemy z wykorzystaniem systemu przerwań. W przypadku urządzeń znakowych najbardziej odpowiednim będzie użycie kolejek FIFO, jednej nadawczej oraz drugiej odbiorczej. Ponieważ w kontekście przerwań nie możemy wywoływać funkcji blokujących, należy użyć specjalnych metod z przyrostkiem _isr (push_isr(), pop_isr() klasy fifo.
Aby pokazać możliwości pracy wielowątkowej w przykładzie stworzymy dwa wątki:

  • odbiorczy, służący do odbioru danych z portu szeregowego, który w zależności od odebranego znaku będzie sterował pracą diod LED:D1 i D2 zamontowanych na płytce STM32Butterfly,
  • nadawczy, którego działanie sprowadzać się będzie do odczytu stanu joysticka oraz transmisję poprze UART informacji tekstowej o jego aktualnej pozycji.

 

 

 

Rys. 1. Schemat 
elektryczny konwertera napięciowego RS232 i sposób jego dołączenia do płytki STM32Butterfly

Rys. 1. Schemat elektryczny konwertera napięciowego RS232 i sposób jego dołączenia do płytki STM32Butterfly

 

 

 

Po podłączeniu zestawu STM32Butterfly do interfejsu RS232 komputera w celu przetestowania działania aplikacji należy uruchomić ulubiony program terminalowy (np. Minicom, Hyperterminal itp.) oraz skonfigurować wybrany port szeregowy z następującymi parametrami transmisji: prędkość – 115200 b/s, liczba bitów danych: 8, 1 bit stopu, brak kontroli parzystości i kontroli przepływu. Po zaprogramowaniu mikrokontrolera w oknie terminala powinien pojawić się komunikat informujący o uruchomieniu programu. Po wciśnięciu na klawiaturze PC klawisza A mamy możliwość włączenia diody LED D1 i jej wyłączenia za pomocą klawisza B. W podobny sposób możemy sterować pracą diody LED D2 – służą do tego celu klawisze C i D. Przechylenie osi joysticka powoduje wyświetlona informacji tekstowej o jej położeniu. Sposób działania aplikacji z podziałem na wątki przedstawiono na rys. 2.

 

 

 

Rys. 2. Sposób 
działania przykładowej aplikacji z podziałem na wątki

Rys. 2. Sposób działania przykładowej aplikacji z podziałem na wątki

 

 

Aplikacja składa się z dwóch wątków, które używają jednego portu szeregowego. Jeden wątek jest odpowiedzialny za odczyt danych z portu szeregowego oraz włączanie i wyłączanie diod LED D1 i D2. Drugi wątek jest odpowiedzialny za cykliczny odczyt stanu joysticka oraz – w przypadku wykrycia odchylenia od położenia standardowego – wysłania informacji o kierunku wychylenia jego osi. Hierarchię klas aplikacji przedstawiono na rys. 3.

 

 

 

Rys. 3. Hierarchia klas 
projektu

Rys. 3. Hierarchia klas projektu

 

 

 

Podobnie jak we wszystkich prezentowanych przykładach klasa the_serialapp jest klasą aplikacji przechowującą wszystkie obiekty. Statyczny obiekt tej klasy jest tworzony w funkcji głównej main() (list. 1).

List. 1. Funkcja główna main

 

 

Deklaracje klasy obiektu aplikacji przedstawiono na list. 2.

List. 2. Deklaracja klasy serialapp

Klasa the_serialapp zawiera obiekt usart klasy led_receiver, która stanowi obiekt portu szeregowego RS232. Obiekt ledrcv klasy led_receiver odpowiedzialny jest za odbiór znaków z portu szeregowego oraz sterowanie pracą LED w zależności od odebranego znaku. Obiekt keytran klasy key_transmitter odpowiedzialny jest za odczyt stanu styków joysticka oraz wysyłanie informacji do portu. Oba obiekty przyjmują referencję do wspólnego obiektu klasy usart_buffered oraz dziedziczą z klasy isix::task_base, więc stanowią odrębne wątki. Transmisja z wykorzystaniem portu szeregowego RS232 jest dupleksowa. Ponieważ jeden wątek tylko odczytuje dane z portu, natomiast drugi tylko zapisuje dane do tego portu, pracują one zupełnie niezależnie i nie wymagają wzajemnej synchronizacji za pomocą semafora, jak miało to miejsce w przypadku obsługi magistrali I2C, która jest simpleksowa. Klasa usart_buffered jest uniwersalną klasą sterownika portu szeregowego RS232 wykorzystującą sprzętowy port USART mikrokontrolera rodziny STM32. Klasa została napisana w taki sposób, aby była możliwość użycia dowolnego portu szeregowego dostępnego w mikrokontrolerze. Deklaracja klasy znajduje się w pliku i2c_host.cpp (list. 3).

List. 3. Deklaracja klasy sterownika portu szeregowego

Klasa została zaprzyjaźniona z handlerami przerwań portów szeregowych, które zostały wcześniej zadeklarowane z linkowaniem typu C, co powoduje wyłączenie manglowania nazw. Funkcje obsługi przerwań są wywoływane przez kontroler sprzętowy w momencie wystąpienia przerwania bez dodatkowych parametrów, co wymusza istnienie dostępu do instancji klasy obsługującej port szeregowy, poprzez wskaźnik lub referencję globalną. Wskaźniki dostępu do poszczególnych instancji klas przypisanych do portów szeregowych zostały umieszczone w nienazwanej przestrzeni nazw w pliku implementacji (usart_buffered.cpp), przez co dostęp do wskaźników jest możliwy tylko w obrębie danego modułu (list. 4).

List. 4. Fragment implementacji klasy portu szeregowego

Zadeklarowanie przyjaźni funkcji z klasą umożliwia wywołanie dowolnych metod z funkcji zaprzyjaźnionej, co zostało wykorzystane do wywołania metody isr() stanowiącej wektor obsługi przerwania. Klasa obsługi portu szeregowego zawiera dwa obiekty tx_queue, rx_queue (list. 5) klasy isix::fifo, które są wykorzystywane jako bufor nadajnika oraz bufor odbiornika. Konstruktor klasy przyjmuje cztery parametry, adres wybranego kontrolera portu szeregowego (np. USART1, USART2), prędkość transmisji z ustawionym argumentem domyślnym na 115200, wielkość kolejek FIFO ustawionych domyślnie na 192 bajty oraz tryb kontroli parzystości z domyślnym argumentem ustawionym na parity_none.

List. 5. Implementacja konstruktora klasy portu szeregowego

Konstruktor odpowiedzialny jest za inicjalizację wybranego układu USART zgodnie z zadanymi parametrami. Na liście inicjalizacyjnej konstruktora są tworzone obiekty kolejek FIFO o zadanej wielkości. Następnie w zależności od numeru portu szeregowego inicjalizowane są linie GPIO tak, aby pełniły one funkcję obsługi układu peryferyjnego, oraz włączany jest wybrany USART, co realizowane jest przez metody periphcfg_usart1(), oraz periphcfg_usart2(). Następnie włączany jest port szeregowy oraz jest konfigurowany podzielnik układu tak, aby pracował z zadaną prędkością poprzez wywołanie metodyset_baudrate(). W następnej kolejności wywoływana jest metoda set_parity(), której zadaniem jest odpowiednie skonfigurowanie bitu parzystości. W zależności od wykorzystanego układu USART, do wskaźników obiektów przypisanych do przerwania przypisywane są adresy obiektu, oraz w kontrolerze NVIC włączane są przerwania. Sterownik posiada dwie podstawowe metody interfejsu umożliwiające wysłanie oraz odbieranie znaków, które mogą być wykorzystane przez inne klasy.

List. 6. Definicja metody wysłania znaku do portu szeregowego

Metoda putchar()(list. 6) jest odpowiedzialna za wysyłanie znaku do portu szeregowego, przyjmuje ona dwa parametry znak do wysłania, oraz maksymalny dopuszczalny czas oczekiwania, na miejsce w kolejce FIFO. Działanie tej metody jest bardzo proste i sprowadza się do próby zapisania danych do kolejki, a następnie wywołanie metody start_tx(), które zadaniem jest rozpoczęcie nadawania znaków, pozostała część jest realizowana przez procedurę obsługi przerwania.

List. 7. Definicja metody odebrania znaku z portu szeregowego 

Metoda getchar()(list. 7) umożliwia odbieranie znaków z portu szeregowego, przyjmuje ona dwa argumenty: referencję do znaku oraz maksymalny czas oczekiwania na ten znak. Działanie tej metody sprowadza się jedynie do wywołania metodypop() kolejki odbiorczej. Jeżeli w buforze jest jakiś znak umieszczony przez procedurę obsługi przerwania wówczas następuje jego odczytanie. Jeżeli w buforze nie ma ani jednego znaku następuje zablokowanie aktualnego wątku do momentu odebrania znaku.
Cała praca realizowana jest głównie przez procedury obsługi przerwania, które są wywoływane w momencie, gdy na wybranym porcie szeregowym jest miejsce w buforze nadawczym, lub został odebrany jakiś znak. Zgłoszenie przerwania od danego portu szeregowego powoduje rozpoczęcie wykonania funkcji usart1_isr_vector() lub usart2_isr_vector()list. 8.

List. 8. Implementacja funkcji obsługi przerwań

W przypadku, gdy do wskaźnika przypisanego do danego portu szeregowego został przypisany jakiś obiekt, wówczas wywoływana jest metoda isr()list. 9, odpowiedzialna za realizację procedury obsługi przerwania.

List. 9. Implementacja metody obsługi przerwań klasy portu szeregowego

Działanie procedury obsługi przerwania jest bardzo proste: sprowadza się do odczytania statusu, kontrolera USART oraz podjęciu odpowiedniej akcji. W przypadku, gdy przerwanie zostało wygenerowane w wyniku odebrania znaku, wówczas jest on odczytywany z rejestru danych, a następnie przekazywany do kolejki. Do wysłania znaku do kolejki FIFO używana jest nieblokująca metoda push_isr(), dedykowana procedurom obsługi przerwań. W przypadku, gdy zostało wygenerowane przerwanie, w wyniku braku danych w buforze nadawczym, wówczas znak odczytywany jest z kolejki nadawczej za pomocą nieblokującej metody pop_isr(), a następnie odczytany znak zapisywany jest do rejestru danych układu USART. W przypadku, gdy nie ma danych w kolejce, zerowana jest flaga zgłoszenia przerwania
Klasaled_receiver (list. 10) odpowiedzialna jest za odbieranie danych z portu szeregowego oraz sterowanie diodami LED w zależności od kodu odebranego znaku. Klasa dziedziczy z klasy bazowej isix::task_base.

List. 10. Deklaracja klasy led_receiver

Klasa zawiera referencję do obiektu sterownika portu szeregowego usart_buffered. Realizacja zadania odbywa się w metodzie wirtualnej main(), stanowiącą odrębny wątek systemowy – list. 11.

List. 11. Implementacja metody main klasy led_receiver

Działanie metody jest proste, sprowadza się do wywołania metody getchar() obiektu sterownika portu szeregowego, która blokuje się do momentu odebrania znaku. W przypadku odebrania prawidłowego kodu znaku, odpowiednie diody LED są włączane lub wyłączane.
Klasa key_transmiter(list. 12) odpowiedzialna jest za odczyt stanu joysticka oraz – w przypadku wykrycia zwarcia jego styków – wysłaniu informacji do portu szeregowego.

List. 12. Deklaracja klasy led_transmitter

Podobnie jak poprzednio klasa przechowuje referencje do obiektu portu szeregowego, a realizacja wątku dokonywana jest przez metodę wirtualną main()list. 13.

List. 13. Implementacja metody main klasy key_transmitter

Na początku za pomocą metody puts() sterownika portu szeregowego są wysyłane teksty powitalne do portu szeregowego, a następnie program wchodzi do pętli głównej. Pętla główna wykonywana jest cyklicznie z czasem DELAY_TIME (25 ms), co umożliwia sprawdzenie stany joysticka eliminując drgania zestyków. W przypadku wykrycia zmiany stanu portów sprawdzany jest numer klawisza, a następnie za pomocą metody puts sterownika portu szeregowego, wypisywane są komunikaty, informujące o pozycji joysticka.

 

O autorze