Skip to main content
jean_prieur
Associate III
November 12, 2013
Question

DAC & ADC speed help

  • November 12, 2013
  • 13 replies
  • 1851 views
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 !

    This topic has been closed for replies.

    13 replies

    Tesla DeLorean
    Guru
    November 12, 2013
    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 (See Profile) Up vote any posts that you find helpful, it shows what's working..
    jean_prieur
    Associate III
    November 12, 2013
    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=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=96

    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 ?

    Tesla DeLorean
    Guru
    November 12, 2013
    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=1411

    [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 (See Profile) Up vote any posts that you find helpful, it shows what's working..
    jean_prieur
    Associate III
    November 13, 2013
    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 !
    Tesla DeLorean
    Guru
    November 13, 2013
    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 (See Profile) Up vote any posts that you find helpful, it shows what's working..
    jean_prieur
    Associate III
    November 13, 2013
    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 ?

    Tesla DeLorean
    Guru
    November 13, 2013
    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 (See Profile) Up vote any posts that you find helpful, it shows what's working..
    jean_prieur
    Associate III
    November 13, 2013
    Posted on November 13, 2013 at 17:28

    Thanks Clive, I will try with DMA.

    jean_prieur
    Associate III
    November 14, 2013
    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 !
    frankmeyer9
    Associate III
    November 14, 2013
    Posted on November 14, 2013 at 13:10

    void

     

    Refresh_ADC_Values(

     

    void

     

    )

    {

    ADC_SoftwareStartConv(ADC1);

    while

     

    (ADC_GetSoftwareStartConvStatus(ADC1) != RESET){}

    }

    Is it normal ?

    Yes, because actually, you are not interested in the status of the SWSTART flag.

    Instead, try implementing an interrupt on the TC flag of the DMA.