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:
parent
4cb6f85e3c
commit
efd8279c1a
246
analog-stm32.c
246
analog-stm32.c
|
|
@ -1,68 +1,181 @@
|
|||
|
||||
/** \file
|
||||
\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__
|
||||
|
||||
#include "cmsis-stm32f4xx.h"
|
||||
#include "arduino.h"
|
||||
#include "delay.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() {
|
||||
|
||||
if (NUM_TEMP_SENSORS) { // At least one channel in use.
|
||||
|
||||
PIOC_2_PORT->MODER |= (GPIO_MODER_MODER0 << ((PIOC_2_PIN) << 1)); // analog mode
|
||||
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);
|
||||
|
||||
init_dma();
|
||||
init_analog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 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.
|
||||
|
|
@ -71,26 +184,35 @@ void analog_init() {
|
|||
|
||||
\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) {
|
||||
// 11.8.2 Managing a sequence of conversions without using the DMA
|
||||
// page 220
|
||||
if (NUM_TEMP_SENSORS > 0) {
|
||||
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
|
||||
// 3 CYCLES
|
||||
for (uint8_t i = 0; i < OVERSAMPLE; i++) {
|
||||
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
|
||||
ADC1->SQR3 |= PIOC_2_ADC; // << (5 * (rank - 1))
|
||||
r = (r - max_temp - min_temp) / (OVERSAMPLE - 2);
|
||||
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
|
||||
|
||||
while (!(ADC1->SR & ADC_SR_EOC) == ADC_SR_EOC);
|
||||
|
||||
return ADC1->DR;
|
||||
/**
|
||||
Start a new ADC conversion.
|
||||
*/
|
||||
void start_adc() {
|
||||
ADC1->CR2 |= ADC_CR2_DMA;
|
||||
}
|
||||
|
||||
#endif /* defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ */
|
||||
|
|
|
|||
7
analog.h
7
analog.h
|
|
@ -53,12 +53,13 @@
|
|||
|
||||
Whether the board needs and implements start_adc(), i.e. analog reads
|
||||
on-demand (as opposed to free-running ADC conversions).
|
||||
|
||||
Currently only implemented on AVR.
|
||||
*/
|
||||
#define NEEDS_START_ADC
|
||||
|
||||
#endif /* __AVR__ */
|
||||
/* #endif __AVR__ */
|
||||
#elif __ARM_STM32F411__
|
||||
#define NEEDS_START_ADC
|
||||
#endif /* __ARM_STM32F411__ */
|
||||
|
||||
void analog_init(void);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue