cancel
Showing results for 
Search instead for 
Did you mean: 

DMA MUX overrun triggered immediately after activation, and stalls any code afterwards

BBeha
Associate II

I am relatively new to STM32 programming. I use STMCubeIDE (HAL & HAL_ex libraries) with STM32H743Zi and Nucleo-144.

My configuration is:

1 Multi Buffer DMA mem to peripheral, circular + clock triggered (54MHz per word, 360 words per buffer = 150 kHz per 'complete')

2 ADC DMA's to sample at minimum frequency, triggered by DMA1(above) (still doesn't work).

Why am I getting MDA Mux overrun?

Attached the main.c file.

Appreciate any help!

6 REPLIES 6
BBeha
Associate II

To make the code more visible :...

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USB_DEVICE_Init();
  MX_LPTIM1_Init();
  MX_LPTIM2_Init();
  MX_ADC1_Init();
  MX_ADC3_Init();
	HAL_GPIO_WritePin(ACTIVATE_GPIO_Port, ACTIVATE_Pin, GPIO_PIN_RESET);
 
	HAL_DMA_RegisterCallback(&hdma_dma_generator0, HAL_DMA_XFER_CPLT_CB_ID, DMA_TransferCompleteCallback);
	HAL_DMA_RegisterCallback(&hdma_dma_generator0, HAL_DMA_XFER_M1CPLT_CB_ID, DMA_TransferCompleteCallback);
	HAL_DMA_RegisterCallback(&hdma_dma_generator0, HAL_DMA_XFER_ERROR_CB_ID, DMA_TransferErrorCallback);
	HAL_LPTIM_PWM_Start(&hlptim1, 2, 1);
	HAL_DMAEx_MultiBufferStart_IT(&hdma_dma_generator0, (uint32_t)(&TxBuffer0), (uint32_t)(&GPIOG->ODR), (uint32_t)(&TxBuffer0), 360);
	HAL_ADCEx_MultiModeStart_DMA(&hadc1, ADC1Buffer, ADC1_N_CH);
	HAL_ADC_Start_DMA(&hadc3, ADC3Buffer, ADC3_N_CH);
	HAL_DMAEx_EnableMuxRequestGenerator(&hdma_dma_generator0);
	// Can't go passt this point!!!
	// ----------------------------
	// Using the debugger PAUSE - I see it re-enters DMAMUX1_OVR_IRQHandler() from within any line of system call like HAL_GPIO_WritePin()
	HAL_GPIO_WritePin(RED_LED_GPIO_Port, RED_LED_Pin, GPIO_PIN_SET);
 
	...
}
 
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
 
  /** Supply configuration update enable 
  */
  HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
  /** Configure the main internal regulator output voltage 
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
 
  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
  /** Macro to configure the PLL clock source 
  */
  __HAL_RCC_PLL_PLLSOURCE_CONFIG(RCC_PLLSOURCE_HSI);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 60;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
  RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  RCC_OscInitStruct.PLL.PLLFRACN = 0;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM2|RCC_PERIPHCLK_USB
                              |RCC_PERIPHCLK_LPTIM1;
  PeriphClkInitStruct.PLL2.PLL2M = 8;
  PeriphClkInitStruct.PLL2.PLL2N = 27;
  PeriphClkInitStruct.PLL2.PLL2P = 4;
  PeriphClkInitStruct.PLL2.PLL2Q = 2;
  PeriphClkInitStruct.PLL2.PLL2R = 2;
  PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_3;
  PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
  PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
  PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
  PeriphClkInitStruct.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_PLL2;
  PeriphClkInitStruct.Lptim2ClockSelection = RCC_LPTIM2CLKSOURCE_PLL2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Enable USB Voltage detector 
  */
  HAL_PWREx_EnableUSBVoltageDetector();
}
 
static void MX_ADC1_Init(void)
{
  ADC_MultiModeTypeDef multimode = {0};
  ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};
  ADC_ChannelConfTypeDef sConfig = {0};
 
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc1.Init.LowPowerAutoWait = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.NbrOfConversion = 10;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }
  ... more channels here
  
}
 
