cancel
Showing results for 
Search instead for 
Did you mean: 

STM32U545 : Configuring ADC1 with GPDMA using "Standard Request Mode".

NFern.1
Associate III

Hi,

Do we have sample code demonstrating continuous conversions for U545 ADC1 with GPDMA using "Standard Request Mode". The sample code should use two or more channels - say IN1, IN2. 

I am migrating a project from F105/F405 and am a little overwhelmed by the setting options to be configured with U545.

The ADC_DMA_Transfer example at STM32Cube\Repository\STM32Cube_FW_U5_V1.8.0\Projects\NUCLEO-U575ZI-Q\Examples\ADC\ADC_DMA_Transfer  uses Linked-List Mode and only demonstrates a single ADC channel. 

Thanks.

1 ACCEPTED SOLUTION

Accepted Solutions
Saket_Om
ST Employee

Hello @NFern.1 

Unfortunately, there is no example ADC with DMA standard mode in STM32CubeU5. However, you can refer to the example ADC_SingleConversion_TriggerSW_DMA in STM32CubeH5.

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.
Saket_Om

View solution in original post

3 REPLIES 3
Saket_Om
ST Employee

Hello @NFern.1 

Unfortunately, there is no example ADC with DMA standard mode in STM32CubeU5. However, you can refer to the example ADC_SingleConversion_TriggerSW_DMA in STM32CubeH5.

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.
Saket_Om

Thanks @Saket_Om , I will refer the H5 project and update with further queries if any.

NFern.1
Associate III

Thanks @Saket_Om , I could get it to work.

I am sharing my implementation for review and suggestions by the Community. The complete project can be found at https://github.com/NereusF1/ADC-DMA-Test_N-U545RE-Q

Tested using NUCLEO-U545RE-Q.

ADC1: VREFINT, IN1, IN2, Temperature Sensor
ADC2: IN3, IN4
ADC calibration done at start-up

CubeMX settings

ADC1 Configuration

ADC1_1.png

If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)

ADC1_2.png

ADC1_3.png

ADC1_4.png

No changes required in the GPIO Configuration done automatically by CubeMX.

ADC1_6.png

DMA configuration is done at the GPDMA1 node in CubeMX.

ADC1_5.png

ADC4 Configuration

ADC4_1.png

If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)

ADC4_2.png

ADC4_3.png

ADC4_4.png

No changes required in the GPIO Configuration done automatically by CubeMX.

ADC4_5.png

DMA configuration is done at the GPDMA1 node in CubeMX.

Assign DMA Channels for ADC1 and ADC4

GPDMA1_1.png

Configure DMA CH1 for ADC1

GPDMA1_3.png

Configure DMA CH0 for ADC4

GPDMA1_4.png

In the NVIC Node, Enable DMA Channel 0 and Channel 1 Interrupts

NVIC_1.png

No changes required in the NVIC Code Generation done automatically by CubeMX.

NVIC_2.png

Clk1.png

 

Clk2.png

Since my process values are slow moving DC values, I am using slower 4MHz clock for ADC.

ICACHE.png

Use the CubeMX default.

SYS.png

To support printf for the Debugger.

Debug.png

Code added to main.c

 
/* USER CODE BEGIN PD */
#define ADC1ChannelCount 4
#define ADC4ChannelCount 2
#define AccValBufLen 100
/* USER CODE END PD */
/* USER CODE BEGIN PV */

uint32_t ADC1_res_buffer[ADC1ChannelCount*2]; // create a buffer with length equal to the Number of Conversions x 2 ; Word size = uint32_t
uint16_t ADC1_res_bkp_buffer[ADC1ChannelCount];
uint32_t ADC1_acc_val_buffer[ADC1ChannelCount]; //accumulated value
uint16_t  ADC1AccValBufCount;  // accumulate 100 maybe 1000 samples
uint32_t ADC1_acc_val_batch_buffer[ADC1ChannelCount];
uint16_t ADC1AccValBufBatchCount;
float ADC1_avg_val_buffer[ADC1ChannelCount];
float ADC1_pin_Volts_buffer[ADC1ChannelCount];

uint32_t cal_vref_data;
uint32_t vref_voltage_mv;
uint32_t vrefint_data;

uint32_t ADC1_ch1_raw;
uint32_t ADC1_ch1_mV;

uint32_t ADC1_ch2_raw;
uint32_t ADC1_ch2_mV;

uint32_t temp_raw;
int32_t temp_celsius;

uint32_t ADC4_res_buffer[ADC4ChannelCount*2]; // create a buffer with length equal to the Number of Conversions x 2 ; Word size = uint32_t
uint16_t ADC4_res_bkp_buffer[ADC4ChannelCount];
uint32_t ADC4_acc_val_buffer[ADC4ChannelCount]; //accumulated value
uint16_t  ADC4AccValBufCount;  // accumulate 100 maybe 1000 samples
uint32_t ADC4_acc_val_batch_buffer[ADC4ChannelCount];
uint16_t ADC4AccValBufBatchCount;
float ADC4_avg_val_buffer[ADC4ChannelCount];
float ADC4_pin_Volts_buffer[ADC4ChannelCount];

