'STM32 Multi-Channel ADC. Unexpected behaviour when unpopulated

I have added ADC functionality to my Nucleo-F446RE development board. 4 channels, DMA enabled, scan and continuous conversion mode enabled, DMA continuous requests enabled, varying sample time per channel. I'll post code at the bottom of this post (all HAL, all done in STM32CubeMX).

I have found some strange behaviour when the channels are unpopulated (e.g., analog channel pin left open). All four channels will hover at around 0.9V with no channels connected. If I add a 3.3V source to channel 0, it'll show 3.3V, but CH1 will show 2.5V, CH2 will show 1.9V, CH3 1.6V. A waterfall effect. That waterfall effect is the same if I move the 3.3V source to CH1 and leave the rest unpopulated, and the waterfall effect loops back around to CH0.

If I give each channel their own source, they'll all show them correctly, but when unpopulated the channels are influenced by the populated channel. Why is this? I have found some sources saying that this is because of the sample+hold capacitor, and the solution is to correct the sampling times, but I have played a lot with the times going from very fast to as slow as possible sampling (I am only interested in sampling the data at 1kHz, but the ADC conversion seems to be, at a minimum, a magnitude above this), but it doesn't make a change. I wondered if changing the analog channel pin configuration to pull-down would help, but again no change.

I am hoping that this isn't something to be too concerned about, as the channels appear correct when populated, but perhaps there is some background influence that I am not seeing even when populated that I want to avoid. I am certain I haven't optimised my circuit, so any advice on that would also be great. There are lots of tutorials and examples online for STM32 ADC DMA with a single channel, but not so many with multi-channel. I also don't find the STM32 provided examples to be too helpful and often seem very inefficient.

ADC definitions

(main clock 180MHz, APB2 prescaler 2 = 90MHz, although I have also dropped it to a prescaler of 16 (11.25MHz) which didn't help)

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 4;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 2;
  sConfig.SamplingTime = ADC_SAMPLETIME_112CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = 3;
  sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = 4;
  sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

DMA definition

__HAL_RCC_ADC1_CLK_ENABLE();

__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0-WKUP     ------> ADC1_IN0
PA1     ------> ADC1_IN1
PA4     ------> ADC1_IN4
PB0     ------> ADC1_IN8
*/
GPIO_InitStruct.Pin = analog1_Pin|analog2_Pin|analog3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = analog4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(analog4_GPIO_Port, &GPIO_InitStruct);

/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_NORMAL;
hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
  Error_Handler();
}

__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);

/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);

Analog read code

(analog_scale is called once per channel every 1kHz)

#include "dma.h"
#include "adc.h"
#include "analog.h"

volatile uint32_t analogBuffer[4]; 

void analog_init()
{
  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&analogBuffer, 4);
}

uint16_t analog_scale(char ch)
{  
  return (uint16_t)(((analogBuffer[ch] * 3.3) / 4096.0) * 1000.0);
}

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{   

}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{   
  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&analogBuffer, 4);
  
  HAL_GPIO_TogglePin(test4_GPIO_Port, test4_Pin);
}


Solution 1:[1]

That's not a Software issue, it's normal hardware behavior.

If ADC pins are floating, they "gather" stray voltages, e.g. from adjacent Sample and Hold Capacitors, from the Voltage Reference or any voltage that is induced in the traces on the PCB or attached cables.

The "Waterfall" effect you see, is simply your input voltage on Channel 0 or 1 coupling through the sample and hold capacitors and resistors from one channel to the next, transferred by the multiplexers parasitic capacitances: a small amount of charge is transferred from one voltage path to the next while switching through the channels, and this charge has no path to flow when the connections are open, except through the ADC, resulting in a pseudo-voltage reading.

To prevent this, tie all unused channels to ground, using appropriate pull down resistors (10 kOhm should be OK …), or if you want a software solution: multiply all unused channels with 0.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1