cancel
Showing results for 
Search instead for 
Did you mean: 

ADC with Circular DMA works almost as expected. It is off by a 16-bit word. Can it be fixed?

KiptonM
Lead

I am reading 4 values, AN0, AN1, Temp, and the Reference. (But reference may go away.)

I am over sampling 16x. and eventually will be starting a sequence every 100 us.

I wanted two buffers, so I could work on one, while filling the other.

I thought a circular buffer would work. I make it 8 16-bit words and have a half-full buffer interrupt go when the first 4 words are done, and have the buffer-full interrupt go when the second 4 words are finished.

It does work that way.

The only issue is that it starts on second memory location not the first.

When I prefill the array with zeros, the array looks like this after the first transfer.

<invalid data> <data 0> <data 1> <data 2> <data 3> 0 0 0

And after the second DMA transfer it looks like this

<data 7> <data 0> <data 1> <data 2> <data 3> <data 4> <data 5> <data 6>

And after the third DMA Transfer it looks like this:

<data 7> <data 8> <data 9> <data 10> <data 11> <data 4> <data 5> <data 6>

Is there a way to fix this so the first data transfer starts on the first memory location? Or what am I doing wrong?

This is a STM32G031

And I am using HAL_ADC_Start_DMA() to start the DMA.

Here is what the ADC registers look like after the first transfer.

ADC_ISR: 200A

ADC_IER: 0010

ADC_CR: 10000001

ADC_CFGR1: 00000003

ADC_CFGR2: 2000000D

ADC_SMPR: 00000336

ADC_AWD1TR: 0FFF0000

ADC_AWD2TR: 0FFF0000

ADC_AWD3TR: 0FFF0000

ADC_CHSELR: 00003003

ADC_DR: 00005DAF

ADC_AWD2CR: 00000000

ADC_AWD3CR: 00000000

ADC_CALFACT: 00000035

ADC_COMMON_CCR: 00C00000

I think I can make it work the way it is, but it would be easier if it were aligned right.

12 REPLIES 12

Walk through the code up to the point where given DMA channel is enabled (yes, inside Cube, it's open source, you can debug it as your own code - in fact, at this moment, it already *is* your own code and your reponsibility).

At that point, the "regular" ADC conversions should not have been started yet, check this. Now look at the ADC registers - is there any status flag set which would trigger a DMA transfer? (I don't use the 'G0 and the ADC modules vary from family to family so you have to understand how DMA is triggered in 'G0 on your own).

Note, that debugging may be intrusive and status flags may be cleared upon reading status or data registers. Again, read the manual for the details.

JW

KiptonM
Lead

Actually that was a good idea. Unfortunately I have not found the problem.

What I did find was that the first word which I labeled <invalid data> was already in the ADC_DR register before I called the HAL_ADC_Start_DMA().

So I tried to read it before I called HAL_ADC_Start_DMA(). It did not make a difference. It still wrote it.

I will keep looking.

KiptonM
Lead

So this time I caught it. Just after you enable the DMA and before you start the ADC the DMA moves the value in the ADC_DR to the first position in the DMA output.

Now I need to find out why the DMA thinks there was a trigger. More reading. Unfortunately I do not completely trust STM32 documentation because of all of the errors I have found in the past.

KiptonM
Lead

Just before I went into the HAL_ADC_Start_DMA(). i printed the status of all the DMA registers out to a serial port and my terminal program. It had the DMA_ISR (DMA1->ISR) register = 0x00000000. But when I looked at the SFR debugger window it was 0x7000 which corresponds to 3 of the 4 interrupt flags for channel 5 which is used for the ADC.

When I tried to clear it with DMA1->IFCR = 0x00007000; according to the debugger window it did not clear. So now I do not know what is right. The debugger or my printing of the value.

sprintf(buf,"DMA_ISR: %08lX\r\n", DMA1->ISR);

KiptonM
Lead

I have wasted too much time on this.

I fixed it by moving the data in the ISR.

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
	//HAL_GPIO_WritePin();
	ADC_DMA_Buffer[0].word[0] = ADC_buffer.word[1];
	ADC_DMA_Buffer[0].word[1] = ADC_buffer.word[2];
	ADC_DMA_Buffer[0].word[2] = ADC_buffer.word[3];
	ADC_DMA_Buffer[0].word[3] = ADC_buffer.word[4];
	task_list.ADCa = 1;
	__NOP();
}
 
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	ADC_DMA_Buffer[1].word[0] = ADC_buffer.word[5];
	ADC_DMA_Buffer[1].word[1] = ADC_buffer.word[6];
	ADC_DMA_Buffer[1].word[2] = ADC_buffer.word[7];
	ADC_DMA_Buffer[1].word[3] = ADC_buffer.word[0];
	task_list.ADCb = 1;
	ADC1->ISR |= ADC_ISR_EOC;
	__NOP();
}

When I am debugging if I put a breakpoint at the __NOP() in either routine the 4th transfer does not work. But if I put the breakpoint on the first line of the ISR the whole routine works fine.

I cannot explain it.

Piranha
Chief II

> I looked at the SFR debugger window it was 0x7000 which corresponds to 3 of the 4 interrupt flags for channel 5 which is used for the ADC.

If I assume that you mean the ISR register, then 0x7000 are 3 bits of a channel 4.

@Piranha​ ,

ST notoriously mixes 0-based and 1-based indexing. Channels in single-port DMA in 'G0 are indexed 1-based, but they are controlled by the DMAMUX which are indexed 0-based, i.e. DMA channel 4 is controlled by DMAMUX channel 5.

A random IDE and random "libraries" may add more confusion to this, if they wish so.

JW

https://community.st.com/s/article/how-to-use-dmamux-on-the-stm32-microcontrollers

@KiptonM​ ,

I can't explain what you see based on your description, but using debugger may be misleading: besides potentially clearing flags as I said above, also note that when program execution is stopped in debugger, peripherals may still keep running.

JW

I had to check if I read it wrong or if I typed it wrong. It is the difference between 0x7000 and 0x70000.

Yes you are correct. It is 0x7000 which is for the UART, not the ADC.

Thank you for that catch.

So there are no interrupts pending when I start the DMA. So I still do not understand why it pulls the existing data from the DR register at startup and does not wait for the first ADC request.