STM32G4: First ADC samples are not correct
Summary: I am sampling the internal voltage reference 5 times using DMA with the ADC set to continuous mode with LL_DMA_LIMITED. I know about the errata that says to ignore the first ADC value, but the other 4 values only become consistent if I use a
HAL_Delay(1)after enabling the ADC. Am I missing something?
Relevant code without the delay:
#include "main.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define ARRAY_SIZE(x) (sizeof (x) / sizeof *(x))
static uint16_t adc_data[5];
static volatile int dma_interrupt_done = 0;
// runs inside main
void main_user_code(void) {
printf("Now booting!\r\n");
// Setup DMA
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_1,
LL_ADC_DMA_GetRegAddr(ADC1, 0));
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uintptr_t) &adc_data);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
// Setup ADC
LL_ADC_StartCalibration(ADC1, LL_ADC_SINGLE_ENDED);
while (LL_ADC_IsCalibrationOnGoing(ADC1)) {}
// Delay needed between end of calibration and beginning of enabling. 100ms is overkill.
HAL_Delay(100);
LL_ADC_ClearFlag_ADRDY(ADC1);
LL_ADC_Enable(ADC1);
while (!LL_ADC_IsActiveFlag_ADRDY(ADC1)) {}
// HAL_Delay(1);
while (1) {
// To change the data length, which must be reset after every time DMA finishes, we must first disable the relevant DMA channel.
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ARRAY_SIZE(adc_data));
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_ADC_REG_StartConversion(ADC1);
HAL_Delay(2000);
while (!dma_interrupt_done) {
};
printf("ADC Results:\r\n");
for (size_t i = 0; i < ARRAY_SIZE(adc_data); ++i) {
printf(" ADC sample [%u] = %u\r\n", i, adc_data[i]);
}
uint16_t cal = *(uint16_t*) VREFINT_CAL_ADDR;
static_assert(ARRAY_SIZE(adc_data) > 1, "We ignore the first sample per the errata list.");
float vref = (VREFINT_CAL_VREF * cal / (float) adc_data[1]) / 1000;
printf("VREF+ (using ADC result index 1)= %.3fV\r\n", vref);
memset(adc_data, 0, sizeof adc_data);
dma_interrupt_done = 0;
}
}
// runs inside DMA handler
void dma_interrupt_handler_code(void) {
dma_interrupt_done = 1;
LL_DMA_ClearFlag_TC1(DMA1);
}Terminal output below. Do you see how samples 1..3 are a bit high and then start lowering until they are consistent?
Now booting!
ADC Results:
ADC sample [0] = 1514
ADC sample [1] = 1519
ADC sample [2] = 1511
ADC sample [3] = 1503
ADC sample [4] = 1499
VREF+ (using ADC result index 1)= 3.267V
ADC Results:
ADC sample [0] = 0
ADC sample [1] = 1473
ADC sample [2] = 1492
ADC sample [3] = 1491
ADC sample [4] = 1490
VREF+ (using ADC result index 1)= 3.369V
ADC Results:
ADC sample [0] = 0
ADC sample [1] = 1473
ADC sample [2] = 1493
ADC sample [3] = 1492
ADC sample [4] = 1492
VREF+ (using ADC result index 1)= 3.369V
ADC Results:
ADC sample [0] = 0
ADC sample [1] = 1474
ADC sample [2] = 1493
ADC sample [3] = 1491
ADC sample [4] = 1491
VREF+ (using ADC result index 1)= 3.366V
ADC Results:
ADC sample [0] = 0
ADC sample [1] = 1475
ADC sample [2] = 1494
ADC sample [3] = 1493
ADC sample [4] = 1492
VREF+ (using ADC result index 1)= 3.364V
ADC Results:
ADC sample [0] = 0
ADC sample [1] = 1471
ADC sample [2] = 1491
ADC sample [3] = 1491
ADC sample [4] = 1490
VREF+ (using ADC result index 1)= 3.373V// Generated ADC init code
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};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* Peripheral clock enable */
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12);
/* ADC1 DMA Init */
/* ADC1 Init */
LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_ADC1);
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_LOW);
LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
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);
/* 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_MODE_NONE;
LL_ADC_Init(ADC1, &ADC_InitStruct);
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;
ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_LIMITED;
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;
LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
LL_ADC_SetGainCompensation(ADC1, 0);
LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4;
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_2CYCLES_5);
LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SINGLE_ENDED);
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT);
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}// Generated DMA init code
static void MX_DMA_Init(void)
{
/* Init with LL driver */
/* DMA controller clock enable */
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMAMUX1);
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}