cancel
Showing results for 
Search instead for 
Did you mean: 

Speed-up data streaming from ADC to USART in DMA mode (using FIFO?)

GSp
Associate II

Hi everyone,

I'm using STM32G071 and my goal is to serial send data acquired by 4 ADC channels as fast as possible.

In detail: I'm using DMA in circular mode to continuously acquire data from ADCs (ideally at 2,5MHz) and fill a buffer which is then sent over USART (baudrate 460800bit/s). I want to send a package of data when I reach 36 samples (9 samples for each of the 4 ADC channels). 

My problem is that the time needed to send that amount of data over USART (~1,5ms) is way longer than the time needed to fill the buffer with new ADC values (~15us) so I loose a large amount of data (the buffer is updated 100 times faster than my capability of sending it). With a circular buffer I will never be able to implement a data stream with no losses. I was thinking about these possible solutions:

1) Use a FIFO buffer instead of a circular buffer. I'm not sure on how could I do it with ADC_DMA and also I'm afraid of going into memory overflow: since my "consumer" is way slower than my "producer", wouldn't my FIFO become huge in a small amount of time?

2) Use USB full speed instead of USART. In this case I should change my microcontroller from STM32G071 (which doesn't have USB full speed) to STM32G0B0. Would this solve the problem? in that case I wuold have a data package sent in ~52us (usb full speed has a f=12Mbit/s), so my consumer would be just slightly slower than my producer and I could be able to use a FIFO with no memory issues.

Can you give me some insight on how the FIFO mode works for DMA? Am I missing something in my reasoning? 

Thank you in advance to who will answer. 

I attach below my code:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 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"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

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

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TIMEOUT_TX 15
#define TIMEOUT_RX 2
#define ADC_BUF_LEN 4
/* USER CODE END PD */

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

/* USER CODE END PM */

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

/* USER CODE BEGIN PV */

 uint16_t adc_buf[ADC_BUF_LEN]; 
 unsigned short int channels[72]={0};
 uint8_t all_channels_evaluated_flag = 0;
 uint8_t float_to_string [50];
 uint8_t numChar;
 uint8_t Rx_data = 0;
 uint8_t contatore = 0;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(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. */
  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_USART2_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_buf,ADC_BUF_LEN);
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
   if(all_channels_evaluated_flag == 1){
  numChar =sprintf((char *)float_to_string, "?");// STRING START CHARACTER
  HAL_UART_Transmit(&huart2, float_to_string, numChar,TIMEOUT_TX);
  HAL_UART_Transmit(&huart2, (uint8_t *)channels,72,TIMEOUT_TX);
  numChar =sprintf((char *)float_to_string, "@");// STRING STOP CHARACTER
  HAL_UART_Transmit(&huart2, float_to_string, numChar,TIMEOUT_TX);
  all_channels_evaluated_flag = 0;
   }
   if(all_channels_evaluated_flag == 2){
  numChar =sprintf((char *)float_to_string, "?");
  HAL_UART_Transmit(&huart2, float_to_string, numChar,TIMEOUT_TX);
  HAL_UART_Transmit(&huart2, ((uint8_t *)channels + 72),72,TIMEOUT_TX);
  numChar =sprintf((char *)float_to_string, "@");
  HAL_UART_Transmit(&huart2, float_to_string, numChar,TIMEOUT_TX);
  all_channels_evaluated_flag = 0;
   }
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
channels[contatore] = adc_buf[0];
channels[contatore+1]= adc_buf[1];
channels[contatore+2]= adc_buf[2];
channels[contatore+3]= adc_buf[3];
contatore=contatore+4;
if(contatore==36){
all_channels_evaluated_flag=1;
}
if(contatore==72){
all_channels_evaluated_flag=2;
contatore=0;
}
}


/* 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 */
3 REPLIES 3
TDK
Guru

> My problem is that the time needed to send that amount of data over USART (~1,5ms) is way longer than the time needed to fill the buffer with new ADC values (~15us)

You've correctly identified the issue. Using a FIFO won't change or solve this issue in any way. You will either need to lower your sample rate, or find a different method to get data out, or live with gaps in the data.

USB FS will max out at around 5-8 Mbps with excellent coding. Note that USB is subject to random delays of tens of ms so you will need to buffer data.

 

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

thank you for your answer,

so the serial communication will always be my bottleneck? isn't there a way to bufferize data at higher speed before sending it at a slower baudrate? I thought that using USB full speed I could be able to dimension a FIFO buffer to do so. Maybe I could go slightly slower than 2,5MHz with ADCs, but not as slow as the serial transmission.

LCE
Principal

As TDK said, you identified the problem, but it seems you ignore your own findings... 😉

For a continuous data flow, even with ADC at 1 MHz at 8 bit per sample => 8 Mbit/s, USB FS will probably fail, no matter what kind of buffering magic you're trying.

So you need a faster interface than USB FS, or you do only some ADC measurements in intervals / bursts: measure "as long as your RAM allows", stop the ADC, send data, start ADC again.