środa, 30 listopada 2022

STM32G0 Bare Metal - konfiguracja DMA dla UART

 

W artykule pokażę jak skonfigurować  DMA dla potrzeb transmisji po UART w STM32G0. Konfiguracja taka różni się nieco w stosunku do  starszych konstrukcji typu STM32F0...F4. W STM32G071 na którym skupiam swoją uwagę znajdziemy jeden moduł DMA wyposażony w 7 kanałów. Dodatkowo znajdziemy tutaj wydzielony moduł do routingu DMA i synchronizacji - DMAMUX. Moduł ten jest domeną nowych konstrukcji STM32. Przyjrzyjmy się zatem jaką drogę trzeba przejść aby np. wysłać stringa po UART przy pomocy DMA. Konfiguracja bez użycia HAL-a tylko czysta praca organiczna na rejestrach. 


Moja baza sprzętowa to :

- płytka developerska z STM32G071KBT6 mojej własnej roboty rzecz jasna,
- programator J-LINK EDU Mini,
- analizator stanów logicznych .

Moja baza "software" :

- STM32 CUBE IDE

W artykule będę się posługiwał projektem testowym stworzonym dla moduł radiowego LORA. Założenie jakie czynię na początek jest takie, że chcę wysłać cyklicznie co jakiś okres czasu stringa np. "messageToSendLora" po UART1 przy pomocy DMA1 na kanale nr 1. Na razie bez użycia jakichkolwiek przerwań najprościej jak tylko się da. Jak skonfigurować piny dla UART i sam UART pokazywałem w artykule TUTAJ  i tego nie będę już opisywał . Zakładam, że moduł UART mamy już skonfigurowany , różnica z wykorzystaniem DMA będzie polegała na funkcji inicjalizacyjnej UART co pokażę.

Zabawę z DMA zaczynam od konfiguracji zegara dla tego modułu :


Pomimo, że na początek przerwań nie będę ruszał, to konfiguruję NVIC-a dla DMA.


Teraz funkcja inicjalizująca UART1 z elementami DMA. Funkcja zaznaczona na dole. Różnica w stosunku do funkcji bez użycia DMA polega m.in na wyłączeniu wszelkich przerwań od UART ponieważ docelowo posługiwać się będziemy przerwaniami od DMA oraz włączenie DMA dla UART za pomocą wpisu w rejestr USART1->CR3. Tutaj mała uwaga napisałem , że wyłączamy wszelkie przerwania od UART, jest tutaj jednak jeden wyjątek mianowicie , przerwanie IDLE czyli przerwanie od braku aktywności na magistrali UART po odebraniu przynajmniej jednego znaku. To przerwanie jest mega przydatne szczególnie w odbiorze przy wykorzystaniu DMA. Przerwanie to upraszcza m.in obsługę bufora kołowego z wykorzystaniem DMA.  Na razie jednak to przerwanie nie uaktywniam aby nie komplikować obecnie zagadnienia.


Teraz inicjalizacja podstawowa DMA :


W inicjalizacji DMA musimy dokonać wpisu w rejestr DMAMUX w którym przyporządkujemy wejście multipleksera dla USART1. Służy do tego pole 6-bitowe DMAREQ w rejestrze DMAMUX_CxCR. W zapisie do rejestru DMAMUX odwołujemy się do kanału nr 0 pomimo, że kanał DMA z którego będę korzystał ma nr 1. Wynika to z tego, że DMAMUX kanały postrzega jako 0...6 a DMA 1...7. Czyli DMAMUX kanał nr 0 oznacza kanał nr 1 dla DMA. To co wpisujemy w pole bitowe DMAMUX wynika z tabelki , którą znajdziemy w manualu na str. nr. 300 :


Z tabelki wynika, że dla USART1 TX przyporządkowana jest cyfra 51 (DEC) / binarnie 110011  i taką wartość wpisuję do rejestru DMAMUX
Kolejne wpisy do rejestrów DMA (CCR i CPAR) to :

