Skip to main content
Explorer II
May 16, 2024
Solved

STM32L4A6 ADC Sequence Time less than expected

  • May 16, 2024
  • 6 replies
  • 2986 views

I'm using ADC1 to measure the Temperature Sensor channel and the Vrefint channel. I expect those to vary very slowly and I am in not in a hurry so I am maximizing the sampling time. These are my settings in STM32CubeIDE:

CKugl1_0-1715886426768.png

CKugl1_1-1715886508737.png

It generates the following in main.c:

 

 

 

static void MX_ADC1_Init(void)
{

 /* USER CODE BEGIN ADC1_Init 0 */

 /* USER CODE END ADC1_Init 0 */

 LL_ADC_InitTypeDef ADC_InitStruct = {0};
 LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
 LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};

 /* Peripheral clock enable */
 LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC);

 /* ADC1 DMA Init */

 /* ADC1 Init */
 LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMA_REQUEST_0);

 LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

 LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_MEDIUM);

 LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

 LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

 LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

 LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);

 LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);

 /* ADC1 interrupt Init */
 NVIC_SetPriority(ADC1_2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
 NVIC_EnableIRQ(ADC1_2_IRQn);

 /* USER CODE BEGIN ADC1_Init 1 */

 /* USER CODE END ADC1_Init 1 */

 /** Common config
 */
 ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
 ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
 ADC_InitStruct.LowPowerMode = LL_ADC_LP_AUTOWAIT;
 LL_ADC_Init(ADC1, &ADC_InitStruct);
 ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
 ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS;
 ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
 ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
 ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
 ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
 LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
 LL_ADC_ConfigOverSamplingRatioShift(ADC1, LL_ADC_OVS_RATIO_256, LL_ADC_OVS_SHIFT_RIGHT_8);
 LL_ADC_SetOverSamplingDiscont(ADC1, LL_ADC_OVS_REG_CONT);
 LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_CHANNEL_VREFINT|LL_ADC_CHANNEL_TEMPSENSOR);
 ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_ASYNC_DIV64;
 ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT;
 LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);

 /* Disable ADC deep power down (enabled by default after reset state) */
 LL_ADC_DisableDeepPowerDown(ADC1);
 /* Enable ADC internal voltage regulator */
 LL_ADC_EnableInternalRegulator(ADC1);
 /* Delay for ADC internal voltage regulator stabilization. */
 /* Compute number of CPU cycles to wait for, from delay in us. */
 /* Note: Variable divided by 2 to compensate partially */
 /* CPU processing cycles (depends on compilation optimization). */
 /* Note: If system core clock frequency is below 200kHz, wait time */
 /* is only a few CPU processing cycles. */
 uint32_t wait_loop_index;
 wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
 while(wait_loop_index != 0)
 {
 wait_loop_index--;
 }

 /** Configure Regular Channel
 */
 LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_VREFINT);
 LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_640CYCLES_5);
 LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SINGLE_ENDED);

 /** Configure Regular Channel
 */
 LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_TEMPSENSOR);
 LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SAMPLINGTIME_640CYCLES_5);
 LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SINGLE_ENDED);
 /* USER CODE BEGIN ADC1_Init 2 */

 /* USER CODE END ADC1_Init 2 */

}

 

 

 

The ADC clock is set to PLLSAI1R, and

 

 

 

 printf("ADC1 clock freq.: %lu\n", HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_ADC));

 

 

 

prints "ADC1 clock freq.: 48000000".

 

EDIT: ADC_CCR is

 

 // 3 2 1
 // 10987654321098765432109876543210
 0b111001000000000000000000

 

Bits 21:18 PRESC[3:0]: ADC prescaler: 1001: input ADC clock divided by 64

 

My theory is that a sequence of conversions should take about 446 milliseconds:

  • (640.5 + 12.5) cycles per channel sample,
  • 2 * (640.5 + 12.5) for two channels,
  • 256 * 2 * (640.5 + 12.5) for 256x oversampling
  • = 334336 cycles
  • fADC_CLK = 48E6 / divADC_CLK
  • = 48E6 / 64 = 750000
  • 334336 cycles * (1/fADC_CLK) = 334336 / 750000
  • = 445,781 us

However, when I run this:

 

 

 

 LL_ADC_ClearFlag_EOS(ADC1);
 __COMPILER_BARRIER();
 start = micros();
 LL_ADC_REG_StartConversion(ADC1);
 while(!LL_ADC_IsActiveFlag_EOS(ADC1) && micros() - start < 1000000);
 assert(LL_ADC_IsActiveFlag_EOS(ADC1));
 printf("EOS wait: %lu us\n", micros() - start);

 

 

 

I consistently see "EOS wait: 1757 us". That is a couple of orders of magnitude less time than I was expecting.

