W tym poście chciałbym omówić zasadę działania przetwornika analogo-cyfrowego ADC. Działanie będzie przedstawione w oparciu o płytkę Nucleo z mikrokontrolerem STM32F4.
[Źródło: http://www.st.com/en/evaluation-tools/nucleo-f411re.html]
Wstęp
W zastosowanym na płytce mikrokontrolerze wbudowano jeden 12 bitowy przetwornik ADC, który został wyprowadzony na 16 wyjść GPIO. Uzyskano to poprzez zastosowanie na wejściu przetwornika multipleksera, którego zadaniem jest rozdzielenie linii sygnałów.
Przetwornik jest 12-bitowy wobec tego dane będą mapowane na 4096 wartości (2^12).
Kanały przetwornika są podłączone w następujący sposób:
- ADC0 - PA0
- ADC1 - PA1
- ADC2 - PA2
- ADC3 - PA3
- ADC4 - PA4
- ADC5 - PA5
- ADC6 - PA6
- ADC7 - PA7
- ADC8 - PB0
- ADC9 - PB1
- ADC10 - PC0
- ADC11 - PC1
- ADC12 - PC2
- ADC13 - PC3
- ADC14 - PC4
- ADC15 - PC5
W zastosowanym mikrokontrolerze wartość 1.2 V jest napięciem referencyjnym.
Program 1
W tym programie zaprezentuje sposób obsługi ADC. Dane będą odczytywane z potencjometru. Został on podłączony do pinu PA0, zasilany jest napięciem 3.3V.
Piny mają tolerancję 0 do 3.3V, dlatego na wejście ADC lepiej nie podłączać napięcia wyższego ponieważ można uszkodzić mikrokontroler. Jeśli natomiast chcemy podłączyć napięcie spoza zakresu wtedy należy zastosować dzielnik napięcia.
- #include "stm32f4xx_adc.h"
- #include "stm32f4xx_gpio.h"
- #include "stm32f4xx_rcc.h"
- int WartoscPrzetwornik = 0;
- void ADC_Konfiguracja();
- int ADC_Konwersja();
- int main(void)
- {
- //Konfiguracja
- ADC_Konfiguracja();
- while(1)
- {
- WartoscPrzetwornik = ADC_Konwersja();
- }
- }
- int ADC_Konwersja()
- {
- //Rozpoczęcie konwersji
- ADC_SoftwareStartConv(ADC1);
- //Odczekanie na wykonanie resetu
- while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
- //Zwrócenie przetworzonych danych
- return ADC_GetConversionValue(ADC1);
- }
- void ADC_Konfiguracja()
- {
- //Struktura dla ADC oraz GPIO
- ADC_InitTypeDef ADC_init_structure;
- GPIO_InitTypeDef GPIO_initStructre;
- //Konfiguracja zegarów dla ADC oraz dla portu GPIOA
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
- //Konfiguracja pinu PA0
- GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;
- //Włączenie pinu w trybie analogowym
- GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
- GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_initStructre);
- //ADC Konfiguracja
- ADC_DeInit();
- //Przetworzone dane wyrównane do prawej
- ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;
- //Napięcie wyjściowe przetwarzane z maksymalną rodzielczością tj. 12b
- ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
- //Włączona konwersja ciągła
- ADC_init_structure.ADC_ContinuousConvMode = ENABLE;
- ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
- //Bez wyzwalania zewnętrznego
- ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
- //Liczba konwersji
- ADC_init_structure.ADC_NbrOfConversion = 1;
- //Pomiar tylko jednego kanału, skanowanie kanałów zostało wyłączone
- ADC_init_structure.ADC_ScanConvMode = DISABLE;
- //Inicjalizacja
- ADC_Init(ADC1,&ADC_init_structure);
- //Włączenie konwersji
- ADC_Cmd(ADC1,ENABLE);
- //Wybranie kanału z którego nastąpi odczyt
- ADC_RegularChannelConfig(ADC1,ADC_Channel_0, 1, ADC_SampleTime_144Cycles);
- }
W celu obserwacji zmiennej wykorzystałem opcję Debug w programie SystemWorkbench. Na rysunku 1.1. przedstawiłem otrzymane prze zemnie wyniki pomiarów. Najniższą wartością była 1, najwyższą 4095. Dodatkowo przedstawiłem dwie wartości z pomiarów w środku stawki.
Rys. 1.1. Wyniki pomiarów
Program 2
W programie drugim wykorzystam ten sam sposób pomiaru ADC, także za pomocą potencjometru. Tym razem natomiast wynik będzie wyświetlany na komputerze poprzez port USART.
Do wysłania danych wykorzystamy funckję printf, która znacznie uprości sposób przesłania informacji do komputera. Funkcja printf będzie wywoływać funkcję __io_putchar() dla każdego bajtu jaki będzie wysyłany do komputera. Ona będzie wchodzić w funkcję send_char(), gdzie program będzie sprawdzał czy bufor nadawczy jest pusty a następnie wykonywał funckję wysłania danych na wybrany port UART.
Rys. 1.2. Wynik działania programu
Poniżej wklejam pełny program:
- #include "stm32f4xx_adc.h"
- #include "stm32f4xx_gpio.h"
- #include "stm32f4xx_rcc.h"
- #include "stm32f4xx_usart.h"
- int WartoscPrzetwornik = 0;
- void ADC_Konfiguracja();
- int ADC_Konwersja();
- void USART_Initialize(void);
- void GPIO_Initialize(void);
- void send_char(char);
- int __io_putchar(int);
- void DelayMain(void);
- int main(void)
- {
- ADC_Konfiguracja();
- GPIO_Initialize();
- USART_Initialize();
- printf("Konwersja ADC potencjometr!\n");
- while(1)
- {
- WartoscPrzetwornik = ADC_Konwersja();
- printf("Adc = %d wynosi %.3fV \n", WartoscPrzetwornik, WartoscPrzetwornik * 3.3f / 4096.0f);
- DelayMain();
- DelayMain();
- }
- }
- int ADC_Konwersja()
- {
- //Rozpoczęcie konwersji
- ADC_SoftwareStartConv(ADC1);
- //Odczekanie na wykonanie resetu
- while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
- //Zwrócenie przetworzonych danych
- return ADC_GetConversionValue(ADC1);
- }
- void ADC_Konfiguracja()
- {
- //Struktura dla ADC oraz GPIO
- ADC_InitTypeDef ADC_init_structure;
- GPIO_InitTypeDef GPIO_initStructre;
- //Konfiguracja zegarów dla ADC oraz dla portu GPIOA
- RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
- RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
- //Konfiguracja pinu PA0
- GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;
- //Włączenie pinu w trybie analogowym
- GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
- GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_initStructre);
- //ADC Konfiguracja
- ADC_DeInit();
- //Przetworzone dane wyrównane do prawej
- ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;
- //Napięcie wyjściowe przetwarzane z maksymalną rodzielczością tj. 12b
- ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
- //Włączona konwersja ciągła
- ADC_init_structure.ADC_ContinuousConvMode = ENABLE;
- ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
- //Bez wyzwalania zewnętrznego
- ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
- //Liczba konwersji
- ADC_init_structure.ADC_NbrOfConversion = 1;
- //Pomiar tylko jednego kanału, skanowanie kanałów zostało wyłączone
- ADC_init_structure.ADC_ScanConvMode = DISABLE;
- //Inicjalizacja
- ADC_Init(ADC1,&ADC_init_structure);
- //Włączenie konwersji
- ADC_Cmd(ADC1,ENABLE);
- //Wybranie kanału z którego nastąpi odczyt
- ADC_RegularChannelConfig(ADC1,ADC_Channel_0, 1, ADC_SampleTime_144Cycles);
- }
- void USART_Initialize(void)
- {
- //konfiguracja układu USART
- USART_InitTypeDef USART_InitStructure;
- //Włączenie zegara dla USART1
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
- //Ustawienie prędkości transmisji 9600bps
- USART_InitStructure.USART_BaudRate = 9600;
- //Długosc wysylanego slowa
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;
- //Ustawienie jednego bitu stopu
- USART_InitStructure.USART_StopBits = USART_StopBits_1;
- //Kontrola parzystości wyłączona
- USART_InitStructure.USART_Parity = USART_Parity_No;
- //Wylaczenie kontroli przepływu danych
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
- //Tryb pracy linii odpowiednio odbior i nadawanie
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
- //Konfiguracja układu
- USART_Init(USART1, &USART_InitStructure);
- //Włączenie USART1
- USART_Cmd(USART1, ENABLE);
- }
- void GPIO_Initialize(void)
- {
- //konfigurowanie portow GPIO
- GPIO_InitTypeDef GPIO_InitStructure;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- //TX dla pinu PA9
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //RX dla pinu PA10
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //Włączenie transmisji na podanych pinach
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
- }
- void send_char(char c)
- {
- while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
- USART_SendData(USART1, c);
- }
- int __io_putchar(int c)
- {
- send_char(c);
- return c;
- }
- void DelayMain(void)
- {
- volatile uint32_t i;
- for (i = 0; i != 0xFFFFF; i++);
- for (i = 0; i != 0xFFFFF; i++);
- for (i = 0; i != 0xFFFFF; i++);
- }