2024-05-16 12:32 PM - edited 2024-05-16 01:00 PM
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:
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:
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?
Solved! Go to Solution.
2024-05-17 02:39 PM - edited 2024-05-17 02:41 PM
I see it now. Even though I selected Enable Regular Conversions: Enable in STM32CubeIDE:
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.
2024-05-17 09:40 AM
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. */
2024-05-17 09:54 AM
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
2024-05-17 10:27 AM
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
2024-05-17 12:51 PM
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.
2024-05-17 02:13 PM
@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.
2024-05-17 02:39 PM - edited 2024-05-17 02:41 PM
I see it now. Even though I selected Enable Regular Conversions: Enable in STM32CubeIDE:
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.
2024-05-18 01:05 AM
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
2024-05-20 08:26 AM
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
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-05-20 05:51 PM
@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.