fbpx

Projekty mikrokontrolerowe często potrzebują dokonywać pomiarów sygnałów analogowych. Niestety same rozumieją tylko ciągi cyfrowe, które mogą przybrać tylko dwa stany. Najczęściej jest to poziom masy oraz zasilania. Aby zmierzyć wartość analogową należy użyć przetwornika analogowo cyfrowego – w skrócie ADC. Praktycznie każdy współczesny mikrokontroler jest w taki przetwornik wyposażony. STM32 oczywiście też.

ADC w STM32

Układ przetwornika analogowo cyfrowego w mikrokontrolerach STM32 jest dosyć rozbudowany. Niektóre z MCU mają nawet dwa, a nawet więcej takich przetworników. Do głównych cech wbudowanych ADC należą:

  • Konfigurowalna rozdzielczość do 12 bitów
  • Mnogość przerwań np. od zakończenia konwersji zwykłej, wstrzykiwanej lub od analogowego watchdoga
  • Tryb konwersji pojedynczy, lub ciągły
  • Automatyczne skanowanie kanałów
  • Programowalny czas próbkowania
  • Generowanie żądania odczytu przez DMA

To tylko najważniejsze rzeczy, które wybrałem z dokumentacji. Ok, ale jak działa taki przetwornik?

Próbkowanie

Sygnał analogowy może przybierać różne wartości napięcia. Do tego jest on ciągły w czasie. Próbkowanie polega na wyznaczeniu momentów pomiarowych sygnału analogowego, który podawany jest na wejście ADC. W efekcie próbkowania dostajemy pojedyncze punkty, które należą do wykresu ciągłego.

Bardzo ważne jest tutaj twierdzenie o próbkowaniu (Kotielnikowa-Shannona). Mówi ono o tym, że aby w zadowalający sposób móc odtworzyć ze spróbkowanego sygnału cyfrowego oryginalny sygnał analogowy, należy próbkować z częstotliwością co najmniej dwukrotnie większą od najwyższej częstotliwości składowej sygnału.

Krótko mówiąc – próbkując sygnał sinusoidalny 1 kHz, powinieneś robić to, co najmniej z częstotliwością 2 kHz. Przykład bliższy życiu – podstawowe próbkowanie dźwięku wykonuje się z częstotliwością 44,1 kHz, ponieważ przyjmuje się, że sygnał mowy ma najwyższą częstotliwość składową około 20 kHz.

Kwantowanie

Jest to tzw. cyfrowy pomiar. Próbkowana wielkość przedstawiana jest w postaci najbliższej wartości cyfrowej. Kwantowanie jest silnie uzależnione od rozdzielczości przetwornika. Mierzona wartość wyrażona jest bowiem w liczbie kwantów, czyli liczbie najmniejszych części, na które można podzielić pełny zakres napięciowy przetwornika.

Mając 12-bitowy przetwornik, jego rozdzielczość wynosi 4096 i tyle mamy tych kwantów. Dajmy na to, że napięcie referencyjne, czyli to, wobec którego porównujemy nasz sygnał wynosi 3,3 V. Zmierzona wartość 4095 odpowiada temu napięciu. Podając 1 V na wejście ADC, próbka będzie miała wartość 1/3,3 * 4095 = 1240,909. Jak widzisz wartość ta nie wypada idealnie na równym kwancie, więc przetwornik przyporządkuje ją do 1241. Na tym polega kwantyzacja 🙂

Metoda, jakiej używa przetwornik jest nieco inna niż takie mnożenie napięć. Przetwornik nie zna wartości napięcia odniesienia. Może na przykład porównywać sygnał analogowy z narastającym sygnałem schodkowym w zakresie napięcia odniesienia, a następnie zapamiętanie liczby tych schodków. Pamiętaj, że po tym etapie dostajemy liczbę kwantów, a nie wartość napięcia.

Kodowanie

Ostatni etap to dostosowanie zmierzonej liczby kwantów do kodowania użytego w aplikacji. W mikrokontrolerach najczęściej jest to jednak zapis binarny liczby kwantów. W ustawieniach ADC można na przykład wyrównać tę wartość do lewej lub prawej w 16-bitowym rejestrze wyjściowym ADC.

Odczyt sygnału joysticka na STM32

Skoro już wiesz z grubsza jak działa ADC, możemy przejść do jego użycia. Joystick taki to nic innego jak dwa potencjometry działające na zasadzie dzielnika rezystancyjnego. Dwie skrajne nóżki potencjometrów są podłączone do + i – zasilania. Środkowa nóżka wyprowadzona jest na zewnątrz i napięcie na niej odwzorowuje pozycję drążka.

Jako że mamy dwie osie to takie potencjometry są dwa. Jak możesz się domyślić, będziemy mierzyć dwa niezależne sygnały. Jeden dla pozycji w osi X, drugi w osi Y.

Dzisiaj działam na NucleoF411RE razem z STM32CubeIDE oraz bibliotekami HAL F4 w wersji 1.24.1.

Schemat

Joystick podłączyłem pod kanały 6 i 7 przetwornika analogowo cyfrowego.

Kod odczytu ADC – Polling

W pierwszej kolejności pokażę Ci najprostszy sposób na odczyt z ADC, czyli “ręczne” zlecanie konwersji ADC oraz odczyt wartości.

Przejdźmy do Cube’a. Utwórz projekt dla płytki Nucleo F411RE. Zaakceptuj domyślną konfogurację peryferiów na płytce. Przyśpiesza to nieco konfigurację – UART jest już z bańki i HCLK jest ustawione na dosyć wysoką wartość 84 MHz.

Napisałem, że podłączyłem się do kanałów IN6 i IN7, dlatego zaznacz je w konfiguracji ADC1.

Zauważysz, że piny na modelu obok się podświetlą. Teraz można przejść do ustawień ADC. Możesz ustawić preskaler zegara jak Ci się podoba. Ja mam ustawione na dzielenie przez 4. Upewnij się, że masz 12-bitową rozdzielczość, choć oczywiście z mniejszą również będzie działać poprawnie. Reszta domyślna tak jak na poniższym screenie.

I to tyle z konfiguracji. Można wygenerować kod i przejść do edytora.

Polling jak wspominałem wcześniej jest to “ręczne” odpytywanie ADC. W takim wypadku trzeba:

  1. Jeżeli trzeba włączy ADC
  2. Zlecić konwersję, czyli pobranie próbki, kwantyzację oraz kodowania
  3. Poczekać na zakończenie konwersji
  4. Odczytać zmierzoną wartość

Pierwszy i drugi punkt robi funkcja

Trzeci punkt zrealizujesz za pomocą

Ostatni to już tylko odczytanie z rejestru. Jest na to oczywiście funkcja

W celu pobrania kolejnej próbki czynność powtórzyć. Dobra, dobra! Nie wiem, czy wiesz, ale ADC może konwertować tylko jeden kanał na raz. Oznacza to, że musi mieć gdzieś wskazane, którym ma się zająć. Domyślnie jest to pierwszy zaznaczony, czyli w naszym wypadku IN6. Dla oczytu innych kanałów przed zleceniem konwersji należy wskazać, z którego kanału ma skorzystać. 

Napisałem to dego prostą funkcję opierając się na kodzie inicjalizującym ADC który wygenerował Cube.

Kod standardowo jest w czwartej sekcji użytkownika.

Ostatecznie odczytanie dwóch kanałów i przesłanie ich na UART2 wygląda tak:

Wynikiem tego są wartości ADC na terminalu. Bez ingerencji w gałkę joysticka, znajduje się ona w centralnym położeniu. Połowa z 12-bitowej rozdzielczości to 2048, czyli około tego wyniku powinniśmy się spodziewać.

Jak widać bywa różnie, ale zawsze gdzieś wokół 2048. Niestety pomiar ADC potrafi mocno pływać. Ma na to wpływ mnóstwo czynników, ale to nie jest dzisiejszy temat. Można na przykład zebrać 10 próbek i uśrednić je. Na pewno będzie wtedy mniej pływania.

Myślę, że czasu konwersji nie ma co badać. Wymienię tylko, że na jego długość mają takie czynniki jak:

  • Czas próbkowania
  • Taktowanie ADC
  • Rozdzielczość

Przetworniki mogą być bardzo szybkie. W STM32 niektóre sięgają maksymalnemu samplingowi na poziomie na przykład 5 MSPS(Megasamples per Second) w STM32F303. Zewnętrzne układy potrafią być jeszcze szybsze.

Kod z pollingiem znajdziesz tutaj.

Kod odczytu ADC – Continuous reading DMA

Nie mogło zabraknąć DMA 🙂 Mało tego! Możes uruchomić ADC raz i zapomnieć o nim całkowicie. Umożliwia to Continuous Conversion Mode. Polega on na tym, że ADC zaraz po zakończonym pomiarze startuje kolejny bez naszej ingerencji.

Dodatkowo, gdy chcesz mierzyć więcej niż jeden kanał przyda się Scan Conversion Mode. Dzięki niemu ADC będzie sam przełączał kolejne kanały. Fajnie, co nie?

Do tego dorzućmy DMA, które samodzielnie odczyta pomiary i wpisze je w odpowiednie miejsce w pamięci. Koniec z ręcznym odpytywaniem i czytaniem pomiarów!

