cancel
Showing results for 
Search instead for 
Did you mean: 

How to trigger a set number of ADC conversions with DMA using a hardware timer?

CDyer.1
Senior

Hi guys, I've posted a couple of questions in the past related to the following and I believe I'm 99% of the way there. For background:

I'm working on STM32F767 with STM32CubeIDE using HAL.

  • I have TIM2 set up as a PWM on both CH1 and CH2 with a period of 200us and a duty cycle of 25% for CH1 and approx. 30% for CH2.
  • I have ADC1 configured at 1.8 Msps.
  • What I want is, on the rising edge of PWM CH2 for ADC to trigger, DMA to read 50 samples (or whatever buffer size I eventually decide on. Right now it's 50) and then for the ADC/DMA to wait until the next rising edge of PWM CH2 to trigger the ADC/DMA for another 50 samples.

Simply put, I want the ADC buffer of size 50 to be filled every time PWM CH2 rises. Now, I've already achieved this with interrupts and polling but I want to leave the CPU out of it as much as possible. I want this process to have little overhead on the CPU as possible.

The problem: Once the first rising edge of the PWM CH2 activates the ADC just after board reset, it just runs forever converting the signal and the DMA updates the buffer. I want the PWM to constantly trigger the ADC or the DMA not to just trigger the ADC once and then run forever.

Main:

volatile uint16_t ADC_Val[50];// = {0};
volatile uint16_t ADC_Total[250] = {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. */
  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_ADC1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_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 */
}

set up and conversion complete callback where i toggle the GPIO for reference:

static 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_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_CC2;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  if (HAL_ADC_Init(&hadc1) != 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_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
 
  /* USER CODE END ADC1_Init 2 */
 
}
 
/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 20000;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 5000;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM2;
  sConfigOC.Pulse = 6000;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
 
  /* USER CODE END TIM2_Init 2 */
  HAL_TIM_MspPostInit(&htim2);
 
}
 
/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void) 
{
 
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
 
}
 
/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
 
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
 
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
 
  /*Configure GPIO pin : PA4 */
  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
  /*Configure GPIO pin : PB0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
  /*Configure GPIO pins : PD8 PD9 */
  GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
 
}
 
/* USER CODE BEGIN 4 */
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
 
    GPIOA->ODR ^= (1 << 4);
    ADC_flag ++;
    //ADC1->SR &= ~(1 << 0x4);
    asm("NOP");
 
}
 
/* 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 */
 
  /* USER CODE END Error_Handler_Debug */
}

Interrupt Handlers for completeness:

void ADC_IRQHandler(void)
{
  /* USER CODE BEGIN ADC_IRQn 0 */
 
  /* USER CODE END ADC_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC_IRQn 1 */
 
  /* USER CODE END ADC_IRQn 1 */
}
 
/**
  * @brief This function handles TIM2 global interrupt.
  */
void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */
 
  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */
 
  /* USER CODE END TIM2_IRQn 1 */
}
 
/**
  * @brief This function handles DMA2 stream0 global interrupt.
  */
void DMA2_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
 
  /* USER CODE END DMA2_Stream0_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_adc1);
  /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */
 
  DMA_flag ++;
 // memcpy(ADC_Total + conversion_flag, ADC_Val, sizeof(ADC_Total));
  
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  /* USER CODE END DMA2_Stream0_IRQn 1 */
}

I have set up a GPIO to toggle every time a conversion is made. TIM2 CH1 is yellow, TIM2 CH2 is blue and the adc complete GPIO toggle is purple. As you can see here, on the first ever rising edge of PWM CH2 the GPIO toggles due to the ADC completing its conversion. This perfect and I want this to repeat every rising edge. However, in the second image it doesn't toggle after the exact same time ever again. It is just constantly running the ADC and toggling without respect to the timer.

Perfect on the first trigger:

0693W000001svEMQAY.pngThen continues without timer trigger

0693W000001svDPQAY.png

I'm convinced that I'm 90% there and all i need to do is clear a bit in a register somewhere ready for the next timer trigger but the reference manual is not clear AT ALL so I've resulted to trial and error. Any help or ideas would be great. There doesn't seem to be any control over this function in the ADC_SR or ADC_CR1/CR2 registers. thanks.

8 REPLIES 8
RMcCa
Senior II

Ditch hal so you can make the chip do what you want, instead of someone else's idea of how to use it. Either use a gated timer to get the number of conversions you want or set the oversampler or use continous conversion mode with half/full dma buffer interrupts. Lots of options if you learn to use the register interface and spend time studying the manual.​

RMcCa
Senior II

Also, better to use the m4 core for this kind of thing.​

S.Ma
Principal

you also could use dma to generate your 50 adc burst pulses on another timer channel as compare toggle and feed this to adc with cyclic dma.

Hi, thanks for the reply. I do not have the time to learn the ins and outs of the microcontroller right now unfortunately. I would like to eventually, but right now my priorities are to get a code base set up so I can ​start doing DSP work.

Why would an M4 core be preferable? I started this project with an M4 but I need the extra clock speed and double precision FPU for DSP work later down the line.

Hi, thanks for the suggestion. Could you please go in to more detail? I'm not sure I understand how that would work? My system is not power limited so I've been considering leaving the ADC running perpetually and then using the timer event to trigger the DMA to transfer the 50 samples. Not sure how to do it yet.

RMcCa
Senior II

Study the block diagram of the chip in the reference manual. The d2 domain contains the dmas, the m4 and many peripherals. This was clearly intended to be used for real time peripheral control and interaction.

Your code will never work as it is for the m7 core if your buffers are in tcram ( which the should be.) Because the dmas can't access the tc memory connected to the m7. That's what the mdma is for. Why do i know any of this? Because I've spent hours RTFM.

Hal will never get you what you need. The h7s are very complicated. If you are not willing to spend the time to try and understand it maybe arduino is what you need.​

> The h7s are very complicated.

I'm using F7. Like you say, it's very complicated. The value in this project isn't whether I use low level or HAL. I can't take 6 months out of this project to go through a 2k page RM and DS and learn bare metal programming. I will have to do it in my own time. If HAL does the job for now then it is good enough. I would like to improve the code further down the line.

>If you are not willing to spend the time to try and understand it maybe arduino is what you need.

I don't really see the need for comments like this.