STM32F411: implement DMA for ADC

Eureka!!!
The STM32 has only one data register for all channels. So I want to use the ADC over DMA.
Issues are: It is not possible to stop the DMA or the ADC without uninit and init the hole parts.
We have more or less one option. Manually start the ADC.
So what we have now. We start the ADC after reading the values from DMA buffer. The ADC now make a conversion of all channels.
We can read also the values because it uses a double buffer mode. With that mode we can read the unused buffer.
This commit is contained in:
Nico Tonnhofer 2015-12-03 23:18:55 +01:00
parent 4cb6f85e3c
commit efd8279c1a
2 changed files with 188 additions and 65 deletions

View File

@ -1,68 +1,181 @@
/** \file /** \file
\brief Analog subsystem, ARM specific part. \brief Analog subsystem, ARM specific part.
STM32F4 goes a different way. The ADC don't have a register for
each channel. We are using DMA instead.
*/ */
#if defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ #if defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__
#include "cmsis-stm32f4xx.h" #include "cmsis-stm32f4xx.h"
#include "arduino.h" #include "arduino.h"
#include "delay.h"
#include "temp.h" #include "temp.h"
#include <string.h>
// DMA ADC-buffer
#define OVERSAMPLE 6
static uint16_t BSS adc_buffer[NUM_TEMP_SENSORS * OVERSAMPLE];
/** Inititalise the analog subsystem. // Private functions
void init_analog(void);
void init_dma(void);
Initialise the ADC and start hardware scan loop for all used sensors. /** Initialize the analog subsystem.
Initialize the ADC and start hardware scan for all sensors.
*/ */
void analog_init() { void analog_init() {
if (NUM_TEMP_SENSORS) { // At least one channel in use. if (NUM_TEMP_SENSORS) { // At least one channel in use.
init_dma();
PIOC_2_PORT->MODER |= (GPIO_MODER_MODER0 << ((PIOC_2_PIN) << 1)); // analog mode init_analog();
PIOC_2_PORT->PUPDR &= ~(3 << ((PIOC_2_PIN) << 1)); // no pullup
PIOC_2_PORT->OSPEEDR |= (3 << ((PIOC_2_PIN) << 1)); // high speed
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Enable clock
/* Set ADC parameters */
/* Set the ADC clock prescaler */
/* Datasheet says max. ADC-clock is 36MHz
We don't need that much. 12MHz is slowest possible.
*/
ADC->CCR |= (ADC_CCR_ADCPRE);
/* Set ADC scan mode */
// scan mode disabled
// reset resolution
// discontinous mode disabled
ADC1->CR1 &= ~( ADC_CR1_SCAN |
ADC_CR1_RES |
ADC_CR1_DISCEN);
/* Set ADC resolution */
// resoltion 10bit
ADC1->CR1 |= ADC_CR1_RES_0;
/* Set ADC data alignment */
// reset = right align
// reset external trigger
//
// disable continous conversion mode
// disable ADC DMA continuous request
// disable ADC end of conversion selection
ADC1->CR2 &= ~( ADC_CR2_ALIGN |
ADC_CR2_EXTSEL |
ADC_CR2_EXTEN |
ADC_CR2_CONT |
ADC_CR2_DDS |
ADC_CR2_EOCS);
/* Set ADC number of conversion */
// 1 conversion
ADC1->SQR1 &= ~(ADC_SQR1_L);
} }
}
/** Initialize all analog pins from config
Initialize the pins to analog mode, no pullup/no pulldown, highspeed
*/
void init_analog() {
#undef DEFINE_TEMP_SENSOR
/*
config analog pins
1. analog mode
2. no pullup
3. high speed
*/
#define DEFINE_TEMP_SENSOR(name, type, pin, additional) \
pin ## _PORT->MODER |= (GPIO_MODER_MODER0 << ((pin ## _PIN) << 1)); \
pin ## _PORT->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((pin ## _PIN) << 1)); \
pin ## _PORT->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR0 << ((pin ## _PIN) << 1));
#include "config_wrapper.h"
#undef DEFINE_TEMP_SENSOR
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Enable clock
/* Set ADC parameters */
/* Set the ADC clock prescaler */
ADC->CCR |= ADC_CCR_ADCPRE;
ADC1->CR1 &= ~(ADC_CR1_RES);
ADC1->CR1 |= ADC_CR1_RES_0;
ADC1->CR1 |= ADC_CR1_SCAN | ADC_CR1_OVRIE;
ADC1->CR2 |= ADC_CR2_DMA;
/* Set ADC number of conversion */
// ((NUM_TEMP_SENSORS) - 1) << 20
ADC1->SQR1 &= ~(ADC_SQR1_L);
ADC1->SQR1 |= (NUM_TEMP_SENSORS - 1) << 20;
// for loop over each channel (0..15) for sequence
#undef DEFINE_TEMP_SENSOR
// for PIO ## ADC >= 10 SRPR1 and ADC -10, else SMPR 2
// 0x06 = 144 cycles
// subt line is to keep compiler happy
#define DEFINE_TEMP_SENSOR(name, type, pin, additional) \
if (NUM_TEMP_SENSORS) { \
uint8_t subt = (pin ## _ADC >= 10) ? 10 : 0; \
if (pin ## _ADC >= 10) { \
ADC1->SMPR1 |= (uint32_t)0x06 << (3 * ((pin ## _ADC) - subt)); \
} else { \
ADC1->SMPR2 |= (uint32_t)0x06 << (3 * ((pin ## _ADC) - subt)); \
} \
subt = (TEMP_SENSOR_ ## name <= 5) ? 0 : (TEMP_SENSOR_ ## name <= 11) ? 6 : 12; \
if (TEMP_SENSOR_ ## name <= 5) { \
ADC1->SQR3 |= pin ## _ADC << (5 * TEMP_SENSOR_ ## name - subt); \
} else \
if (TEMP_SENSOR_ ## name <= 11) { \
ADC1->SQR2 |= pin ## _ADC << (5 * (TEMP_SENSOR_ ## name - subt)); \
} else { \
ADC1->SQR1 |= pin ## _ADC << (5 * (TEMP_SENSOR_ ## name - subt)); \
} \
}
#include "config_wrapper.h"
#undef DEFINE_TEMP_SENSOR
ADC1->CR2 |= ADC_CR2_CONT;
ADC1->CR2 |= ADC_CR2_ADON; // A/D Converter ON / OFF
ADC1->CR2 |= ADC_CR2_SWSTART;
}
/** Init the DMA for ADC
*/
void init_dma() {
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // Enable clock
/**
We have two DMA streams for ADC1. (DMA2_Stream0 and DMA2_Stream4)
We take DMA2 Stream4.
See reference manual 9.3.3 channel selection (p. 166)
*/
// 1. Disable DMA-Stream
DMA2_Stream4->CR &= ~DMA_SxCR_EN;
while(DMA2_Stream4->CR & DMA_SxCR_EN); // we wait until it is disabled.
uint32_t tmp_CR = 0; //DMA2_Stream4->CR;
// 2. perihperal port register address
DMA2_Stream4->PAR = (uint32_t)&ADC1->DR;
// 3. memory address
DMA2_Stream4->M0AR = (uint32_t)&adc_buffer;
// 4. total number of data items
DMA2_Stream4->NDTR = NUM_TEMP_SENSORS * OVERSAMPLE;
// 5. DMA channel
// channel 0
tmp_CR &= ~(DMA_SxCR_CHSEL);
// 6.
// 7. priority
// Very high
tmp_CR |= DMA_SxCR_PL;
// 8. FIFO
DMA2_Stream4->FCR &= ~(DMA_SxFCR_DMDIS);
// 9. config the rest
/*
* halfword for memory and periphal: the 12bit ADC is 16bit right aligned
* memory inc.: we read any adc and doing a step of 16bits after each conversion
* circular mode: repeat until inf
* double buffer mode: we write to one adress and read the other
*/
tmp_CR &= ~(DMA_SxCR_DIR);
tmp_CR |= DMA_SxCR_MSIZE_0 |
DMA_SxCR_PSIZE_0 |
DMA_SxCR_MINC |
DMA_SxCR_CIRC |
DMA_SxCR_TCIE;
DMA2_Stream4->CR = tmp_CR;
// 10. Enable DMA-Stream
DMA2_Stream4->CR |= DMA_SxCR_EN;
while(!(DMA2_Stream4->CR & DMA_SxCR_EN));
NVIC_SetPriority(DMA2_Stream4_IRQn,
NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 3, 1));
NVIC_EnableIRQ(DMA2_Stream4_IRQn); // Enable interrupt generally.
}
/**
DMA2 Stream4 interrupt.
Happens every time the complete stream is written.
In that case, we can turn of the ADC until we read it.
Must have the same name as in cmsis-startup_stm32f411xe.s.
*/
void DMA2_Stream4_IRQHandler(void) {
DMA2->HIFCR = DMA_HIFCR_CTCIF4;
ADC1->CR2 &= ~(ADC_CR2_DMA); // clear DMA flag; we restart it in start_adc()
} }
/** Read analog value. /** Read analog value.
@ -71,26 +184,35 @@ void analog_init() {
\return Analog reading, 10-bit right aligned. \return Analog reading, 10-bit right aligned.
STM32F4 goes a different way. The ADC don't have a register for
each channel. We need a DMA soon to convert and hold all datas.
*/ */
//#include "delay.h"
uint16_t analog_read(uint8_t index) { uint16_t analog_read(uint8_t index) {
// 11.8.2 Managing a sequence of conversions without using the DMA if (NUM_TEMP_SENSORS > 0) {
// page 220 uint16_t r = 0;
uint16_t temp;
uint16_t max_temp = 0;
uint16_t min_temp = 0xffff;
ADC1->SMPR1 &= ~(ADC_SMPR1_SMP12); // PIOC_2_ADC 12 for (uint8_t i = 0; i < OVERSAMPLE; i++) {
// 3 CYCLES temp = adc_buffer[index + NUM_TEMP_SENSORS * i];
max_temp = max_temp > temp ? max_temp : temp;
min_temp = min_temp < temp ? min_temp : temp;
r += temp;
}
ADC1->SQR3 &= ~(ADC_SQR3_SQ1); // rank 1 r = (r - max_temp - min_temp) / (OVERSAMPLE - 2);
ADC1->SQR3 |= PIOC_2_ADC; // << (5 * (rank - 1)) return r;
} else {
return 0;
}
// memset(adc_buffer[!(DMA2_Stream4->CR & DMA_SxCR_CT)], 0, sizeof(adc_buffer[0]));
}
ADC1->CR2 |= ADC_CR2_ADON // A/D Converter ON / OFF /**
| ADC_CR2_SWSTART; // Start Conversion of regular channels Start a new ADC conversion.
*/
while (!(ADC1->SR & ADC_SR_EOC) == ADC_SR_EOC); void start_adc() {
ADC1->CR2 |= ADC_CR2_DMA;
return ADC1->DR;
} }
#endif /* defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ */ #endif /* defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ */

View File

@ -53,12 +53,13 @@
Whether the board needs and implements start_adc(), i.e. analog reads Whether the board needs and implements start_adc(), i.e. analog reads
on-demand (as opposed to free-running ADC conversions). on-demand (as opposed to free-running ADC conversions).
Currently only implemented on AVR.
*/ */
#define NEEDS_START_ADC #define NEEDS_START_ADC
#endif /* __AVR__ */ /* #endif __AVR__ */
#elif __ARM_STM32F411__
#define NEEDS_START_ADC
#endif /* __ARM_STM32F411__ */
void analog_init(void); void analog_init(void);