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ęć.