2025-11-19 6:49 AM
We're using the following circuit to measure a battery voltage @ 20 kHz for use in motor control.
We are having some issues though, where the ADC suddenly starts measuring 0 V (0 in the data register) and gets locked in this state. In this state, we also measured 0 V at the MCU pin on the board, and a resistance < 20 Ohm between the pin and GND, both using a multimeter. The issue is not fixed by a reset (pulse on the NRST pin), only removing the battery and power cycling the MCU.
How and why can/does this happen?
We initially thought it was overvoltage and tried connecting a PSU directly to an ADC pin on the STM32G474RE NUCLEO board. We only started seeing abnormalities at 6 V and destroyed the ADC at around 9 V, so this does not seem to be the reason.
The ADC is clocked 170/4 = 42.5 MHz and configured as follows (from IOC file):
ADC1.CommonPathInternal=null|null|null|null
ADC1.DMAContinuousRequests=DISABLE
ADC1.EOCSelection=ADC_EOC_SEQ_CONV
ADC1.EnableAnalogWatchDog1=false
ADC1.EnableInjectedConversion=ENABLE
ADC1.EnableRegularConversion=DISABLE
ADC1.ExternalTrigInjecConv=ADC_EXTERNALTRIGINJEC_T1_TRGO
ADC1.IPParameters=EOCSelection,EnableRegularConversion,EnableInjectedConversion,EnableAnalogWatchDog1,DMAContinuousRequests,master,InjectedRank-10\#ChannelInjectedConversion,InjectedChannel-10\#ChannelInjectedConversion,Rank1_Channel,InjectedSamplingTime-10\#ChannelInjectedConversion,InjectedOffsetNumber-10\#ChannelInjectedConversion,Rank2_Channel,InjNumberOfConversion,ExternalTrigInjecConv,Overrun,InjectedRank-13\#ChannelInjectedConversion,InjectedChannel-13\#ChannelInjectedConversion,InjectedSamplingTime-13\#ChannelInjectedConversion,InjectedOffsetNumber-13\#ChannelInjectedConversion,CommonPathInternal
ADC1.InjNumberOfConversion=2
ADC1.InjectedChannel-10\#ChannelInjectedConversion=ADC_CHANNEL_3
ADC1.InjectedChannel-13\#ChannelInjectedConversion=ADC_CHANNEL_2
ADC1.InjectedOffsetNumber-10\#ChannelInjectedConversion=ADC_OFFSET_NONE
ADC1.InjectedOffsetNumber-13\#ChannelInjectedConversion=ADC_OFFSET_NONE
ADC1.InjectedRank-10\#ChannelInjectedConversion=1
ADC1.InjectedRank-13\#ChannelInjectedConversion=2
ADC1.InjectedSamplingTime-10\#ChannelInjectedConversion=ADC_SAMPLETIME_2CYCLES_5
ADC1.InjectedSamplingTime-13\#ChannelInjectedConversion=ADC_SAMPLETIME_2CYCLES_5
ADC1.Overrun=ADC_OVR_DATA_OVERWRITTEN
ADC1.Rank1_Channel=ADC_CHANNEL_3
ADC1.Rank2_Channel=ADC_CHANNEL_2
ADC1.master=1
PA2.Mode=IN3-Single-Ended
PA2.Signal=ADC1_IN3And the following code (generated mostly by CubeMX) is used for initialisation
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
ADC_InjectionConfTypeDef sConfigInjected = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
/** Configure Injected Channel
*/
sConfigInjected.InjectedChannel = ADC_CHANNEL_3;
sConfigInjected.InjectedRank = ADC_INJECTED_RANK_1;
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfigInjected.InjectedSingleDiff = ADC_SINGLE_ENDED;
sConfigInjected.InjectedOffsetNumber = ADC_OFFSET_NONE;
sConfigInjected.InjectedOffset = 0;
sConfigInjected.InjectedNbrOfConversion = 2;
sConfigInjected.InjectedDiscontinuousConvMode = DISABLE;
sConfigInjected.AutoInjectedConv = DISABLE;
sConfigInjected.QueueInjectedContext = DISABLE;
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJEC_T1_TRGO;
sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONV_EDGE_RISING;
sConfigInjected.InjecOversamplingMode = DISABLE;
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK)
{
Error_Handler();
}
/** Configure Injected Channel
*/
sConfigInjected.InjectedChannel = ADC_CHANNEL_2;
sConfigInjected.InjectedRank = ADC_INJECTED_RANK_2;
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
HAL_Delay(1);
ADC1->IER |= ADC_IER_JEOSIE; // Enable end of injected sequence of conversion interrupt
HAL_ADC_Start(&hadc1);
ADC1->CR |= ADC_CR_JADSTART; // Enable injected conversions on the selected trigger
/* USER CODE END ADC1_Init 2 */
}
2025-11-19 6:56 AM
This sounds like latch-up behavior.
Prevention would depend on where this is occurring. If this is a motor application, strong diodes preventing negative voltages and overvoltages where they shouldn't be can help. Nothing wrong with the circuit shown.
2025-11-19 1:10 PM
>How and why can/does this happen?
As @TDK said, it looks like a latch-up. This "happens" if you force a high current in any pin of a chip, + or - , maybe a spike on another pin; not on this pin with 1.5M to 36V ; the switching of a relais can be enough...i had this (on Fujitsu cpu ), or from any inductive (motor?) load. So check all connections - also GND - to anything that could generate inductive behaviour.
2025-11-19 2:12 PM
Trying to minimize battery discharge and setting high value resistive divider for this purpose, than put capacitor 0.1uF to lower impedance seen by adc back to low value required by SAR adc architecture, helps to save on op-amp buffer. But this trick works only for very low sampling rate, lets say 1 sec per sample or so.
Running 20 kHz sampling ruin all idea and may present a threat for adc, since switching S/H circuitry may inject voltage into 0.1uF cap ANY polarity, negative is not excluding, and than adc behavior is unpredictable.
Short summary: to have 1.5 Mohm resistors and high sampling rate you must use a op-amp buffer.
2025-11-20 12:01 AM - edited 2025-11-20 12:02 AM
One more vote for: this voltage divider is too high impedance for the STM32 ADC input.
And maybe put some TVS / zener diode in parallel to the capacitor to protect the ADC input.
I miss the high impedance input AVR ADCs... ;)
2025-11-20 12:12 AM
Beat me to it.
Either a buffer amplifier, or a lower impedance divider network.
A SAR-ADC works by charging up a capacitor, which means the imput must deliver a sufficient current in the configured time.
And I would strongly recommend to add at least a zener diode to every input connected to voltages above 3.6V, notwithstanding any divider networks.
On a related note, the "latch-up" issue is known since the first CMOS devices. And most often kills the affected IC.