STM32 多通道模数转换器。未填充时的意外行为

STM32 Multi-Channel ADC. Unexpected behaviour when unpopulated

我已将 ADC 功能添加到我的 Nucleo-F446RE 开发板。 4 个通道,启用 DMA,启用扫描和连续转换模式,启用 DMA 连续请求,每个通道的采样时间不同。我将 post 代码放在这个 post 的底部(所有 HAL,全部在 STM32CubeMX 中完成)。

我发现通道未填充时有一些奇怪的行为(例如,模拟通道引脚保持打开状态)。在没有连接任何通道的情况下,所有四个通道都将徘徊在 0.9V 左右。如果我将 3.3V 源添加到通道 0,它将显示 3.3V,但 CH1 将显示 2.5V,CH2 将显示 1.9V,CH3 1.6V。瀑布效应。如果我将 3.3V 源移至 CH1 并保留其余部分,瀑布效应是相同的,并且瀑布效应循环回到 CH0。

如果我为每个频道提供自己的来源,它们都会正确显示它们,但当未填充时,频道会受到填充频道的影响。为什么是这样?我发现一些消息来源说这是因为采样+保持电容器,解决方案是纠正采样时间,但我已经玩了很多时间从非常快到尽可能慢的采样(我只是有兴趣以 1kHz 的频率对数据进行采样,但 ADC 转换似乎至少比这个高一个数量级),但它并没有做出改变。我想知道将模拟通道引脚配置更改为下拉是否有帮助,但还是没有改变。

我希望这不是什么需要太担心的事情,因为频道在填充时看起来是正确的,但也许有一些背景影响,即使在填充时我也看不到,我想避免。我确定我没有优化我的电路,所以任何关于这方面的建议也很好。单通道的STM32 ADC DMA 网上有很多教程和例子,但多通道的就没那么多了。我也没有发现 STM32 提供的示例太有帮助,而且通常看起来效率很低。

ADC 定义

(主时钟 180MHz,APB2 预分频器 2 = 90MHz,虽然我也将其降为 16(11.25MHz)的预分频器,但没有帮助)

  /** 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 定义

__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_scale 每通道每 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);
}

这不是软件问题,这是正常的硬件行为。

如果 ADC 引脚浮动,它们会“收集”杂散电压,例如来自相邻的采样和保持电容器、来自电压参考或在 PCB 或连接电缆上的迹线中感应的任何电压。

您看到的“瀑布”效应只是通道 0 或 1 上的输入电压通过采样保持电容器和电阻从一个通道耦合到下一个通道,由多路复用器寄生电容传输:少量在通过通道切换时,电荷从一个电压路径转移到下一个电压路径,当连接打开时,此电荷没有路径流动,除了通过 ADC,导致 pseudo-voltage 读数。

为防止这种情况,请将所有未使用的通道接地,使用适当的下拉电阻(10 kOhm 应该没问题……),或者如果您需要软件解决方案:将所有未使用的通道乘以 0。