cancel
Showing results for 
Search instead for 
Did you mean: 

Slow ADC with DMA

CDyer.1
Senior

Hi guys,

I'm using STM32F767 nucleo with STM32Cubeide. I've configured an ADC to operate with DMA at a sample rate of 1.8Msps. I've calculated this sample speed by:

HCLK = 216MHz

PCLK2 = 108MHz

Prescaler of 4 : ADCCLK = 27MHz

Resolution (12bits + 3)

Therefore sample rate = 27Mhz / 15 = 1.8Msps

The ADC is hardware triggered by TIM2 to sample 50 samples using DMA and I am toggling a GPIO pin when the 50 samples are collected by using HAL_ADC_ConvCpltCallback().

My issue: it takes 56us for the timer to trigger the ADC to start, toggle the respective GPIO, get the 50 samples and then toggle the GPIO again. While I expect some latency due to using the HAL and toggling pins, I'm expecting somewhere in the region of a handful of microseconds. At this speed I'm experiencing 50 samples/56us = 893Ksps about half the expected speed. Reducing the resolution to 10 bits while it made some difference, it wasn't as large a difference as it should have been. So something is wrong.

code:

int main(void)
{
  HAL_Init();
 
  SystemClock_Config();
 
  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_IT(&htim2, TIM_CHANNEL_2);
  HAL_NVIC_SetPriority(TIM2_IRQn,15,0);
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
 
 
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 216;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Activate the Over-Drive mode 
  */
  if (HAL_PWREx_EnableOverDrive() != 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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)
  {
    Error_Handler();
  }
}
 
/**
  * @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 */
 
  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_RESET;
  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.Pulse = 7500;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
  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);
 
}
 
/* USER CODE BEGIN 4 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
 
	if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
	{
		GPIOB->ODR ^= (1 << 4);
		asm("NOP");
 
	}
 
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	GPIOB->ODR ^= (1 << 4);
	GPIOA->ODR ^= (1 << 4);
	if(pulse_count == 0)
	{
	HAL_ADC_Stop_DMA(&hadc1);//, ADC_Val, sizeof(ADC_Val));
	memcpy(ADC_Total_1, ADC_Val, sizeof(ADC_Total_1));
	HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
	pulse_count = 1;
	}
	else if(pulse_count == 1)
	{
		HAL_ADC_Stop_DMA(&hadc1);//, ADC_Val, sizeof(ADC_Val));
		memcpy(ADC_Total_2, ADC_Val, sizeof(ADC_Total_1));
		HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
		pulse_count = 2;
	}
	else if(pulse_count == 2)
	{
		HAL_ADC_Stop_DMA(&hadc1);//, ADC_Val, sizeof(ADC_Val));
		memcpy(ADC_Total_3, ADC_Val, sizeof(ADC_Total_1));
		HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
		pulse_count  = 3;
	}
	else if(pulse_count == 3)
	{
		HAL_ADC_Stop_DMA(&hadc1);//, ADC_Val, sizeof(ADC_Val));
		memcpy(ADC_Total_4, ADC_Val, sizeof(ADC_Total_1));
		HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
		pulse_count = 4 ;
	}
	else if(pulse_count == 4) {
		HAL_ADC_Stop_DMA(&hadc1);//, ADC_Val, sizeof(ADC_Val));
		memcpy(ADC_Total_5, ADC_Val, sizeof(ADC_Total_1));
		HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
		pulse_count = 0 ;
	}
	GPIOA->ODR ^= (1 << 4);
	asm("NOP");
}
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
	//GPIOB->ODR ^= (1 << 4);
}

Is there anything obviously wrong in my setup code? It's strange as my calculations and setup (to me at least) seem correct.

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

> I just don't understand why this is.

The sizeof operator returns the number of bytes the object takes. It does not return the length of the array.

uint16_t ADC_Val[50];
assert(sizeof(ADC_Val) == 100);

So when you call HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val)), you are asking for 100 conversions, not 50.

There is a method to find the length of the array given its pointer:

assert(sizeof(ADC_Val) / sizeof(*ADC_Val) == 50);

but it may be better to just define it explicitly:

#define ADC_VAL_LENGTH 50;
uint16_t ADC_Val[ADC_VAL_LENGTH];
 
...
 
HAL_ADC_Start_DMA(&hadc1, ADC_Val, ADC_VAL_LENGTH),

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

View solution in original post

7 REPLIES 7
TDK
Guru

> HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));

You're probably taking 100 samples, not 50, and probably overrunning the buffer. How is ADC_Val defined?

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

Whoops, completely missed the declarations from the code:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
/* USER CODE END Includes */
 
 
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
 
TIM_HandleTypeDef htim2;
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM2_Init(void);
/* USER CODE BEGIN PFP */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint16_t ADC_Val[50];
uint16_t ADC_Total_1[50];
uint8_t pulse_count = 0;
 
/* USER CODE END 0 */

As you can see, ADC_Val is defined as a uint16_t array with 50 values. I don't think I'm overrunning the buffer as the OVR bit in ADC1_SR is never set.

It is exactly as I guessed. You are taking 100 samples and overrunning the buffer. Luckily you have another buffer defined directly after it, so it is not causing issues for you yet.
If you feel a post has answered your question, please click "Accept as Solution".

Perhaps my understanding is fundamentally flawed would you be willing to explain why I am taking 100 samples? We can agree that the buffer ADC_Val is an uint16_t array of 50. My understanding is that once the ADC is enabled, the DMA would fill the buffer assigned to it and then generate an interrupt indicating that the end of the buffer had been reached. In this case ADC_Val which has 50 elements from the ADC so the interrupt would be generated once the 50 samples had been taken? The DMA is circular so once the buffer has been filled, the DMA starts from the first address of the buffer and then does it all again. The interrupt is dealt with in the DMA handler but HAL_ADC_ConvCpltCallback() is also called once the interrupt had been cleared. This is when I look at the data. Any help would be great.

Also, checking ADC_Val when half the buffer is filled with HAL_ADC_ConvHalfCpltCallback() all 50 samples have been taken as you have suggested .So you're correct, I just don't understand why this is.

TDK
Guru

> I just don't understand why this is.

The sizeof operator returns the number of bytes the object takes. It does not return the length of the array.

uint16_t ADC_Val[50];
assert(sizeof(ADC_Val) == 100);

So when you call HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val)), you are asking for 100 conversions, not 50.

There is a method to find the length of the array given its pointer:

assert(sizeof(ADC_Val) / sizeof(*ADC_Val) == 50);

but it may be better to just define it explicitly:

#define ADC_VAL_LENGTH 50;
uint16_t ADC_Val[ADC_VAL_LENGTH];
 
...
 
HAL_ADC_Start_DMA(&hadc1, ADC_Val, ADC_VAL_LENGTH),

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

Ah, so it was a misunderstanding of how sizeof() operator works. A uint16_t array of size 50 is indeed 100 bytes and it makes perfect sense. I've defined it explicitly and now it works perfectly. I wouldn't have guessed the sizeof operator and that's showing my inexperience. Thanks for the help, much appreciated.