Bufor kołowy #3: przykład obsługi UART

UART STM32 bufor cykliczny

W tym artykule, przedstawię zastosowanie bufora kołowego na przykładzie UARTa, działającego w oparciu o przerwania. Zakładam, że wiesz, czym są i jak działają przerwania oraz jakie korzyści płyną z ich stosowania.

Kod z tego przykładu powinieneś bez większego problemu przenieść na dowolny mikrokontroler, jakiego używasz. Ja wykorzystam tutaj biblioteki SPL dla mikrokontrolerów STM32, ponieważ wydaje mi się, że dla osób, które programują inne mikrokontrolery, kod będzie bardziej czytelny, niż w przypadku operacji na rejestrach. W artykule pominę proces inicjalizacji i opiszę wyłącznie fragmenty dotyczące bufora kołowego. Całkowite kody źródłowe, wraz z funkcjami inicjalizującymi i główną funkcją programu, dla mikrokontrolera STM32F103, a także dla mikrokontrolera AVR ATmega328, znajdziesz na moim githubie.

Odbiór danych

Zaczniemy od danych przychodzących. Przy inicjalizacji należy pamiętać o włączeniu przerwania Rx UARTa.
Na początku definiujemy tablicę oraz nasz bufor cykliczny. Uwaga, przypominam, że jeśli korzystamy z jakiejś zmiennej w programie głównym i przerwaniu należy zaopatrzyć ją w przydomek volatile!

Słowo kluczowe volatile przed strukturą informuje kompilator, że wszystkie elementy struktury są ulotne, dzięki czemu nie musimy tego słowa pisać przed każdą zmienną w deklaracji struktury:

Trzeba jednak dodać volatile przed zmienną wskaźnikową, ponieważ bez tego wskaźnik byłby traktowany jako volatile, ale dane na które wskazuje już nie.

Dane do bufora odbiorczego dodajemy w procedurze obsługi przerwania od danych przychodzących (Rx), a odczytujemy je z bufora dopiero wtedy, gdy zajdzie taka potrzeba, np. gdy przyjdzie ich określona ilość, przyjdzie określony znak lub gdy procesor nie będzie wykonywał innych ważniejszych zadań. Nie stosując buforów, gdy dane przychodzą szybciej niż możemy je obsłużyć dochodzi do ich nieodwracalnej utraty.

Na początku ISR identyfikujemy źródło naszego przerwania:

Jeśli jest to przerwanie od danych przychodzących to zajmujemy się ich wrzucaniem do bufora. Cały kod dotyczący bufora kołowego jest niemal identyczny jak ten, który był zamieszczony w poprzednim artykule w funkcji circ_buffer_put_char(), więc pominąłem większość komentarzy, które tam były, aby nie zaciemniać kodu. Zamiast wywołania funkcji wrzuciłem jej ciało do procedury obsługi przerwania. Jedyną różnicą, jaka została wprowadzona, jest odwoływanie się bezpośrednio do pól bufora cyklicznego jako zmiennej globalnej, a nie przez wskaźnik. Stąd znaki „.” zamiast „->”. Jest jeszcze jedna linia, która może Cię zastanawiać:

Flaga USART_IT_RXNE pochodzi od „Receive Data register not empty”. Jest ona automatycznie czyszczona po wywołaniu funkcji USART_ReceiveData(). Jednak jak wiadomo, w buforze może nie być miejsca i tej funkcji nie uda się nam wywołać. W takim wypadku, gdybyśmy nie wyczyścili tej flagi, przerwanie byłoby cały czas wyzwalane, bo w rejestrze odbiorczym cały czas znajduje się bajt, którego nie możemy odczytać.

Do odczytania danych z bufora odbiorczego posłuży nam funkcja uart_get_char(), która różni się od znanej już Tobie funkcji circ_buffer_get_char() tylko tym, że tutaj, tak jak w procedurze obsługi przerwania, odwołujemy się do zmiennej globalnej bufora cyklicznego.

Dobrze, to odbiór danych mamy już za sobą. Teraz zajmiemy się wysyłaniem.

Wysyłanie danych

Może zastanawiasz się po co buforować dane wychodzące i korzystać z przerwań podczas wysyłania? W celu automatyzacji! Już wyjaśniam. Każdy interfejs komunikacyjny potrzebuje określonej ilości czasu na wysłanie pojedynczego bajta danych. Dlatego, podczas wysyłania bloku danych, chcąc wysyłać kolejne bajty, musielibyśmy za każdym razem czekać, aż zostanie zakończone wysłanie poprzedniego.  Zamiast tego, możemy wykorzystać bufor, do którego wrzucamy cały blok danych i przejść do innych czynności. Cała reszta będzie realizowana w przerwaniu od Tx, które jest wyzwalane za każdym razem, gdy zwolni się miejsce w rejestrze nadawczym UART i można wysłać kolejny bajt.

Podobnie jak przy odbiorze danych, definiujemy tablicę oraz bufor cykliczny.

Następnie tworzymy funkcję służącą do wysyłania pojedynczych znaków, a w zasadzie – do wrzucania ich do bufora.

Jak widzisz, niewiele się ona różni od znanej już Ci funkcji queue_put_char(). W przypadku wysyłania, przerwań nie włączamy przy inicjalizacji. Robimy to dopiero po dodaniu znaku do bufora, przed wyjściem z funkcji. Odpowiada za to linia:

Pozostało już tylko napisanie procedury obsługi przerwania od Tx.

Flaga USART_IT_TXE pochodzi od „Transmit Data register empty” i jest ona automatycznie ustawiana przy wywołaniu funkcji USART_SendData() przez co przerwanie będzie cały czas wyzwalane dopóki nie wyślemy wszystkich znaków z bufora i go nie wyłączymy.

Podsumowanie

Tak oto dobiegamy do końca cyklu o buforze kołowym. Mam nadzieję, że po przeczytaniu całości wszystko stało się w miarę jasne. Jak widać, podczas korzystania z UARTa całość sprowadza się tylko do korzystania z dwóch funkcji:

Inne, będą opierały się właśnie na nich. Dla przykładu – funkcja do wysyłania stringów:

Poniżej przedstawiam jeszcze główną funkcję programu:

Jak widać, na początku musimy pamiętać jedynie o inicjalizacji UARTa i kontrolera przerwań, a potem już dowolnie korzystamy z opisywanych tu funkcji 🙂 Przypominam, że w razie potrzeby, ciała funkcji inicjalizujących oraz całkowite kody źródłowe z tego projektu znajdziesz na moim githubie.

Zostaw komentarz!

Podobała Ci się seria o buforze kołowym? Masz jakieś pytania lub uwagi? Zostaw mi proszę komentarz, a jeśli uważasz artykuły za wartościowe – podaj je dalej 🙂 Fajnie by było gdyby trafiły do większej ilości osób. Pozdro i do następnego! 😉