fbpx

Temat UART w połączeniu z DMA co chwilę wraca jak bumerang. Zwłaszcza odbieranie danych na mikrokontrolerze sprawia najwięcej problemów. Dlaczego? Bo nie wiemy ile tych danych przyjdzie. Stąd odbiór po DMA jest nieco kiepskim pomysłem, bo musimy z góry określić ilość odbieranych danych. Ale nie zawsze…

UART w połączeniu z DMA

Jakiś czas temu pisałem o świetnym wykorzystaniu odbioru na DMA w połączeniu z przerwaniem UART od stanu bezczynności, czyli IDLE. Znajdziesz na moim blogu dwa artykuły jak taki mechanizm napisać pod mikrokontrolery F4 (o tutaj), oraz F1 (o tutaj). Przypomnę w skrócie, na czym taka konstrukcja polega.

  1. Konfigurujemy UART do pracy z DMA po to, aby był on w stanie wysyłąc żądania transferu do DMA.
  2. Włączamy przerwanie od bezczynności UART, czyli IDLE.
  3. DMA konfigurujemy tak, aby czytał z rejestru UART DR i wpisywał gdzieś do przygotowanej tablicy w pamięci RAM (z inkrementacją).
  4. Włączamy odbiór z UARTa poprzez DMA.
  5. Zakończenie odbioru może nastąpić w dwóch momentach
    1. Odebraliśmy określoną z góry ilość znaków – dostajemy przerwanie DMA Transfer Complete
    2. Odebraliśmy mniej znaków, ale UART wszedł w stan bezczynności na linii RX – dostajemy przerwanie UART IDLE

Finalnie dostajemy przerwanie po zakończonym odbiorze niezależnie ile tych znaków odebraliśmy. Znamy więc moment, kiedy należałoby się tymi danymi zająć. Super, co nie?

Implementacja na rejestrach

W poprzednich wpisach dotyczących STM32F4 i STM32F1 robiłem to na pieszo z wykorzystaniem rejestrów. Dlaczego? Otóż biblioteki HAL ni w ząb nie wspierały przerwania IDLE. Trzeba było się posiłkować własną implementacją i dorzucać obsługę przerwania IDLE na UARcie.

Takie działanie niosło ze sobą mnóstwo wad i niedoskonałości.

Po pierwsze nie jest to rozwiązanie uniwersalne, o czym przekonali się moi kursanci. Osoby działające na STM32L4, czy STM32F7 zderzyły się z problemem nieco inaczej wyglądających rejestrów. Dla każdego mikrokontrolera tak naprawdę trzeba robić osobne podejście i osobną implementację, bo miały drobne niuanse w działaniu DMA czy UARTa. To może być uciążliwe.

Po drugie moja implementacja nie była odporna na to, że IDLE występował również po odebraniu równej ilości zleconych znaków na DMA. My się zajmowaliśmy już odebranymi danymi w przerwaniu od DMA, a tutaj weszło IDLE i de facto robiłem wtedy drugi raz to samo…

Przydałoby się coś bardziej uniwersalnego i działającego, prawda? Coś, co będzie działało dla każdej rodziny STM32. Już jest!!!

UART IDLE w bibliotekach HAL

Pod koniec 2020 roku ST Microelectronics dodało w końcu obsługę przerwania UART IDLE! Dla wszystkich rodzin. Oznacza to dla nas – programistów – jednolite użycie tego przerwania, oraz jego obsługi niezależnie od rodziny mikrokontrolera.

To wszystko standardowo jak to dla HALa – robi się “za nas”. W takim sensie, że zlecamy odbiór specjalną funkcją i mamy do dyspozycji specjalny Callback. Jest on wywoływany w dwóch sytuacjach, czyli przy DMA Transfer Complete i przy UART Idle.

Zlecenie odbioru jest bardzo podobne do zwykłego zlecenia odbioru po DMA. Po prostu po ustawieniu odbierania na DMA włączane jest przerwanie IDLE. Po drodze jest ustawiana też informacja, że to był tryb odbioru z IDLEm, aby HAL w obsłudze przerwania wiedział co robić.

