cancel
Showing results for 
Search instead for 
Did you mean: 

Why do I get inaccurate values on my analog to digital converter on my STM32MP1?

msche.1
Associate III

I've just watched a few tutorials on ADC, and I've enabled IN1 on ADC1 which seems to map to the connector labeled A3 as per this doc.

I then added the following to my main.c within the CM4 project.

uint32_t raw = 0;
char msg[100];
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
raw = HAL_ADC_GetValue(&hadc1);
HAL_Delay(100);
sprintf(msg, "%d\r\n", raw);
VIRT_UART_Transmit(&huart0, msg, strlen(msg));

Then I plugged pin A3 directly into GND so I could validate it's working by getting a reading of 0, but I get either 32768, 65518, or 0. I've also tried to supply it 1v and it seems to give 65518 constantly.

I've also tried to switch to hdac2 by creating the same configurations in the IOC and I had the same results. It seems like it may be reading multiple different values from the different ports and displaying them all as they come in, but I don't even have anything defined in the other INs.

Furthermore, if I did define multiple INs for one ADC (which I didn't but seems allowed), how would i know which one I'm reading from?

It's also worth mentioning ADC1 has the following error:

Partially disabled conflict with:
 
SAI2 (SAI B): Mode SPDIF TX Transmiter

I'm not sure how to fix this, but as I said, I tried using adc2 as well with the same results and multiple different INs.

1 ACCEPTED SOLUTION

Accepted Solutions
msche.1
Associate III

Well I'm not sure why this fixed it, but I changed my resolution from 16 to 12 bit.

  hadc2.Init.Resolution = ADC_RESOLUTION_12B;

View solution in original post

7 REPLIES 7
Kevin HUBER
ST Employee

Hi @msche.1​ ,

Thanks to have shared your .ioc.

To be able to get a usable value by reading the ADC, you must have a reference which is VREFBUF.

I advice you to read the reference manual (RM0436 revision 5) page 1693: https://www.st.com/resource/en/reference_manual/DM00327659-.pdf

and the ADC page on the wiki: https://wiki.st.com/stm32mpu/wiki/ADC_internal_peripheral#Features

Then you have two choices to enable VREFBUF.

  • Engineering mode (without Linux)

If you are not using linux you have to enable the VREFBUF after the ADC_Init() but before starting it:

  __HAL_RCC_VREF_CLK_ENABLE(); // Enable the VREF clock
  HAL_SYSCFG_VREFBUF_HighImpedanceConfig(SYSCFG_VREFBUF_HIGH_IMPEDANCE_DISABLE); // Disable the high impedance mode which is the default one read page 1694 of refman
  HAL_SYSCFG_VREFBUF_VoltageScalingConfig(SYSCFG_VREFBUF_VOLTAGE_SCALE1); // To set the volage to 2.5v
  HAL_SYSCFG_EnableVREFBUF(); // To enable VREFBUF
 

Then you must calibrate your adc before using it:

  if(HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED) != HAL_OK)
    {
      /* Calibration Error */
      Error_Handler();
    }

And finally you can use the code that you wrote in your first post to read the value.

  • In Production mode (with linux running):

You have to enable VREFBUF in the DTS for making it startable by Linux, you can enable it with CubeMx.

0693W00000JPKNzQAP.png 

Then you have to configure the VREFBUF, I advise you to read this page:

https://wiki.st.com/stm32mpu/wiki/Regulator_overview#Microcontroller_device_internal_regulator

----------------------------------------

In a first time, I advise you to start by testing your code in engineering mode.

Note that this is setting VREF+=2.5V, so conversion range between 0 and 2.5V.

Hope it helps,

Regards,

Kevin

In order to give better visibility on the answered topics, please click on 'Select as Best' on the reply which solved your issue or answered your question. See also 'Best Answers'

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.

@Kevin HUBER​ thanks for your help, but I'm unfortunately still having issues after the suggested changes. Can you please check to make sure I'm implementing your change correctly?

ADC_Init seems to be called in MX_ADC2_Init that was generated by the IOC.

So I've modified that function in the user editable portion as per your comments to the following:

static void MX_ADC2_Init(void)
{
 
  /* USER CODE BEGIN ADC2_Init 0 */
 
  /* USER CODE END ADC2_Init 0 */
 
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN ADC2_Init 1 */
 
  /* USER CODE END ADC2_Init 1 */
  /** Common config
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_16B;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.NbrOfConversion = 1;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
  hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc2.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
  hadc2.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */
  __HAL_RCC_VREF_CLK_ENABLE(); // Enable the VREF clock
  HAL_SYSCFG_VREFBUF_HighImpedanceConfig(SYSCFG_VREFBUF_HIGH_IMPEDANCE_DISABLE); // Disable the high impedance mode which is the default one read page 1694 of refman
  HAL_SYSCFG_VREFBUF_VoltageScalingConfig(SYSCFG_VREFBUF_VOLTAGE_SCALE1); // To set the volage to 2.5v
  HAL_SYSCFG_EnableVREFBUF(); // To enable VREFBUF
 
  if(HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED) != HAL_OK)
  {
    /* Calibration Error */
    Error_Handler();
  }
  /* USER CODE END ADC2_Init 2 */
 
}

I'm also not clear on what engineering and production mode are, are those debug and release respectively?

I'm actually not physically connected to the stm32mp1 as I'm on an M1 and had problems building locally. So I've got a linux VM and I'm transferring the debug build to the MP1 over scp and then using the `fw_cortex_m4.sh` to get the binary on the M4. I'm not sure if this is causing issues.

msche.1
Associate III

Well I'm not sure why this fixed it, but I changed my resolution from 16 to 12 bit.

  hadc2.Init.Resolution = ADC_RESOLUTION_12B;

@Kevin HUBER​ wouldn't I want to set the voltage reference to 0v?

How do I calculate the max value? If I set to 2.5 ref, I have tested that ~3.8v returns 4096, but if I go down to 2.5v, I see a value of about 120. Even stranger as I increase voltage from 2.5 to 3.8, I don't really see a linear increase.

Btw, I'm using ADC now as I need a faster value reading rate than callbacks provided.

Ok, I guess 2.5 is a max, not a min. I was not getting useable results b/c I was using ANA1 and as per the docs that should be A3, but it is actually A2.

Hello @msche.1​ ,

Regarding your question about "engineering" mode and "production" mode, you have an explanation here: https://wiki.st.com/stm32mpu/wiki/STM32CubeMP1_development_guidelines#Cortex-M4_Startup_Engineering_mode_-28-E2-80-9CMCU_Single_Core_like-E2-80-9D-29

To sum up quickly, engineering mode is for debug purpose with only your M4 Firmware running on your board. You have access to all the peripherals.

In Production mode, the Linux is running in parallel of you M4 firmware on the board. You have to be careful to assign the peripherals needed to your firmware to the M4 CORE and avoid using these peripherals in the A7 CORE. Same for all the resources that can be accessed by the two sides.

You can find more information about peripheral assignment in the wiki: https://wiki.st.com/stm32mpu/wiki/ETZPC_internal_peripheral

Ok, I guess 2.5 is a max, not a min.

Yes, the reference is the maximum that can be measured.

Regards,

Kevin

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.

@Kevin HUBER​ thanks for the clarification. I've started using Engineering mode and some of the code provided earlier to read analog values with DMA, but I'm not getting accurate values after forwarding an elf file from M7 to M4. When I debug in, I do see correct values. Any idea what could be going on?

More details here.