uint32_t ADC4_ch1_raw;
uint32_t ADC4_ch1_mV;

uint32_t ADC4_ch2_raw;
uint32_t ADC4_ch2_mV;

/* USER CODE END PV */

ADC Calibration.

Linking our memory buffers to the DMA process and ADC start-up.

  /* USER CODE BEGIN 2 */
	HAL_Delay(2000);  // hopefully 2s is a long time in the MCU world for the ADC peripheral to power up and get ready for calibration
	HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); // this is optional and uses internal calibration values programmed by the factory
	HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);
	HAL_ADCEx_Calibration_Start(&hadc4, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
	HAL_ADCEx_Calibration_Start(&hadc4, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);
	HAL_Delay(1000); // wait for things to settle down before starting the ADC process

	HAL_ADC_Start_DMA(&hadc1, ADC1_res_buffer, ADC1ChannelCount*2); // this actually starts the process: DMA-transfer the data stream from &hadc1 into ADC1_res_buffer, which holds 2 conversions
	HAL_ADC_Start_DMA(&hadc4, ADC4_res_buffer, ADC4ChannelCount*2);
  /* USER CODE END 2 */

 

  /* USER CODE BEGIN WHILE */
  while (1)
  {
		if(ADC1AccValBufBatchCount>0)
		{
			for (int i=0; i<ADC1ChannelCount; i++)
			{
				ADC1_avg_val_buffer[i]=(ADC1_avg_val_buffer[i] + (((float)ADC1_acc_val_batch_buffer[i]/(float)ADC1AccValBufBatchCount)))/2.0;
				ADC1_acc_val_batch_buffer[i]=0;  // clear the accumulated value
			}
			ADC1AccValBufBatchCount=0;   // clear the accumulated count

	          // Calculate the VREF using VREFINT
			if(ADC1_avg_val_buffer[0]<1) // protect from divide by zero
			{
				vref_voltage_mv=0; //adcbufAvgValue[0] will ramp-up and VREF calc will ramp-down slowly due to ADC averaging delays
			}
			else
			{
				// Read calibration data into RAM first if ICache is on
				cal_vref_data = *(__IO uint32_t *)VREFINT_CAL_ADDR; // Example address

				vrefint_data = ADC1_avg_val_buffer[0];

				// Calculate voltage in mV
				vref_voltage_mv = __HAL_ADC_CALC_VREFANALOG_VOLTAGE(cal_vref_data, vrefint_data, ADC_RESOLUTION_14B);

				ADC1_ch1_raw =  ADC1_avg_val_buffer[1]; // Get raw ch1 reading
				ADC1_ch1_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hadc1, vref_voltage_mv, ADC1_ch1_raw, ADC_RESOLUTION_14B); // Example ch1 calculation

				ADC1_ch2_raw =  ADC1_avg_val_buffer[2]; // Get raw ch1 reading
				ADC1_ch2_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hadc1, vref_voltage_mv, ADC1_ch2_raw, ADC_RESOLUTION_14B); // Example ch1 calculation

				// Or for temperature:
				temp_raw = ADC1_avg_val_buffer[3]; // Get raw temp sensor reading
				temp_celsius = __HAL_ADC_CALC_TEMPERATURE(&hadc1, vref_voltage_mv, temp_raw, ADC_RESOLUTION_14B); // Example Temp calculation
			}

		}

		if(ADC4AccValBufBatchCount>0)
		{
			for (int i=0; i<ADC4ChannelCount; i++)
			{
				ADC4_avg_val_buffer[i]=(ADC4_avg_val_buffer[i] + (((float)ADC4_acc_val_batch_buffer[i]/(float)ADC4AccValBufBatchCount)))/2.0;
				ADC4_acc_val_batch_buffer[i]=0;  // clear the accumulated value
			}
			ADC4AccValBufBatchCount=0;   // clear the accumulated count

	          // Calculate the VREF using VREFINT
			if(ADC4_avg_val_buffer[0]<1) // protect from divide by zero
			{
				vref_voltage_mv=0; //adcbufAvgValue[0] will ramp-up and VREF calc will ramp-down slowly due to ADC averaging delays
			}
			else
			{
				ADC4_ch1_raw =  ADC4_avg_val_buffer[0]; // Get raw ch1 reading
				ADC4_ch1_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hADC4, vref_voltage_mv, ADC4_ch1_raw, ADC_RESOLUTION_12B); // Example ch1 calculation

				ADC4_ch2_raw =  ADC4_avg_val_buffer[1]; // Get raw ch1 reading
				ADC4_ch2_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hADC4, vref_voltage_mv, ADC4_ch2_raw, ADC_RESOLUTION_12B); // Example ch1 calculation
			}

		}

    /* USER CODE END WHILE */

 

/* USER CODE BEGIN 4 */

