cancel
Showing results for 
Search instead for 
Did you mean: 

DAC & ADC speed help

jean_prieur
Associate III
Posted on November 12, 2013 at 15:54

Hello,

I'm coding a real time system based on STM32F427 requiring a lot of ressources and using almost all peripherials (ADC, DAC, SPI, I2aC, USART, USB, SDIO).

This is why I want to reduce the ressource needs of my peripherials and especially the ADC and DAC.Theses analog I/Os don't have to be very fast (500 Hz max). I'm a newbie with microcontrolers, soI have two questions:

1) When I set the DAC output to a voltage with

DAC_SetChannel1Data(DAC_Align_12b_R, voltage);

the microcontroler set a voltage on his pin. But after it will not take ressources if I do nothing ? I mean, until I do not set another value, the DAC will nottake ressources of the uC ?

2) I config my ADC (ADC 1 & ADC2) to work with DMA and scanning 4 channels. But this operation is continuous and I don't know the sampling rate. I want to have the control and to ask for a sample when this is necessary. Is it possible ? For example ''GetOneAdcSample();'' My code is below:

void

ADC_Config(

void

)

{

// ADC init

ADC_CommonInitTypeDef ADC_CommonInitStructure;

// Set of the CLOCK for DMA2 + GPIOC + ADC1 + ADC2

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2, ENABLE);

// Config of DMA2 (direct access memory) Stream0 channel0

DMA_InitTypeDef DMA_InitStructure;

DMA_InitStructure.DMA_Channel = DMA_Channel_0;

// USE CHANNEL 0

DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&TabADC;

// Save values in TabADC(4)

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC_CCR_ADDRESS;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

DMA_InitStructure.DMA_BufferSize = 4;

// BUFFER SIZE (same size as TabADC)

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

DMA_InitStructure.DMA_Priority = DMA_Priority_High;

DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;

DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

DMA_Init(DMA2_Stream0, &DMA_InitStructure);

DMA_Cmd(DMA2_Stream0, ENABLE);

// DMA2_Stream0 enable

// GPIO config pins C4 C4 A2 A3

GPIO_InitTypeDef GPIO_InitStructure;

// GPIOC

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;

GPIO_Init(GPIOC, &GPIO_InitStructure);

//GPIOA

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;

GPIO_Init(GPIOA, &GPIO_InitStructure);

// Init ADC ''Common''

ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;

ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;

ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;

ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles ;

ADC_CommonInit(&ADC_CommonInitStructure);

// Init ADC ''1'' channels 15, 3

ADC_InitTypeDef ADC_InitStructure;

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

ADC_InitStructure.ADC_ScanConvMode = ENABLE;

ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

ADC_InitStructure.ADC_ExternalTrigConv = 0;

ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

ADC_InitStructure.ADC_NbrOfConversion = 2;

// 2 conv because 2 channels

ADC_Init(ADC1, &ADC_InitStructure);

// ADC1 regular channels 15, 14 configuration

ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_56Cycles);

ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 2, ADC_SampleTime_56Cycles);

// Init ADC ''2'' channels 14, 2

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

ADC_InitStructure.ADC_ScanConvMode = ENABLE;

ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

ADC_InitStructure.ADC_ExternalTrigConv = 0;

ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

ADC_InitStructure.ADC_NbrOfConversion = 2;

ADC_Init(ADC2, &ADC_InitStructure);

// ADC2 regular channels 1, 2 configuration

ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_56Cycles);

ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 2, ADC_SampleTime_56Cycles);

// Enable DMA request after last transfer (Multi-ADC mode)

ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

// Enable ADC1 and ADC2

ADC_Cmd(ADC1, ENABLE);

ADC_Cmd(ADC2, ENABLE);

// Start ADC1 Software Conversion

ADC_SoftwareStartConv(ADC1);

}

I hope I made it clear.

Thanks !

13 REPLIES 13
Posted on November 12, 2013 at 16:10

1) You set-and-forget, you are writing to a register that latches the value written. It will not use any further processor cycles, although it might consume current.

2) Don't use Circular DMA mode, reprogram the DMA for each set of samples, and  then start the conversion. Wait on DMA TC as the EOC signal. If the sampling is periodic, consider triggering with a timer.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
jean_prieur
Associate III
Posted on November 12, 2013 at 17:49

Thanks Clive !

So I think I have to code something like this:

https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/ADC%20regular%20simultanous%20dual%20mode%20with%20timer%20external%20trigger&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C...

But in this code I'm unable to find where is the the link between the timer (which should trigg a new ADC sample each time) and the ADC... How can I ask the ADC to do a new conversion in the timer interruption ?

Posted on November 12, 2013 at 18:11

You don't use continuous mode, but rather select a trigger source, and set it up.

https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Help%20with%20simple%20ADC%20on%20STM32F4&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=...

[DEAD LINK /public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/ADC%20in%20Dual%20Mode%20Simultaneously&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=310]This thread 1/4 way down

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
jean_prieur
Associate III
Posted on November 13, 2013 at 16:12

Thanks for theses examples.

In order to make things easier now I don't use DMA, this is my new code:

