Wstęp
Programowanie ADC przez DMA pozwala na dość duże ułatwienie pracy, zwłaszcza gdy wykorzystuje się pomiar więcej niż jednego kanału. Wynik otrzymywane z przetwarzania danych zostają umieszczone w rejestrze DR. Aby umożliwić poprawne rozpoznawanie z jakiego kanału pochodzą otrzymane dane należy wykorzystać tablicę, której zadaniem będzie przechowywanie danych odczytanych z poszczególnego kanału.
Dodatkowo należy pamiętać o odpowiedniej konfiguracji ADC, mianowicie ustawienia włączenia skanowania kanałów. Dzięki takiemu ustawieniu po pierwszym pomiarze z kanału nastąpi automatyczne rozpoczęcie pomiaru na kolejnych kanałach.
Podłączenie
Potencjometry zostały podłączone do następujących wyprowadzeń.
- Potencjometr 1 - PA0
- Potencjometr 2 - PA1
- Potencjometr 3 - PA4
- Potencjometr 4 - PA5
Pozostałe piny podłączyłem między 3,3V a GND.
Do komunikacji z komputerem i wyświetlania danych wykorzystałem USART2, ponieważ został on podłączony do wbudowanego konwertera USART/USB na płytce Nucleo.
Rys. 1.1. Zdjęcie połączonego układu
Program - Pomiar wartości z czterech kanałów
Pozostałe informacje odnośnie działania programu przedstawiłem w komentarzach.
#include <stdio.h> #include <stdint.h> #include "stm32f10x.h" volatile uint32_t timer_ms = 0; //Deklaracja głównej tablicy przechowującej dane z adc uint16_t ADCValue[4] = {0}; void GPIOInit(void); void USARTInit(void); void ADCInit(void); void DMAInit(void); void SendChar(char); int __io_putchar(int); void SysTickInit(void); void delay_ms(int); void SysTick_Handler(); int main(void) { int i; SysTickInit(); GPIOInit(); USARTInit(); DMAInit(); ADCInit(); while (1) { for (i = 0; i < 4;i++) { printf("ADC%d = %d ", i, ADCValue[i]); printf("[V] = %.3f\n", ADCValue[i] * (3.3/4096.0)); } printf("\n"); delay_ms(1000); } } void GPIOInit(void) { GPIO_InitTypeDef GPIOInit; //Zegary RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //Piny dla USART-u //PA2 Tx GPIOInit.GPIO_Pin = GPIO_Pin_2; GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIOInit); //PA3 Rx GPIOInit.GPIO_Pin = GPIO_Pin_3; GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIOInit); //ADC piny //PA0 - ADC kanał 0| PA1 - ADC kanał 1|PA4 - ADC kanał 4|PA5 - ADC kanał 5 GPIOInit.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5; GPIOInit.GPIO_Mode = GPIO_Mode_AIN; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIOInit); //Dioda GPIO_StructInit(&GPIOInit); GPIOInit.GPIO_Pin = GPIO_Pin_5; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIOInit); } void USARTInit(void) { USART_InitTypeDef USARTInit; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //Ustawienie prędkości transmisji 9600bps USARTInit.USART_BaudRate = 9600; //Ustawienie dlugosci slowa USARTInit.USART_WordLength = 8; //Ustawienie bitu stopu USARTInit.USART_StopBits = USART_StopBits_1; //Brak kontroli parzystosci USARTInit.USART_Parity = USART_Parity_No; //Kontrola przepływu danych USARTInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //Tryb pracy TX i RX USARTInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USARTInit); USART_Cmd(USART2, ENABLE); } //Konfiguracja kanałów ADC void ADCInit(void) { ADC_InitTypeDef ADCInit; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); ADC_StructInit(&ADCInit); //Jeden przetwornik praca niezalezna ADCInit.ADC_Mode = ADC_Mode_Independent; //Wlaczenie skanowania kanalow, //konieczne przy wiekszej liczbie wykorzystywanych kanalow ADCInit.ADC_ScanConvMode = ENABLE; //Tryb pracy ciągłu ADCInit.ADC_ContinuousConvMode = ENABLE; //Wyzwalanie zewnetrzne wylaczone ADCInit.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //Liczba kanałów ADCInit.ADC_NbrOfChannel = 4; //Inicjalizacja ADC_Init(ADC1, &ADCInit); //Deklaracja kanałów ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 4, ADC_SampleTime_239Cycles5); //Wlaczenie DMA dla ADC1 ADC_DMACmd(ADC1, ENABLE); //Wlaczenie ADC1 ADC_Cmd(ADC1, ENABLE); //Reset rejestrów kalibracyjnych ADC1 ADC_ResetCalibration(ADC1); //odczekanie na zakonczenie resetowania while (ADC_GetResetCalibrationStatus(ADC1)); //Rozpoczecie kalibracji ADC_StartCalibration(ADC1); //odczekanie na zakonczenie kalibracji while(ADC_GetCalibrationStatus(ADC1)); //Rozpoczęcie konwercji ADC_SoftwareStartConvCmd(ADC1, ENABLE); } //Konfiguracja DMA void DMAInit(void) { DMA_InitTypeDef DMAInit; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_StructInit(&DMAInit); //deklaracja zrodla danych, rejestr ADC1 DR //można też zdeklarowac &ADC1->DR DMAInit.DMA_PeripheralBaseAddr = (uint32_t)0x4001244C; //Adres początku bloku do przesłania DMAInit.DMA_MemoryBaseAddr = (uint32_t)ADCValue; //Kierunek transferu z urządzenia do pamięci DMAInit.DMA_DIR = DMA_DIR_PeripheralSRC; //Długośc bufora danych DMAInit.DMA_BufferSize = 4; //Wylaczenie automatycznego zwiekszania adresu ADC DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //Wlaczenie automatycznego zwiekszania adresu po stronie DMA DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable; //Rozmiar przesyłanych danych ADC, 16b DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //Rozmiar przesyłanych danych po stronie pamięci DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //Tryb pracy ciągły DMAInit.DMA_Mode = DMA_Mode_Circular; //Wylaczenie obslugi transferu z pamieci do pamieci DMAInit.DMA_M2M = DMA_M2M_Disable; //Zapisanie konfiguracji DMA_Init(DMA1_Channel1, &DMAInit); //Wlaczenie DMA DMA_Cmd(DMA1_Channel1, ENABLE); } void SysTickInit(void) { SysTick_Config(SystemCoreClock / 1000); } void SysTick_Handler() { if (timer_ms) timer_ms--; } void delay_ms(int time) { timer_ms = time; while (timer_ms); } void SendChar(char c) { while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); USART_SendData(USART2, c); } int __io_putchar(int c) { SendChar(c); return c; }
Na rysunku 1.2. przedstawiłem okno programu terminal z wynikami jakie otrzymałem z każdego kanału.
Rys. 1.2. Terminal
Zamiast potencjometrów można wykorzystać inne układy analogowe np. wewnętrzny termometr, fotorezystor bądź różnego typu czujniki. Należy pamiętać, jak już wspomniałem we wcześniejszych postach, że ADC posiada tolerancję do 3,6V. W przypadku podłączenia wyższego napięcia można bardzo łatwo uszkodzić mikrokontroler znajdujący się na płytce.