ADC Read Maximizing Precision and Accuracy
Hello community,
I'm looking for some feedback on how to optimize the ADC peripheral when using a handful of single-ended channels.
I have an STM32H735G-DK and have 4 NTC3950 type thermistors connected to 4 of the Analog pins from the Arduino headers, attached in the R2 position of a voltage divider.
| Name | Port/Pin | ADC Number | ADC Channel | R1 Resistance |
| temp0 | PC0 | ADC3 | Channel 10 | 487100 |
| temp1 | PH2 | ADC3 | Channel 13 | 463500 |
| temp2 | PC2_C | ADC3 | Channel 0 | 468800 |
| temp3 | PC3_C | ADC3 | Channel 1 | 468200 |
The goal is to gather the 4 analog values and convert them to temperatures - the readings need to be accurate to the correct temperature and consistently the correct temperature (I would very much prefer to not have much of a software filter to smooth the readings).
Using CubeMX, the peripheral is being initialized as follows:
static void MX_ADC3_Init(void)
{
/* USER CODE BEGIN ADC3_Init 0 */
/* USER CODE END ADC3_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC3_Init 1 */
/* USER CODE END ADC3_Init 1 */
/** Common config
*/
hadc3.Instance = ADC3;
hadc3.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
hadc3.Init.Resolution = ADC_RESOLUTION_12B;
hadc3.Init.DataAlign = ADC3_DATAALIGN_RIGHT;
hadc3.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc3.Init.LowPowerAutoWait = DISABLE;
hadc3.Init.ContinuousConvMode = DISABLE;
hadc3.Init.NbrOfConversion = 1;
hadc3.Init.DiscontinuousConvMode = DISABLE;
hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc3.Init.DMAContinuousRequests = DISABLE;
hadc3.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
hadc3.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
hadc3.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc3.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc3.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc3) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC3_SAMPLETIME_24CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSign = ADC3_OFFSET_SIGN_NEGATIVE;
if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC3_Init 2 */
HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
/* USER CODE END ADC3_Init 2 */
}The task being used to sample and convert the raw value to voltage looks similar to this:
static uint32_t temp_getAdcRead(uint32_t adcChannel) {
ADC_ChannelConfTypeDef sConfig = {
.Channel = adcChannel,
.Rank = ADC_REGULAR_RANK_1,
.SamplingTime = ADC3_SAMPLETIME_24CYCLES_5,
.SingleDiff = ADC_SINGLE_ENDED,
.OffsetNumber = ADC_OFFSET_NONE,
.Offset = 0,
.OffsetSign = ADC3_OFFSET_SIGN_NEGATIVE,
};
if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
{
Error_Handler();
}
HAL_ADC_Start(&hadc3);
HAL_ADC_PollForConversion(&hadc3, 500);
return HAL_ADC_GetValue(&hadc3);
}
static uint32_t temp_getResistanceFromRaw(uint32_t raw, uint32_t r1) {
return (r1 * raw) / (0xFFF - raw);
}
static float temp_getTemp(uint32_t adcChannel, uint32_t r1) {
// Get raw ADC reading
uint32_t raw = temp_getAdcRead(adcChannel);
uint32_t resistance = temp_getResistanceFromRaw(raw, r1);
return temp_getTempFromResistance(resistance));
}The "temp get" task is will use these calls once every second to sample and determine temp approximately every 1 second. The function for converting the calculated resistance to temperature is not shown, it essentially searches through the generic NTC3950 table and finds the sampled temperature for a given resistance. Very simple.
I'm looking for some hints and guidance on how to make the HAL_ADC_Start() to return a more consistent and accurate value. A few things I've tried:
- ADC clock speed
- channel sample time
- Changing number of conversion to 4 to sample all thermistors in one block
- Oversampling (which doesn't seem to return the averaged value like I would imagine it should)
- Using V_Ref_Int to help introduce an offset for the readings
Short of changing the mode to a continuous read and have the peripheral feed a DMA and average a massive pool of reads, I don't know how else to make this more accurate. I would appreciate any suggestions and feedback with the design.
Thanks,
- Taylor
