cancel
Showing results for 
Search instead for 
Did you mean: 

implement maximum speed adc to usb transfert

julien12657
Associate III

I am trying that :

sprintf((char*)buffer,"%d\r",HAL_ADC_GetValue(&hadc));

CDC_Transmit_FS(buffer,8);

but the speed is only 3 or 4kS/s

Someone has something better ?

53 REPLIES 53
S.Ma
Principal

Some embedded programming know how maybe missing here and following steps. Adc value can be stored in uint16_t 16 bit variable, made of 2x8 bytes. Just push these 2 bytes raw ro usb without any formatting by sprintf. And decidd if you send bit 0..7 or bit8..15 first.

julien12657
Associate III

Hi Bob,

I am using continuous conversion mode

hadc.Init.ContinuousConvMode = ENABLE;

and a sampling time of 239.5 cycles :

sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;

so I don't think I need to poll for conversion

julien12657
Associate III

I am totaly agree with you KIC8462852 EPIC204278916, but it is always more clear with a piece of real code, because usually what's causing troubles are the details.

S.Ma
Principal

Download Teraterm-like utility and using the com port, no need to make complex strings, just output the data and forget the 8 or 64 bytes granularity.

You'll have to create a char SW FIFO to accumulate the text you want to push to the console. Add markers like "/n to go to next like and be able to save your console output as CSV Excel format. send the data in plain HEXA format text first: every 16 bit ADC value will correspond to 4 char (16 bit) output. add a space between hex data and you'll be able to feed a spreadsheet. If you try to optimize the bandwidth and datarate, you'll be defocusing from having a baseline reference. Break the problem in smaller pieces to overcome step by step.

Bob S
Principal

> "so I don't think I need to poll for conversion"

Yes, you do. At the very least you need to check the EOC (end of conversion) flag and only call HAL_ADC_GetValue() when that flag is set (calling GetValue() will then clear that flag). This is what the HAL_ADC_PollForConversion() does. If you continually read the ADC's data register, which is what HAL_ADC_GetValue() does, you will keep reading the SAME value from the previous conversion until the next conversion happens. And the number of times you get that SAME value will not be consistent, because after some GetValue() calls you will end up calling the USB transmit function, or a USB interrupt will happen, or some other interrupt (system tick, etc.).

Which brings me to another point I had overlooked before. I don't know which CPU you are using, or what your clock speeds are, or what your conversion clock rate is (you only showed the "sample time" value). BUT... it is quite possible that all of the USB code (most of which runs from interrupts) could delay your polling loop so that you miss a sample. Ideally you would use either interrupts or DMA to transfer the ADC samples into a buffer. Then your main polling loop could take data from that buffer, format however you wish and send to the USB interface. If using interrupts you need to ensure that the ADC interrupt has a higher priority than the USB interrupt.

There are a few threads on this forum about using DMA with UARTS. The same idea applies to the ADC. Look for those threads and see if you can apply those ideas to your ADC.

julien12657
Associate III

KIC8462852 EPIC204278916, do you have a little piece of code, only one instruction to begin if you want.

Bob S
Principal

@S.Ma​ - What is the *significant* difference or advantage of outputting 4 ASCII hex characters followed by a "\r" or space, and outputting an ASCII decimal representation of the same values? Other than only needing 5 bytes total for each value instead of 7 bytes.

He HAS the baseline reference. His original code read one ADC value and sent it to the USB virtual com port. His issue is that the transfer rate was too slow. Hence all these messages about buffering data and sending to the USB in larger chunks. That *IS* the next step. Then worry about missing data samples due to polling delays. That is where interrupts or DMA comes in.

Hint about using DMA - configure the DMA for "circular" mode. You don't need any DMA interrupts, just let it keep filling and refilling your buffer. Your polling code looks for data in that buffer, extracts it, formats it as you like and adds it to your "pending USB data" buffer. Once your "pending USB data" buffer is full, send it. Then start filling it again from the DMA buffer.

julien12657
Associate III

I am using a STM32f072

the adc is configured as follow :

static void MX_ADC_Init(void)
{
 
  ADC_ChannelConfTypeDef sConfig;
 
    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = ENABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
}

I know how to use DMA for the ADC, I know how to configure it as circular, I will try some polling as you suggest...

Bob S
Principal

If you know how to use DMA circular mode with the ADC, the I would strongly suggest skipping the polling step (manually checking for ADC end-of-conversion then reading the data register) and go straight to the DMA configuration. You will need to end up using DMA or interrupts anyway to avoid polling delays possibly missing data. Using DMA, your "polling" then consists of checking to see if DMA has stored any new data in the circular buffer. The DMA will ensure you don't miss data samples (presuming your circular buffer is large enough).

julien12657
Associate III

I've tried this :

char buffer[64];
int ADC_buffer[1];
 HAL_ADC_Start_DMA(&hadc, (uint32_t *)ADC_buffer, 1);
 
  while (1)
  {
  
 
    snprintf( buffer, sizeof(buffer), "%6d\r", ADC_buffer[0] );
    CDC_Transmit_FS((uint8_t*)buffer,7);
 
 
  }

it works, but at the same speed as before (without DMA).