STM32G071 ADC VREF giving odd results on new batch of chips
We have a design that has been working fine for some time now using STM32G071CBT6 in LQFP48 but a new batch is suddenly failing with ADC readings from VREFINT that are quite a way out of spec.
Previous versions have always been slightly low - reading maybe ~3.15v on a ~3.25v supply voltage but that's always been "close enough", but now the latest batch of devices are dipping below 3.0v on the same ~2.35v supply and triggering our under-voltage detection.
We are monitoring a few voltages using the ADC, including the supply voltage which is commoned with VDD/VDDA, VBAT and VREF pins - decoupling is per the power supply scheme shown in the datasheet DS12232.
VREFBUF is disabled.
Since we are not worried too much about absolute accuracy, we are not running the calibration routine on the ADC.
System clock is 16MHz and ADC sampling is 79.5 cycles, ADC resolution is 12 bit.
The ground pin is solidly grounded, supply voltage going into the pins is ~3.25v and I can't see any significant current being injected into pins anywhere.
Setup code generated by CubeIDE, USE_TIMEOUT is not defined:
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN0 --> ADC_12VA
PA1 ------> ADC1_IN1 --> ADC_12VB
PA2 ------> ADC1_IN2 --> ADC_V_IN
PA3 ------> ADC1_IN3 --> ADC_5V
*/
/* USER CODE END ADC1_Init 0 */
LL_ADC_InitTypeDef ADC_InitStruct = {0};
LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
*/
GPIO_InitStruct.Pin = ADC_12VA_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(ADC_12VA_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_12VB_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(ADC_12VB_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_V_IN_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(ADC_V_IN_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_5V_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(ADC_5V_GPIO_Port, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_5, LL_DMAMUX_REQ_ADC1);
LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PRIORITY_LOW);
LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PDATAALIGN_HALFWORD);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MDATAALIGN_HALFWORD);
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
#define ADC_CHANNEL_CONF_RDY_TIMEOUT_MS ( 1U)
#if (USE_TIMEOUT == 1)
uint32_t Timeout ; /* Variable used for Timeout management */
#endif /* USE_TIMEOUT */
ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2;
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);
LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_6RANKS;
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_LIMITED;
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;
LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH);
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_AWD_CH_VREFINT_REG|LL_ADC_AWD_CH_TEMPSENSOR_REG);
LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_79CYCLES_5);
LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_2, LL_ADC_SAMPLINGTIME_1CYCLE_5);
LL_ADC_DisableIT_EOC(ADC1);
LL_ADC_DisableIT_EOS(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);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_TEMPSENSOR);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SAMPLINGTIME_COMMON_1);
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_3, LL_ADC_CHANNEL_0);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, LL_ADC_SAMPLINGTIME_COMMON_1);
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_1);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SAMPLINGTIME_COMMON_1);
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_5, LL_ADC_CHANNEL_2);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_2, LL_ADC_SAMPLINGTIME_COMMON_1);
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_6, LL_ADC_CHANNEL_3);
/* Poll for ADC channel configuration ready */
#if (USE_TIMEOUT == 1)
Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
#endif /* USE_TIMEOUT */
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
{
#if (USE_TIMEOUT == 1)
/* Check Systick counter flag to decrement the time-out value */
if (LL_SYSTICK_IsActiveCounterFlag())
{
if(Timeout-- == 0)
{
Error_Handler();
}
}
#endif /* USE_TIMEOUT */
}
/* Clear flag ADC channel configuration ready */
LL_ADC_ClearFlag_CCRDY(ADC1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_3, LL_ADC_SAMPLINGTIME_COMMON_1);
/* USER CODE BEGIN ADC1_Init 2 */
adc_dma_setup();
/* USER CODE END ADC1_Init 2 */
}
And here's the code calculating VDD from VREFINT using the macro from stm32g0xx_ll_adc.h:
vdda = (uint16_t)__LL_ADC_CALC_VREFANALOG_VOLTAGE(gADCRawData, LL_ADC_RESOLUTION_12B);
On a "good" device the values look like:
VREFINT CAL value in flash is 1660
ADC readings are hovering around ~1585 at ~3.3VDD giving a calculated VDD of ~3.145v
On the "bad" device, the values are:
VREFINT CAL value in flash is 1662
ADC readings are hovering around ~1660 at ~3.3VDD giving a calculated VDD of ~3.000v
Since we originally set a low voltage detect threshold of <3.000v this is occasionally dipping below that, an error of 300mv seems very large even if the ADC is not calibrated.
Is this simply a case where we should run the ADC Calibration routine (LL_ADC_StartCalibration() etc...) and it will correct it or is something else going on here? The error seems a bit too large for it to be down to lack of calibration.