static void MX_ADC3_Init(void)
{
  ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};
  ADC_ChannelConfTypeDef sConfig = {0};
 
  hadc3.Instance = ADC3;
  hadc3.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc3.Init.Resolution = ADC_RESOLUTION_12B;
  hadc3.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc3.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc3.Init.LowPowerAutoWait = ENABLE;
  hadc3.Init.ContinuousConvMode = ENABLE;
  hadc3.Init.NbrOfConversion = 11;
  hadc3.Init.DiscontinuousConvMode = DISABLE;
  hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc3.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
  hadc3.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  hadc3.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
  hadc3.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc3) != HAL_OK)
  {
    Error_Handler();
  }
  AnalogWDGConfig.WatchdogNumber = ADC_ANALOGWATCHDOG_2;
  AnalogWDGConfig.ITMode = ENABLE;
  AnalogWDGConfig.HighThreshold = 2000000;
  AnalogWDGConfig.LowThreshold = 500000;
  if (HAL_ADC_AnalogWDGConfig(&hadc3, &AnalogWDGConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
	... more channels here
}
 
static void MX_DMA_Init(void) 
{
  HAL_DMA_MuxRequestGeneratorConfigTypeDef pRequestGeneratorConfig = {0};
  HAL_DMA_MuxSyncConfigTypeDef pSyncConfig = {0};
 
  __HAL_RCC_DMA1_CLK_ENABLE();
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  hdma_dma_generator0.Instance = DMA1_Stream0;
  hdma_dma_generator0.Init.Request = DMA_REQUEST_GENERATOR0;
  hdma_dma_generator0.Init.Direction = DMA_MEMORY_TO_PERIPH;
  hdma_dma_generator0.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_dma_generator0.Init.MemInc = DMA_MINC_ENABLE;
  hdma_dma_generator0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_dma_generator0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_dma_generator0.Init.Mode = DMA_CIRCULAR;
  hdma_dma_generator0.Init.Priority = DMA_PRIORITY_MEDIUM;
  hdma_dma_generator0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  hdma_dma_generator0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
  hdma_dma_generator0.Init.MemBurst = DMA_MBURST_SINGLE;
  hdma_dma_generator0.Init.PeriphBurst = DMA_PBURST_SINGLE;
  if (HAL_DMA_Init(&hdma_dma_generator0) != HAL_OK)
  {
    Error_Handler( );
  }
 
  /* Configure the DMAMUX request generator for the selected DMA stream */
  pRequestGeneratorConfig.SignalID = HAL_DMAMUX1_REQ_GEN_LPTIM1_OUT;
  pRequestGeneratorConfig.Polarity = HAL_DMAMUX_REQ_GEN_RISING_FALLING;
  pRequestGeneratorConfig.RequestNumber = 1;
  if (HAL_DMAEx_ConfigMuxRequestGenerator(&hdma_dma_generator0, &pRequestGeneratorConfig) != HAL_OK)
  {
    Error_Handler( );
  }
 
  /* Configure the DMAMUX synchronization parameters for the selected DMA stream */
  pSyncConfig.SyncSignalID = HAL_DMAMUX1_SYNC_EXTI0;
  pSyncConfig.SyncPolarity = HAL_DMAMUX_SYNC_NO_EVENT;
  pSyncConfig.SyncEnable = DISABLE;
  pSyncConfig.EventEnable = ENABLE;
  pSyncConfig.RequestNumber = 1;
  if (HAL_DMAEx_ConfigMuxSync(&hdma_dma_generator0, &pSyncConfig) != HAL_OK)
  {
    Error_Handler( );
  }
 
  /* DMA interrupt init */
  /* DMA2_Stream1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMAMUX1_OVR_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);
}

TDK
Guru

The overrun flag is set when a DMA request is generated before the DMA has been able to service the previous request.

DMA at 54MHz is quite fast. If that's not beyond the capabilities of the DMA then it's close.

Similarly, an interrupt at 150kHz is taxing the system quite a bit and possibly it cannot keep up.

Slow your times down and see if it works at much lower speeds first. Then increase to see where it breaks.

If you feel a post has answered your question, please click "Accept as Solution".
berendi
Principal

HAL functions are poorly documented, if you don't use them exactly the way they are used in the example projects, the result is practically unpredictable.

What are you using the DMA request generator for? What event should trigger what action and why? What should DMA copy where and when? How is LPTIM, a GPIO port output, and EXTI0 involved in all this?

Can you describe the task to be solved?

Thanks Peter,

The task at hand is streaming a double buffer constantly out, synchronized to a clock.

Clock configuration has LPTIM1 running at 54MHz (CPU at 480MHz) (it will actually be an external clock one it works)

I actually do not need the functionality of the DMA request generator - just the functionality of the synchronization to the clock, but when placing NONE for the DMA request generation trigger (in the IDE), I could not find a way to activate the stream at all (HAL_DMAEx_MultiBufferStart_IT does not start the stream).

The ADC channels on the other hand - are at low priority in terms of timing (I don't care for latency or skipping measurements there) - but I could not find a way to "relax" them. Is there a way to have them conducted in the DMA "spare time"?

Is there a way to activate the DMA (mem to peripheral, circular mode + clock synchronization) without a hardware trigger?

Thanks for your reply!

BBeha
Associate II

Thanks TDK,

I will try to relax the requirements.

Please see the answer to 'berendi' to better understand my concerns

I still don't understand all the requirements from your description, so I can't give a complete answer, just some starting points.

Clicking around in CubeMX, experimenting with a barely documented and painfully slow library is not likely to solve the problem. Get the reference manual, open it on the big screen, you are going to need it a lot.

DMA at 54 MHz is really pushing it. I'm not saying impossible, but you have to know the system very thoroughy, much better than I, to be able to guarantee that the solution is working under all circumstances and no DMA overrun can ever occur. Forget it unless you can invest a couple of weeks full time studying and working on that, and have a knowledgeable ST support engineer at your disposal.

The problem is that

  • DMA needs 4 cycles to copy a data unit from here to there. Optimizing DMA FIFO parameters might improve this, but I'd have to study the workings of DMA a bit to say for sure.
  • Although the CPU core is running at 480 MHz, the system bus and peripherals (including DMA) run at 240 MHz.
  • 240 / 54 = 4.444

Can you see the problem here? There is barely any margin, anything that delays the DMA transaction has a high chance of causing an overflow.

Now open Figure 1. System architecture and Table 8. Register boundary addresses in the reference manual side by side. Find the DMA controller (that's easy), the memory holding the buffer, and the GPIO port receiving the data from DMA. If anything else touches the same memory are, it will delay DMA. If anything else touches any of the GPIO ports or other AHB4/APB4 peripherals, it will delay DMA. I would give it up at this point with my knowledge.

I would draw the line maybe at 24 MHz. If you need more than that, go directly to ST support.

The actual code to copy data from memory to a GPIO port at a fixed frequency is rather simple compared to the above.

Peripheral clocks for DMA, DMAMUX, TIM and GPIO should be enabled in RCC, and GPIO bits should be set to output mode for this to work.

TIM5->ARR = DIVISOR - 1; // set timer period. I won't experiment with DIVISOR < 10.
TIM5->DIER = TIM_DIER_UDE; // enable DMA on timer update (overflow) event
DMAMUX1_Channel0->CCR = (DMA_REQUEST_TIM5_UP << DMAMUX_CxCR_DMAREQ_ID_Pos); // map timer update event to DMA1 Stream0.
DMA1->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0; // clear DMA complete flags
DMA1_Stream0->PAR = (uint32_t)&(GPIOx->BSRR);
DMA1_Stream0->M0AR = (uint32_t)bits_out; // address of the buffer
DMA1_Stream0->NDTR = out_n; // bytes to transfer
DMA1_Stream0->CR =
	DMA_SxCR_MSIZE_1 | // 10: 32 bit
	DMA_SxCR_PSIZE_1 | // 10: 32 bit
	DMA_SxCR_MINC    | // memory address increment
	DMA_SxCR_DIR_0   | // 00: P->M, 01:M->P, 10:M->M
	DMA_SxCR_CIRC    | // circular mode
	DMA_SxCR_EN; // enable DMA channel

Now start the timer issuing

TIM5->CR1 = TIM_CR1_CEN;

If that works, we can continue with enabling the timer on an external trigger next time. (or you can find it among my recent posts)