cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G070 ADC continuous conversion with 6 channel with DMA circular buffer does not work

JCuna.1
Senior

I have a code for stm32g070 which configure the adc as continuous conversion mode, sequencer set to fully configurable, scan conversion mode, dma continuous request, end of sequence conversion, oversampling mode right shift to 5 and ratio 32x.

Also I configure the dma as circular mode with peripheral to memory direction, using an array buffer uint16_t with 1200 halfword size, and enabling half transfer, complete transfer, and error transfer interrupt.

When the conversion start, the half transfer rise, but the buffer is completely full, and after complete transfer end, the buffer is overwritten.

Here the code:

void my_adc_start_scanning_channels_with_dma(void){
	__IO uint32_t backup_setting_adc_dma_transfer = 0U;
	__IO uint32_t wait_loop_index = 0UL;
 
	if(LL_ADC_IsInternalRegulatorEnabled(MY_ADC_HANDLER) != 0){
		if(LL_ADC_IsEnabled(MY_ADC_HANDLER)){
			LL_ADC_Disable(MY_ADC_HANDLER);
 
			while(LL_ADC_IsDisableOngoing(MY_ADC_HANDLER) != 0);
		}
 
		LL_ADC_DisableInternalRegulator(MY_ADC_HANDLER);
	}
 
	if(LL_DMA_IsEnabledChannel(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL) != 0){
		LL_DMA_DisableChannel(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL);
	}
 
//	LL_DMAMUX_SetRequestID(MY_ADC_DMA_HANDLER, LL_DMAMUX_CHANNEL_0, LL_DMAMUX_REQ_ADC1);
 
	LL_DMA_SetMode(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL, LL_DMA_MODE_CIRCULAR);
 
	LL_DMA_SetDataTransferDirection(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
	LL_DMA_SetPeriphAddress(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL, LL_ADC_DMA_GetRegAddr(MY_ADC_HANDLER, LL_ADC_DMA_REG_REGULAR_DATA));
	LL_DMA_SetMemoryAddress(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL, (uint32_t)my_adc_buffer);
	LL_DMA_SetDataLength(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL, 1200);
 
	/* Enable DMA transfer interruption: transfer complete */
	LL_DMA_EnableIT_TC(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL);
 
	/* Enable DMA transfer interruption: half transfer */
	LL_DMA_EnableIT_HT(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL);
 
	/* Enable DMA transfer interruption: transfer error */
	LL_DMA_EnableIT_TE(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL);
 
	flag_my_adc_transfer_complete = 0;
	LL_DMA_EnableChannel(MY_ADC_DMA_HANDLER, MY_ADC_DMA_CHANNEL);
 
 
	// enabling voltage regulator for adc
	LL_ADC_EnableInternalRegulator(MY_ADC_HANDLER);
 
	/* Delay for ADC stabilization time */
	/* Wait loop initialization and execution */
	/* Note: Variable divided by 2 to compensate partially              */
	/*       CPU processing cycles, scaling in us split to not          */
	/*       exceed 32 bits register capacity and handle low frequency. */
	wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US / 10UL) * ((SystemCoreClock / (100000UL * 2UL)) + 1UL));
	while (wait_loop_index != 0UL){
	  wait_loop_index--;
	};
 
	/* Disable ADC DMA transfer request during calibration */
	/* Note: Specificity of this STM32 series: Calibration factor is          */
	/*       available in data register and also transferred by DMA.          */
	/*       To not insert ADC calibration factor among ADC conversion data   */
	/*       in DMA destination address, DMA transfer must be disabled during */
	/*       calibration.                                                     */
	backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(MY_ADC_HANDLER);
	LL_ADC_REG_SetDMATransfer(MY_ADC_HANDLER, LL_ADC_REG_DMA_TRANSFER_NONE);
 
	// Run ADC self calibration
	LL_ADC_StartCalibration(MY_ADC_HANDLER);
 
	while(LL_ADC_IsCalibrationOnGoing(MY_ADC_HANDLER) != 0);
 
	/* Restore ADC DMA transfer request after calibration */
	LL_ADC_REG_SetDMATransfer(MY_ADC_HANDLER, backup_setting_adc_dma_transfer);
 
    /* Delay between ADC end of calibration and ADC enable.                   */
    /* Note: Variable divided by 2 to compensate partially                    */
    /*       CPU processing cycles (depends on compilation optimization).     */
    wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);
    while(wait_loop_index != 0){
      wait_loop_index--;
    }
 
    /* Enable ADC */
	LL_ADC_Enable(MY_ADC_HANDLER);
 
	while (LL_ADC_IsActiveFlag_ADRDY(MY_ADC_HANDLER) == 0);
 
	LL_ADC_REG_StartConversion(MY_ADC_HANDLER);
}12