Jak wygląda kod z przerwania? Po pierwsze HAL sprawdza w strukturze UARTa, czy odbiór był zlecany z użyciem IDLE. Jeśli tak, to będzie sprawdzana i kasowana również ta flaga. Później sprawdza, czy to był odbiór z użyciem DMA, bo to również ma znaczenie i kasuje odpowiednie flagi. Na końcu wywołuje specjalny Callback.

Ustawmy odbiór na DMA z użyciem IDLE

Po pierwsze trzeba pobawić się w CubeMX. Od dawna korzystam z STM32CubeIDE, więc CubeMX jest w nim zaszyty.

Na czym dzisiaj oprę swoje działania? Mam pod ręką Nucleo-F411RE, które używam między innymi w swoim Kursie STM32 dla Początkujących. STM32CubeIDE w wersji 1.6.0 i biblioteki HAL F4 v1.26.1.

Po pierwsze musimy mieć ustawiony UART. Jeśli tworzysz projekt domyślnie z wyboru Nucleo, to masz już skonfigurowany UART2 na 11520. Trzeba mu dorzucić dwie rzeczy.

Oczywiście trzba ustawić DMA na linię odbiorczą USART2_RX. Zrób dokładnie jak ja, czyli z autoinkrementacją po stronie pamięci.

Numer Streamu/Kanału nie ma większego znaczenia. Chcemy tylko odbierać coś na UART, więc nic innego nam nie przeszkodzi.

Dalej trzeba włączyć przerwania od UARTa. Przecież chcemy korzystać z przerwania UART IDLE. Przerwanie od DMA jest z domysłu włączone.

Na koniec przerzucenie włączenia przerwań po wszystkich inicjalizacjach peryferiów. Nie jest to konieczne, ale poprawia czytelność kodu i niekiedy działanie. Wyznaję zasadę, że zawsze inicjalizacja peryferiów jest w pierwszej kolejności i dopiero później mogę myśleć o włączaniu przerwań.

Mamy wszystko, czego potrzebujemy. Przejdźmy do kodu.

Jak używać DMA z przerwaniem UART IDLE

Nie pozostało nam nic innego jak po prostu użyć DMA i IDLE do odebrania jakiegoś komunikatu. Zróbmy to.

HAL ułatwia nam odbieranie na maksa. Potrzebujemy tablicy, do której będziemy odbierać znaki. Zazwyczaj tworzę ją w obszarze zmiennych globalnych w wyznaczonym komentarzami odpowiednim miejscu pliku main.c.

/* USER CODE BEGIN PV */
uint8_t ReceiveBuffer[32]; // Receive from UART Buffer
/* USER CODE END PV */

Teraz musimy ustawić odbiór po DMA. Samo odbieranie z wykorzystaniem DMA załatwia nam funkcja HAL_UART_Receive_DMA(…), jednak ona się nam tutaj nie przyda. Ona nastawia sztywną ilość znaków do odbioru i tyle. Potrzebujemy przecież dodatkowo włączyć przerwanie IDLE.

Dlatego musimy użyć zupełnie innej funkcji. Jest ona z dopiskiem “Ex”, czyli funkcji rozszerzonych z API HALa. Będzie to funkcja HAL_UARTEx_ReceiveToIdle_DMA(…), która przyjmuje dokładnie te same argumenty, co ta zwykła od odbioru na DMA.

Użyjmy więc tej specjalnej funkcji w mainie jeszcze przed startem głównej pętli. Odpowiednim miejscem na to jest sekcja użytkownika numer 2.

/* USER CODE BEGIN 2 */

// Start to listening on UART with DMA + Idle interrupt detection
// the Callback is in USER CODE 4 section
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, ReceiveBuffer, 32);
/* USER CODE END 2 */

Pamiętaj, że my ustawiamy “nasłuch” i nie musimy czekać na zakończenie odbioru. Dostaniemy przerwanie w swoim czasie. Właśnie – przerwanie. W HALu pamiętamy, że obsługą przerwań zajmuje się biblioteka. Nam wystawiane są callbacki do reagowania na odpowiednie przerwania.

