简体   繁体   English

使用 STM32F0 ADC 单独读取不同的输入

[英]Individually read distinct inputs with STM32F0 ADC

STM32F072CBU microcontroller. STM32F072CBU 微控制器。

I have multiple inputs to the ADC and would like to read them individually and separately.我有多个 ADC 输入,并希望单独和单独读取它们。 STMcubeMX produces boilerplate code which assumes I wish to read all of the inputs sequentially, and I have not been able to figure out how to correct this. STMcubeMX 生成样板代码,假设我希望按顺序读取所有输入,但我无法弄清楚如何更正此问题。

This blog post expresses the same problem I am having, but the solution given doesn't seem to work. 这篇博文表达了我遇到的同样问题,但给出的解决方案似乎不起作用。 Turning the ADC on and off for each conversion correlates with error in the returned value.为每次转换打开和关闭 ADC 与返回值中的错误相关。 Only when I configure a single ADC input in STMcubeMX and then poll without de-initializing the ADC are accurate readings returned.只有当我在 STMcubeMX 中配置单个 ADC 输入,然后在不取消初始化 ADC 的情况下进行轮询时,才会返回准确的读数。

cubeMX's adc_init function: cubeMX 的 adc_init 函数:

/* ADC init function */
static void MX_ADC_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;

    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_41CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_2;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_3;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_4;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_VREFINT;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

main.c主文件

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC_Init();
  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */
  //HAL_TIM_Base_Start_IT(&htim3);
  init_printf(NULL, putc_wrangler);
  HAL_ADCEx_Calibration_Start(&hadc);
  HAL_ADC_DeInit(&hadc); // ADC is initialized for every channel change
  schedule_initial_events();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  event_loop();
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  /* USER CODE END 3 */

}

My process right now for turning the ADC off and reinitializing to change channels:我现在关闭 ADC 并重新初始化以更改频道的过程:

// Set up
  ADC_ChannelConfTypeDef channelConfig;

  channelConfig.SamplingTime = samplingT;
  channelConfig.Channel = sensorChannel;
  channelConfig.Rank = ADC_RANK_CHANNEL_NUMBER;

  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  if (HAL_ADC_ConfigChannel(&hadc, &channelConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

// Convert
  uint16_t retval;

  if (HAL_ADC_Start(&hadc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  if (HAL_ADC_PollForConversion(&hadc, 1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  if (HAL_ADC_GetError(&hadc) != HAL_ADC_ERROR_NONE)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  retval = (uint16_t) HAL_ADC_GetValue(&hadc);

  if (HAL_ADC_Stop(&hadc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

// Close
  HAL_ADC_DeInit(&hadc);

At this point I'm not really sure that there's a way to accomplish what I want, STM32 seems dead set on active ADC lines being in the regular group and being converted in order.在这一点上,我不确定是否有办法完成我想要的,STM32 似乎在有源 ADC 线路上处于常规组并按顺序转换。

If you want to read several ADC channels in single conversion mode then you have to change the channel setting before each reading, but you do not have to reinit the ADC.如果要在单次转换模式下读取多个 ADC 通道,则必须在每次读取之前更改通道设置,但不必重新初始化 ADC。 Simply do as below, select the new channel (you can change sampling time too if it must be different for the channels but generally it can be the same), select the channel rank and then call the HAL_ADC_ConfigChannel function.简单的做如下,选择新的通道(你也可以改变采样时间,如果通道必须不同,但一般可以相同),选择通道等级,然后调用HAL_ADC_ConfigChannel函数。 After this you can perform a conversion.在此之后,您可以执行转换。

void config_ext_channel_ADC(uint32_t channel, boolean_t val)
{
  ADC_ChannelConfTypeDef sConfig;

  sConfig.Channel = channel;
  sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;

  if(True == val)
  {
    sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  }
  else
  {
    sConfig.Rank = ADC_RANK_NONE;
  }

  HAL_ADC_ConfigChannel(&hadc, &sConfig);
}

uint32_t r_single_ext_channel_ADC(uint32_t channel)
{
  uint32_t digital_result;

  config_ext_channel_ADC(channel, True);

  HAL_ADCEx_Calibration_Start(&hadc);

  HAL_ADC_Start(&hadc);
  HAL_ADC_PollForConversion(&hadc, 1000);
  digital_result = HAL_ADC_GetValue(&hadc);
  HAL_ADC_Stop(&hadc);

  config_ext_channel_ADC(channel, False);

  return digital_result;
}

An example for usage:使用示例:

#define SUPPLY_CURRENT  ADC_CHANNEL_5
#define BATTERY_VOLTAGE ADC_CHANNEL_6

uint16_t r_battery_voltage(uint16_t mcu_vcc)
{
  float vbat;
  uint16_t digital_val;

  digital_val = r_single_ext_channel_ADC(BATTERY_VOLTAGE);
  vbat = (mcu_vcc/4095.0) * digital_val;
  vbat = vbat * 2;         // 1/2 voltage divider

  return vbat;
}

uint16_t r_supply_current(uint16_t mcu_vcc)
{
  float v_sense, current;
  uint16_t digital_val;

  digital_val = r_single_ext_channel_ADC(SUPPLY_CURRENT);
  v_sense = (mcu_vcc/4095.0) * digital_val;
  current = v_sense * I_SENSE_GAIN;

  return current;
}

This code was used on an STM32F030.此代码用于 STM32F030。 For reading the internal temperature sensor and reference voltage a slightly different version of the above seen functions needed as additional enable bits must be set.为了读取内部温度传感器和参考电压,需要设置与上述功能略有不同的版本,因为必须设置额外的启用位。

void config_int_channel_ADC(uint32_t channel, boolean_t val)
{
  ADC_ChannelConfTypeDef sConfig;
  sConfig.Channel = channel;

  if(val == True)
  {
    if(channel == ADC_CHANNEL_VREFINT)
    {
      ADC->CCR |= ADC_CCR_VREFEN;
      hadc.Instance->CHSELR = (uint32_t)(ADC_CHSELR_CHSEL17);
    }
    else if(channel == ADC_CHANNEL_TEMPSENSOR)
    {
      ADC->CCR |= ADC_CCR_TSEN;
      hadc.Instance->CHSELR = (uint32_t)(ADC_CHSELR_CHSEL16);
    }

    sConfig.Rank          = ADC_RANK_CHANNEL_NUMBER;
    sConfig.SamplingTime  = ADC_SAMPLETIME_239CYCLES_5;
  }
  else if(val == False)
  {
    if(channel == ADC_CHANNEL_VREFINT)
    {
      ADC->CCR &= ~ADC_CCR_VREFEN;
      hadc.Instance->CHSELR = 0;
    }
    else if(channel == ADC_CHANNEL_TEMPSENSOR)
    {
      ADC->CCR &= ~ADC_CCR_TSEN;
      hadc.Instance->CHSELR = 0;
    }

    sConfig.Rank          = ADC_RANK_NONE;
    sConfig.SamplingTime  = ADC_SAMPLETIME_239CYCLES_5;
  }

  HAL_ADC_ConfigChannel(&hadc,&sConfig);
}

uint32_t r_single_int_channel_ADC(uint32_t channel)
{
  uint32_t digital_result;

  config_int_channel_ADC(channel, True);

  HAL_ADCEx_Calibration_Start(&hadc);

  HAL_ADC_Start(&hadc);
  HAL_ADC_PollForConversion(&hadc, 1000);
  digital_result = HAL_ADC_GetValue(&hadc);
  HAL_ADC_Stop(&hadc);

  config_int_channel_ADC(channel, False);

  return digital_result;
}

Example usage internal voltage reference for MCU VDD calculation:用于 MCU VDD 计算的示例使用内部电压参考:

#define VREFINT_CAL_ADDR   ((uint16_t*) ((uint32_t) 0x1FFFF7BA))

static float FACTORY_CALIB_VDD = 3.31;

uint16_t calculate_MCU_vcc()
{
  float analog_Vdd;
  uint16_t val_Vref_int = r_single_int_channel_ADC(ADC_CHANNEL_VREFINT);

  analog_Vdd = (FACTORY_CALIB_VDD * (*VREFINT_CAL_ADDR))/val_Vref_int;

  return analog_Vdd * 1000;
}

Internal temperature sensor reading:内部温度传感器读数:

#define TEMP30_CAL_ADDR  ((uint16_t*) ((uint32_t) 0x1FFFF7B8))
#define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2))

static float FACTORY_CALIB_VDD = 3.31;

float r_MCU_temp(uint16_t mcu_vcc)
{
  float temp;
  float slope = ((110.0 - 30.0)/((*TEMP110_CAL_ADDR) - (*TEMP30_CAL_ADDR)));

  uint16_t ts_data = r_single_int_channel_ADC(ADC_CHANNEL_TEMPSENSOR);

  temp = ((mcu_vcc/FACTORY_CALIB_VDD) * ts_data)/1000;
  temp = slope * (temp - (*TEMP30_CAL_ADDR)) + 30;

  return round_to(temp, 0);
}

Note that calibration data addresses might be different for your MCU, check the datasheet for more information.请注意,您的 MCU 的校准数据地址可能不同,请查看数据表了解更多信息。

I had the similar problem.我有类似的问题。 I was using STM32F091RC.我使用的是 STM32F091RC。 On ADC_V_PIN I had external multiplexer.在 ADC_V_PIN 我有外部多路复用器。 On ADC_T_PIN I had NTC reading.在 ADC_T_PIN 上,我读取了 NTC。 I was controlling external multiplexer with GPIOs (this is not seen in the code below).我用 GPIO 控制外部多路复用器(这在下面的代码中没有看到)。 What I needed was multiple successive readings of ADC_V_PIN, after that single ADC_T_PIN reading.我需要的是在单个 ADC_T_PIN 读数之后多次连续读取 ADC_V_PIN。 With default sequential reading of ADC I would get between each external multiplexed reading of ADC_V_PIN also ADC_T_PIN reading.使用 ADC 的默认顺序读取,我会在 ADC_V_PIN 的每个外部多路复用读取和 ADC_T_PIN 读取之间获得。 Using HAL_ADC_ConfigChannel multiple times seemed to OR with each other, and I had problems with reading.多次使用 HAL_ADC_ConfigChannel 似乎彼此或,我在阅读时遇到了问题。 So I didn't use HAL_ADC_ConfigChannel at all.所以我根本没有使用 HAL_ADC_ConfigChannel。 Instead, I reconfigured CHSELR register before each software triggered AD conversion.相反,我在每个软件触发 AD 转换之前重新配置了 CHSELR 寄存器。 This was the way to make internal and external multiplexer work together.这是使内部和外部多路复用器协同工作的方式。

Here is initialization code:下面是初始化代码:

    GPIO_InitStruct.Pin = ADC_V_PIN;
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    HAL_GPIO_Init(ADC_V_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = ADC_T_PIN;
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    HAL_GPIO_Init(ADC_T_PORT, &GPIO_InitStruct);

    g_AdcHandle.Instance = ADC1;

    if (HAL_ADC_DeInit(&g_AdcHandle) != HAL_OK)
    {
        /* ADC initialization error */
        Error_Handler();
    }

    g_AdcHandle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
    g_AdcHandle.Init.Resolution = ADC_RESOLUTION_12B;
    g_AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    g_AdcHandle.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;;
    g_AdcHandle.Init.ContinuousConvMode = DISABLE;
    g_AdcHandle.Init.DiscontinuousConvMode = ENABLE;
    g_AdcHandle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    g_AdcHandle.Init.LowPowerAutoWait      = DISABLE;
    g_AdcHandle.Init.LowPowerAutoPowerOff  = DISABLE;

    g_AdcHandle.Init.ExternalTrigConv      = ADC_SOFTWARE_START;
    g_AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;


    g_AdcHandle.Init.DMAContinuousRequests = DISABLE;
    g_AdcHandle.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;
    g_AdcHandle.Init.SamplingTimeCommon    = ADC_SAMPLETIME_239CYCLES_5;

    if (HAL_ADC_Init(&g_AdcHandle) != HAL_OK)
    {
      /* ADC initialization error */
      Error_Handler();
    }

    if (HAL_ADCEx_Calibration_Start(&g_AdcHandle) != HAL_OK)
    {
      /* Calibration Error */
      Error_Handler();
    }

while(1){

        ADC1->CHSELR = ADC_CHSELR_CHSEL0;
        HAL_ADC_Start(&g_AdcHandle);
        HAL_ADC_PollForConversion(&g_AdcHandle, 10);
        V = HAL_ADC_GetValue(&g_AdcHandle);
        HAL_ADC_Stop(&g_AdcHandle);

        ADC1->CHSELR = ADC_CHSELR_CHSEL10;
        HAL_ADC_Start(&g_AdcHandle);
        HAL_ADC_PollForConversion(&g_AdcHandle, 10);
        T = HAL_ADC_GetValue(&g_AdcHandle);
        HAL_ADC_Stop(&g_AdcHandle);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM