I am experiencing the following problem. When I setup an ADC with DMA for 5 channels, I get lower readings than the expected ones.
We have PCBs of the same batch in 3 countries, but only the ones tested in China are failing. At all times the ADC readings read lower than the expected values for all the ADC channels, but the PCBs at the other countries return the expected reading in all channels. All those PCBs are identical and they are driving small motors, have a keypad, have some LEDs UI, and a UART debugging port were we connect a USB to TTL UART cable.
Here is the ADC configuration with DMA (source code attached too):
CubeMX CubeMX schematic schematic
schematic
We have measured the reference voltage and all the expected voltages on ADC input pins (some are fixed and known) and all looking good too. On the other hand, when I sample only one ADC channel in polling mode, I do not get this problem, the voltages read as expected.
For example: the readings of a healthy ADC reading at 10-bit resolution should read something like 914 raw value which will correspond to 3652mV for the battery voltage, as opposed to the lower reading which will be something like 837 raw value which corresponds to 3346mV for the battery voltage.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file adc.c
* @brief This file provides code for the configuration
* of the ADC instances.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV10;
hadc1.Init.Resolution = ADC_RESOLUTION_10B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = ENABLE;
hadc1.Init.LowPowerAutoPowerOff = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.NbrOfConversion = 5;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_160CYCLES_5;
hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_160CYCLES_5;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_LOW;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
sConfig.Rank = ADC_REGULAR_RANK_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* ADC1 clock enable */
__HAL_RCC_ADC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
*/
GPIO_InitStruct.Pin = V_BAT_IN_Pin|I_BAT_IN_Pin|VARIANT_ID_Pin|BOARD_ID_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_IRQn);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_ADC_CLK_DISABLE();
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
*/
HAL_GPIO_DeInit(GPIOA, V_BAT_IN_Pin|I_BAT_IN_Pin|VARIANT_ID_Pin|BOARD_ID_Pin);
/* ADC1 DMA DeInit */
HAL_DMA_DeInit(adcHandle->DMA_Handle);
/* ADC1 interrupt Deinit */
HAL_NVIC_DisableIRQ(ADC1_IRQn);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file dma.c
* @brief This file provides code for the configuration
* of all the requested memory to memory DMA transfers.
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "dma.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/*----------------------------------------------------------------------------*/
/* Configure DMA */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/**
* Enable DMA controller clock
*/
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file BxADC.c
* @brief Analogue interface with HAL and data conversions
*
******************************************************************************
* @attention
*
* Copyright Statement:
* Copyright (c) Bboxx Ltd 2023
* The copyright in this document, which contains information of a proprietary and confidential nature,
* is vested in Bboxx Limited. The content of this document may not be used for purposes other
* than that for which it has been supplied and may not be reproduced, either wholly or in part,
* nor may it be used by, or its contents divulged to, any other person who so ever without written permission
* of Bboxx Limited.
*
******************************************************************************
*/
/* USER CODE END Header */
//----------------------------------------------------------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------------------------------------------------------
#include <stdbool.h>
#include "adc.h"
#include "stm32g0xx_hal_adc.h"
#include "main.h"
#include "Bboxx.h"
#include "BxMotor.h"
#include "BxADC.h"
#include "BxMessage.h"
#include "BxSerial.h"
//----------------------------------------------------------------------------------------------------------------------
// Defines
//----------------------------------------------------------------------------------------------------------------------
typedef enum {
V_BAT,
I_BAT,
VARIANT_ID,
BOARD_ID,
TEMPERATURE,
NUM_OF_ADC_CH
} ADC_CHS;
//----------------------------------------------------------------------------------------------------------------------
// Variables
//----------------------------------------------------------------------------------------------------------------------
static uint16_t ADCreadings[NUM_OF_ADC_CH];
static uint16_t ADCoffset;
static uint16_t ADCref = ADCREF;
static uint16_t ADCpsuConv = ((ADCREF*(ADCPSUR1+ADCPSUR2))/(ADCMAX*ADCPSUR1)) * RESULTMAG;
static uint16_t ADCiConv = (ADCREF/(ADCIGAIN*ADCISENSER));
extern ADC_HandleTypeDef hadc1;
//----------------------------------------------------------------------------------------------------------------------
// Functions
//----------------------------------------------------------------------------------------------------------------------
void ADC_Initialise(void)
/**
* @brief Initialises all ADC
* @param None
* @retval None
*/
{
HAL_GPIO_WritePin(En3v3A_GPIO_Port, En3v3A_Pin, GPIO_PIN_RESET); // Turn On current amp power
HAL_GPIO_WritePin(En3v3B_GPIO_Port, En3v3B_Pin, GPIO_PIN_RESET); // Turn On IDs (+Motor) power
HAL_ADC_Start_DMA(&hadc1, (uint16_t*)ADCreadings, sizeof(ADCreadings)/sizeof(ADCreadings[V_BAT])); // start ADC conversion
return;
}
void ADC_Calibrate(void)
/**
* @brief Calibrates ADC
* @param None
* @retval None
*/
{
HAL_Delay(2000);
static bool firstTime = true;
// Run once
while (firstTime) {
// Calibrate to a known voltage such as the battery
uint16_t volt = ADC_Get_Voltage();
if (volt > (uint16_t)3750u) {
firstTime = false; // Stop if battery voltage is reached
ADCoffset = ADCreadings[I_BAT]; // Save the run mode current offset
Set_String_Tx_Buffer("calibration done ");
Message_16_Bit_Number(volt);
Set_String_Tx_Buffer(" mV and ");
Message_16_Bit_Number(ADCref);
Set_String_Tx_Buffer(" mV");
Set_String_Tx_Buffer(NEWLINE);
} else {
ADCref++; // Increase gain via adding error to the reference voltage
}
}
}
uint16_t ADC_Remove_Offset(uint16_t RawValue)
/**
* @brief Removes the offset from the ADC circuit
* @param None
* @retval None
*/
{
if(RawValue < ADCoffset)
RawValue =0;
else
RawValue -= ADCoffset;
return RawValue;
}
uint16_t ADC_Convert_Voltage(uint16_t RawValue)
/**
* @brief Converts the Raw ADC data into mM
* @param None
* @retval None
*/
{
uint32_t Result =0;
RawValue =ADC_Remove_Offset(RawValue);
ADCpsuConv = ((ADCref*(ADCPSUR1+ADCPSUR2))/(ADCMAX*ADCPSUR1)) * RESULTMAG;
Result =(ADCpsuConv * RawValue); // scale result
Result =(Result>>ADCVARDIVIDE);
return (uint16_t) Result;
}
uint16_t ADC_Convert_Current(uint16_t RawValue)
/**
* @brief Converts the Raw ADC data into mA
* @param None
* @retval None
*/
{
uint32_t Result =0;
RawValue =ADC_Remove_Offset(RawValue);
ADCiConv = (ADCref/(ADCIGAIN*ADCISENSER));
Result = (ADCiConv * RawValue);
Result =(Result>>ADCVARDIVIDE);
return (uint16_t) Result;
}
uint16_t ADC_Convert_ID(uint16_t RawValue)
/**
* @brief Converts the Raw ADC data into An ID voltage 10*units
* @param None
* @retval None
*/
{
uint16_t Result16 =0; // For Faster calculation
uint32_t Result32 =0; // For higher resolution
RawValue =ADC_Remove_Offset(RawValue); // Needs 32bit calculation
Result32 =(ADCref * RawValue); // scale result
Result16 =(uint16_t)(Result32>>ADCVARDIVIDE); // mV
Result16 += 50; // add 50mV for rounding up
Result16 = Result16/100; // Scale for 10*units
return (uint8_t) Result16;
}
uint16_t ADC_Get_Voltage(void)
/**
* @brief returns the value of the PSU voltage (mV)
* @param None
* @retval None
*/
{
return ADC_Convert_Voltage(ADCreadings[V_BAT]);
}
uint16_t ADC_Get_Current(void)
/**
* @brief returns the current current value (mA)
* @param None
* @retval None
*/
{
return ADC_Convert_Current(ADCreadings[I_BAT]);
}
uint8_t ADC_Get_Variant(void)
/**
* @brief returns the Variant as defined by R68//R70 in 10*units
* @param None
* @retval None
*/
{
return ADC_Convert_ID(ADCreadings[VARIANT_ID]);
}
uint8_t ADC_Get_BoardID(void)
/**
* @brief returns the ID as defined by R67//R71 in 10*units
* @param None
* @retval None
*/
{
return ADC_Convert_ID(ADCreadings[BOARD_ID]);
}
uint16_t ADC_Get_Temperature(void)
/**
* @brief returns the Temp in 100*C
* @param None
* @retval None
*/
{
return ADCreadings[TEMPERATURE];
}
void ADC_Print_Raw(void)
/**
* @brief Prints the Raw ADC data in serial terminal
* @param None
* @retval None
*/
{
Set_String_Tx_Buffer("ADCref== ");
Message_16_Bit_Number(ADCref);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCpsuConv== ");
Message_16_Bit_Number(ADCpsuConv);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCiConv== ");
Message_16_Bit_Number(ADCiConv);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCoffset== ");
Message_16_Bit_Number(ADCoffset);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCvoltageRAW== ");
Message_16_Bit_Number(ADCreadings[V_BAT]);
Set_String_Tx_Buffer("\t\tADCvoltage== ");
Message_16_Bit_Number_Formatted(ADC_Get_Voltage(), BareNumber);
Set_String_Tx_Buffer("mV");
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCcurrentRAW== ");
Message_16_Bit_Number(ADCreadings[I_BAT]);
Set_String_Tx_Buffer("\t\tADCcurrentRAW== ");
Message_16_Bit_Number_Formatted(ADC_Get_Current(), BareNumber);
Set_String_Tx_Buffer("mA");
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCvariantRAW== ");
Message_16_Bit_Number(ADCreadings[VARIANT_ID]);
Set_String_Tx_Buffer("\t\tADCvariant== ");
Message_16_Bit_Number_Formatted(ADC_Get_Variant(), BareNumber);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("ADCboardIDRAW== ");
Message_16_Bit_Number(ADCreadings[BOARD_ID]);
Set_String_Tx_Buffer("\t\tADCboardID== ");
Message_16_Bit_Number_Formatted(ADC_Get_BoardID(), BareNumber);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("Temperature== ");
Message_16_Bit_Number_Formatted(ADC_Get_Temperature(), BareNumber);
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("En3v3A== ");
Message_8_Bit_Number(HAL_GPIO_ReadPin(En3v3A_GPIO_Port, En3v3A_Pin));
Set_String_Tx_Buffer(NEWLINE);
Set_String_Tx_Buffer("En3v3B== ");
Message_8_Bit_Number(HAL_GPIO_ReadPin(En3v3B_GPIO_Port, En3v3B_Pin));
Set_String_Tx_Buffer(NEWLINE);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
//ADC_Print_Raw();
}
//----------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------< end of file >----------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
HAL_ADCEx_Calibration_Start()for most boards.