cancel
Showing results for 
Search instead for 
Did you mean: 

STM32h503 TIMER-ADC-DMA Low Layer Example

bepot
Associate II

Is there any example will guide about ADC being triggered with timer and being transferred to RAM with DMA for STM32H5 series?

8 REPLIES 8
Sarra.S
ST Employee

Welcome @bepot to ST Community, 

Actually, as far as I know, there is no example in the H5 CubeFW package that covers this. 

But there is an LL_example that performs an ADC conversion at each trigger event from a timer and transfers data with DMA into a table in F4CubeFW, you can tailor this example to stm32H5 .

 

Hope that helps!

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.

Thank you for your reply,

In fact I need a low layer driver example with linked list used in DMA. To process ADC data while string new ones, I need a ping buffer or scatter gather memory or linked list memory. But I cannot find any example with stm32h5. The only example with linked list I could find is RAM2RAM example. 

Hello again @bepot

Unfortunately, there is no low-layer example that covers this for the moment! 

I made a request to add this example in the future releases of STM32CubeH5. (Internal ticket number: 164113).

Thank you! 

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.

nix
Associate

hi, i just had a similar problem and i managed to get something togather.

its reading the temperature sensor and the adc vref with the GPDMA in circular mode. 
it seems that cube MX is not generating the node init at all on the H503 and on the H563 it is missing the  "LL_DMA_ConfigLinkUpdate"

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @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 "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint16_t ADC_DMA_buff[2] = {0};
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
static void MX_GPDMA1_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  /* System interrupt init*/
  NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* SysTick_IRQn interrupt configuration */
  NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),15, 0));

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

/* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPDMA1_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  LL_FLASH_SetLatency(LL_FLASH_LATENCY_5);
  while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_5)
  {
  }

  LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE0);
  while (LL_PWR_IsActiveFlag_VOS() == 0)
  {
  }
  LL_RCC_HSI_Enable();

   /* Wait till HSI is ready */
  while(LL_RCC_HSI_IsReady() != 1)
  {
  }

  LL_RCC_HSI_SetCalibTrimming(64);
  LL_RCC_HSI_SetDivider(LL_RCC_HSI_DIV_2);
  LL_RCC_PLL1_SetSource(LL_RCC_PLL1SOURCE_HSI);
  LL_RCC_PLL1_SetVCOInputRange(LL_RCC_PLLINPUTRANGE_8_16);
  LL_RCC_PLL1_SetVCOOutputRange(LL_RCC_PLLVCORANGE_WIDE);
  LL_RCC_PLL1_SetM(2);
  LL_RCC_PLL1_SetN(31);
  LL_RCC_PLL1_SetP(2);
  LL_RCC_PLL1_SetQ(2);
  LL_RCC_PLL1_SetR(2);
  LL_RCC_PLL1P_Enable();
  LL_RCC_PLL1_Enable();

   /* Wait till PLL is ready */
  while(LL_RCC_PLL1_IsReady() != 1)
  {
  }

  LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL1);

   /* Wait till System clock is ready */
  while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL1)
  {
  }

  LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
  LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
  LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
  LL_RCC_SetAPB3Prescaler(LL_RCC_APB3_DIV_1);

  LL_Init1msTick(248000000);

  LL_SetSystemCoreClock(248000000);
}

/**
  * @brief Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  LL_RCC_PLL2_SetSource(LL_RCC_PLL2SOURCE_HSI);
  LL_RCC_PLL2_SetVCOInputRange(LL_RCC_PLLINPUTRANGE_8_16);
  LL_RCC_PLL2_SetVCOOutputRange(LL_RCC_PLLVCORANGE_WIDE);
  LL_RCC_PLL2_SetM(2);
  LL_RCC_PLL2_SetN(35);
  LL_RCC_PLL2_SetP(2);
  LL_RCC_PLL2_SetQ(2);
  LL_RCC_PLL2_SetR(15);
  LL_RCC_PLL2R_Enable();
  LL_RCC_PLL2_Enable();

   /* Wait till PLL is ready */
  while(LL_RCC_PLL2_IsReady() != 1)
  {
  }

}

/**
  * @brief ADC1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
  LL_ADC_InitTypeDef ADC_InitStruct = {0};
  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};

  LL_RCC_SetADCDACClockSource(LL_RCC_ADCDAC_CLKSOURCE_PLL2R);

  /* Peripheral clock enable */
  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC);

  /* USER CODE BEGIN ADC1_Init 1 */
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  /* USER CODE END ADC1_Init 1 */

  /** Configure the ADC multi-mode
  */
  ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_ASYNC_DIV2;
  LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);

  /** Common config
  */
  ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
  ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
  LL_ADC_Init(ADC1, &ADC_InitStruct);
  ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
  ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
  LL_ADC_REG_SetSamplingMode(ADC1, LL_ADC_REG_SAMPLING_MODE_NORMAL);

  /* Disable ADC deep power down (enabled by default after reset state) */
  LL_ADC_DisableDeepPowerDown(ADC1);
  /* Enable ADC internal voltage regulator */
  LL_ADC_EnableInternalRegulator(ADC1);
  /* Delay for ADC internal voltage regulator stabilization. */
  /* Compute number of CPU cycles to wait for, from delay in us. */
  /* Note: Variable divided by 2 to compensate partially */
  /* CPU processing cycles (depends on compilation optimization). */
  /* Note: If system core clock frequency is below 200kHz, wait time */
  /* is only a few CPU processing cycles. */
  uint32_t wait_loop_index;
  wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
  while(wait_loop_index != 0)
  {
    wait_loop_index--;
  }

  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_TEMPSENSOR);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SAMPLINGTIME_2CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SINGLE_ENDED);
  LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT|LL_ADC_PATH_INTERNAL_TEMPSENSOR);

  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_VREFINT);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_2CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SINGLE_ENDED);
  /* USER CODE BEGIN ADC1_Init 2 */
	LL_ADC_StartCalibration(ADC1, LL_ADC_SINGLE_ENDED);
	while (LL_ADC_IsCalibrationOnGoing(ADC1));

	wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
	while(wait_loop_index != 0)
	{
	wait_loop_index--;
	}

	LL_ADC_ClearFlag_ADRDY(ADC1);
	LL_ADC_Enable(ADC1);

	while (!LL_ADC_IsEnabled(ADC1));

	wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
	while(wait_loop_index != 0)
	{
	wait_loop_index--;
	}

 	LL_ADC_REG_StartConversion(ADC1);
  /* USER CODE END ADC1_Init 2 */

}