// Called when first half of adc buffer is filled
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{

	if(hadc->Instance == ADC1)
	{
			for (int i=0; i<ADC1ChannelCount ; i++)
			{
			  ADC1_acc_val_buffer[i]=ADC1_acc_val_buffer[i]+ADC1_res_buffer[i]; // moved all heavy lifting to 100ms timer loop
			  ADC1_res_bkp_buffer[i]=(uint16_t)ADC1_res_buffer[i];  // for viewing the instantaneous value only
			}

			ADC1AccValBufCount++;
			if(ADC1AccValBufCount>=AccValBufLen)
			{
				for (int i=0; i<ADC1ChannelCount; i++)
				{
					ADC1_acc_val_batch_buffer[i]=ADC1_acc_val_buffer[i]; // hand-over for processing in less critical threads
					ADC1_acc_val_buffer[i]=0;  // clear the accumulated value
				}
				ADC1AccValBufBatchCount=ADC1AccValBufCount; // hand-over for processing  in less critical threads
				ADC1AccValBufCount=0;   // clear the accumulated count
			}
	}

	if(hadc->Instance == ADC4)
	{
			for (int i=0; i<ADC4ChannelCount ; i++)
			{
			  ADC4_acc_val_buffer[i]=ADC4_acc_val_buffer[i]+ADC4_res_buffer[i]; // moved all heavy lifting to 100ms timer loop
			  ADC4_res_bkp_buffer[i]=(uint16_t)ADC4_res_buffer[i];  // for viewing the instantaneous value only
			}

			ADC4AccValBufCount++;
			if(ADC4AccValBufCount>=AccValBufLen)
			{
				for (int i=0; i<ADC4ChannelCount; i++)
				{
					ADC4_acc_val_batch_buffer[i]=ADC4_acc_val_buffer[i]; // hand-over for processing in less critical threads
					ADC4_acc_val_buffer[i]=0;  // clear the accumulated value
				}
				ADC4AccValBufBatchCount=ADC4AccValBufCount; // hand-over for processing  in less critical threads
				ADC4AccValBufCount=0;   // clear the accumulated count
			}
	}

}

// Called when adc buffer is completely filled
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{

	if(hadc->Instance == ADC1)
	{
		for (int i=0; i<ADC1ChannelCount; i++)
		{
		  ADC1_acc_val_buffer[i]=ADC1_acc_val_buffer[i]+ADC1_res_buffer[ADC1ChannelCount+i]; // moved all heavy lifting to 100ms timer loop
		  ADC1_res_bkp_buffer[i]=(uint16_t)ADC1_res_buffer[ADC1ChannelCount+i];  // for viewing the instantaneous value only
		}

		ADC1AccValBufCount++;
		if(ADC1AccValBufCount>=AccValBufLen)
		{
			for (int i=0; i<ADC1ChannelCount; i++)
			{
				ADC1_acc_val_batch_buffer[i]=ADC1_acc_val_buffer[i]; // hand-over for processing  in less critical threads
				ADC1_acc_val_buffer[i]=0;  // clear the accumulated value
			}
			ADC1AccValBufBatchCount=ADC1AccValBufCount; // hand-over for processing  in less critical threads
			ADC1AccValBufCount=0;   // clear the accumulated count
		}
	}

	if(hadc->Instance == ADC4)
	{
		for (int i=0; i<ADC4ChannelCount; i++)
		{
		  ADC4_acc_val_buffer[i]=ADC4_acc_val_buffer[i]+ADC4_res_buffer[ADC4ChannelCount+i]; // moved all heavy lifting to 100ms timer loop
		  ADC4_res_bkp_buffer[i]=(uint16_t)ADC4_res_buffer[ADC4ChannelCount+i];  // for viewing the instantaneous value only
		}

		ADC4AccValBufCount++;
		if(ADC4AccValBufCount>=AccValBufLen)
		{
			for (int i=0; i<ADC4ChannelCount; i++)
			{
				ADC4_acc_val_batch_buffer[i]=ADC4_acc_val_buffer[i]; // hand-over for processing  in less critical threads
				ADC4_acc_val_buffer[i]=0;  // clear the accumulated value
			}
			ADC4AccValBufBatchCount=ADC4AccValBufCount; // hand-over for processing  in less critical threads
			ADC4AccValBufCount=0;   // clear the accumulated count
		}
	}
}

/* USER CODE END 4 */

 

TCONV per channel = TSMPL + TSAR = [814_min + 17_for_14bits] × Tadc
TCONV per channel = 831 × (ADC_Clk_PreScaler/Fadc) = 831*(1/4Mhz) = 207.75us
For ADC1 with 4 channels = 207.75us x 4 = 831us ~ 1ms assuming delays for the DMA buffer complete call-backs.
Now accumulating 100 samples = 1ms x 100 = 100ms.

So every 100ms we hand-over a new accumulated count from the Conversion Complete Call-back functions.
Hence the earlier accumulated count should be processed before this new accumulated count arrives to ensure
that we do not miss some readings.

I plan to move this checking / processing to a 10ms timer loop in production code.

 

Watch1.png

Readings are within +/-10mV of the signal source.

 

Attached:   .ioc file and main.c file

 

References:

ADC_SingleConversion_TriggerSW_DMA 

https://community.st.com/t5/stm32-mcus-products/stm32u545re-q-not-getting-adc1-conversion-data-via-gpdma/td-p/776887