Kursy • Poradniki • Inspirujące DIY • Forum
Przetwornik ADC w STM32 F1
Mikrokontroler STM32F103RB jest wyposażony w dwa, 12-bitowe przetworniki analogowo-cyfrowe. Każdy z przetworników posiada na wejściu multiplekser, dzięki któremu można odczytywać dane nawet z 16 różnych linii wejściowych.
Dodatkowo opisywany układ posiada wbudowany czujnik temperatury oraz źródło napięcia referencyjnego 1,2V. O ile czujnik temperatury jest mało dokładny i można nim mierzyć tylko zmiany temperatury, o tyle napięcie referencyjne możemy wykorzystać do przetestowania naszego sposobu odczytu napięcia za pomocą ADC.
Jak interpretować wynik?
Najpierw odrobina matematyki. Pomiar wykonywany jest względem napięcia zasilania, czyli 3.3V. Czujnik jest 12-bitowy, więc wynik jest liczbą z zakresu 0 - 4095 (212 = 4096 wartości). Jeśli na wejściu przetwornika pojawi się napięcie Vadc, powinniśmy odczytać następującą wartość:
ADC = Vadc / 3.3V * 4096
Przykład: wiemy, że napięcie referencyjne to 1.2V, jaki powinniśmy otrzymać odczyt?
ADC = 1.2V / 3.3V * 4096 = 1489
Spróbujmy teraz napisać program, który odczyta wartość napięcia referencyjnego, a wynik wyśle przez UART do komputera PC - nauczyliśmy się, jak to zrobić w poprzedniej części kursu. Dzięki temu sprawdzimy, czy uzyskamy wynik zgodny z naszymi oczekiwaniami.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw ponad 120 elementów do przeprowadzenia wszystkich ćwiczeń z kursu można nabyć u naszych dystrybutorów! Dostępne są wersje z płytką Nucleo lub bez niej!
Zamów w Botland.com.pl »STM32 - pomiar napięcia referencyjnego
Jak wiemy, STM32F103 posiada dwa przetworniki ADC. Napięcie referencyjne jest podłączone tylko do pierwszego z nich, oznaczonego jako ADC1. Pierwszy krok, to jak zwykle podłączenie zegara:
1 |
__HAL_RCC_ADC1_CLK_ENABLE(); |
Przetwornik ADC nie może pracować z zegarem o częstotliwości wyższej od 14 MHz.
Nie konfigurowaliśmy jeszcze pętli PLL, więc nasz układ pracuje z domyślnym taktowaniem 8MHz. Oznacza to, że nawet bez dzielnika mieścimy się w zakresie pracy przetwornika analogowo-cyfrowego. Niestety najmniejsza wartość dzielnika to 2, czyli zegar naszego przetwornika będzie pracował z częstotliwością 4 MHz. Konfigurujemy go następującym kodem:
1 2 3 4 |
RCC_PeriphCLKInitTypeDef adc_clk; adc_clk.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk.AdcClockSelection = RCC_ADCPCLK2_DIV2; HAL_RCCEx_PeriphCLKConfig(&adc_clk); |
Następnie musimy zadeklarować zmienną konfiguracyjną przetwornika i ustawić parametry pracy:
1 2 3 4 5 6 7 8 9 10 |
ADC_HandleTypeDef adc; adc.Instance = ADC1; adc.Init.ContinuousConvMode = ENABLE; adc.Init.ExternalTrigConv = ADC_SOFTWARE_START; adc.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc.Init.ScanConvMode = ADC_SCAN_DISABLE; adc.Init.NbrOfConversion = 1; adc.Init.DiscontinuousConvMode = DISABLE; adc.Init.NbrOfDiscConversion = 1; HAL_ADC_Init(&adc); |
Wybieramy tryb ciągły, nasz przetwornik będzie wykonywał jeden pomiar po drugim. Ustawiamy liczbę kanałów na 1 oraz wyzwalanie na brak – uruchomimy przetwornik programowo.
Teraz możemy wykonać autokalibrację przetwornika. Powinniśmy wykonać ją raz na początku programu. Dzięki temu odczyty będą dokładniejsze:
1 |
HAL_ADCEx_Calibration_Start(&adc); |
Następnie ustawiamy parametry multipleksera. Przetwornik może odczytywać dane nawet z 16 wejść zewnętrznych (numerowanych od 0 do 15), termistora (kanał 16) lub napięcia referencyjnego (kanał 17). Właśnie ten kanał (17) nas interesuje:
1 2 3 4 5 |
ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = ADC_CHANNEL_VREFINT; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); |
Ostatni parametr to liczba cykli przeznaczonych na próbkowanie wartości wejściowej. Wybieramy wartość 13.5 cyklu na pomiar (możliwe są wartości od 1.5 cyklu do 239.5).
Więcej informacji o działaniu modułu znajdziemy w Reference Manual, rozdział 11.
Włączenie przetwornika ADC
Nasz przetwornik jest już skonfigurowany. Pozostaje uruchomić pomiar:
1 |
HAL_ADC_Start(&adc); |
Przetwornik działa w trybie ciągłym, nie musimy więc ponownie startować pomiaru, ani czekać na jego zakończenie. Wystarczy odczytać wartość za pomocą funkcji: HAL_ADC_GetValue.
Chcemy sprawdzić, jakie otrzymujemy rezultaty, nasz wynik wyślemy więc przez UART. W poprzednich częściach kursu omówiliśmy jak przekierować wyjście funkcji printf. Kod jest więc prosty:
1 2 |
uint32_t value = HAL_ADC_GetValue(&adc); printf("Adc = %ld (%.3fV)\r\n", value, value * 3.3f / 4096.0f); |
Cały program wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
#include <string.h> #include "stm32f1xx.h" UART_HandleTypeDef uart; void send_char(char c) { HAL_UART_Transmit(&uart, (uint8_t*)&c, 1, 1000); } int __io_putchar(int ch) { if (ch == '\n') send_char('\r'); send_char(ch); return ch; } int main(void) { SystemCoreClock = 8000000; // taktowanie 8Mhz HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); GPIO_InitTypeDef gpio; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pin = GPIO_PIN_2; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_AF_INPUT; gpio.Pin = GPIO_PIN_3; HAL_GPIO_Init(GPIOA, &gpio); uart.Instance = USART2; uart.Init.BaudRate = 115200; uart.Init.WordLength = UART_WORDLENGTH_8B; uart.Init.Parity = UART_PARITY_NONE; uart.Init.StopBits = UART_STOPBITS_1; uart.Init.HwFlowCtl = UART_HWCONTROL_NONE; uart.Init.OverSampling = UART_OVERSAMPLING_16; uart.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&uart); RCC_PeriphCLKInitTypeDef adc_clk; adc_clk.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk.AdcClockSelection = RCC_ADCPCLK2_DIV2; HAL_RCCEx_PeriphCLKConfig(&adc_clk); ADC_HandleTypeDef adc; adc.Instance = ADC1; adc.Init.ContinuousConvMode = ENABLE; adc.Init.ExternalTrigConv = ADC_SOFTWARE_START; adc.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc.Init.ScanConvMode = ADC_SCAN_DISABLE; adc.Init.NbrOfConversion = 1; adc.Init.DiscontinuousConvMode = DISABLE; adc.Init.NbrOfDiscConversion = 1; HAL_ADC_Init(&adc); HAL_ADCEx_Calibration_Start(&adc); ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = ADC_CHANNEL_VREFINT; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); HAL_ADC_Start(&adc); while (1) { uint32_t value = HAL_ADC_GetValue(&adc); printf("Adc = %ld (%.3fV)\r\n", value, value * 3.3f / 4096.0f); } } |
Natomiast wyniki działania programu przedstawia poniższy zrzut ekranu:
Jak widać otrzymaliśmy, prawie, to co wychodziło z naszych obliczeń. Spodziewaliśmy się wyniku 1489, a otrzymaliśmy 1490-1491. Widzimy więc, że nasz program działa poprawnie. Warto zwrócić uwagę, że wynik nie jest idealny, a ostatnia cyfra zmienia się w kolejnych pomiarach.
Czasami wahania te mogą być również trochę większe.
Zadanie domowe 6.1
Oblicz częstotliwość próbkowania, którą ustawiliśmy w pierwszym przykładzie (czyli częstotliwość z jaką są wykonywane pomiary). Niezbędne informacje są w Reference Manual mikrokontrolera.
STM32 - pomiar napięcia zewnętrznego
Pierwszy przykład dał nam możliwość upewnienia się, że umiemy obsługiwać przetwornik ADC. Jednak pomiar znanego napięcia referencyjnego nie jest zbyt pasjonujący. Spróbujmy więc zmierzyć napięcie podłączone z zewnątrz.
Elementy, wykorzystane w dalszej części artykułu:
Bez specjalnych zabezpieczeń możemy do wejścia przetwornika podłączyć tylko napięcia z zakresu od Vss do Vdd, czyli od 0V do 3.3V. Każde napięcie spoza tego zakresu może uszkodzić mikrokontroler.
Do wejścia przetwornika analogowo-cyfrowego można podłączyć napięcie z zakresu 0V - 3.3V. Wyjście poza ten zakres może spowodować nieodwracalne uszkodzenie mikrokontrolera. Szczególnie należy uważać, jeśli w układzie wykorzystujemy dwa napięcia zasilania: 3.3V oraz 5V.
Aby temu zapobiec podłączymy potencjometr między masę, a zasilanie 3.3V – w takim układzie, kręcąc potencjometrem będziemy mogli regulować napięcie w pełnym, bezpiecznym zakresie.
Używając multimetru będziemy mogli również zweryfikować nasze wyniki. Podłączmy napięcie z potencjometru do pinu PA0. Jest to jednocześnie wejście kanału 0 przetwornika ADC1.
Zdjęcie złożonego układu:
Program będzie bardzo podobny do poprzedniego, omówmy więc tylko różnice. Pierwsza zmiana, to konieczność skonfigurowania pinu PA0 jako wejścia analogowego. W tym celu piszemy:
1 2 3 |
gpio.Mode = GPIO_MODE_ANALOG; gpio.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &gpio); |
Jak widać poznaliśmy nowy tryb pracy pinu – wejście analogowe (Mode Analog). Poprzednio odczytywaliśmy dane z kanału 17, teraz interesuje nas kanał 0. Wybieramy go następującym kodem:
1 2 3 4 5 |
ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = ADC_CHANNEL_0; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); |
Pozostały kod wygląda dokładnie tak samo jak w poprzednim przykładzie. Czas uruchomić program i obserwować odbierane dane zmieniając nastawy potencjometru. Warto również porównać wyniki ze zmierzonymi za pomocą multimetru.
Uwaga! Na czas pomiaru multimetrem najlepiej jest odłączyć mikrokontroler (odpiąć środkową nóżkę potencjometru od wejścia) - inaczej wyniki mogą być obarczone znacznym błędem.
Cały kod wygląda więc następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
#include <string.h> #include "stm32f1xx.h" UART_HandleTypeDef uart; void send_char(char c) { HAL_UART_Transmit(&uart, (uint8_t*)&c, 1, 1000); } int __io_putchar(int ch) { if (ch == '\n') send_char('\r'); send_char(ch); return ch; } int main(void) { SystemCoreClock = 8000000; // taktowanie 8Mhz HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); GPIO_InitTypeDef gpio; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pin = GPIO_PIN_2; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_AF_INPUT; gpio.Pin = GPIO_PIN_3; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_ANALOG; gpio.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &gpio); uart.Instance = USART2; uart.Init.BaudRate = 115200; uart.Init.WordLength = UART_WORDLENGTH_8B; uart.Init.Parity = UART_PARITY_NONE; uart.Init.StopBits = UART_STOPBITS_1; uart.Init.HwFlowCtl = UART_HWCONTROL_NONE; uart.Init.OverSampling = UART_OVERSAMPLING_16; uart.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&uart); RCC_PeriphCLKInitTypeDef adc_clk; adc_clk.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk.AdcClockSelection = RCC_ADCPCLK2_DIV2; HAL_RCCEx_PeriphCLKConfig(&adc_clk); ADC_HandleTypeDef adc; adc.Instance = ADC1; adc.Init.ContinuousConvMode = ENABLE; adc.Init.ExternalTrigConv = ADC_SOFTWARE_START; adc.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc.Init.ScanConvMode = ADC_SCAN_DISABLE; adc.Init.NbrOfConversion = 1; adc.Init.DiscontinuousConvMode = DISABLE; adc.Init.NbrOfDiscConversion = 1; HAL_ADC_Init(&adc); HAL_ADCEx_Calibration_Start(&adc); ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = ADC_CHANNEL_0; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); HAL_ADC_Start(&adc); while (1) { uint32_t value = HAL_ADC_GetValue(&adc); printf("Adc = %ld (%.3fV)\r\n", value, value * 3.3f / 4096.0f); } } |
Uruchomiony program prezentował następujące dane:
Pomiar uzyskany multimetrem w moim przypadku wynosił: 1.460 V, więc jak widać wskazania przesyłane z STM32 do komputera były zbliżone.
Czujnik oświetlenia
Czas na kolejny przykład. Odłączmy potencjometr, a w jego miejsce podłączmy fotorezystor oraz rezystor tworząc dzielnik napięcia. Schemat montażowy widoczny jest poniżej:
Układ złożony w praktyce:
Teraz możemy zobaczyć jak zmieniają się odczyty naszego układu, w zależności od poziomu oświetlenia. Dokładnie w ten sposób możemy zbudować światłoluba, czyli prostego robota szukającego silniejszego źródła światła. Podobnie działają również czujniki odbiciowe, używane w robotach typu linefollower, czy micromouse.
Zadanie domowe 6.2
Napisz program do automatycznej lampki, która będzie zapalała diody święcące w zależności od poziomu zewnętrznego oświetlenia.
Zadanie domowe 6.3
Za pomocą funkcji ADC_RegularChannelConfig ustalamy czas pobierania próbek przez przetwornik. Spróbuj zmieniać ten czas, porównaj stabilność i wynik odczytów. Przeprowadź testy dla wartości z zakresu od ADC_SampleTime_1Cycles5 do ADC_SampleTime_239Cycles5.
Pomiar napięcia z kilku kanałów ADC na STM32
Spróbujmy odczytać dane z więcej niż jednego czujnika. Na początek zmieniamy konfigurację naszego układu. Do pinów PA0 i PA1 podłączymy potencjometry, a w programie spróbujemy odczytywać wartości z obu wejść.
Układ złożony w praktyce:
Poprzednio uruchomiliśmy przetwornik w trybie ciągłym, a funkcją HAL_ADC_GetValue pobieraliśmy odczytaną wartość. Takie działanie było wystarczające w przypadku jednego wejścia. Jednak gdyby przetwornik sprawdzał dwa wejścia to nie moglibyśmy pobrać dwóch wartości. HAL_ADC_GetValue zwraca wynik ostatniego pomiaru, jednak nie pozwala na wybór, które wejście chcemy odczytać.
Moglibyśmy wykorzystać tryb ciągły i np. w przerwaniach pobierać dane – jest to popularna metoda w przypadku mikrokontrolerów 8-bitowych, np. AVR. Wywołujemy HAL_ADC_GetValue zaraz po zakończeniu konwersji i zapisujemy wynik w buforze. Ponieważ przerwanie jest zgłaszane zaraz po otrzymaniu kolejnego wyniku, możemy pobrać dane z obu czujników.
Takie rozwiązanie jest jednak bardzo nieefektywne - procesor "co chwilę" musi obsługiwać przerwania tylko po to, żeby buforować wyniki pomiarów.
W dalszej części kursu poznamy znacznie lepsze rozwiązanie z użyciem DMA.
Teraz wykorzystajmy jednak tryb pojedynczego odczytu - będziemy uruchamiać przetwornik i czekać na wynik. Wykorzystujemy 2 wejścia, więc musimy je odpowiednio skonfigurować:
1 2 3 |
gpio.Mode = GPIO_MODE_ANALOG; gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1; HAL_GPIO_Init(GPIOA, &gpio); |
Wyłączamy tryb ciągły, nie chcemy żeby przetwornik ciągle odczytywał dane:
1 |
adc.Init.ContinuousConvMode = DISABLE; |
Będziemy odczytywać dwa kanały, napiszemy więc funkcję, która skonfiguruje multiplekser wejściowy przetwornika, uruchomi pomiar, poczeka na zakończenie i odczyta wyniki. Oto jej treść:
1 2 3 4 5 6 7 8 9 10 11 12 |
int adc_read(uint32_t channel) { ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = channel; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); HAL_ADC_Start(&adc); HAL_ADC_PollForConversion(&adc, 1000); return HAL_ADC_GetValue(&adc); } |
Pierwsza część, to wybór kanału, następna, to uruchomienie konwersji i oczekiwanie na zakończenie konwersji wykorzystując funkcję HAL_ADC_PollForConversion. Gdy dane są gotowe, odczytujemy je za pomocą HAL_ADC_GetValue.
Mając tę funkcję możemy w pętli głównej programu odczytać oba wejścia przetwornika:
1 2 3 4 5 6 7 |
uint16_t value = adc_read(ADC_CHANNEL_0); float v = (float)value * 3.3f / 4096.0f; printf("ADC0 = %d (%.3fV) ", value, v); value = adc_read(ADC_CHANNEL_1); v = (float)value * 3.3f / 4096.0f; printf("ADC1 = %d (%.3fV)\n", value, v); |
Cały kod programu wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
#include <string.h> #include "stm32f1xx.h" UART_HandleTypeDef uart; ADC_HandleTypeDef adc; void send_char(char c) { HAL_UART_Transmit(&uart, (uint8_t*)&c, 1, 1000); } int __io_putchar(int ch) { if (ch == '\n') send_char('\r'); send_char(ch); return ch; } int adc_read(uint32_t channel) { ADC_ChannelConfTypeDef adc_ch; adc_ch.Channel = channel; adc_ch.Rank = ADC_REGULAR_RANK_1; adc_ch.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&adc, &adc_ch); HAL_ADC_Start(&adc); HAL_ADC_PollForConversion(&adc, 1000); return HAL_ADC_GetValue(&adc); } int main(void) { SystemCoreClock = 8000000; // taktowanie 8Mhz HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); GPIO_InitTypeDef gpio; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pin = GPIO_PIN_2; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_AF_INPUT; gpio.Pin = GPIO_PIN_3; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_ANALOG; gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1; HAL_GPIO_Init(GPIOA, &gpio); uart.Instance = USART2; uart.Init.BaudRate = 115200; uart.Init.WordLength = UART_WORDLENGTH_8B; uart.Init.Parity = UART_PARITY_NONE; uart.Init.StopBits = UART_STOPBITS_1; uart.Init.HwFlowCtl = UART_HWCONTROL_NONE; uart.Init.OverSampling = UART_OVERSAMPLING_16; uart.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&uart); RCC_PeriphCLKInitTypeDef adc_clk; adc_clk.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk.AdcClockSelection = RCC_ADCPCLK2_DIV2; HAL_RCCEx_PeriphCLKConfig(&adc_clk); adc.Instance = ADC1; adc.Init.ContinuousConvMode = DISABLE; adc.Init.ExternalTrigConv = ADC_SOFTWARE_START; adc.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc.Init.ScanConvMode = ADC_SCAN_DISABLE; adc.Init.NbrOfConversion = 1; adc.Init.DiscontinuousConvMode = DISABLE; adc.Init.NbrOfDiscConversion = 1; HAL_ADC_Init(&adc); HAL_ADCEx_Calibration_Start(&adc); while (1) { uint16_t value = adc_read(ADC_CHANNEL_0); float v = (float)value * 3.3f / 4096.0f; printf("ADC0 = %d (%.3fV) ", value, v); value = adc_read(ADC_CHANNEL_1); v = (float)value * 3.3f / 4096.0f; printf("ADC1 = %d (%.3fV)\n", value, v); } } |
Wynik działania programu widoczny jest na poniższym zrzucie ekranu. Przy okazji widać tutaj w praktyce, dlaczego programiści tak bardzo lubią printf:
Zadanie domowe 6.4
Podłącz więcej czujników i potencjometrów, np. dwa fotorezystory i dwa potencjometry. Napisz program, który wysyła wszystkie dane do komputera za pomocą UARTa.
Prawie jak światłolub...
Czas zastąpić potencjometry fotorezystorami. Poprzednio wykorzystywaliśmy jeden, teraz możemy podłączyć dwa czujniki.
Układ złożony w praktyce:
Na początek wystarczy nam program, który już mamy - za jego pomocą możemy sprawdzić jak nasz światłlub reaguje na zmiany oświetlenia. Jeśli odpowiednio ustawimy czujniki, będziemy mogli obserwować zmiany odczytów w zależności od kierunku, z którego oświetlimy nasz układ.
Napisanie programu, który na podstawie wartości odczytanych z czujników podejmie decyzję, w którą stronę ma się poruszać robot jest już dosyć łatwe - i będzie to świetna praca domowa.
Zadanie domowe 6.5
Napisz program do sterowania światłolubem. Zamiast sterowania silnikami, wysyłaj na złącze szeregowe, co robot powinien robić, np. komendy "prosto", "w lewo", "w prawo".
Podsumowanie
Tym razem omówiliśmy podstawy wykorzystywania przetwornika ADC dostępnego w układach STM32. Na ten moment widoczne są dwie główne zalety (względem AVR) - duża liczba dostępnych kanałów oraz stosunkowo niski czas wykonania pomiaru. Jednak prawdziwa rewolucja dopiero przed nami, mowa oczywiście o DMA, które odmieni sposób korzystania z ADC!
Nawigacja kursu
Autor kursu: Piotr Bugalski
Testy: Piotr Adamczyk
Redakcja: Damian Szymański
Powiązane wpisy
ADC, kursSTM32F1HAL, potencjometr, stm32, uart
Trwa ładowanie komentarzy...