- ustalenie kierunku przepływu danych (dla USART TX) : pamięć do peryferium,  bo dane z pamięci przesyłamy do bufora sprzętowego USART gdyby rzecz dotyczyła USART RX to wtedy kierunek przepływu byłby odwrotny :  peryferium do pamięci .

- włączenie inkrementacji pamięci : automatyczna inkrementacja danych przesyłanych z pamięci do peryferium. Bez tego byśmy przesłali np. tylko jeden znak z całego stringa.

W rejestrze DMA CPAR podajemy adres rejestru sprzętowego dla USART TX czyli rejestru TDR.

I to już byłby koniec podstawy konfiguracji DMA dla USART TX. Zajmiemy się teraz funkcją , która będzie przekazywała naszego stringa do DMA i w której DMA przejmie kontrolę nad naszym stringiem :


W funkcji wysyłającej stringa musimy :
- wyłączyć DMA ,
- wyzerować wszystkie flagi (bez tego DMA się nie odpali ponownie),
- podać adres pierwszego elementu wysyłanej wiadomości,
- podać długość wysyłanej wiadomości,
- włączyć DMA

Wywołajmy naszą funkcję wysyłającą stringa cyklicznie co ok 200 ms i sprawdźmy czy to działa :


Analizator stanów logicznych pokazuje, że wszystko działa tak jak powinno. String jest wysyłany cyklicznie a więc nasza konfiguracja DMA zakończyła się powodzeniem :


Teraz dopiero można sobie dodać przerwania od DMA, na pewno przydać się może przerwanie DMA TC (transfer complete). 

Nadawanie po UART z użyciem DMA jest proste i nie ma co tu się dalej rozwodzić. Ciekawszym zagadnieniem , które wymaga większego skupienia jest odbiór danych UART z wykorzystaniem DMA. Ponieważ w tym przypadku może brakować nam informacji jak długa będzie odbierana wiadomość. A daną tę musimy podać w procesie konfiguracji DMA , również w przypadku odbioru . Na wstępie napiszę , że problem ten rozwiąże nam pośrednio przerwanie IDLE znajdujące się w module UART. Jak ono działa? Otóż po wykryciu pierwszego nadchodzącego znaku na linii UART RX, uruchamiane jest badanie aktywności tej linii. Jeżeli nie będzie żadnej aktywności przez czas równy długości jednego znaku, zostanie wywołane przerwanie IDLE.
 
Za pomocą kodu wyjaśnię krok po kroku jak odebrać komunikat przychodzący na linii UART RX o zmiennej długości przy pomocy DMA

Najpierw zerknijmy na inicjalizację UART :


W inicjalizacji UART kluczowym zagadnieniem jest prawidłowe włączenie przerwania IDLE , bo kryje się tutaj pewien haczyk, wynikający prawdopodobnie z wadliwej implementacji tego przerwania w MCU STM-a. Problem polega na tym, że przerwanie IDLE jest wywoływane na pusto po włączeniu UART-a. Nie chcemy pustych wywołań przerwania bo to wprowadza zamieszanie. Tym bardziej, że przerwanie to będzie nam wpływać na zachowanie DMA ale o tym później. Ciekawi mnie czy w bibliotece HAL ten problem jest jakoś rozwiązany czy nie, muszę to kiedyś sprawdzić. Operując na rejestrach jesteśmy w stanie sobie poradzić z takimi problemami stosunkowo łatwo. Ja zastosowałem w kodzie inicjalizacji UART zwłokę po której kasuję flagę przerwania IDLE i włączam to przerwanie po włączeniu UART. Czas zwłoki ustaliłem eksperymentalnie.

Oczywiście dostrzeżenie tego problemu, zinterpretowanie i naprawa zajęło mi trochę czasu ale nie był to absolutnie zmarnowany czas, ponieważ traktuję to jako  naukę i doskonały trening dla moich neuronów.

Kolejnym krokiem będzie konfiguracja wybranego kanału DMA do współpracy z UART RX :


Do współpracy z UART RX wybrałem kanał DMA nr 2 czyli ekwiwalent kanału nr 1 w DMAMUX.
Aby DMAMUX widział, że chcemy korzystać z UART RX musimy wpisać do jego rejestru wartość DEC 50. Wartość ta wynika z tabelki o której pisałem w przypadku konfiguracji DMA dla  UART TX.
W rejestrze CCR ustawiamy inkrementację pamięci, włączamy przerwanie TC (transfer complete). Kierunek przepływu danych - peryferium -> pamięć i jest to domyślne ustawienie w rejestrze, więc pomijamy milczeniem zapis do tego pola bitowego. W rejestrze CPAR podajemy adres sprzętowego bufora odbiorczego dla UART RX

Powoli zbliżamy się do końca. Teraz trzeba stworzyć funkcję , która będzie nam fizycznie uruchamiała DMA jako pośrednika w odbiorze UART RX.



Funkcja dma1_uart1_received_message(char *receive_buff, uint32_t buff_len) przyjmuje dwa argumenty. Wskaźnik na bufor odbiorczy i długość tego bufora. Do bufora odbiorczego będą spływać odbierane bajty z UART RX za pośrednictwem DMA. Długość naszego bufora odbiorczego musimy ustalić tak aby pomieściła się w nim najdłuższa odbierana wiadomość plus dodatkowe znaki takie jak np. \r\n. Ja w ramach testów będę odbierał komunikat o następującej treści : "messageToSendLora". Nadawcą tego komunikatu jest moduł radiowy LORA , czyli dane będę pobierał z "powietrza" :).

Widzimy zatem, że na etapie konfiguracji DMA podajemy  na sztywno długość odbieranych danych a odbierać chcemy komunikaty o różnej długości  przy założeniu, że nie przekraczają one zadeklarowanej w konfiguracji DMA długości. Ideą odbioru w wykorzystaniem DMA komunikatów o różnej długości jest wykorzystanie przerwania UART RX IDLE w którym zatrzymujemy DMA i przechodzimy następnie np. do analizy komunikatu lub przepisania odebranej wiadomości do bufora kołowego.

Mój program testowy do odebrania komunikatu :



Mój bufor odbiorczy ma długość 36 bajtów. Czyli ponad dwa razy więcej niż odbierana wiadomość. Musiałem dać taką długość ponieważ w procesie inicjalizacji modułu radiowego LORA na UART RX pojawiają się dane wysyłane przez moduł radiowy lokalnie. Przed wejściem w pętlę główną programu uruchamiam funkcję dma1_uart1_received_message(). Od tej chwili DMA nadstawia uszy i monitoruje bufor sprzętowy UART RX. Przerwanie IDLE zostanie wywołane w przypadku wykrycia przerwy w odbieranym komunikacie. Wtedy zatrzymuję DMA i ustawiam flagę pomocniczą UartIdleFlag. W pętli głównej reaguję na ustawioną flagę. Sprawdzam czy w buforze odbiorczym znajduje się komunikat "messageToSendLora" jeśli tak to zmieniam stan diody LED. Komunikaty przychodzą mi cyklicznie co ok 200 ms i są nadawane przez inny moduł LORA drogą radiową, więc z takim okresem powinna migać dioda LED. W kolejnym kroku czyszczę bufor odbiorczy i ponownie odpalam funkcję dma1_uart1_received_message() i tak w kółko. 

Myślę , że w tak naiwnie prosty sposób przedstawiłem jak przerwanie IDLE wspomaga nam proces odbioru DMA UART RX komunikatów o zmiennej długości i jak ten mechanizm działa.

DMA to bardzo przydatny i efektywny wynalazek w MCU. Warto go poznać i stosować . Implementacja DMA w STM32G0 umożliwia wykorzystanie tego mechanizmu nawet w przypadku modułów, które nie są do współpracy z DMA przewidziane.


Pozdrawiam

PICmajster
picmajster.blog@gmail.com

Brak komentarzy:

Prześlij komentarz