cancel
Showing results for 
Search instead for 
Did you mean: 

ADC channel accuracy issue on STM32F030F4P6

DRicc.2
Associate III

I recently have been working on a custom board with the stm32f030f4p6 for a gas sensor project, I just got it to bootload successfully and blink some LEDs so now I'm using the ADC channel to read values from it and converting it to a voltage level for testing sake (the final code will convert the values in ppm). 

I've noticed almost a 30% error in the com port that I'm reading from an L476RG I have hooked up to via some external wires and I'm not exactly sure how to make it more accurate. For example, the current ADC voltage value I'm reading is 2.36 on PuTTY but on my multimeter, I only see 1.86 volts.

 

Here is a picture of the schematic: 

O9q9k3v1.png

And the layout: 

DRicc2_0-1716426639731.png

The ADC is set to a 12-bit resolution and the sample size is at 1.5 cycles, I tried increasing the sample size but this didn't seem to do much.

Unfortunately, when I made this board I didn't realize I read the datasheet wrong and chose a UART IC when this MCU can only use USART, so I have no way of reading from the terminal without using the L476RGs ADC port and I feel using two MCUs for one setup also isn't helping. 

Here is the code: 

 

 

 

#include "main.h"
#include <stdlib.h> //Standard library header file since we use a logarithmic function
#include <stdio.h>  //Standard header filer
#include <string.h> //String header filer
#define ledadc GPIO_PIN_7


uint16_t ADCval;             //Unsigned 16 bit variable, 16 bit registers to hold 12 bit ADC data
uint16_t BATval;             //Unsigned 16 bit variable, to store the battery value
float Voltage;               //Float variable, that will take the 12 bit ADC "ADCVal" data and produce a voltage output
float BATVoltage;            //Float variable, that takes the BATval and stores it as voltage value


ADC_HandleTypeDef hadc;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC_Init(void);

int main(void)
{


  HAL_Init();


  SystemClock_Config();


  MX_GPIO_Init();
  MX_ADC_Init();

  while (1)
  {
	  HAL_ADC_Start(&hadc);                      //Starts the ADC on the STM32
	  HAL_ADC_PollForConversion(&hadc, 100);     //Pulls the data from the ADC of the MCU to gather the voltage values from the gas sensor
	  ADCval = HAL_ADC_GetValue(&hadc);          //Pulls ADC value from channel zero, stores it in the voltage variable we have created
	  Voltage = (ADCval *5.0) /(4095);          //Obtains voltage value by taking ADC value from HAL, dividing it by the 16 bit resolution,
	 		                                          //And multiplying it by the reference voltage of the STM32 (3.3V)
	  if(Voltage<2){
		  HAL_GPIO_WritePin(GPIOA,ledadc,GPIO_PIN_SET);
		  HAL_Delay(500);
		  HAL_GPIO_WritePin(GPIOA,ledadc,GPIO_PIN_RESET);
		  HAL_Delay(500);
	  }

  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI14;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSI14State = RCC_HSI14_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.HSI14CalibrationValue = 16;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief ADC Initialization Function
  *  None
  * @retval None
  */
static void MX_ADC_Init(void)
{

  /* USER CODE BEGIN ADC_Init 0 */

  /* USER CODE END ADC_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC_Init 1 */

  /* USER CODE END ADC_Init 1 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SEQ_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_OVERWRITTEN;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
  */
  sConfig.Channel = ADC_CHANNEL_5;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC_Init 2 */

  /* USER CODE END ADC_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  *  None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  *   file: pointer to the source file name
  *   line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

 

 

 

 

Any help is greatly appreciated. 

12 REPLIES 12
raptorhal2
Lead

Check the sensor specs for output impedance. 1.5 cycles is very short if the sensor output is not an opamp.

Separate input from output. Insert a break point on line 38 and determine if ADCValue is what you expect. Then you know where to look further.

Putty should need only MCU Tx & Rx, UART or USART should work the same.

I checked the datasheet (Link here: https://www.mouser.com/datasheet/2/321/605-00008-MQ-2-Datasheet-370464.pdf), but couldn't find anything on the output impedance. Also, I have the ADCValue displaying alongside the voltage. 

Should I leave the gas sensor powered on for 24-72 hours and then try it again? I read articles where it was said that the gas sensor needs to properly warm up before proper measurements can be taken. 

Muhammed Güler
Senior III

The signal structure in the post below was obtained with a 10k resistor RC filter at the opamp output. Your sampling time is too short for a device with unknown output impedance. By increasing the sampling time, you can ensure that the error falls within the acceptable range.


https://community.st.com/t5/stm32-mcus-products/adc-sampling-time-measurement/td-p/116547

I increased the sampling size to 239.5 cycles, which is the highest on the stm32f030f4p6, also that post you linked it doesn't seem anyone answered your question, so can it be used as a reference?

I put the post so you can have an idea of ​​what happens to the signal during ADC conversion. If you measure the voltage of the ADC input pin, especially when taking multiple samples, you will see similar transient voltages.
You can find the minimum sampling time you should use by measuring the ADC input voltage and determining the time during which the signal becomes stable.
If the maximum sampling time setting is not enough for you, you can also reduce the ADC frequency.

raptorhal2
Lead

I haven't used an F03, but typically you have to GPIO init the adc channel pins to analog mode.

>  Voltage = (ADCval *5.0) /(4095); //Obtains voltage value by taking ADC value from HAL, dividing it by the 16 bit resolution,
> //And multiplying it by the reference voltage of the STM32 (3.3V)

So, why do you multiply by 5.0?

Also, perform calibration before starting conversions.

JW

Based my formula off this website: https://learn.sparkfun.com/tutorials/analog-to-digital-conversion/relating-adc-value-to-voltage. The 5V is the system voltage, as I'm powering the gas sensor from my DC to DC boost converter which outputs 5 volts.

> The 5V is the system voltage, as I'm powering the gas sensor from my DC to DC boost converter which outputs 5 volts.

No. The ADC does not care how do you power your gas sensor. ADC works out of its reference voltage, which comes from the VREF+ pin, which in your STM32 with small package is internally connected to VDDA,. which is at 3.3V according to your schematics.

JW