Przetwornik ADC (analogowo-cyfrowy) w mikrokontrolerach STM32 to kluczowe narzędzie do cyfrowego przetwarzania sygnałów analogowych, niezbędne w robotyce do odczytu czujników napięciowych, potencjometrów czy joysticków. W tym przewodniku omawiamy konfigurację i pomiar napięcia za pomocą biblioteki HAL w środowisku STM32CubeIDE, z przykładami kodu, obliczeniami oraz praktycznymi wskazówkami.
Zasady działania przetwornika ADC w STM32
Przetworniki w rodzinie STM32, np. STM32F103 czy F411RE, są najczęściej 12‑bitowe, co daje rozdzielczość 4096 poziomów (0–4095). Sygnał 0–Vref (zwykle 3,3 V) jest kwantyzowany do tych dyskretnych wartości.
Przykład: dla 1,0 V przy Vref = 3,3 V wartość cyfrowa to około 1241 (1/3,3 × 4095 ≈ 1240,9).
Proces obejmuje próbkowanie (chwilowy odczyt) i kwantyzację (przypisanie poziomu). ADC nie zna rzeczywistego Vref – porównuje wejście z wewnętrznym schodkiem napięciowym. Uwaga krytyczna: nigdy nie podawaj na wejście ADC napięcia powyżej 3,3 V – grozi to uszkodzeniem!
W robotyce ADC służy do typowych zadań, takich jak:
- odczytu pozycji joysticka (dwa potencjometry jako dzielniki napięcia),
- pomiaru napięcia z potencjometru na pinie, np. PC4,
- monitoringu czujników (np. odległości, światła) w autonomicznych robotach.
Konfiguracja ADC w STM32CubeMX i HAL
Do generowania kodu skorzystaj z STM32CubeMX – to najprostsza ścieżka dla początkujących. Przykład oparto na płytce Nucleo‑F411RE (HAL F4 v1.24.1).
Krok 1 – konfiguracja pinu i kanału ADC
Wykonaj poniższe ustawienia w STM32CubeMX:
- w STM32CubeMX wybierz pin analogowy, np. PA0 (kanał ADC1_IN0),
- w sekcji ADC1 skonfiguruj parametry jak poniżej,
- ustaw Resolution na 12 bits,
- wyłącz Continuous Conversion Mode dla pomiaru na żądanie (oszczędność energii),
- ustaw Number of Conversions na 1 dla pojedynczego kanału lub więcej dla sekwencji (np. 3 kanały),
- dobierz Sampling time do źródła sygnału (krótszy dla szybkich zmian, dłuższy dla dużej impedancji).
Przykład połączenia: potencjometr – jedna końcówka do 3,3 V, druga do GND, środkowa do PA0.
Krok 2 – generowanie kodu i inicjalizacja
Po wygenerowaniu projektu HAL tworzy uchwyt, np. ADC_HandleTypeDef hadc1. Zainicjalizuj ADC w main() wywołując funkcję inicjalizacyjną:
/* USER CODE BEGIN 2 */
MX_ADC1_Init(); // Automatycznie generowane przez CubeMX
/* USER CODE END 2 */
Pomiar napięcia – metody w HAL
Biblioteka HAL oferuje kilka metod akwizycji – najprostszy na start jest polling (odpytywanie).
Metoda 1 – polling (na żądanie)
Poniższy kod uruchamia konwersję, czeka na wynik i przelicza wartość na napięcie:
while (1) {
HAL_ADC_Start(&hadc1); // Rozpocznij konwersję
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { // Czekaj maks. 10 ms
uint16_t rawValue = HAL_ADC_GetValue(&hadc1); // 12-bit (0-4095)
// Oblicz napięcie: V = (rawValue / 4095.0) * Vref
float voltage = (rawValue * 3.3f) / 4095.0f;
printf("Raw: %u, Napięcie: %.2f V\n", rawValue, voltage); // UART
}
HAL_Delay(100); // Dla czytelności
}
W przypadku sekwencji wielu kanałów skonfiguruj regularną grupę i odczytuj wartości kolejno po każdym zakończeniu konwersji.
Metoda 2 – przerwania (IRQ)
Aby nie blokować pętli głównej, w STM32CubeMX włącz globalne przerwanie ADC w NVIC, a następnie skorzystaj z wywołania zwrotnego po zakończeniu konwersji:
HAL_ADC_Start_IT(&hadc1); // Uruchom z przerwaniem
// W callbacku:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc->Instance == ADC1) {
uint16_t rawValue = HAL_ADC_GetValue(hadc);
float voltage = (rawValue * 3.3f) / 4095.0f;
// Obsłuż dane, np. sterowanie
}
}
Metoda 3 – DMA (Direct Memory Access) – dla wysokiej wydajności
Dla szybkiego, ciągłego próbkowania (np. audio, szybkie sensory) skonfiguruj DMA i uruchom transfer do bufora:
HAL_ADC_Start_DMA(&hadc1, buffer, BUFSIZE);
Porównanie metod
Poniższa tabela podsumowuje zalety, wady i typowe zastosowania każdej metody akwizycji sygnału:
| Metoda | Zalety | Wady | Zastosowanie w robotyce |
|---|---|---|---|
| Polling | prosta, przewidywalna | blokuje pętlę główną | prototypy, pojedyncze pomiary |
| IRQ | asynchroniczna, nie blokuje | wymaga obsługi przerwań | czujniki czasu rzeczywistego |
| DMA | minimalne obciążenie CPU, ciągłe próbkowanie | bardziej złożona konfiguracja | audio, szybkie sensory |
Praktyczne przykłady z robotyki
Przykład 1 – odczyt joysticka
Joystick to dwa potencjometry (osie X i Y) na dwóch kanałach, np. PA0 i PA1. Surowe wartości mieszczą się w zakresie 0–4095, a do sterowania robotem wygodnie jest zmapować je na −100%…+100% (dead‑zone wokół środka poprawia stabilność).
Przykład 2 – wyświetlanie na LCD i LED (np. ZL27ARM)
Mierz napięcie na PC4, oblicz:
float voltage = (raw * 3.3f) / 4095.0f;
Następnie pokaż wartość na LCD (Nokia 5110) i bargraf na LED. Dla regularnych pomiarów wyzwalaj akwizycję co 100 ms z SysTick.
Przykład 3 – wielokanałowy pomiar na żądanie
Dla STM32F411RE skonfiguruj sekwencję trzech kanałów (np. IN0, IN1, IN2), a następnie po wywołaniu HAL_ADC_PollForConversion() pobieraj kolejne próbki przez wielokrotne HAL_ADC_GetValue().
Obliczenia i kalibracja
Wzór na napięcie: Vin = (ADCraw / (2^rozdzielczość − 1)) × Vref. Dla 12 bitów: Vin = (raw / 4095) × 3,3 V.
Kalibracja: użyj HAL_ADCEx_Calibration_Start() w celu poprawy dokładności; rozważ pomiar wewnętrznego Vref (~1,2 V) do kompensacji wahań zasilania w konstrukcjach bateryjnych.
Najczęstsze błędy i pułapki to:
- szum – zwiększ sampling time albo zastosuj filtrację cyfrową,
- nieliniowość – dla efektywnej rozdzielczości > 12 bit zastosuj oversampling i uśrednianie,
- zakres wejściowy – tylko 0–3,3 V (dopasuj dzielnik napięcia).
Zaawansowane wskazówki dla robotyków
Przy ambitniejszych projektach przydadzą się poniższe praktyki:
- szybkość próbkowania – w STM32H7 do 5 MSPS, lecz warstwa HAL dodaje narzut; dla rekordów użyj LL,
- wielokrotne przetworniki – niektóre STM32 mają do 3 bloków ADC; synchronizuj je przy pomiarach fazowych (np. silniki BLDC),
- integracja z RTOS – przesyłaj dane z IRQ/DMA do zadań przez kolejki FreeRTOS,
- debug – użyj oscyloskopu (pin testowy) i UART printf do logowania surowych wartości i napięć.