In the previous guide (here), we took a look at single channel continuous conversion interrupt driven ADC configuration. In this guide, we shall see how to configure the ADC to work in multiple channel with DMA.
In this guide, we shall cover the following:
we talked about how to configure the ADC to read a single channel in three different modes, polling, continuous with polling and interrupt. Those can be valid for simple application like temperature control of a room or heat-sink. However, in case multiple channel, ADC in interrupt is not recommended and requires careful management to acquire the data in correct sequence. This is when DMA come to solve the issue. In this guide, we shall look at the DMA and how to configure it to acquire the data from two channel.
In this guide we will cover the following:
- What is DMA
- Why should we use DMA when dealing with multiple channel in ADC
- Configuring the ADC and DMA
- Required parts, schematics and connection
- Code
- Result
1. What is DMA
DMA which stands for Direct Memory Access is part of MCU that handle data transfer from peripheral to memory without invoking CPU at all which relief cpu to do something else, like processing the acquired adc vlaue for dsp processing etc
DMA can be operate in three different mode, Memory to Memory (moving one variable from one location to another), memory to peripheral (send string from memory to PC through UART) and peripheral to memory like in our case acquiring data from adc.
2. Why should we use DMA when dealing with multiple channel in ADC
Let say you want ti acquire data from adc from 3-channel in continuous mode. Since each conversion requires 15 cycles for 12-bit since the adc clock is the core frequency over (16MHz/2=8MHz), thats means generating interrupts at rate near half mega hertz which will effect the performance of the mcu. Hence, using DMA in such case makes sense.
3.1 Configure the ADC
First thing, we need to figure out which pins to use, since here the board is used is Nucleo-144,
Since we want to use A0 and A1 of arduino connector and they are PA3 and PC0
To configure the ADC, first we enable GPIO clock access to the required pins PA3 and PC0 in our case:
/*Enable clock access to GPIOA and GPIOC*/ RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN|RCC_AHB1ENR_GPIOCEN;
The configure the pins as analog mode:
/*Configure the pins in ADC mode*/ GPIOA->MODER|=GPIO_MODER_MODER3; GPIOC->MODER|=GPIO_MODER_MODER0;
Now, we enable clock access to ADC1:
/*Enable clock access to ADC1*/ RCC->APB2ENR|=RCC_APB2ENR_ADC1EN;
Enable scan mode to read multichannel:
/*Enable scan mode*/ ADC1->CR1|=ADC_CR1_SCAN;
Then we set DMA, DDS, CON and set length to 2 and rank to be channel3 and channel 10 and enable the ADC. However, we shall not start the conversion yet, as following:
ADC1->CR1|=ADC_CR1_SCAN; /*Enable continuous/ DMA and DDS*/ ADC1->CR2|=ADC_CR2_CONT|ADC_CR2_DDS|ADC_CR2_DMA; /*Set length to 2 channels*/ ADC1->SQR1|=(1<<20); /*Set ch3 first first and ch10 second in the conversion sequence*/ ADC1->SQR3|=(3<<0)|(10<<5); /*Enable the ADC*/ ADC1->CR2|=ADC_CR2_ADON;
3.2 Configuring the DMA:
First we need to find which stream and channel to use:
From the table, we shall use DMA2_Stream0 Channel 0:
We start by enabling DMA clock access
/*Enable clock access to DMA2*/ RCC->AHB1ENR|=RCC_AHB1ENR_DMA2EN;
Then disable the channel and wait until disabled
/*Disable the strsam*/ DMA2_Stream0->CR&=~DMA_SxCR_EN; /*Wait until the stream is disabled */ while(DMA_SxCR_EN==(DMA2_Stream0->CR&DMA_SxCR_EN));
The we configure the DMA as following
Setting the dma to circular mode, memory and peripheral size of 16 bit and memory increment
/*Set memory size to half-word*/ DMA2_Stream0->CR|=DMA_SxCR_MSIZE_0; /*Set peripheral size to half word*/ DMA2_Stream0->CR|=DMA_SxCR_PSIZE_0; /*Enable memory increment*/ DMA2_Stream0->CR|=DMA_SxCR_MINC; /*Enable circular mode*/ DMA2_Stream0->CR|=DMA_SxCR_CIRC; /*Set peripheral is the controller*/ DMA2_Stream0->CR|=DMA_SxCR_PFCTRL;
Then selecting peripheral source to be the ADC1->DRC, set memory location to adc_data array and length to 2
/*Set number of transfer to 2*/ DMA2_Stream0->NDTR=2; /*Set peripheral address to ADC1->DR*/ DMA2_Stream0->PAR=(uint32_t)&(ADC1->DR); /*Set memory address to adc_data*/ DMA2_Stream0->M0AR=(uint32_t)&(adc_data); /*Enable the DMA stream*/
Finally enable the DMA stream and start coversion:
/*Enable the DMA stream*/ DMA2_Stream0->CR|=DMA_SxCR_EN; /*Start ADC conversion*/ ADC1->CR2|=ADC_CR2_SWSTART;
3.3: Getting the data:
We shall use a function that return the desired value for the specific channel:
uint16_t ReadChannel(adc_Channel_t ch) { return adc_data[ch]; }
3.4: Header file:
#ifndef ADC_DMA_H_ #define ADC_DMA_H_ #include "stdint.h" typedef enum { A0=0, A1=1 }adc_Channel_t; void adc_dma_2ch_init(); uint16_t ReadChannel(adc_Channel_t ch); #endif /* ADC_DMA_H_ */
4. Connection:
We shall use the following:
- STM32F767-Nucleo-144
- 2x 1KOhm potentiometer
- Breadboard
- Hookup wires
5. Code:
You may download the code from here:
Results:
Compile and upload the code to your board, open serial terminal and set the baudrate to 115200 and rotate the pot and you should see something similar:
Happy coding 🙂
Add Comment