What am I missing?

    This topic has been closed for replies.
    Best answer by CKugl.1

    I see it now. Even though I selected Enable Regular Conversions: Enable in STM32CubeIDE:

    CKugl1_0-1715981715196.png

    that, apparently, doesn't do anything. It's up to the user to manually set

    Bit 0 ROVSE: Regular Oversampling Enable
    This bit is set and cleared by software to enable regular oversampling.


    0: Regular Oversampling disabled
    1: Regular Oversampling enabled

    I added 

     

     ADC1->CFGR2 |= ADC_CFGR2_ROVSE_Msk;

     

    to my initialization function and now I am getting "EOS wait: 445799 us", which is what I expected in the first place.

    6 replies

    CKugl.1Author
    Explorer II
    May 17, 2024

    P.S. I verified the ~1.7 ms time to EOS with a 'scope.

    ADC_SMPR1 is 0b111

    Bits 29:0 SMP[9:0][2:0]: Channel x sampling time selection; 111: 640.5 ADC clock cycles

     

    ADC_SMPR2 is 0b111000000000000000000000

    Bits 26:0 SMP[18:10][2:0]: Channel x sampling time selection; 111: 640.5 ADC clock cycles

    These appear to be correct for channels 0 and 17.

    #define LL_ADC_CHANNEL_VREFINT (LL_ADC_CHANNEL_0 | ADC_CHANNEL_ID_INTERNAL_CH) /*!< ADC internal channel
     connected to VrefInt: Internal voltage reference.
     On STM32L4, ADC channel available only on ADC instance: ADC1. */
    #define LL_ADC_CHANNEL_TEMPSENSOR (LL_ADC_CHANNEL_17 | ADC_CHANNEL_ID_INTERNAL_CH) /*!< ADC internal channel
     connected to internal temperature sensor.
     On STM32L4, ADC channel available only on ADC instances: ADC1, ADC3. */

     

     

     

     

     

    CKugl.1Author
    Explorer II
    May 17, 2024

    Interestingly, if I change the oversampling ratio to 16x, the time to EOS remains about the same.

    Now that I have changed the OVSR to 16x, ADC_CFGR2 is

    // 3 2 1
    // 10987654321098765432109876543210
     0b10001100
    // Bits 4:2 OVSR[2:0]: Oversampling ratio
    // 011: 16x

     

     

    Graduate
    May 17, 2024

    The error is a factor of approx 256, so it must be the oversampling ratio functionality.

    The ratio bits are correctly set, so perhaps it is something in setting ROVS.

    CKugl.1Author
    Explorer II
    May 17, 2024

    @raptorhal2 wrote:

    The error is a factor of approx 256, so it must be the oversampling ratio functionality.

    I agree. I'm beginning to think oversampling doesn't work at all on this chip.


    The ratio bits are correctly set, so perhaps it is something in setting ROVS.


    I have ROVS at 0.

    ADC_CFGR2

    Bit 10 ROVSM: Regular Oversampling mode
    This bit is set and cleared by software to select the regular oversampling mode.


    0: Continued mode: When injected conversions are triggered, the oversampling is temporary
    stopped and continued after the injection sequence (oversampling buffer is maintained during
    injected sequence)
    1: Resumed mode: When injected conversions are triggered, the current oversampling is aborted
    and resumed from start after the injection sequence (oversampling buffer is zeroed by injected
    sequence start)

    I'm not using injection, so it seems to me this bit wouldn't make much difference.

    CKugl.1Author
    Explorer II
    May 17, 2024

     

    Here is a dump of all of the ADC registers prior to LL_ADC_REG_StartConversion(ADC1):

     3 2 1 0
     10987654321098765432109876543210
    ISR : ADC interrupt and status register : 0b00000000000000000000000000000011
    IER : ADC interrupt enable register : 0b00000000000000000000000000010000
    CR : ADC control register : 0b00010000000000000000000000000001
    CFGR : ADC configuration register 1 : 0b10000000000000000101000000000011
    CFGR2 : ADC configuration register 2 : 0b00000000000000000000000100011100
    SMPR1 : ADC sampling time register 1 : 0b00000000000000000000000000000111
    SMPR2 : ADC sampling time register 2 : 0b00000000111000000000000000000000
    TR1 : ADC analog watchdog 1 threshold register : 0b00001111111111110000000000000000
    TR2 : ADC analog watchdog 2 threshold register : 0b00000000111111110000000000000000
    TR3 : ADC analog watchdog 3 threshold register : 0b00000000111111110000000000000000
    SQR1 : ADC group regular sequencer register 1 : 0b00000000000000010001000000000001
    SQR2 : ADC group regular sequencer register 2 : 0b00000000000000000000000000000000
    SQR3 : ADC group regular sequencer register 3 : 0b00000000000000000000000000000000
    SQR4 : ADC group regular sequencer register 4 : 0b00000000000000000000000000000000
    DR : ADC group regular data register : 0b00000000000000000000001110010001
    JSQR : ADC group injected sequencer register : 0b00000000000000000000000000000000
    OFR1 : ADC offset register 1 : 0b00000000000000000000000000000000
    OFR2 : ADC offset register 2 : 0b00000000000000000000000000000000
    OFR3 : ADC offset register 3 : 0b00000000000000000000000000000000
    OFR4 : ADC offset register 4 : 0b00000000000000000000000000000000
    JDR1 : ADC group injected rank 1 data register : 0b00000000000000000000000000000000
    JDR2 : ADC group injected rank 2 data register : 0b00000000000000000000000000000000
    JDR3 : ADC group injected rank 3 data register : 0b00000000000000000000000000000000
    JDR4 : ADC group injected rank 4 data register : 0b00000000000000000000000000000000
    AWD2CR : ADC analog watchdog 1 configuration register : 0b00000000000000000000000000000000
    AWD3CR : ADC analog watchdog 3 Configuration Register : 0b00000000000000000000000000000000
    DIFSEL : ADC differential mode selection register : 0b00000000000000000000000000000000
    CALFACT : ADC calibration factors : 0b00000000000000000000000000111000
    CSR : ADC common status register : 0b00000000000000000000000000000011
    CCR : ADC common configuration register : 0b00000000111001000000000000000000
    CDR : ADC common group regular data register : 0b00000000000000000000001110010001
    EOS wait: 1755 us
    Temperature: 24 °C

     

    CKugl.1AuthorAnswer
    Explorer II
    May 17, 2024

    I see it now. Even though I selected Enable Regular Conversions: Enable in STM32CubeIDE:

    CKugl1_0-1715981715196.png

    that, apparently, doesn't do anything. It's up to the user to manually set

    Bit 0 ROVSE: Regular Oversampling Enable
    This bit is set and cleared by software to enable regular oversampling.


    0: Regular Oversampling disabled
    1: Regular Oversampling enabled

    I added 

     

     ADC1->CFGR2 |= ADC_CFGR2_ROVSE_Msk;

     

    to my initialization function and now I am getting "EOS wait: 445799 us", which is what I expected in the first place.

    Super User
    May 18, 2024

    Thanks for coming back with the solution.

    This then appears to be the case where CubeMX does not properly generate a LL function/macro call to set ADC_CFGR2.ROVSE , correct? ( @Amel NASRI , can you or whomever is in charge, please have a look? Thanks.)

    Maybe this is also a point where you may want to reconsider the merits (easy! fast!) and demerits (solving this problem may have eaten up all that massive time saving, you have to take measures to avoid CubeMX to gulp your fix upon regeneration, you needed to learn the ins and outs of registers anyway, mapping of LL naming to registers is not that straightforward so that's another set of things to learn, etc.) of programming through clicking.

    Please click on "Accept as solution" in your post so that the thread is marked as solved.

    JW

     

    CKugl.1Author
    Explorer II
    May 21, 2024

    @waclawek.jan wrote:

    ...

    Maybe this is also a point where you may want to reconsider the merits (easy! fast!) and demerits (solving this problem may have eaten up all that massive time saving, you have to take measures to avoid CubeMX to gulp your fix upon regeneration, you needed to learn the ins and outs of registers anyway, mapping of LL naming to registers is not that straightforward so that's another set of things to learn, etc.) of programming through clicking.

     

    ...


    Well, as you can see, I have already given up on the HAL for those reasons. The LL libraries are not far removed from register-level programming; I'd end up just re-implementing a bunch of macros that the LL libraries provide. Where I find it hard to program at the register level is for something like the SDMMC. It is complex, and there seem to be a lot of undocumented assumptions about timing. Unfortunately, no LL library is provided for the SDMMC and the HAL for it is buggy as heck. If someone has done a low-level library for the SDMMC, I'd be interested.

    Technical Moderator
    May 20, 2024

    Hi @waclawek.jan ,

    Thanks for bringing this case to my attention. I reported the issue to our development team.

    Internal ticket number: 181796 (This is an internal tracking number and is not accessible or usable by customers).

    @CKugl.1, as suggested by @waclawek.jan I selected your reply as solution for your question. Thanks for the follow-up and the sharing with the community.

    I assume that you can call 

    LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_GRP_REGULAR_CONTINUED);

    -Amel 

    Technical Moderator
    September 23, 2024

    Hello,

    Please note that issue #181796 was fixed in the STM32CubeMX 6.12.0 release.

    -Amel