Tym razem omówimy konfigurację przetwornika ADC, odkryjemy podstawy współpracy z DMA oraz nauczymy się korzystać z STMStudio!
Mierząc napięcie możemy np. monitorować stan zasilania urządzenia, czy odczytywać informacje z czujników analogowych. Rozwiązanie okazuje się szczególnie użyteczne, jeśli dodatkowo do pracy zaprzęgniemy moduł DMA.
Uwaga! Ten kurs został zarchiwizowany. Sprawdź najnowszy kurs STM32 »
Przed przejściem do ćwiczeń praktycznych warto odświeżyć wiedzę teoretyczną. Jeśli uważasz, że znasz zasadę działania przetwornika i jego ograniczenia, to możesz pominąć poniższą sekcję. Zachęcam jednak do jej przeczytania!
Przetwornik analogowo-cyfrowy
ADC (ang. Analog to Digital Converter) jest jednym z podstawowych peryferiów będącym na wyposażeniu niemal każdego mikrokontrolera.
Jak sama nazwa wskazuje, dokonuje on konwersji
wartości napięcia sygnału analogowego na postać cyfrową.
Czym jednak różni się sygnał analogowy od cyfrowego? Warto w tym miejscu zwrócić uwagę na podstawowe własności i tym samym ograniczenia związane z cyfrowym przetwarzaniem sygnałów.
Dyskretyzacja
Sygnał analogowy jest sygnałem ciągłym. Oznacza to tyle, że między dwiema dowolnie bliskimi chwilami zawsze istnieje trzecia, w której sygnał również istnieje i przyjmuje pewną wartość.
Czy jesteśmy w stanie zmierzyć wszystkie te wartości? Niestety nie.
Cyfrowe urządzenia pomiarowe, takie jak np. wbudowany w mikrokontroler przetwornik ADC, mają pewną częstotliwość graniczną, z którą są w stanie wykonywać pomiary. Oznacza to tyle, że istnieje określony czas pomiędzy następującymi po sobie pomiarami, w którym nie jesteśmy w stanie wykonać kolejnego.
Graficzne przedstawienie procesu próbkowania. Źródło: https://pl.wikipedia.org/wiki/Pr%C3%B3bkowanie
Czerwone strzałki oznaczają chwile dokonania pomiarów. Można zauważyć, że pomiędzy nimi sygnał analogowy (oznaczony szarą linią) istnieje i przyjmuje konkretne wartości. Wynika z tego, że wszystkie informacje o poziomie sygnału znajdujące się pomiędzy pomiarami zostają utracone. Taki proces nazywamy dyskretyzacją (zamiana czasu sygnału z ciągłego na dyskretny).
Kwantyzacja
Sygnał analogowy charakteryzuje się wartością o nieskończonej dokładności. Pomiędzy dwiema dowolnie bliskimi wartościami poziomu napięcia istnieje trzecia, którą sygnał może przyjąć. Jak zapewne się domyślacie, nie jesteśmy w stanie zmierzyć napięcia z nieskończoną dokładnością.
Ogranicza nas rozdzielczość przetwornika,
czyli ilość poziomów, które jest on w stanie przyjąć.
Na powyższym obrazku pomiary przyjmują tylko 8 wartości (w tym wypadku rozdzielczość przetwornika to 3 bity). Zakładając, że jest to pełny zakres przetwornika mierzącego napięcie w zakresie 0-3V, wartości jakie moglibyśmy z niego otrzymać to:
Oznacza to tyle, że mierząc napięcie w okolicach 2V otrzymamy wynik 1.875V lub 2.25V. Niezbyt zadowalająca dokładność... Dlatego tak samo jak w aparatach cyfrowych, przy ADC zależy nam zazwyczaj na jak największej rozdzielczości (czyli na jak największej dokładności pomiarowej).
Dość teorii, zabierzmy się za programowanie!
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw elementów do przeprowadzenia wszystkich ćwiczeń z kursu STM32 F4 można nabyć u naszego dystrybutora! Zestaw zawiera m.in. płytkę Discovery, wyświetlacz OLED, joystick oraz enkoder.
Masz już zestaw? Zarejestruj go wykorzystując dołączony do niego kod. Szczegóły »
STM32 F4 - konfiguracja przetwornika ADC
Krok 1. Tworzymy nowy projekt w STM32CubeMX o nazwie 02_ADC. Krok 2. W sekcji peryferia rozwijamy przetwornik ADC1. Co się w nim znajduje?
Możliwe wejścia przetwornika ADC1.
IN0...IN15 - pierwsze 16 pozycji, to wejścia kanałów pozwalające na pomiar napięcia na pinach mikrokontrolera.
Włączając je, automatycznie uaktywnione zostają konkretne
wyprowadzenia w centralnym widoku ekranu.
Temperature Sensor Channel - kanał ten pozwala na pomiar z wewnętrznego czujnika temperatury. Dzięki temu jesteśmy w stanie zmierzyć temperaturę mikrokontrolera podczas pracy bez dodatkowych układów zewnętrznych.
Vrefint Channel - pozwala na pomiar wewnętrznego napięcia odniesienia w celu korekcji dokonywanych pomiarów.
Vbat Channel - zaprojektowany w celu pomiaru napięcia zasilania. Dokonuje pomiaru na pinie VBAT (6).
Kanał VBAT dzieli kanał przetwornika z czujnikiem temperatury,
więc tylko jeden z nich może być używany w danym momencie.
External-Trigger-for-Injected/Regular-conversion - ta funkcjonalność pozwala na rozpoczęcie konwersji bezpośrednio po wywołaniu przerwania zewnętrznego.
Krok 3. Uruchamiamy kanał Temperature Sensor Channel. Krok 4. W zakładce Configuration otwieramy moduł ADC.
Przejście do ustawień przetwornika.
Naszym oczom ukaże się główny panel konfiguracyjny przetwornika ADC.
Panel konfiguracyjny przetwornika ADC.
Przyjrzyjmy się po kolei wszystkim opcjom w tym panelu. W sekcji ADC_Settings:
Clock Prescaler - dzielnik zegara taktującego przetwornik. Im wyższą wartość tu ustawimy, z tym mniejszą częstotliwością będzie taktowany przetwornik. Tym samym pomiary będą wykonywane wolniej.
Resolution - rozdzielczość pomiarowa przetwornika. Aby uzyskać jak największą dokładność pomiaru, należy ustawić maksymalną rozdzielczość (12 bitów).
Jeżeli zależy nam na szybszym wykonywaniu pomiarów, to można ją zmniejszyć ponieważ pomiary o mniejszej dokładności trwają krócej.
Data Alignment - wyniki pomiarów w rejestrze mogą być dosunięte do lewej lub do prawej. Dla naszych zastosować powinniśmy korzystać ze standardowego zapisu liczb, a więc takiego gdzie wynik dosunięty jest do prawej.
Scan Conversion Mode - pozwala na dokonanie konwersji całej grupy kanałów. Jeżeli w przetworniku mamy zdefiniowanych kilka konwersji przy włączonym Scan Conversion Mode zostaną one wykonane jednym ciągiem.
Continuous Conversion Mode - jeżeli włączone, przetwornik rozpoczyna kolejną konwersję lub grupę konwersji natychmiast po zakończeniu poprzedniej (ta funkcjonalność działa tylko z użyciem przerwań lub DMA).
Discontinuous Conversion mode - pozwala na wykonanie sprecyzowanej ilości pomiarów wewnątrz zdefiniowanej grupy konwersji.
DMA Continuous Requests - jeżeli włączone, to po każdym pomiarze zostaje wysłane zapytanie do DMA o przesłanie zmierzonej wartości do zmiennej docelowej.
End Of Conversion Selection - pozwala wybrać czy flaga EOC (End OF Conversion) zostanie ustawiona po wykonaniu każdej pojedynczej konwersji, czy po wykonaniu całej sekwencji konwersji.
W sekcji ADC_Regular_ConverionMode:
Number of Conversion - definiuje ilość konwersji w jednej sekwencji pomiarów.
External Trigger Conversion Edge - pozwala wybrać oraz skonfigurować zdarzenie, które zainicjuje rozpoczęcie pomiaru. Nie będziemy korzystać z tej funkcjonalności.
Następnie wyświetlone są wszystkie konwersje, które wykonane będą w jednej sekwencji pomiarów. Każdą konwersję opisuje poniższy zestaw parametrów.
Rank - oznacza pozycję w kolejce wykonywanych pomiarów.
Channel - definiuje źródło konwersji.
Sampling time - określa czas konwersji w cyklach zegara. Wraz ze wzrostem czasu konwersji rośnie stabilność i rzetelność pomiarów.
Reszta opcji nie będzie nas teraz interesować.
Krok 5. Konfiguracja konwersji.
Ponieważ będziemy mierzyć tylko jeden kanał, pozostawiamy Scan Conversion Mode wyłączone. Na razie będziemy korzystać z trybu blokującego (polling), a więc Continunous Conversion Mode również pozostawiamy wyłączone.
Ponad częstotliwość pomiarów (która i tak dla naszych potrzeb jest tu zdecydowanie za duża) będziemy cenić dokładność, dlatego możemy zwiększyć Clock Prescaler oraz czas próbkowania (Sampling Time) do wartości maksymalnych.
Konfiguracja ADC do pomiaru temperatury.
Krok 6. Wygenerowanie i zaimportowanie projektu.
Tak jak w poprzednim artykule, generujemy projekt o nazwie 02_ADC pod środowisko SW4STM32 i importujemy go do naszego IDE. Po zaimportowaniu projektu warto zwrócić uwagę na nową rzecz, która pojawia się pliku main. W sekcji Private variables znajduje się struktura zawierająca informacje dotyczące skonfigurowanego przetwornika ADC.
Takie struktury będą się pojawiać przy prawie wszystkich peryferiach. Zazwyczaj są jednym z parametrów wywoływanych funkcji dotyczących tych peryferii.
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_ADC1_Init(void);
Krok 7. Uruchomienie przetwornika ADC.
Musimy teraz odnaleźć funkcję rozpoczynającą pomiary. Szukając jej w podany wcześniej sposób, czyli wpisując frazę HAL_ADCi wciskając Ctrl+Spacja otrzymamy na początku bardzo dużo wyników rozpoczynających się od HAL_ADCEx.
Dopisek Ex (Extended) oznacza, że jest to funkcja z grupy funkcji dodatkowych, czy też zaawansowanych. W praktyce są to po prostu rzadziej używane funkcje, które mają często nieco bardziej skomplikowane zastosowanie.
Ponieważ chcemy rozpocząć pomiary, naszą funkcją będzie HAL_ADC_Start. Po wpisaniu tej frazy widać, że dostępne są 3 funkcje rozpoczynające się w ten właśnie sposób.
Funkcje pozwalające na rozpoczęcie konwersji ADC.
Pierwsza z nich rozpoczyna pomiary w tzw. trybie blokującym. Druga uruchamia konwersje z wykorzystaniem DMA. W ostatniej zostaje wykorzystany mechanizm przerwań. Aby korzystać z ostatnich dwóch trzeba najpierw dokonać odpowiedniej konfiguracji w Cube.
Wszystkie powyższe metody zostały kolejno opisane w tym artykule.
Ponieważ jeszcze nie skonfigurowaliśmy DMA oraz przerwań dla przetwornika ADC, uruchomimy konwersje w trybie blokującym. Zauważmy, że funkcja przyjmuje tylko jeden argument, a konkretnie wspomnianą wcześniej strukturę hadc1.
Funkcja przyjmuje konkretnie wskaźnik na strukturę, dlatego należy użyć operatora dereferencji &, aby przekazać odpowiedni typ do funkcji:
/* USER CODE BEGIN 2 */
HAL_ADC_Start(&hadc1);
/* USER CODE END 2 */
Tyle wystarczy, aby uruchomić konwersje przetwornika ADC.
Krok 8. Odczyt pomiarów.
Potrzebna nam będzie zmienna do przechowywania danych. Zdefiniujemy ją jako globalną - przyda się nam to w późniejszej części artykułu. Wiemy, że dokonujemy pomiarów z rozdzielczością 12 bitów, dlatego zastosowanie zmiennej 8 bitowej będzie powodować utratę dokładności pomiarów. W zupełności wystarczająca będzie jednak zmienna 16 bitowa.
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint16_t PomiarADC;
/* USER CODE END PV */
Następnie musimy sprawdzić, czy pomiar został zakończony. Jeśli tak, możemy odczytać zmierzoną wartość. Do sprawdzenia, czy konwersja została zakończona użyjemy funkcji:
HAL_ADC_PollForConversion
Jako pierwszy parametr przyjmuje ona standardowo wskaźnik na strukturę ADC. Drugim parametrem jest czas, po którym funkcja porzuci sprawdzanie, czy konwersja się zakończyła. Jeżeli wykryto zakończenie konwersji, funkcja zwraca HAL_OK.
Interesująca może się okazać informacja dotycząca czasu trwania pojedynczej konwersji. Przy obecnej konfiguracji przetwornik ADC taktowany jest częstotliwością 16 MHz. Prescaler ustawiony jest na 8, a ilość cykli konwersji zwiększyliśmy do 480 (należy do tego dodać 12 na stałe dodanych cykli).
Finalnie czas konwersji = (8*492)/16000000 = 0.000246s = 246 μs.
Do odczytania wartości zmierzonej użyjemy funkcji HAL_ADC_GetValue. Funkcja ta zwraca wynik pomiaru, który można przypisać do naszej zmiennej. Po zakończeniu każdego pomiaru musimy rozpocząć nowy. Pełny kod tej operacji przedstawia się następująco:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) {
PomiarADC = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Start(&hadc1);
}
/* USER CODE END WHILE */
Świetnie! Odczytaliśmy wartość z czujnika temperatury. Jest ona jednak mało użyteczna, bo nie otrzymujemy jej w stopniach, tylko w skali przetwornika ADC.
Aby doprowadzić do czytelnej dla człowieka postaci
należy dokonać kilku elementarnych przekształceń.
Zaglądając do reference manuala mikrokontrolera, na stronie 222 znajdziemy informację:
Wzór na obliczanie temperatury na podstawie wewnętrznego czujnika.
W tym wypadku Vsense będzie naszą wartością zmierzoną, a wartości V25 i Avg_Slope odnajdziemy na stronie 116 dokumentacji mikrokontrolera:
Brakujące wartości.
Nie zapominając o odpowiednim przekształceniu jednostek (dotyczy Avg_Slope) dodajemy te wartości i równanie do programu. Cały kod przedstawiono poniżej.
Zmienne globalne (aby można je było potem podejrzeć):
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint16_t PomiarADC;
float Temperature;
float Vsense;
/* USER CODE END PV */
Plik main wyglądać będzie następująco:
/* USER CODE BEGIN 2 */
HAL_ADC_Start(&hadc1);
const float V25 = 0.76; // [Volts]
const float Avg_slope = 0.0025; //[Volts/degree]
const float SupplyVoltage = 3.0; // [Volts]
const float ADCResolution = 4095.0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { // Oczekiwanie na zakonczenie konwersji
PomiarADC = HAL_ADC_GetValue(&hadc1);// Pobranie zmierzonej wartosci
Vsense = (SupplyVoltage*PomiarADC)/ADCResolution;// Przeliczenie wartosci zmierzonej na napiecie
Temperature = ((Vsense-V25)/Avg_slope)+25;// Obliczenie temperatury
HAL_ADC_Start(&hadc1);// Rozpoczecie nowej konwersji
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Skąd bierze się wzór na Vsense? Przypomnijmy, że pomiar z przetwornika jest 12 bitowy. Oznacza to, że dla minimalnej wartości napięcia (0V) dostaniemy wynik 0 z przetwornika. Natomiast dla maksymalnej wartości napięcia (czyli napięcia zasilania mikrokontrolera, w tym wypadku 3V) otrzymamy wartość maksymalną, czyli 4095. Prosta proporcja prowadzi do użytego wzoru.
Tym sposobem napisaliśmy program odczytujący w pętli temperaturę z wewnętrznego czujnika mikrokontrolera. Należy go teraz skompilować i wgrać na płytkę. Jak jednak sprawdzić co znajduje się w zmiennej PomiarADC, czy Temperature? W tym celu posłużymy się programem STMStudio.
STMStudio - instalacja
StmStudio jest narzędziem od ST, pozwalającym na wygodną graficzną wizualizację danych.
Sprawdza się w tym zastosowaniu o wiele lepiej niż debugger.
Krok 1. Pobieramy archiwum ze strony producenta. Krok 2. Rozpakowujemy je i rozpoczynamy instalację. Krok 3. Uruchamiamy program.
Bardzo możliwe, że pojawi się komunikat o braku Javy. Jeżeli tak, to w przeglądarce plików należy wybrać ścieżkę do javaw.exe (np. C:Program Files (x86)Javajre1.8.0_74binjavaw.exe).
Informacja o braku Javy.
STMStudio - podstawowa obsługa
Z głównego menu wybieramy File > Import Variables.
Import zmiennych do programu.
Następnie wybieramy skompilowany plik programu (tym razem w formacie elf). Przykładowa ścieżka do pliku to: ...KursSTM32Programy 2_ADCSW4STM32 2_ADCDebug 2_ADC.elf
Wybór odpowiedniego pliku.
W panelu dodawania zmiennych powinno pojawić się bardzo dużo wyrażeń. Odnajdujemy i zaznaczamy nasze zmienne. Jeżeli nie widzimy ich na pierwszy rzut oka, można posłużyć się wyszukiwarką. W oknie wyszukiwarki należy wpisać nazwę zmiennej (lub jej część), zaznaczyć ją kliknięciem i zatwierdzić przyciskiem Import.
Import wybranych zmiennych.
Postępujemy tak z trzema zmiennymi:
PomiarADC,
VSense,
Temperature.
Zmienne zostaną dodane do sekcji w lewej górnej części ekranu. Zaznaczamy wszystkie trzy, klikamy na nie prawym przyciskiem myszy i i wybieramy Send to > Var Viewer 1.
Dalsze operacje na wybranych zmiennych.
Ostatnią rzeczą którą należy zrobić, jest zmiana typu połączenia na ST-Link SWD oraz typu wyświetlania zmiennych na Table.
Ostatnie ustawienia STMStudio.
W centralnej części ekranu powinien pojawić się wiersz ze zmienną. Aby nawiązać połączenie, wybieramy Run > Start.
Jeżeli połączenie się nie uda, oznacza to,
że jesteśmy połączeni z płytką gdzieś indziej (ST-Link Utility / debugger).
Jeżeli wszystko poszło zgodnie z planem naszym oczom powinna ukazać się zmieniająca się wartość zmierzona przez ADC oraz obliczone na tej podstawie napięcie oraz temperatura.
Podgląd danych z mikrokontrolera STM32 F4.
Działanie czujnika można przetestować zwiększając temperaturę mikrokontrolera. Efekt ten można uzyskać poprzez pocieranie obudowy układu scalonego, albo przyłożenie do niego czegoś ciepłego (ja zbliżyłem mikrokontroler do wylotu chłodzenia w laptopie i po kilku sekundach otrzymałem wyniki jak na obrazku poniżej).
Odczyt wyższej temperatury.
STM32 F4 pomiar ADC z wykorzystaniem przerwań
Jak już wspominałem przy okazji artykułu dotyczącego GPIO, metody oparte o polling, a więc o ciągłe odpytywanie są mało optymalne. Spróbujmy skonfigurować przetwornik tak, aby sam informował nas o momencie zakończenia konwersji.
Konfiguracja Cube
Ponieważ będziemy korzystać z przerwań, możemy włączyć opcję Continuous Conversion Mode. Całą resztę pozostawiamy bez zmian.
Edycja ustawień przetwornika ADC.
Aby po każdej konwersji wywoływało się przerwanie, należy je aktywować dla przetwornika ADC w zakładce NVIC Settings.
Aktywacja przerwania.
To tyle jeśli chodzi o zmiany w konfiguracji. Generujemy projekt i przechodzimy do Workbencha.
Obsługa przerwania
Tak jak przy GPIO, musimy odnaleźć funkcję Callback, która wywoła się po zakończeniu konwersji. Możemy to zrobić wyszukując, tak jak poprzednio, frazę weak*callback (Search > File).
Wyszukiwanie odpowiedniej funkcji.
Funkcja która nas interesuje, to HAL_ADC_ConvCpltCallback(Conversion Complete Callback). Należy ją zdefiniować w pliku main i przenieść tam cały kod przeliczający wartości.
Możemy jednak pominąć dwa zbędne w tym wypadku elementy.
Po pierwsze, nie musimy już sprawdzać funkcją PollForConversion,czy konwersja została zakończona. Po drugie, ponieważ włączyliśmy Continuous Conversion Mode, kolejne pomiary będą się wykonywać automatycznie po sobie bez naszego udziału.
Przekopiowany do przerwania kod powinien wyglądać następująco:
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint16_t PomiarADC;
float Temperature;
float Vsense;
const float V25 = 0.76; // [Volts]
const float Avg_slope = 0.0025; //[Volts/degree]
const float SupplyVoltage = 3.0; // [Volts]
const float ADCResolution = 4096.0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
PomiarADC = HAL_ADC_GetValue(&hadc1); // Pobranie zmierzonej wartosci
Vsense = (SupplyVoltage*PomiarADC)/(ADCResolution-1); // Przeliczenie wartosci zmierzonej na napiecie
Temperature = ((Vsense-V25)/Avg_slope)+25; // Obliczenie temperatury
}
/* USER CODE END PFP */
Wszystkie stałe również musimy wyciągnąć z funkcji main, tak aby były dostępne w przerwaniu.
Podczas kopiowania i wklejania fragmentów kodu jego formatowanie może się rozjeżdżać. Aby nie poprawiać tego ręcznie, wystarczy użyć skrótu Shift+Ctrl+f, aby dokonać autoformatowania kodu.
Ostatnią rzeczą jaka została do zrobienia, to uruchomienie przetwornika w odpowiednim trybie. W funkcji main powinna pozostać tylko jedna dopisana przez nas linijka.
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_ADC_Start_IT(&hadc1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Po zbudowaniu i wgraniu programu możemy przetestować jego działanie - tak jak poprzednio za pomocą STMStudio. Efekt działania programu powinien być identyczny.
STM32 F4 pomiar ADC z wykorzystaniem DMA
Jak dotąd za każdym razem gdy chcieliśmy odczytać pewną wartość, czy to stanu na pinie, czy pomiaru ADC, musieliśmy w tym celu wywołać funkcję zwracającą wynik. Czy nie można jednak obejść się bez tego i od razu przekierować danych z peryferii do odpowiednich zmiennych?
Otóż można! Służy do tego DMA (Direct Memory Access), którego zadaniem jest szybki transfer danych pomiędzy obszarami pamięci, oraz pomiędzy peryferiami, a pamięcią.
Dzięki temu możliwa jest ciągła wymiana danych praktycznie bez udziału procesora.
Przykład: joystick analogowy z przyciskiem
W tym przykładzie użyjemy dwuosiowy joystick analogowy z przyciskiem. Znajduje się on na wyposażeniu dedykowanego pod ten kurs zestawu elementów.
Używany joystick. Źródło zdjęcia: http://botland.com.pl
Jostick posiada 5 wyprowadzeń:
GND - podłączamy pod którekolwiek z wyprowadzeń GND na płytce Discovery.
+5V - podłączamy pod wyprowadzenie 3V (taki jest zakres pomiarowy przetwornika).
VRx - sygnał analogowy z osi pionowej (przód - tył). Podłączamy do pinu PA1.
VRy - sygnał analogowy z osi poziomej (prawo-lewo). Podłączamy do pinu PA2.
SW - Wyjście sygnału z przycisku - podłączamy pod pin PA3.
Konfiguracja w Cube
Tworzymy nowy projekt o nazwie 02_ADC_DMA. W konfiguracji peryferii wybieramy kanały IN1 oraz IN2 przetwornika ADC1.
Odpowiednia konfiguracja wyprowadzeń.
Wchodzimy do panelu przetwornika ADC. Opcje, które należy ustawić przedstawione są po niżej:
Clock Prescaler, Resolution, Data Alignment - konfigurujemy tak jak w poprzednich przykładach.
Scan Conversion Mode - ponieważ w jednej grupie konwersji będziemy teraz dokonywać pomiaru dwóch kanałów, chcielibyśmy żeby były one wykonane od razu po sobie. Tę funkcjonalność zapewnia nam włączenie Scan Conversion Mode.
Continuous Conversion Mode - chcielibyśmy, żeby po zakończeniu grupy konwersji przetwornik automatycznie rozpoczynał nowe pomiary bez potrzeby ingerencji z naszej strony. Należy więc włączyć Continuous Conversion Mode.
Discontinuous Conversion Mode - można to traktować niejako jak przeciwieństwo poprzedniej opcji. Wynika stąd, że tryb ten musi być wyłączony.
DMA Continuous Request - włączenie tej opcji spowoduje wysłanie zapytania do kontrolera DMA po każdej zakończonej konwersji. Ponieważ w tym przykładzie chcemy wykorzystać DMA, należy tę opcję włączyć.
End Of Conversion Selection - pozostawiamy bez zmian,
Następnie musimy skonfigurować poszczególne pomiary. Operację tę należy rozpocząć od zwiększenia wartości Number Of Conversion do 2. Następnie wypełnić wszystkie pola, które się pojawią zgodnie z naszymi preferencjami.
Najbardziej istotne w tym momencie jest skonfigurowanie pomiarów oznaczonych jako rank 1 i rank 2 tak, aby dokonywały konwersji na różnych kanałach.
Po skonfigurowaniu przetwornika przechodzimy do zakładki DMA.
Konfiguracja DMA dla przetwornika ADC.
Wybieramy Add aby dodać nowy kanał DMA i konfigurujemy go na ADC1. W dolnej części panelu pojawią się parametry transmisji, w których zmieniamy jedynie Mode na Circular. Konkretne informacje na temat poszczególnych opcji dostępnych w panelu konfiguracyjnym DMA zostaną przedstawione przy okazji innego artykułu.
Następnie przechodzimy do zakładki NVIC i upewniamy się że przerwania od DMA są włączone, a przerwania od ADC nie.
Odpowiednia konfiguracja przerwań.
Tym samym zakończyliśmy konfigurację ADC z DMA. Generujemy i importujemy nowy projekt.
Pozostało nam już tylko napisać odpowiedni kod, aby uruchomić konwersje przetwornika z wykorzystaniem DMA. Tak jak poprzednio, potrzebujemy zmiennej, do której wczytamy pomiary. Tym razem użyjemy w tym celu tablicy dwuelementowej, ponieważ przetwornik będziemy wykonywał dwa pomiary w jednej sekwencji konwersji.
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint16_t Joystick[2];
/* USER CODE END PV */
Następnie w funkcji main uruchamiamy konwersje przetwornika ADC z DMA. Funkcja HAL_ADC_Start_DMA jako drugi argument przyjmuje adres pamięci, do której mają być wpisywane wyniki pomiarów.
Nazwa tablicy jest adresem jej pierwszego elementu, więc wystarczy wpisać tam Joystick. Ostatnim parametrem jest ilość przesyłanych danych, która w tym wypadku wynosi 2.
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1, Joystick, 2);
/* USER CODE END 2 */
To już wszystko co musimy zrobić, aby uruchomić ADC wykorzystujące DMA. Po uruchomieniu programu na mikrokontrolerze możemy sprawdzić efekt jego działania za pomocą STMStudio.
Aby zaimportować wszystkie elementy tablicy do STMStudio,
należy zaznaczyć pole Expand table elements.
Import wszystkich elementów tablicy.
Obydwie pozycje tablicy powinny pokazywać wartości na poziomie ~2000. Wychylając gałkę joysticka do przodu (od przewodów) wartość zmiennej Joystick[0] powinna się zwiększać.
Maksymalna wartość jaką może przyjąć, to wartość wynikająca z ustawionej rozdzielczości pomiarów, a więc 4095. Wychylając gałkę do tyłu (w stronę przewodów) wartość powinna maleć. Analogiczne działanie można zaobserwować w drugiej osi.
Działanie joysticka w praktyce - animacja.
Warto zauważyć jak wygodne w użyciu jest DMA. Po odpowiednim skonfigurowaniu peryferii, programista nie musi się przejmować praktycznie niczym. Wszystko dzieje się automatycznie i jedyne co musimy zrobić to uruchomić proces jedną funkcją.
Zadania dodatkowe do przećwiczenia
Aby opanować dobrze opisane do tej pory materiały proponuję napisać program, wykonujący poniższe zadania:
Po wychyleniu joysticka poniżej 25% lub powyżej 75% swojej wartości maksymalnej, na płytce DiscoveryF4 powinna zapalić się odpowiadająca kierunkowi wychylenia dioda. Po powrocie joysticka do pozycji 25-75% dioda powinna zgasnąć.
Naciśnięcie przycisku na joysticku powinno spowodować inwersję sterowania. Oznacza to tyle, że wychylenie joysticka, w którąś ze stron ma powodować zapalenie się diody po przeciwnej do kierunku wychylenia stronie. Po ponownym naciśnięciu przycisku sterowanie powinno wrócić do domyślnego.
Jeżeli temperatura mikrokontrolera wzrośnie powyżej 30°, powinien włączyć się alarm. Jest on sygnalizowany miganiem wszystkich diod z częstotliwością 3Hz.
Alarm ma wyższy priorytet niż informacje pochodzące z joysticka. Może być zresetowany niebieskim przyciskiem na płytce discovery. Alarm po zresetowaniu włączy się na nowo się dopiero wtedy, gdy temperatura spadnie poniżej 30° i ponownie przekroczy tę wartość.
Działanie powyższego algorytmu podejrzeć można na powyższym filmie:
Plik binarny z programem realizującym podaną wyżej funkcjonalność oraz cały projekt znajdziecie w załączniku. Wszystkie wyprowadzenia zostały podłączone tak jak w przykładzie z joystickiem.
Podsumowanie
Dziś nauczyliśmy się obsługiwać przetwornik ADC w 3 trybach: blokującym, z wykorzystaniem przerwań oraz z wykorzystaniem DMA. Wiemy również jak odczytać temperaturę mikrokontrolera z wewnętrznego czujnika. Nauczyliśmy się także korzystać z STMStudio, które ułatwia współpracę z mikrokontrolerami od ST dzięki łatwo dostępnemu podglądowi zmiennych.
Uwaga! Ten kurs został zarchiwizowany. Sprawdź najnowszy kurs STM32 »
W następnym odcinku przyjrzymy się komunikacji z mikrokontrolerem za pomocą UART. Poznamy też wygodniejszy sposób wgrywania programu (przez debugger w SW4STM32). Jeżeli macie jakieś pytania lub uwagi, to piszcie w komentarzach!
Nie chcesz przeoczyć kolejnych części kursu? Skorzystaj z poniższego formularza i zapisz się na powiadomienia o nowych artykułach!
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...