Dla odbioru UART na DMA z wykorzystaniem przerwania IDLE jest jeden wspólny callback – HAL_UARTEx_RxEventCallback(…). W nim dostajemy oczywiście informację, który UART wywołał nam callback, ale nie tylko. W drugim argumencie dostajemy liczbę bajtów, które udało się odebrać na UART.

Jest to niesamowicie cenna informacja, bo przerwanie mogło wystąpić po różnej ilości znaków! Po to właśnie korzystamy z IDLE. Mają tę informację mamy już komplet tego, co potrzebujemy do parsowania.

Jak napisać naszą obsługę callbacku? Wystarczy sprawdzić, czy to nasz UART wywołał, później wykonać swoją robotę. Na przykład parsowanie lub wsadzenie do jakiegoś bufora (np. kołowego) do późniejszej obróbki poza przerwaniem.

Na samym końcu koniecznie musisz włączyć ponownie nasłuch. W taki sam sposób jak w funkcji main. Musisz od nowa ustawić DMA i UART w odbiór z wykorzystaniem IDLE. Cały callback będzie wyglądał więc tak.

/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    // Check if UART2 trigger the Callback
    if(huart->Instance == USART2)
    {
        //
        // Do something
        //
        
        // Start to listening again - IMPORTANT!
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2, ReceiveBuffer, 32);
    }
}
/* USER CODE END 4 */

I to tyle. Sprawdźmy, czy to działa.

Działanie odbioru

Mamy ustawione 32 bajty do odebrania na DMA. Wyślijmy mniej. Na przykład string “Ukulele”. Ma on zdecydowanie mniej znaków niż zaplanowana ilość dla DMA. Powinno nam w takim razie wejść przerwanie od UART IDLE.

Postawiłem pułapkę w naszym callbacku, puściłem program i wysłałem napis.

I… włala! Program stanął na breakpoincie. Z prawej strony wrzuciłem do Live Expression to, co nas interesuje. Rozmiar odebranego stringa – 7 bajtów. To przekazuje nam callback i należy wykorzystać później.

Dorzuciłem też samą tablicę, do której chciałem odbierać na UART i jest tam moje Ukulele 🙂

Działa jak należy!

Parsowanie stringów

W dalszej kolejności należałoby to, co otrzymaliśmy przeparsować, czyli zareagować na to, co przyszło. Jest to operacja dużo bardziej złożona i można podzielić ją na parsowanie proste i złożone.

Temat odbierania i parsowania Stringów na UART bardzo dokładnie opracowałem w moim Kursie STM32 dla Początkujących.

Właśnie są otwarte zapisy do trzeciej edycji. Kurs obejmuje bardzo szeroki zakres programowania STM32. Poznajesz nie tylko sam mikrokontroler, ale właśnie różne techniki programowania i radzenia sobie w embedded. Przykładowo właśnie odbieranie znaków na UART, buforowanie ich w buforze kołowym i późniejszą obróbkę stringów.

Sprawdź cały program na https://kursstm32.pl/. Dołączyć do trzeciej edycji można do piątku 04.06.2021 do godziny 20:00. Później nie będzie możliwości dołączenia do kursu!

Podsumowanie

W końcu doczekaliśmy się obsługi UART z DMA i przerwaniem IDLE. ST wykonało to w taki sposób, że użycie tej metody odbioru jest bajecznie proste.

Jedyne co musisz zrobić to ustawić DMA i przerwania na UART, oraz skorzystać ze startu odbioru i callbacku.

Metoda ta jest taka sama dla wszystkich rodzin STM32, więc śmiało wypróbuj to na swoim mikrokontrolerze!

Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.

Podobne artykuły

.

2 Komentarze

Olek · 09/06/2021 o 10:56

Cześć,

Z tego co widzę można nawet to uruchomić w trybie circular. Dzięki temu nie będziemy musieli uruchamiać w callbacku odbioru. Jedyna różnica, że Size nie podaje ilości odebranych danych od ostatniego przerwania, tylko pozycję w buforze, więc musimy zapamiętać poprzednią pozycję i odpowiednio kopiować dane.

Pozdrawiam,

    Mateusz Salamon · 28/06/2021 o 12:08

    O, tego nie zbadałem jeszcze. Dzięki za wskazówkę z Size 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *