Z natury jestem człowiekiem , który lubi wszelkiego rodzaju wyzwania. Taki ze mnie Hobby Man Warrior :). W szczególności lubię rozwiązywać zagadki i szarady związane z mikrokonrolerami. Szczególnie sporo wrażeń w tym względzie przysparzają mi MCU ATSAM firmy Microchip, dlatego daże je szczególnym uczuciem :). Traktuję je jako poligon dla moich szarych komórek. Duży szacunek należy się konstruktorom ATSAM-ów , że potrafili zaprojektować w tak wyrafinowany sposób peryferium jakim jest moduł DMAC . Jest sporym wyzwaniem dla hobbysty skonfigurować DMA w ATSAM ,ale można z tego tytułu odnieść sporo satysfakcji z powodzenia tej operacji. W artykule pokażę jak skonfigurować i uruchomić DMA w ATSAM na przykładzie kopiowania tablicy i dodatkowo zobaczymy kod konfiguracji użycia DMA i SPI. Te dwa przykłady praktycznie wyczerpują retrospektywę dotyczącą konfiguracji DMA w ATSAM i na ich bazie można będzie zrobić taką konfigurację dla dowolnej innej konstelacji wymiany danych np. pamięć->peryferium czy peryferium->pamięć.
DMA jest jednym z tych peryferium , którego jak się nie pozna to wydaje się być zbędne. Ale jak już się pozna to otwierają nam się drzwi do krainy wielu ciekawych możliwości. Szczególnie "modnym" zastosowaniem jest wymiana danych w peryferiach takich jak SPI, I2C, ADC przy pomocy kontrolera DMA.
DMA znacznie przyspiesza wiele operacji związanych z transferem danych do/z peryferium/pamięci, automatyzuje i upraszcza procesy związane z obsługą peryferiów . Powoduje to, że nasz mikrokontroler dostaje "kopa". Użycie DMA w niektórych zastosowaniach porównałbym do wymiany dysku magnetycznego na SSD w PC.
Generalnie możemy wyróżnić typowe realizacje transferu przy pomocy DMA a są to :
- z peryferium do peryferium
- z peryferium do pamięci
- z pamięć do peryferium
- z pamięci do pamięci
Metody wyzwalania transefru DMA :
- programowe
- przy pomocy Event System (domena ATSAM-ów i chyba XMEG)
- przerwania od peryferiów
Do dyspozycji mamy 8 kanałów DMA i jest to całkowicie wystarczająca ilość. Przykładowo do kopiowania danych pamięć-pamięć wystarczy jeden kanał ale do obsługi np SPI dwa kanały jeden obsługuje nadawanie TX a drugi odbiór RX etc.
Mamy do dyspozycji mechanizm priorytetów i arbitrażu wykorzystujący schemat Roub-Roubin. Poniżej schemat blokowy modułu DMAC :
To co wyróżnia implementację DMA w ATSAM w stosunku do innych producentów i modeli MCU z rdzeniem ARM to użycie deskryptorów. Sam mechanizm deskryptorów jest znany ze świata systemów operacyjnych i tam wykorzystywany jest do obsługi plików. W przypadku naszego mikrokontrolera deskryptor to struktura danych przechowująca dane indeksujące fizyczny transfer pomiędzy modułem DMA a światem zewnętrznym. Implementacja DMA z użyciem deskryptorów w pamięci SRAM jest właśnie tym wyrafinowanym rozwiązaniem w ATSAM-ach. Fizyczna postać deskryptora poniżej w zaznaczonym fragmencie pliku nagłówkowego :
To co w zasadzie wystarczy sobie przyswoić to przyjąć apriori, że deskryptor jest strukturą o znanej budowie wewnętrznej i tyle. A skąd znanej ? m.in z pliku nagłówkowego dmac.h. Deskryptor musimy zdefiniować (wypełnić strukturę danymi) dla każdego kanału oddzielnie. Jest to dodatkowy narzut do konfiguracji kanałów DMA.
Co to oznacza dla przeciętnego "Kowalskiego" ? no powiem szczerze problemy w prawidłowej konfiguracji DMA. Problem polega na tym, że nie wiadomo jak deskryptorów użyć w programie :). Posiłkując się samą dokumentacją nic nie wskóramy bez przykładu a tam go nie ma. Oczywiście jeśli chodzi o programowanie z wykorzystaniem biblioteki ASF to pewnie znajdziemy coś w dokumentacji tej biblioteki ale ja nie używam tego "wspomagacza" co by mi mózg się nie stępił za szybko :) a poza tym mam alergię na ATMEL STUDIO, gdzie właśnie ASF rządzi. Na szczęście znalazłem na stronie dla Developerów Microchipa kawałek kodu z którego byłem w stanie zajarzyć jak użyć deskryptora w programie. A jak już raz się użyje to potem z górki.
Co to oznacza dla przeciętnego "Kowalskiego" ? no powiem szczerze problemy w prawidłowej konfiguracji DMA. Problem polega na tym, że nie wiadomo jak deskryptorów użyć w programie :). Posiłkując się samą dokumentacją nic nie wskóramy bez przykładu a tam go nie ma. Oczywiście jeśli chodzi o programowanie z wykorzystaniem biblioteki ASF to pewnie znajdziemy coś w dokumentacji tej biblioteki ale ja nie używam tego "wspomagacza" co by mi mózg się nie stępił za szybko :) a poza tym mam alergię na ATMEL STUDIO, gdzie właśnie ASF rządzi. Na szczęście znalazłem na stronie dla Developerów Microchipa kawałek kodu z którego byłem w stanie zajarzyć jak użyć deskryptora w programie. A jak już raz się użyje to potem z górki.
Ciekawy jestem czy użycie deskryptorów w pamięci SRAM wpływa na wydajność całego procesu transferu , trzeba by było to sprawdzić i porównać "timingi" z innym MCU gdzie takiego mechanizmu nie ma . Spróbuje to kiedyś zrobić .
Przejdźmy do konkretów bo samą teorią zagadnienia nie rozpoznamy. Najpierw poczynimy jakieś założenia czyli co chcemy zrobić przy pomocy DMA. Na tapetę biorę najprostszą formę transferu z pamięci do pamięci czyli przepiszemy sobie np. zawartość jednej tablicy do drugiej. Klasyczne kopiowanie tablicy bez użycia DMA możemy zrealizować np. tak :
#define DATA_LENGHT 1024
uint8_t SourceData[DATA_LENGHT];
uint8_t DesinationData[DATA_LENGHT];
Przejdźmy do konkretów bo samą teorią zagadnienia nie rozpoznamy. Najpierw poczynimy jakieś założenia czyli co chcemy zrobić przy pomocy DMA. Na tapetę biorę najprostszą formę transferu z pamięci do pamięci czyli przepiszemy sobie np. zawartość jednej tablicy do drugiej. Klasyczne kopiowanie tablicy bez użycia DMA możemy zrealizować np. tak :
#define DATA_LENGHT 1024
uint8_t SourceData[DATA_LENGHT];
uint8_t DesinationData[DATA_LENGHT];
/*copy table without DMA*/
void copy_clasic(uint8_t * sourceTab, uint8_t * desinationTab, int indeks){
for (int i = 0 ; i < indeks ; i++) desinationTab[i] = sourceTab[i];
}
wywołanie funkcji :
copy_clasic(SourceData, DesinationData, DATA_LENGHT);
lub tak :
#include <string.h>
/*copy table without DMA*/
void copy_memcpy(uint8_t * sourceTab, uint8_t * desinationTab, int indeks){
memcpy(desinationTab, sourceTab, indeks);
}
wywołanie funkcji :
copy_memcpy(SourceData, DesinationData, DATA_LENGHT);
Drugi z tych sposobów czyli przy użyciu funkcji bibliotecznej memcpy() jest ciut szybsza w timingach.
Retrospektywa kodu :
Cały kod umieściłem w jednym pliku main.c :
Najpierw zerkniemy jak wygląda główna część kodu :
Na początku deklarujemy sobie dwie tablice SourceData i DesinationData. Każda z nich ma 1024 elementów jedno-bajtowych. My chcemy za pomocą DMA przepisać zawartość całej tablicy SourceData do tablicy DesinationData. Przed przepisaniem wypełnimy tablicę SourceData liczbą 1.Czyli wszystkie elementy tej tablicy będą miały wartość 1 i to samo chcemy zobaczyć w tablicy DesinationData po zadziałaniu DMA.
Kluczowym fragmentem kodu jest deklaracja deskryptorów a wygląda ona tak :
/* Create DMA descriptor in global variable (128-bit aligned), use 1 channel */
volatile __attribute__((__aligned__(128))) DmacDescriptor descriptor[2];
volatile __attribute__((__aligned__(128))) DmacDescriptor write_back_descriptor[2];
volatile __attribute__((__aligned__(128))) DmacDescriptor descriptor[2];
volatile __attribute__((__aligned__(128))) DmacDescriptor write_back_descriptor[2];
Mamy tutaj deklarację dwóch deskryptorów a w szczególności dwóch tablic descriptor[2] i write_back_descriptor[2] , tablice są typu DmacDescriptor czyli typu reprezentującego strukturę deskryptora. Zawsze deskryptory występują parami tzn descriptor i write_back_descriptor. Pomimo, że nie odbieramy żadnych danych tylko wysyłamy w jednym kierunku to write_back_descriptor zawsze powinien być zadeklarowany. Tablica oznacza, że możemy użyć dwóch oddzielnych deskryptorów dla różnych trensmisji np. descriptor[0] lub descriptor[1].
Ja w programie wykorzystuję descriptor[1]. Co oznacza magiczne wyrównanie 128 ??? w deklaracji deskryptorów. Tak ma być wyrównana pamięć SRAM pod strukturę descryptora . Struktura ta ma wymiar 128 bitów lub inaczej 16 bajtów a wyjdzie nam ten rachunek jak spojrzymy na zdjęcie jakie zamieściłem z pliku nagłówkowego dmac.h ciut tam wyżej. W sumie jak rozebrać deskryptor na czynniki pierwsze to nie jest to jakaś straszna magia. Szkoda tylko jak pisałem wcześniej, że w dokumentacji MCU brakuje przykładów użycia deskryptorów.
W kolejnym fragmencie programu mamy wypełnienie tablicy SourceData wartością 1. Robię to najprościej jak się da czyli za pomocą funkcji bibliotecznej memset(). Następnie odpalamy dwie funkcje konfigurujące moduł DMAC i deskryptor :
DMA_descriptor_init();
DMA_Init();
DMA_Init();
Przyjrzyjmy się tym funkcjom :
I jeszcze funkcja obsługująca przerwanie od zakończonej transmisji DMA, jeśli transfer DMA się powiedzie to zapali nam się dioda LED na pinie PA27.
Kod opisany jest raczej czytelnie. Możemy go stosować jako szablon. Oczywiście analizując go trzeba mieć rejestry przed oczami.
Kod możemy sobie skopiować z forum ATMEL gdzie go zamieściłem.
ATMEL FORUM. Próbowałem się wspomóc forum kiedy stanąłem przed małym problemem, który sam sobie w końcu rozwiązałem :) . Warto nadmienić, że kopiowanie 1024 elementów nie jest szczególnym wyzwaniem dla MCU i tutaj wielkiego pazura DMA nam nie pokaże.
Poniżej link do strony dla developerów Microchipa gdzie znajdziemy przykład konfiguracji DMA dla SPI : LINK . Ta konfiguracja była punktem wyścia do moich ustawień.
Podsumowując udało mi się "pokonać" DMA w ATSAM :) co było zadaniem nie trywialnym i wymagało kumulacji szarych komórek :). Nie mniej satysfakcja gwarantowana. Takie boje oprócz satysfakcji z pokonania zagadnienia/problemu dają dużą dawkę nauki, dzięki której jestem w stanie potem spojrzeć na MCU np. innego producenta z innymi rozwiązaniami dla peryferiów i mój mózg szybko jest w stanie zatrybić te inne rozwiązania. Ale to raczej nic nowego nie odkryłem, że trenowanie mózgu daje efekty tak jak trenowanie bicepsa :). Zabawę z MCU traktuję m.in jako taki dobry trening dla mózgu :) . Może w ten sposób człowiek cokolwiek uchroni się przed Alzheimerem :)
Pozdrawiam
PICmajster
picmajster.blog@gmail.com
picmajster.blog@gmail.com
Linki :
Control IO port using DMAC for Cortex M0+ devices
ATSAML10E16A
pozdrawiam
OdpowiedzUsuń