Skip to main content
Graduate II
May 23, 2024
Question

ADC channel accuracy issue on STM32F030F4P6

  • May 23, 2024
  • 6 replies
  • 3957 views

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. 

    This topic has been closed for replies.

    6 replies

    Graduate
    May 23, 2024

    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.

    DRicc.2Author
    Graduate II
    May 23, 2024

    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. 

    Graduate II
    May 23, 2024

    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

    DRicc.2Author
    Graduate II
    May 23, 2024

    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?

    Graduate II
    May 23, 2024

    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.

    Graduate
    May 24, 2024

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

    Super User
    May 24, 2024

    >  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

    DRicc.2Author
    Graduate II
    May 24, 2024

    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.

    Super User
    May 25, 2024

    > 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

     

    DRicc.2Author
    Graduate II
    May 26, 2024

    I'm multiplying it by 5 because I am using pin A5 on the L476RG to read the value of the gas sensor, and that board is plugged into my computer and powered off of 5V, I have no way off reading directly off of my PCB bc I messed up with the pin configuration for USART and chose a UART IC instead. This was from reading the pinout incorrectly on the datasheet. 

    Right now I have a wire soldered on from my gas sensor to a breadboard, with both grounds of the PCB and L476RG connected, and the analog pin from the L476RG connected after a resistor:

     

    DRicc2_0-1716756054618.png


    I know this isn't exactly the best way to do this, but I don't see any other way since there's no room on the pads of the MCU to solder a wire directly to it. I'm also assuming the cable length doesn't help. 

     

    Super User
    May 27, 2024

    Again, the ADC does not "know" about your 5V supply to the gas sensor.

    The ADC works in that way that it outputs value which represents the ratio between input signal and the ADC reference voltage, which is connected to VREF+ pin of STM32 (in small packages VREF+ is connected internally to VDDA).

    JW

    DRicc.2Author
    Graduate II
    May 29, 2024

    So I read the schematic of the L476RG, and you are correct it is connected to 3V3 as well. Its unsurprising since most MCUs operate at that voltage range anyway.