piątek, 27 listopada 2015

[7a] STM32 M3 - Nucleo - F103RB - DMA z ADC

W tym poście chciałbym opisać w jaki sposób odczytać dane z ADC przez DMA.

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.