wtorek, 27 października 2015

[2] STM32 Nucleo F411RE - ADC

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.

  1. #include "stm32f4xx_adc.h"
  2. #include "stm32f4xx_gpio.h"
  3. #include "stm32f4xx_rcc.h"
  4. int WartoscPrzetwornik = 0;
  5. void ADC_Konfiguracja();
  6. int ADC_Konwersja();
  7. int main(void)
  8. {
  9.  //Konfiguracja
  10.  ADC_Konfiguracja();
  11.     while(1)
  12.     {
  13.      WartoscPrzetwornik = ADC_Konwersja();
  14.     }
  15. }
  16. int ADC_Konwersja()
  17. {
  18.   //Rozpoczęcie konwersji
  19.   ADC_SoftwareStartConv(ADC1);
  20.   //Odczekanie na wykonanie resetu
  21.   while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
  22.   //Zwrócenie przetworzonych danych
  23.   return ADC_GetConversionValue(ADC1);
  24. }
  25. void ADC_Konfiguracja()
  26. {
  27.  //Struktura dla ADC oraz GPIO
  28.   ADC_InitTypeDef ADC_init_structure;
  29.   GPIO_InitTypeDef GPIO_initStructre;
  30.   //Konfiguracja zegarów dla ADC oraz dla portu GPIOA
  31.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
  32.   RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
  33.   //Konfiguracja pinu PA0
  34.   GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;
  35.   //Włączenie pinu w trybie analogowym
  36.   GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
  37.   GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
  38.   GPIO_Init(GPIOA, &GPIO_initStructre);
  39.   //ADC Konfiguracja
  40.   ADC_DeInit();
  41.   //Przetworzone dane wyrównane do prawej
  42.   ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;
  43.   //Napięcie wyjściowe przetwarzane z maksymalną rodzielczością tj. 12b
  44.   ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
  45.   //Włączona konwersja ciągła
  46.   ADC_init_structure.ADC_ContinuousConvMode = ENABLE;
  47.   ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  48.   //Bez wyzwalania zewnętrznego
  49.   ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  50.   //Liczba konwersji
  51.   ADC_init_structure.ADC_NbrOfConversion = 1;
  52.   //Pomiar tylko jednego kanału, skanowanie kanałów zostało wyłączone
  53.   ADC_init_structure.ADC_ScanConvMode = DISABLE;
  54.   //Inicjalizacja
  55.   ADC_Init(ADC1,&ADC_init_structure);
  56.   //Włączenie konwersji
  57.   ADC_Cmd(ADC1,ENABLE);
  58.   //Wybranie kanału z którego nastąpi odczyt
  59.   ADC_RegularChannelConfig(ADC1,ADC_Channel_0, 1, ADC_SampleTime_144Cycles);
  60. }

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:

  1. #include "stm32f4xx_adc.h"
  2. #include "stm32f4xx_gpio.h"
  3. #include "stm32f4xx_rcc.h"
  4. #include "stm32f4xx_usart.h"
  5. int WartoscPrzetwornik = 0;
  6. void ADC_Konfiguracja();
  7. int ADC_Konwersja();
  8. void USART_Initialize(void);
  9. void GPIO_Initialize(void);
  10. void send_char(char);
  11. int __io_putchar(int);
  12. void DelayMain(void);
  13. int main(void)
  14. {
  15.  ADC_Konfiguracja();
  16.  GPIO_Initialize();
  17.  USART_Initialize();
  18.  printf("Konwersja ADC potencjometr!\n");
  19.     while(1)
  20.     {
  21.      WartoscPrzetwornik = ADC_Konwersja();
  22.      printf("Adc = %d wynosi %.3fV \n", WartoscPrzetwornik, WartoscPrzetwornik * 3.3f / 4096.0f);
  23.      DelayMain();
  24.      DelayMain();
  25.     }
  26. }
  27. int ADC_Konwersja()
  28. {
  29.   //Rozpoczęcie konwersji
  30.   ADC_SoftwareStartConv(ADC1);
  31.   //Odczekanie na wykonanie resetu
  32.   while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
  33.   //Zwrócenie przetworzonych danych
  34.   return ADC_GetConversionValue(ADC1);
  35. }
  36. void ADC_Konfiguracja()
  37. {
  38.  //Struktura dla ADC oraz GPIO
  39.   ADC_InitTypeDef ADC_init_structure;
  40.   GPIO_InitTypeDef GPIO_initStructre;
  41.   //Konfiguracja zegarów dla ADC oraz dla portu GPIOA
  42.   RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
  43.   RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
  44.   //Konfiguracja pinu PA0
  45.   GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;
  46.   //Włączenie pinu w trybie analogowym
  47.   GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
  48.   GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
  49.   GPIO_Init(GPIOA, &GPIO_initStructre);
  50.   //ADC Konfiguracja
  51.   ADC_DeInit();
  52.   //Przetworzone dane wyrównane do prawej
  53.   ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;
  54.   //Napięcie wyjściowe przetwarzane z maksymalną rodzielczością tj. 12b
  55.   ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
  56.   //Włączona konwersja ciągła
  57.   ADC_init_structure.ADC_ContinuousConvMode = ENABLE;
  58.   ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  59.   //Bez wyzwalania zewnętrznego
  60.   ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  61.   //Liczba konwersji
  62.   ADC_init_structure.ADC_NbrOfConversion = 1;
  63.   //Pomiar tylko jednego kanału, skanowanie kanałów zostało wyłączone
  64.   ADC_init_structure.ADC_ScanConvMode = DISABLE;
  65.   //Inicjalizacja
  66.   ADC_Init(ADC1,&ADC_init_structure);
  67.   //Włączenie konwersji
  68.   ADC_Cmd(ADC1,ENABLE);
  69.   //Wybranie kanału z którego nastąpi odczyt
  70.   ADC_RegularChannelConfig(ADC1,ADC_Channel_0, 1, ADC_SampleTime_144Cycles);
  71. }
  72. void USART_Initialize(void)
  73. {
  74.   //konfiguracja układu USART
  75.   USART_InitTypeDef USART_InitStructure;
  76.   //Włączenie zegara dla USART1
  77.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
  78.   //Ustawienie prędkości transmisji 9600bps
  79.   USART_InitStructure.USART_BaudRate = 9600;
  80.   //Długosc wysylanego slowa
  81.   USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  82.   //Ustawienie jednego bitu stopu
  83.   USART_InitStructure.USART_StopBits = USART_StopBits_1;
  84.   //Kontrola parzystości wyłączona
  85.   USART_InitStructure.USART_Parity = USART_Parity_No;
  86.   //Wylaczenie kontroli przepływu danych
  87.   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  88.   //Tryb pracy linii odpowiednio odbior i nadawanie
  89.   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  90.   //Konfiguracja układu
  91.   USART_Init(USART1, &USART_InitStructure);
  92.   //Włączenie USART1
  93.   USART_Cmd(USART1, ENABLE);
  94. }
  95. void GPIO_Initialize(void)
  96. {
  97.   //konfigurowanie portow GPIO
  98.   GPIO_InitTypeDef  GPIO_InitStructure;
  99.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  100.   //TX dla pinu PA9
  101.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  102.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  103.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  104.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  105.   GPIO_Init(GPIOA, &GPIO_InitStructure);
  106.   //RX dla pinu PA10
  107.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  108.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  109.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  110.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  111.   GPIO_Init(GPIOA, &GPIO_InitStructure);
  112.   //Włączenie transmisji na podanych pinach
  113.   GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
  114.   GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
  115. }
  116. void send_char(char c)
  117. {
  118.  while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
  119.  USART_SendData(USART1, c);
  120. }
  121. int __io_putchar(int c)
  122. {
  123.  send_char(c);
  124.  return c;
  125. }
  126. void DelayMain(void)
  127. {
  128.  volatile uint32_t i;
  129.  for (= 0; i != 0xFFFFF; i++);
  130.  for (= 0; i != 0xFFFFF; i++);
  131.  for (= 0; i != 0xFFFFF; i++);
  132. }