cancel
Showing results for 
Search instead for 
Did you mean: 

Setting up ADC on Regular Triple Simultaneous mode with DMA on STM32F429

feliperibas
Associate II

Hi, I'm having really a hard time setting up my ADCs to work with DMA using the STM32F429I-DISC1. My objective:

 

have a clock source from a Timer at a given frequency as trigger to the ADCs, each ADC would then read a specific pin (let's say ADC1-> pin_x; ADC2->pin_y and ADC3->pin_z). Since nothing is working regardless of the configuration I set, I'm trying with very big time intervals to avoid busy bus and so on. So I've set the TIMER8 to have a period of 5s and tested it individually with the interrupt enabled to make sure it is triggering and having the correct interval. Now the idea is to trigger the ADCs to perform a few conversions of the pins in order to do some oversampling. 

Here is my ADCs configurations:

image.png

 

image.png

 

 

image.png

 

 

image.png

 

The ADC3 is identical to ADC2. Timer8 Trigger Event Selection is set to "Update Event".

 

Here is my main function:

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

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

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

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

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_CRC_Init();
  MX_I2C3_Init();
  MX_SPI5_Init();
  MX_FMC_Init();
  MX_LTDC_Init();
  MX_DMA2D_Init();
  MX_ADC2_Init();
  MX_ADC3_Init();
  MX_ADC1_Init();
  MX_USB_OTG_HS_HCD_Init();
  MX_TIM8_Init();
  MX_TouchGFX_Init();
  /* Call PreOsInit function */
  MX_TouchGFX_PreOSInit();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of defaultTask */
  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* creation of GUI_Task */
  GUI_TaskHandle = osThreadNew(TouchGFX_Task, NULL, &GUI_Task_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

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

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

 

My ADC initializing functions:

static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_MultiModeTypeDef multimode = {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_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure the ADC multi-mode
  */
  multimode.Mode = ADC_TRIPLEMODE_REGSIMULT;
  multimode.DMAAccessMode = ADC_DMAACCESSMODE_1;
  multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_5CYCLES;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
  if(HAL_ADCEx_MultiModeStart_DMA(&hadc1, test, 3) != HAL_OK)
  //if(HAL_ADCEx_MultiModeStart_DMA(&hadc1, test, 5) != HAL_OK)
  //if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&(adc_L.val), 1) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  /* USER CODE END ADC1_Init 2 */

}

/**
  * @brief ADC2 Initialization Function
  *  None
  * @retval None
  */
static void MX_ADC2_Init(void)
{

  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.ScanConvMode = DISABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 1;
  hadc2.Init.DMAContinuousRequests = DISABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */
  //if(HAL_ADC_Start_DMA(&hadc2, (uint32_t*)&(adc_R.val), 1) != HAL_OK)
  if(HAL_ADC_Start(&hadc2) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  /* USER CODE END ADC2_Init 2 */

}

/**
  * @brief ADC3 Initialization Function
  *  None
  * @retval None
  */
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 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc3.Instance = ADC3;
  hadc3.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc3.Init.Resolution = ADC_RESOLUTION_12B;
  hadc3.Init.ScanConvMode = DISABLE;
  hadc3.Init.ContinuousConvMode = DISABLE;
  hadc3.Init.DiscontinuousConvMode = DISABLE;
  hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc3.Init.NbrOfConversion = 1;
  hadc3.Init.DMAContinuousRequests = DISABLE;
  hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc3) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC3_Init 2 */
  //if(HAL_ADC_Start_DMA(&hadc3, (uint32_t*)&(adc_M.val), 1) != HAL_OK)
  if(HAL_ADC_Start(&hadc3) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  /* USER CODE END ADC3_Init 2 */

}

 

Timer8 initializing function:

static void MX_TIM8_Init(void)
{

  /* USER CODE BEGIN TIM8_Init 0 */

  /* USER CODE END TIM8_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM8_Init 1 */

  /* USER CODE END TIM8_Init 1 */
  htim8.Instance = TIM8;
  htim8.Init.Prescaler = 14399;
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim8.Init.Period = 49999;
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim8.Init.RepetitionCounter = 0;
  htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim8) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim8, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM8_Init 2 */
  HAL_TIM_Base_Start(&htim8);
  /* USER CODE END TIM8_Init 2 */

}

 

Problem is: it only fires HAL_ADC_ConvCpltCallback once at the beginning of the debugging and not anymore. My "test" variable is defined as "uint32_t test[3];".

 

I've already tried almost all combinations possible... tried changing EOC flag to "end of all conversions", tried circular and normal DMA modes, tried also some other ADC modes (continuous, injected, scan etc) but I only get one of the following results:

- either the callback keeps getting inifintely called blocking the rest of the application and leading to ADC overrun or;

- it triggers once and never more

Also pausing the debug session shows me that the results stored on the test variable are the ones from the beginning of the session, regardless if I change the voltage level ate the ADC pins. What am I missing here?

9 REPLIES 9
TDK
Super User

> either the callback keeps getting inifintely called blocking the rest of the application and leading to ADC overrun

With circular mode enabled, you will need to slow down the rate of conversions so the CPU can keep up. Increase the sampling time and/or use a larger buffer for storage so the interrupts get called less often.

If you feel a post has answered your question, please click "Accept as Solution".
feliperibas
Associate II

That was the initial problem, using DMA Continuous Requests as Enabled with Circular DMA, was causing it to infinitely trigger, regardless of the ADC conversion rate. Now that the ADC is triggered by a Timer with a very large period (multiple seconds), my expectation was that the ADCs perform the conversion very fast due to its ADC_CLK being very high, and that the DMA should transfer this result to the memory. If that would work, I could start thinking about increasing the sampling rate of the ADC but even with this current one, it is not working. Definitely not because the CPU is not keeping up, since we are talking about seconds. Or have I misunderstood your point?

Have you found a solution for this? I am having this exact problem, with these exact two outcomes (either my adc callback fires once and never again, or it fires continuously and floods things so that no other code can run). I too have tried almost every permutation of settings I can think to try, and both ChatGPT and Claude are unable to offer insight into what might be wrong.

 

thanks!

sb_st
Senior

(I don't mean to hijack this thread - it seems similar enough to what I am trying to do that I'd like to amplify it, but not sure if that means making a new post about it). 

I think the sticky bit here is that:

  • We want to trigger the ADC periodically with a timer. So we start the ADC once, then let the timer trigger it periodically
  • If DMA is in circular mode, the 'trigger' part of things becomes irrelevant - once the DMA is started, it just fires the DMA complete interrupt constantly once the buffer is full. It doesn't seem to "convert once, then stop and wait for the next trigger". This is consistent with the way DMA behaves in other contexts, I guess, but isn't quite what we want here. But:
  • If DMA is in normal mode, it 'kicks off' once one ADC read is complete. The timer trigger does not 'restart' it, and (to my knowledge) there's no "access point" for restarting the DMA automatically at the timer trigger point. As far as I can tell, we have to:
    • Set the timer trigger up
    • start DMA in normal mode
    • allow the trigger to fire the ADC, which in turn triggers the DMA read (?)
    • In/after the DMA complete interrupt, we read the results, then need to re-start the DMA
    • Wait for the next trigger moment?

This all seems a little convoluted compared to how the triggering machinery works outside of the DMA context...so convoluted that I'm quite sure I am misunderstanding something. 

TDK
Super User

Perhaps get the examples working, then use those as a base:

STM32CubeF4/Projects/STM32446E_EVAL/Examples/ADC/ADC_TripleModeInterleaved/Src/main.c at 34f54dd3b7ce267453350d1bb0fe5b35d623774d · STMicroelectronics/STM32CubeF4

Quite a few differences between those and the posted code.

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

> If DMA is in normal mode, it 'kicks off' once one ADC read is complete. The timer trigger does not 'restart' it, and (to my knowledge) there's no "access point" for restarting the DMA automatically at the timer trigger point.

Which is not the way things work, as I understand it.
You would need to trigger the ADC to get this chain going, not DMA.

Assuming the F429 is not too dissimiliar from the F407, here a snapshot from the reference manual of the latter:

Ozone_0-1762179071060.png

I have always used DMA in circular mode for this purpose, although with a larger number of transfers at once.
This should work as well in dual and triple mode.

IMHO dual and triple modes only make sense if you need a set of ADC channel data in shorter intervals than possible by one ADC alone. I never used it.

feliperibas
Associate II

The whole topic is for me too confusing for multiple reasons.

 

Some parts of the documentation seems to say that DMA in Circular mode is used when you do not want to set up the DMA every time manually after each triggered conversion, which was exactly my initial purpose. Also, this is different than having the ADC mode with "Continuous Mode: Enabled", which would trigger infinitely conversions after the last one is done (which is NOT what I want). So it seemed clear that Continuous Mode: Disabled with DMA Mode: Circular is exactly what I need. But it seems this circular mode keeps generating requests even if no new conversion is done

Setting the the DMA Mode to Normal, on the other hand, would seem to solve the problem as long as I keep activating the DMA manually after every trigger. I did try this option as well but it did not work, it only triggered once. Here I must admit, perhaps I've activated the DMA in a wrong way after the trigger or forgot to clear/set important flags, not sure.

In the end, responding to your question @sb_st , no I did not find a solution so I just gave up this freaking DMA usage for this case. I found the whole documentation quite poor on covering the multiple mode of the ADCs. Also following the steps presented on document UM1725 did not help at all. Now I am running on Multimode with interrupt only, reading manually the values from the ADCs, which was also kind of cumbersom. The documentation of the ADC Extended driver seems to be the one with all MultiMode HAL functions, but guess what? No "HAL_ADCEx_MultiModeStart_IT()" exists, even though it seems possible to use Multimode with DMA Disabled.

At the User Manual itself if you go to ADC->Multimode ADC->Regular Simultaneous Mode->Triple mode sub-chapter, it is explicitely only mentioning DMA procedure, as if it would not be possible to use it without DMA.

Furthermore, at the ADC_CDR register explanation:

feliperibas_0-1762182601150.png

it seems the upper 2 bytes holds one conversion value whereas the lower 2 bytes, another converted value, on Multimode, alternating. But on the Multimode Chapter itself:

feliperibas_1-1762182674600.png

it leads me to believe the ADC_CDR is completely overwritten by a single result every time. Perhaps I just did not get anything about the whole concept of this ADC but as for now, the documentation is just ***. So I've set up manually the registers to work in a kind of Multimode Simultaneous Regular conversions with interrupt at the end and that's it.

 

> ... it leads me to believe the ADC_CDR is completely overwritten by a single result every time.

Yes, it is.
Which is exactly the same behavior as with ADC_DR and multi-channel conversion sequences.
You need to pick up the result immediately, or it will be overwritten.
ST has the ADC designed this way, and assumes you use DMA for more complex and time-consuming setups.

Just as a counterexample, the LPC14xx and LPC17xx devices I dealt with have individual result registers for each channel.

How is that? I mean, on one explanation it says the 4 bytes are split, being the top 2 one value and the bottom 2 another value. On the second explanation it says ADC_CDR[31:0] = ADCx_DR[15:0]. To be coherent with the first explanation, shouldnt it be ADC_CDR[15:0] = ACDx_DR[15:0] (besides maybe explicitely mentioning ADC_CDR[31:16] = ADC_CDR[15:0])? The way it is written seems it overwrites the whole 32bit ADC_CDR with the new ADCx_DR, reseting the upper 32:16 bits.