/**
  * @brief GPDMA1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_GPDMA1_Init(void)
{

  /* USER CODE BEGIN GPDMA1_Init 0 */
	LL_DMA_InitNodeTypeDef NodeConfig = {0};
	LL_DMA_LinkNodeTypeDef Node_GPDMA1_Channel0 = {0};
  /* USER CODE END GPDMA1_Init 0 */

  LL_DMA_InitLinkedListTypeDef DMA_InitLinkedListStruct = {0};

  /* Peripheral clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPDMA1);

  /* USER CODE BEGIN GPDMA1_Init 1 */
  /* GPDMA1_REQUEST_ADC1 Init */
	NodeConfig.SrcAddress               = (uint32_t)&ADC1->DR;
	NodeConfig.DestAddress              = (uint32_t)&ADC_DMA_buff[0];
	NodeConfig.BlkDataLength           = sizeof(ADC_DMA_buff);
	NodeConfig.DestAllocatedPort = LL_DMA_DEST_ALLOCATED_PORT1;
	NodeConfig.DestHWordExchange = LL_DMA_DEST_HALFWORD_PRESERVE;
	NodeConfig.DestByteExchange = LL_DMA_DEST_BYTE_PRESERVE;
	NodeConfig.DestBurstLength = 1;
	NodeConfig.DestIncMode = LL_DMA_DEST_INCREMENT;
	NodeConfig.DestDataWidth = LL_DMA_DEST_DATAWIDTH_HALFWORD;
	NodeConfig.SrcAllocatedPort = LL_DMA_SRC_ALLOCATED_PORT0;
	NodeConfig.SrcByteExchange = LL_DMA_SRC_BYTE_PRESERVE;
	NodeConfig.DataAlignment = LL_DMA_DATA_ALIGN_ZEROPADD;
	NodeConfig.SrcBurstLength = 1;
	NodeConfig.SrcIncMode = LL_DMA_SRC_FIXED;
	NodeConfig.SrcDataWidth = LL_DMA_SRC_DATAWIDTH_HALFWORD;
	NodeConfig.TransferEventMode = LL_DMA_TCEM_BLK_TRANSFER;
	NodeConfig.Mode = LL_DMA_NORMAL;
	NodeConfig.BlkRptCount = 1;
	NodeConfig.TriggerPolarity = LL_DMA_TRIG_POLARITY_MASKED;
	NodeConfig.BlkHWRequest = LL_DMA_HWREQUEST_SINGLEBURST;
	NodeConfig.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
	NodeConfig.Request = LL_GPDMA1_REQUEST_ADC1;
	NodeConfig.UpdateRegisters = (LL_DMA_UPDATE_CTR1 | LL_DMA_UPDATE_CTR2 | LL_DMA_UPDATE_CBR1 | LL_DMA_UPDATE_CSAR | LL_DMA_UPDATE_CDAR | LL_DMA_UPDATE_CTR3 | LL_DMA_UPDATE_CBR2 | LL_DMA_UPDATE_CLLR);
	NodeConfig.NodeType = LL_DMA_GPDMA_LINEAR_NODE;
	LL_DMA_CreateLinkNode(&NodeConfig, &Node_GPDMA1_Channel0);

	LL_DMA_ConnectLinkNode(&Node_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5, &Node_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5);

	LL_DMA_SetLinkedListBaseAddr(GPDMA1, LL_DMA_CHANNEL_0, (uint32_t)&Node_GPDMA1_Channel0);
	LL_DMA_ConfigLinkUpdate(GPDMA1, LL_DMA_CHANNEL_0, (LL_DMA_UPDATE_CTR1 | LL_DMA_UPDATE_CTR2 | LL_DMA_UPDATE_CBR1 | LL_DMA_UPDATE_CSAR | LL_DMA_UPDATE_CDAR | LL_DMA_UPDATE_CTR3 | LL_DMA_UPDATE_CBR2 | LL_DMA_UPDATE_CLLR),(uint32_t)&Node_GPDMA1_Channel0);

  /* USER CODE END GPDMA1_Init 1 */
  DMA_InitLinkedListStruct.Priority = LL_DMA_LOW_PRIORITY_HIGH_WEIGHT;
  DMA_InitLinkedListStruct.LinkStepMode = LL_DMA_LSM_FULL_EXECUTION;
  DMA_InitLinkedListStruct.LinkAllocatedPort = LL_DMA_LINK_ALLOCATED_PORT0;
  DMA_InitLinkedListStruct.TransferEventMode = LL_DMA_TCEM_LAST_LLITEM_TRANSFER;
  LL_DMA_List_Init(GPDMA1, LL_DMA_CHANNEL_0, &DMA_InitLinkedListStruct);
  /* USER CODE BEGIN GPDMA1_Init 2 */
  LL_DMA_EnableChannel(GPDMA1, LL_DMA_CHANNEL_0);
  /* USER CODE END GPDMA1_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.
  * @PAram  file: pointer to the source file name
  * @PAram  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 */

this code is working here in cubeIDE on the nucleo STM32H503 board. the raw temperature sensor and vref values can be seen in  ADC_DMA_buff 

Thank you @nix for sharing you project

Could you please share an .ioc file that reproduces the issue from CubeMX side?

Thank you! 

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.

hi, the H503_TEST.ioc file is in the above attached H503_TEST.rar file.

to complete the list of problems that occur (for me) with that adc to dma stuff:

  1. in the ADC1 tab, i set the dma continuous request to enabled, but:
    ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
    is not generated, without it dont seems to request the DMA
  2. with the H503KBUx selected, i can configure almost nothing in the GPDMA tabs
    nix_0-1708945518243.png

    so it only generates the DMA_InitLinkedListStruct.
    (with the H563) it it does generate the nodeconfig and the Node_GPDMA1_Channel0 init.

  3. on both (H503 and H563) the LL_DMA_ConfigLinkUpdate is missing, without the DMA starts with no init and will set the user error flag

the GPDMA node should be initialised as static.. it works in the code i posted, but in my main project where i do more then just ADC to DMA the ram gets changed and as the GPDMA reads the node continuous it must stay what it is.

i think thats it so far

Sarra.S
ST Employee

 

Hello @bepot 

Thank you for your valuable effort, I confirm the addressed issues and have reported them to be fixed! 

  • 1st issue : 174620: Internal ticket number
  • 2nd and 3rd issue: 174684 Internal ticket number 

EDIT: see the previous comment foe issue 2 and 3.

Thank you! 

Sarra

 

 

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.

Hello again @nix 

After more discussion with the CubeMX dev team, the points 2 and 3 are not really issues, 

in fact, to configure the GPDMA tabs, you have to do it in utilities as shown here: 

SarraS_0-1709126842951.png

a file named linkedlist.c will be automatically generated and you won't need to manually add the LL_DMA_ConfigLinkUpdate or any other other configuration! 

Sorry for the misunderstanding, I hope it's more clear now! 

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.