Do dzieła. Skonfigurujmy tę magię. Poprawmy też delikatnie wahania odczytów. Tak się prezentuje konfiguracja.

Lećmy od góry. Zwiększyłem preskaler ADC. Będzie działał trochę dłużej, ale powinno poprawić to stabilność odczytu(dłuższy sampling).

Włączyłem Scan Conversion Mode i Continuous Conversion Mode z wiadomych względów.

Włączyłem DMA Continuous Requests. Pozwólmy, aby ADC sam szturchał DMA, że ma już gotowe dane do odczytu.

Upewnij się, że End of Conversion Selection masz ustawione na zakończenie konwersji pojedynczego kanału.

Teraz w sekcji ADC_Regular_ConversionMode ustaw ilość konwersji na 2. Chcemy, aby ADC automatycznie skanowało dwa kanały.

Tutaj jeszcze ustawiłem sampling każdego z kanałów na wartość maksymalna 480 cykli zegara. Wpłynie to na stabilność odczytów.

Tak przygotowaną konfigurację możesz wygenerować i przejść do kodu.

Narobiliśmy się, ale co wpisać w kodzie. Otóż cały kod, który będzie nam konwertował sygnał analogowy i pisał do przygotowanych wcześniej zmiennych to, uwaga:

Tak! Wystarczy uruchomić ADC w trybie DMA i już odczyty z obu kanałów lecą do wybranej tablicy (u mnie uint16_t Joystick[2]). Warto tutaj wspomnieć, że lecą one z maksymalną możliwą prędkością.

Jak szybko w tej konfiguracji? Sprawdźmy poprzez zmianę stanu pinu testowego podczas każdego zakończonego transferu DMA.

Obydwa kanały odczytywane są co 92 µs, czyli wychodzi jakieś 10,8 kHz. Nie ma szału, ale pamiętaj, że w tej chwili mamy bardzo długi czas samplowania.

Kod z odczytem ciągłym znajdziesz tutaj.

No dobra, ale jeśli chciałbym uzyskać 44,1 kHz to po prostu skracam sampling i próbuję tym sposobem wcelować w 44,1 kHz? Zmniejszyć czas samplowania tak, ale czy celować na ślepo tak, aby to była pełna prędkość ADC? Niekoniecznie. Jest lepsza metoda!

Kod odczytu ADC – Timer triggering conversion

Możesz zaprzęgnąć któryś z Timerów, aby w równych odstępach czasu zlecał konwersję sygnału analogowego. Dzięki temu ustawiając Timer na 44,1 kHz będziemy pewni, że kolejne próbki będą pobierane w odpowiednim czasie. Let’s do this!

Ja zostawię czas konwersji tak jak był. Spróbujmy wycelować Timerem w 200 Hz regularnego samplingu. To będzie wystarczające dla działania z joystickiem. Takie urządzenie nie wymaga częstego czytania. Przejdźmy do Cube’a. Najpierw ADC.

Jak widzisz wyłączyłem Continuous Conversion Mode. Tym razem to nie ADC będzie decydowało o tym, kiedy wystartować konwersję.

W sekcji ADC_Regular_ConversionMode jest takie ustawienie jak External Trogger Conversion Source. Do wyboru masz kilka Timerów oraz rodzaj triggera. Albo jest to Capture Compare Event albo Out Event. Ustaw Timer 2 Trigger Out Event. Będzie on występował po przepełnieniu licznika TIM2.

Teraz przejdź do ustawień TIM2.

Clock Source ustaw na Internal Clock. Taktowanie dostarczone, teraz konfiguracja przepełnienia.

Chcemy uzyskać 200 Hz, czyli każde przepełnienie powinno wystąpić dokładnie co 5 ms. W pierwszej kolejności ustawmy Preskaler. Częstotliwość “główna” to 84 MHz. Dzieląc przez 84, uzyskasz tick na poziomie 1 µs. Dzielny dalej. Podział na 8400 da już 0,1 ms. Może być, co? Wpisz 8400-1 lub 8399.

Teraz ustaw okres zliczania na 49, aby uzyskać 5 ms na przepełnieniu licznika. 

Ważne, abyś ustawił TRGO, które jest wyzwalaczem dla ADC. Trigger Event Selection ustaw na Update Event, aby przekazywać odpowiednie zdarzenie dla ADC. Teraz przejdźmy do kodu.

Kod jest bardzo podobny do tego, który obsługuje ADC + DMA w trybie ciągłym.

Jedyną różnicą jest wystartowanie Timera przed uruchomieniem ADC. Teraz samplowanie powinno odbywać się co 5 ms.