void
ADC_Config(
void
)
{
// Enable peripheral clocks
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 , ENABLE);
// GPIO configuration of pins C4 C4 A2 A3 en ANALOG INPUTS
GPIO_InitTypeDef GPIO_InitStructure;
// GPIOC
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; 
// Mode analog
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Init ADC ''Common''
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles ;
ADC_CommonInit(&ADC_CommonInitStructure);
// ADC1 & ADC2 init
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = 0;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 2; 
// 2 channels to convert
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Init(ADC2, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 2, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 2, ADC_SampleTime_56Cycles);
// Enable ADC1 to ADC3
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
}
uint16_t Get_ADC_Value(uint8_t ADC_NUMBER)
{
static
uint16_t ADC_Convert;
switch
(ADC_NUMBER)
{
case
1: 
// At each call the ADC1 must return the converted value of one of the two channel
ADC_SoftwareStartConv(ADC1);
while
(ADC_GetSoftwareStartConvStatus(ADC1) != RESET){ADC_Convert = 0;}
ADC_Convert = ADC_GetConversionValue(ADC1);
break
;
case
2: 
// At each call the ADC2 must return the converted value of one of the two channel
ADC_SoftwareStartConv(ADC2);
while
(ADC_GetSoftwareStartConvStatus(ADC2) != RESET){ADC_Convert = 0;}
ADC_Convert = ADC_GetConversionValue(ADC2);
break
;
}
return
ADC_Convert;
}

I still have 4 channels to convert (two on ADC1 and two on ADC2). When I want to get a value of the ADC1 I use

Get_ADC_Value(1);

but it always return the value of the first channel, not the second. Is there an error on my code ? I try to change

ADC_ScanConvMode

and

ADC_ContinuousConvMode

but this do not solve the problem... Thanks !
Posted on November 13, 2013 at 16:35

You don't wait for the second sample on the channel. You do a single start conversion, and two consecutive values are generated at the sample rate you have coded. The second sample will arrive rapidly after the first. The use of DMA is again suggested.

This is also wrong

ADC_InitStructure.ADC_ScanConvMode = DISABLE;

You have a list of TWO channels, you are thus scanning through a list, this should be enabled.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
jean_prieur
Associate III
Posted on November 13, 2013 at 16:57

So when I have more than one channel linked to an ADC I have to use DMA ?

Posted on November 13, 2013 at 17:09

Given the rates at which the conversions are generated, and the fact you only have one holding register in the ADC, it is the only practical way to safely, and orderly, do multiple conversions. It's not going to stop and wait for you to catch up. With slow sampling rates you might be able to get around the issue, but your solution will be very fragile.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
jean_prieur
Associate III
Posted on November 13, 2013 at 17:28

Thanks Clive, I will try with DMA.

jean_prieur
Associate III
Posted on November 14, 2013 at 12:24

It's working with DMA ! I just have to use Refresh_ADC_Values(); and the values of my ADC1 and ADC2 channels are updated in the table TabADC[4];

static 
uint16_t TabADC[4];
void
Refresh_ADC_Values(
void
)
{
ADC_SoftwareStartConv(ADC1);
while
(ADC_GetSoftwareStartConvStatus(ADC1) != RESET){}
}
void
ADC_Config(
void
)
{
// ADC initialisation
ADC_CommonInitTypeDef ADC_CommonInitStructure;
// CLOCK init for DMA2 + GPIOA + GPIOC + ADC1 + ADC2
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2, ENABLE);
// DMA2 config (direct access memory) Stream0 channel0
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0; 
// USE CHANNEL 0
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&TabADC; 
// Save in TabADC(4)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC_CCR_ADDRESS; 
// Use the adress 0x40012308
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = 4; 
// BUFFER SIZE (same size than TabADC)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream0, ENABLE); 
// DMA2_Stream0 enable
// GPIO config pins C4 C4 A2 A3 > ANALOG INPUTS
GPIO_InitTypeDef GPIO_InitStructure;
// GPIOC
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Initialisation de l'ADC ''Common''
ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles ;
ADC_CommonInit(&ADC_CommonInitStructure);
// Initialisation de l'ADC ''1'' channels 15, 3
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; 
// Faible résolution pour des valeurs entre 0 et 256
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = 0;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 2; 
// 2 conversions car 2 channels à convertir
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Init(ADC2, &ADC_InitStructure);
// ADC1 regular channels 15, 14 configuration
ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 2, ADC_SampleTime_56Cycles);
// ADC2 regular channels 1, 2 configuration
ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 2, ADC_SampleTime_56Cycles);
// Enable DMA request after last transfer (Multi-ADC mode)
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);
// ENABLE ADC1 & ADC2
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
}

But I still have a little problem... If I Refresh_ADC_Values(); and in the next instruction I want to display the ADC values (converted in the TabADC[4];) itdoesn't works: it displays the last values befores the conversion. I think this is beause the ADC has not had time to convert and fill the table. If I wait between Refresh_ADC_Values(); and the check of the table values are correctly filled. Is it normal ? How can I do to know if my values are updated ? Thanks !