1 ACCEPTED SOLUTION

Accepted Solutions

When you stop the mcu in debugger, ADC and DMA keep running.

JW

View solution in original post

15 REPLIES 15
TDK
Guru

> When the conversion start, the half transfer rise, but the buffer is completely full, and after complete transfer end, the buffer is overwritten.

The ADC doesn't wait for your handler to complete before writing more data. If your conversions are fast, it is expected that more than half of the buffer will be populated by the time you get into your HT interrupt. If your code can't keep up with the rate of conversions, the buffer will be overwritten before you can process it.

Consider slowing the conversions down by increasing sampling time, or by using a timer to trigger conversions.

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

These are my sampling time calculation:

  • 6 channels
  • 12.5cycles sampling time
  • oversampling ratio 32x
  • synchronous clock mode divide by 2
  • system clock 64Mhz so the adc clock is 32Mhz
  • Buffer 1200, so 600 samples is half buffer, this is equivalent to 100 sequences because 6 channels

Time sampling for one sample: 32 * ((12.5 + 12.5) / 32000000) = 25us

Time sampling for one sequence: 6 * 25us = 150us

Time sampling for half complete buffer: 100 * 150us = 15ms

Time sampling for complete transfer buffer = 30ms

PD: the issue si the same with 32 size buffer or 1200 size buffer.

So there is no sense with this kind of time. This is a slow operation ought to oversampling ratio and 6 channels scanning.

I have a breakpoint in the half transfer callback function, and the buffer is complete in the first half transfer execution.

JCuna.1
Senior

I realize that the breakpoint for half transfer callback function is called before enabling adc, how is that possible?

ADC needs sw trigger to start conversion, or at least I understand that.

> I realize that the breakpoint for half transfer callback function is called before enabling adc, how is that possible?

After which point in the code above, exactly?

At that moment (or just before it), read out and check/post ADC, relevant DMAMUX and DMA registers content.

JW

JCuna.1
Senior

I am sorry, was a mistake. The compilation has -O2 flag enable so, when i create a breakpoint in the line of enabling adc, this is no created correctly and the function is already finish. So the adc already start the conversion.

However I am still having the issue with buffer filled completely when half transfer dma flag arise.

How do you know? How do you check that, exactly?

Also your calculation is incorrect. One sample takes up one position in the buffer (so a sequence of 6 samples takes up 6 positions in the buffer), so if one sample is converted in 25us, half of the buffer i.e. 600 samples are converted thus half of buffer is filled in 15ms.

JW

> "How do you know? How do you check that, exactly?"

I create a breakpoint in assembler compiled code just before of start adc conversion, so the half transfer dma interrupt does not trigger. After that part, the interrupt arise.

> "Also your calculation is incorrect. One sample takes up one position in the buffer (so a sequence of 6 samples takes up 6 positions in the buffer), so if one sample is converted in 25us, half of the buffer i.e. 600 samples are converted thus half of buffer is filled in 15ms."

You are right, I write down incorrectly my calculation. for a buffer of 1200 samples, 600 samples are equivalent to 100 completed sequences. So my calculation is like this:

150us * 100 = 15ms

15 ms is the time for half transfer interrupt, and 30 ms is for transfer complete interrupt

Anyway, this does not explain the strange behavior of adc filling the complete buffer of dma and rising a half transfer interrupt.

There is an explanation for what you're seeing, but I do not think it's in the code that you've presented. Providing a minimal working example that compiles would be helpful.

Or, as JW suggests, providing DMA and ADC registers at the time of the anomaly would shed some light.

I don't know how invested you are in the LL library, but I would ditch it in favor of either direct register access or HAL functions.

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

To be sure about the time calculation, i put a toggle pin code in half and complete callback functions. Then I snif the pin and the result is very accurate.

0693W00000GZ7PCQA1.png