Wyszło 4,93 ms, czyli około 202,8 Hz, więc bardzo blisko. Można by się zastanowić, dlaczego nierówne 5. Ustawiając przepełnienie na 50 uzyskałem według analizatora 5,03 ms, a więc trochę lepiej. Pytanie jak bardzo dokładny mam analizator. Podejrzewam, że nie jest to sprzęt wysokiej klasy 🙂

Spróbuj uzyskać samplowanie Audio, czyli 44,1 kHz. Kod z triggerowaniem timerem znajdziesz tutaj.

Podsumowanie

Jak widzisz wbudowane w STM32 przetworniki analogowo cyfrowe mają ogromne możliwości. Ich konfiguracja jest naprawdę rozbudowana, a jednocześnie prosta mając Cube’a. Przy dobrym skonfigurowaniu, użycie ADC może sprowadzić się jedynie do jego wystartowania, co jest mega udogodnieniem. W Arduino tego nie ma 😉

Co myślisz o ADC w STM32? Podziel się swoją opinią w komentarzu.

Pełny projekt wraz z biblioteką znajdziesz jak zwykle na moim GitHubie: link

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.


Raz na jakiś czas wyślę Ci też e-mail z ciekawymi rzeczami które znalazłem


7 Komentarzy

Fonak · 18/10/2019 o 19:09

Witam,
Faktycznie STM32 ma dość rozbudowany moduł ADC i jego poznanie wymaga sporo zaangażowania i czasu którego zawsze brakuje :). Może wiec fajnym pomysłem byłoby podzielenie artykułu na części (nie koniecznie po sobie następujące) i opisane tych bardziej zaawansowanych trybów w następnych odcinkach o ADC. Do napisania komentarza o zaawansowanych trybach ADC skłoniła mnie wręcz szczątkowa ilość tutoriali na innych stronach o tematyce związanej z STM32, nawet forum producenta ma niewiele wątków w tym temacie.
Co do samego artykułu to bardzo dobry.

Fonak · 17/10/2019 o 17:44

Witam,
Dobry artykuł lecz uważam że do pełni szczęścia powinny zostać opisane jeszcze takie tryby pracy jak:
– “dual reguar simultaneous mode” bardzo przydatne gdy zachodzi konieczność pomiaru prądu i napięcia jednocześnie, mocy (pomiar kąta) czy impedancji (mostek LCR)
– “dual fast interleaved mode” przydatne w przypadku konieczności podwojenia maksymalnej częstotliwości próbkowania
– wszystkie tryby z “injected”
– tryb z przerwaniami “IT”
– tryb DMA z użyciem dwóch naprzemiennie wykorzystywanych buforów – przydatne w sytuacji gdy procesor obrabia dane w pierwszym buforze natomiast przetwornik zapisuje do drugiego, następnie role sie zamieniają.
– przydałby się dokładniejszy opis odnośnie taktowania ADC w odniesieni do czasu konwersji
PS. Tak wiem że STM32F411 nie ma dwóch modułów ADC lecz nawet wiekowy STM32F103 je ma (popularny chiński bluepill lub klon maple).

    Mateusz Salamon · 17/10/2019 o 19:35

    Hej, dzięki za spory komentarz. Zgadam się z tym, że mój artykuł to dopiero początek. ADC w STMach jest na prawdę bardzo rozbudowany. Do tego dochodzą tak jak zauważyłeś różnice między rodzinami w ilości przetworników czy nawet ficzerów w nich zawartych. Niestety gdybym miał to wszystko opisać w jednym wpisie to wyszła by mała książka, a tego raczej unikam tworząc artykuły. Na pewno pojawią się z czasem również te bardziej zaawansowane tryby i zastosowania ADC.

Lukasz · 17/10/2019 o 11:53

Fajny artykuł. Ja korzystam z płytki stm32f072rb i przy pomocy ADC i DMA muszę odczytać pomiar z dwóch kanałów – temp wew procesora oraz stężenie gazu w otoczeniu z czujnika mq5. W CubeMX dla mojej płytki nie ma jednak opcji Number of Conversions. I teraz się zastanawiam czy ma to duże znaczenie?

    Mateusz Salamon · 17/10/2019 o 12:16

    Hmm chyba domyślnie skanuje wszystkie wybrane kanały bo F0 chyba nie na możliwości pomiaru w trybie injected. Spróbuj 🙂

Marcin · 16/10/2019 o 20:51

Dobry materiał, polecam. II część używam, I zacznę

    Mateusz Salamon · 16/10/2019 o 21:46

    Dzięki 🙂 Warto zawsze zastanowić się która metoda przypasuje do projektu. Nie ma jednej, najlepszej 😀

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Serwis wykorzystuje pliki cookies. Korzystając ze strony wyrażasz zgodę na wykorzystywanie plików cookies. Więcej informacji znajdziesz na stronie Polityka Prywatności

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close