Skip to main content
Visitor II
March 1, 2024
Solved

ADC with DMA restart

  • March 1, 2024
  • 3 replies
  • 2846 views

Hello,

I'm using ADC together with DMA.

At first start of ADC and DMA all worked like expected.

At 2nd and following starts, the first value received from DMA is always zero / invalid.

Now I have found out that it works if between two DMA transfers I use
LL_ADC_Disable(ADC2);
and afterwards
LL_ADC_Enable(ADC2);

//code snippet:

LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1, 100U);
LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);
LL_ADC_REG_StartConversion(ADC2);

while (LL_DMA_GetDataLength(DMA2, LL_DMA_CHANNEL_1) > 0U)
{
}

LL_ADC_REG_StopConversion(ADC2);
LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_1);

LL_ADC_Disable(ADC2);
while(LL_ADC_IsDisableOngoing(ADC2))
{
}
LL_ADC_Enable(ADC2);

 

Now my questions:

Is disable/enable of the ADC absolutely necessary?

Or is there a less brute way to reuse the ADC together with DMA?

Thanks in advance.

Best regards

Johann

 

    This topic has been closed for replies.
    Best answer by Pierre_Paris

    Hello @jhoerd,

    In circular mode, the result array is normally updated indefinitely. Please use this line to use the circular mode :

     

    LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

     

    If I resume :

    • 1st call : "first Item0 mit correct value from adc/dma."
    • 2nd call : "My first received value stays at zero."
    • 3rd call : What happens ? Is the two first value stays at zero and so on for next calls ? If yes, there is an incremental address somewhere in your code.

    I advise you to download the package STM32Cube_FW_G4 by clicking here and to have a look to the project under the root "Projects\NUCLEO-G474RE\Examples_LL\ADC\ADC_GroupsRegularInjected_Init". It's an interesting example on how to use the DMA to perform a continuous data stream. Also, DMA is configured to transfer conversion data in an array. 

    Can you enable the transfer complete DMA transfer interrupt ?

    Best Regards,

    Pierre

    3 replies

    ST Employee
    March 1, 2024

    Hello @jhoerd,

    Thank you for your question !

    Here some questions/remarks to help your investigation :

    • Disable/Enable is not a normal behavior.
    • Can you please share ADC/DMA handler ?
    • Is an overrun occurs ? (OVR = 1) If the DMA could not serve the DMA transfer request in time, the ADC stops generating DMA requests and the data corresponding to the new conversion is not transferred by the DMA. The DMA transfer requests are blocked until the software clears the OVR bit.
    • What is the value of DMACFG ? Please, share your registers values.

    Best Regards,

    Pierre

    jhoerdAuthor
    Visitor II
    March 4, 2024

    Hello Pierre,

    here my code to intitialize ADC2/DMA

    //ADC2

    LL_ADC_SetGainCompensation(ADC2, 0);
    LL_ADC_SetOverSamplingScope(ADC2, LL_ADC_OVS_DISABLE);
    LL_ADC_DisableDeepPowerDown(ADC2);
    LL_ADC_EnableInternalRegulator(ADC2);initTypeDef.Resolution = LL_ADC_RESOLUTION_12B;
    initTypeDef.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
    initTypeDef.LowPowerMode = LL_ADC_LP_MODE_NONE;
    if (LL_ADC_Init(ADC2, &initTypeDef) != SUCCESS)
    {
    //@what: error ADC_Init
    }

    regInitTypeDef.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
    regInitTypeDef.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE; //means one Channel
    regInitTypeDef.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
    regInitTypeDef.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS; //LL_ADC_REG_CONV_SINGLE
    regInitTypeDef.DMATransfer = LL_ADC_REG_DMA_TRANSFER_LIMITED;//LL_ADC_REG_DMA_TRANSFER_NONE;
    regInitTypeDef.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;

    if (LL_ADC_REG_Init(ADC2, &regInitTypeDef) != SUCCESS)
    {
    //@what: error ADC_Reg_Init
    }

    LL_ADC_REG_SetSequencerRanks(ADC2, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_3);
    LL_ADC_SetChannelSingleDiff(ADC2, LL_ADC_CHANNEL_3, LL_ADC_SINGLE_ENDED);
    LL_ADC_StartCalibration(ADC2, LL_ADC_SINGLE_ENDED);
    while (LL_ADC_IsCalibrationOnGoing(ADC2) != 0)
    {
    //check Timeout
    }

    LL_ADC_SetChannelSamplingTime(ADC2, static_cast<uint32_t>(LL_ADC_CHANNEL_3),
    LL_ADC_SAMPLINGTIME_6CYCLES_5); //PA6

    //Now initialization of DMA:

    LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
    LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_HIGH);
    LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
    LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
    LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
    LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
    LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);

    LL_DMA_SetPeriphRequest(DMA2, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_ADC2);
    LL_DMA_ConfigAddresses(DMA2, LL_DMA_CHANNEL_1, //(uint32_t)&ADC2->DR,
    LL_ADC_DMA_GetRegAddr(ADC2, LL_ADC_DMA_REG_REGULAR_DATA),
    reinterpret_cast<uint32_t>(&ReceiveBuffer[0]),
    LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

     

    //Now cyclic start/restart of DMA request:

    LL_ADC_ClearFlag_EOC(ADC2);
    LL_ADC_ClearFlag_EOS(ADC2);
    LL_ADC_ClearFlag_EOSMP(ADC2);
    LL_ADC_ClearFlag_OVR(ADC2);
    LL_ADC_ClearFlag_ADRDY(ADC2);

    LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1, 100U);
    LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);
    LL_ADC_REG_StartConversion(ADC2);

    while (LL_DMA_GetDataLength(DMA2, LL_DMA_CHANNEL_1) > 0U)
    {
    }LL_ADC_REG_StopConversion(ADC2);
    LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_1);

     

    //OVRFlag:

    Like the code above, i think, I am already resetting OVR flag before

    next start:

    LL_ADC_ClearFlag_OVR(ADC2);

    ADC and DMA registers before first call:

    ADC2->ISR: 0x0
    ADC2->IER: 0x0
    ADC2->CR: 0x10000001
    ADC2->CFGR: 0x80003001
    ADC2->CFGR2: 0x0
    ADC2->SQR1: 0xc0
    ADC2->SQR2: 0x0
    ADC2->SQR3: 0x0
    ADC2->SQR4: 0x0
    ADC2->SMPR1: 0x200
    ADC2->SMPR2: 0x0
    ADC2->JSQR: 0x0
    ADC2->JDR1: 0x0
    ADC2->JDR2: 0x0
    ADC2->JDR3: 0x0
    ADC2->JDR4: 0x0
    DMA2_Channel1->CCR: 0x2580
    DMA2_Channel1->CPAR: 0x50000140
    DMA2_Channel1->CMAR: 0x200003b4
    ADC12->CSR: 1
    ADC12->CCR: c30000
    ADC12->CDR: 0
    ADC2->DR: 0

     

    Registers before 2ndCall without disable/enable ADC2

    ADC2->ISR: 0x0
    ADC2->IER: 0x0
    ADC2->CR: 0x10000001
    ADC2->CFGR: 0x80003001
    ADC2->CFGR2: 0x0
    ADC2->SQR1: 0xc0
    ADC2->SQR2: 0x0
    ADC2->SQR3: 0x0
    ADC2->SQR4: 0x0
    ADC2->SMPR1: 0x200
    ADC2->SMPR2: 0x0
    ADC2->JSQR: 0x0
    ADC2->JDR1: 0x0
    ADC2->JDR2: 0x0
    ADC2->JDR3: 0x0
    ADC2->JDR4: 0x0
    DMA2_Channel1->CCR: 0x2580
    DMA2_Channel1->CPAR: 0x50000140
    DMA2_Channel1->CMAR: 0x200003b4
    ADC12->CSR: 1
    ADC12->CCR: c30000
    ADC12->CDR: fc20000
    ADC2->DR: fc2

     

    As you can see in the code above, there is configured a limited transfer.

    So I think, when DMA reached his limit he stopps transfer, but adc2

    continous conversion, until ovr-Flag is set. But I think, I'm  clearing

    OVR-Flag before every start.

     

    Thanks for your help.

    ST Employee
    March 4, 2024

    Hello @jhoerd,

    Try to configure your DMA in circular mode (DMACFG bit is equal to 1 in ADC_CFGR). In this mode, the ADC generates a DMA transfer request each time a new conversion data is available in the data register, even if the DMA has reached the last DMA transfer. This allows configuring the DMA in circular mode to handle a continuous analog input data stream.

    FYI : If an overrun occurs (OVR = 1) because the DMA could not serve the DMA transfer request in time, the ADC stops generating DMA requests and the data corresponding to the new conversion is not transferred by the DMA. Which means that all the data transferred to the RAM can be considered as valid.

    Best Regards,

    Pierre

    jhoerdAuthor
    Visitor II
    March 4, 2024

    Hello Pierre,

    I changed Flag to DMACFG to 1 (means Circular Mode) but this doesn't show any effect.

    First call: with CMACFG=1:

    ADC2->ISR: 0x0
    ADC2->IER: 0x0
    ADC2->CR: 0x10000001
    ADC2->CFGR: 0x80003003
    ADC2->CFGR2: 0x0
    ADC2->SQR1: 0xc0
    ADC2->SQR2: 0x0
    ADC2->SQR3: 0x0
    ADC2->SQR4: 0x0
    ADC2->SMPR1: 0x200
    ADC2->SMPR2: 0x0
    ADC2->JSQR: 0x0
    ADC2->JDR1: 0x0
    ADC2->JDR2: 0x0
    ADC2->JDR3: 0x0
    ADC2->JDR4: 0x0
    DMA2_Channel1->CCR: 0x2580
    DMA2_Channel1->CPAR: 0x50000140
    DMA2_Channel1->CMAR: 0x200003b4
    ADC12->CSR: 1
    ADC12->CCR: c30000
    ADC12->CDR: 0
    ADC2->DR: 0

    Item0: 4034
    Item1: 4034
    Item2: 4035
    Item3: 4035

    I receive first Item0 mit correct value from adc/dma.

    At 2nd Call, also with DMACFG=1

    ADC2->ISR: 0x0
    ADC2->IER: 0x0
    ADC2->CR: 0x10000001
    ADC2->CFGR: 0x80003003
    ADC2->CFGR2: 0x0
    ADC2->SQR1: 0xc0
    ADC2->SQR2: 0x0
    ADC2->SQR3: 0x0
    ADC2->SQR4: 0x0
    ADC2->SMPR1: 0x200
    ADC2->SMPR2: 0x0
    ADC2->JSQR: 0x0
    ADC2->JDR1: 0x0
    ADC2->JDR2: 0x0
    ADC2->JDR3: 0x0
    ADC2->JDR4: 0x0
    DMA2_Channel1->CCR: 0x2580
    DMA2_Channel1->CPAR: 0x50000140
    DMA2_Channel1->CMAR: 0x200003b4
    ADC12->CSR: 1
    ADC12->CCR: c30000
    ADC12->CDR: fc30000
    ADC2->DR: fc3

    Item0: 0
    Item1: 4035
    Item2: 4035

    My first received value stays at zero.

    (Again: if I disable/enalbe ADC2 between measurements,

    the first value will be valid at all calls:)

    //firstCall:

    ADC12->CDR: 0
    ADC2->DR: 0

    Item0: 4033
    Item1: 4035
    Item2: 4035

     

    //2nd and following calls:

    ADC2->DR: fc3
    Timespan: 478
    Item0: 4035
    Item1: 4035
    Item2: 4035

    Have you any other idea?

    Best Regards

    johann

     

     

    ST Employee
    March 5, 2024

    Hello @jhoerd,

    In circular mode, the result array is normally updated indefinitely. Please use this line to use the circular mode :

     

    LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

     

    If I resume :

    • 1st call : "first Item0 mit correct value from adc/dma."
    • 2nd call : "My first received value stays at zero."
    • 3rd call : What happens ? Is the two first value stays at zero and so on for next calls ? If yes, there is an incremental address somewhere in your code.

    I advise you to download the package STM32Cube_FW_G4 by clicking here and to have a look to the project under the root "Projects\NUCLEO-G474RE\Examples_LL\ADC\ADC_GroupsRegularInjected_Init". It's an interesting example on how to use the DMA to perform a continuous data stream. Also, DMA is configured to transfer conversion data in an array. 

    Can you enable the transfer complete DMA transfer interrupt ?

    Best Regards,

    Pierre

    jhoerdAuthor
    Visitor II
    March 6, 2024
    Hello Pierre,
     
    I tried out your advices. and I see the following context:
     
    LL_ADC_REG_SetDMATransfer(ADC2, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
    LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
     
    LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1, 100U);
    LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);
    LL_ADC_REG_StartConversion(ADC2);
     
    while(!LL_DMA_IsActiveFlag_TC1(DMA2))
    {
    }
    /* ---- LL_ADC_REG_StopConversion(ADC2); --*/
    LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_1);
    LL_DMA_ClearFlag_TC1(DMA2);
     
    If not calling LL_StopConversion at the end of conversion sequence,
    all works as expected. When calling it at the end of conversion,
    the first recorded item at next dma start is zero.
     
    As far as good. But my actual goal is to use ADC3, ADC4 in dual interleaved mode.
    One dma request, when both finished on measure cycle.
     
    It shows the same error as usage of single ADC2.
    1st start of measurement:
    Item0: 4092
    Item1: 4092
    Item2: 4093
    Item3: 4092
    Item4: 4093
    Item5: 4092
     
    2nd and following starts of measurement:
    Item0: 1
    Item1: 0
    Item2: 4093
    Item3: 4092
    Item4: 4093
    Item5: 4092
    (two items zero because of one dma reqeust for concated result of adc3 and adc4)
     
    Here the relevant part of my configuration:
    //ADC3 and Common:
        commonInitTypeDef.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4;
        commonInitTypeDef.Multimode = LL_ADC_MULTI_DUAL_REG_INTERL;
        commonInitTypeDef.MultiDMATransfer = LL_ADC_MULTI_REG_DMA_LIMIT_RES12_10B;
        commonInitTypeDef.MultiTwoSamplingDelay = LL_ADC_MULTI_TWOSMP_DELAY_1CYCLE;
        LL_ADC_CommonInit(ADC345_COMMON, &commonInitTypeDef)
     
      regInitTypeDef.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
      regInitTypeDef.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;  //means one Channel
      regInitTypeDef.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
      regInitTypeDef.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS; 
      regInitTypeDef.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
      regInitTypeDef.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
     
    //ADC4:
      regInitTypeDef.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
      regInitTypeDef.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;  //means one Channel
      regInitTypeDef.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
      regInitTypeDef.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
      regInitTypeDef.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE; 
      regInitTypeDef.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
     
    //DMA:
              LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_5,
                                    LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_PRIORITY_VERYHIGH | LL_DMA_MODE_CIRCULAR 
                                    | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD
                                    | LL_DMA_MDATAALIGN_WORD);
     
              LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_5, LL_DMAMUX_REQ_ADC3);
          LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_5, (uint32_t)&ADC345_COMMON->CDR, 
                           reinterpret_cast<uint32_t>(&ReceiveBuffer[0]),
                                   LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5));
     
    //start of adc/dma
            LL_DMA_ClearFlag_TC5(DMA1);
            LL_ADC_ClearFlag_EOC(ADC3);
            LL_ADC_ClearFlag_EOC(ADC4);
            LL_ADC_ClearFlag_EOS(ADC3);
            LL_ADC_ClearFlag_EOS(ADC4);
            LL_ADC_ClearFlag_EOSMP(ADC3);
            LL_ADC_ClearFlag_EOSMP(ADC4);
            LL_ADC_ClearFlag_OVR(ADC3);
            LL_ADC_ClearFlag_OVR(ADC4);
            LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, 100U);
            LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
            LL_ADC_REG_StartConversion(ADC3);
            while(!LL_DMA_IsActiveFlag_TC5(DMA1))
            {
            }
            LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);
            LL_DMA_ClearFlag_TC5(DMA1);
     
    I try to use it the same way as adc2 in the above example.
    Do you have any idea, what I have to change in the dual interleaved example?
     
    Many thanks in advance.
    Best regards
    Johann

     

     

     

     

     

     

     

    jhoerdAuthor
    Visitor II
    March 7, 2024

    Hello Pierre,

    I have solved the problem with ADC3/4 in dual interleaved mode with dma restart.

    I have to change "MultiDmaTransfer" of ADC345 from LL_ADC_MULTI_REG_DMA_LIMIT_RES12_10B

    to:

    LL_ADC_MULTI_REG_DMA_UNLMT_RES12_10B.

    The behaviour of ADC345_COMMON is identical to ADC2 from the

    previous example.

    Many thanks again for your support

    Best regards

    Johann