cancel
Showing results for 
Search instead for 
Did you mean: 

32F417 DMA double buffer mode

PHolt.1
Senior III

Hi All,

I have a waveform generator which uses TIM4 to trigger the DACs and they trigger DMA to transfer 32-bit words to the DACs (concurrently).

I am using the DMA double buffer mode. So I have two source pointers, which are supposed to get flipped each time the transfer count reaches zero.

				// 	CT is bit 19; 1 = wavebuf1 is being read, so we can update wavebuf0.
				// It was found that this check is not needed; there were no artefacts anyway.
 
				if ((DMA1_Stream5->CR & (1<<19)) != 0)
				{
					wavebuf0[i]=dac1val|(dac2val<<16);
				}
				else
				{
					wavebuf1[i]=dac1val|(dac2val<<16);
				}

I have it more or less running but found a couple of things which I'd like to clarify:

1) The double buffer mode seems pointless because one can write to the buffer(s) anyway at any time, with no artefacts on the output waveform. Am I right that this mode is intended for when you want to always start with a fresh and complete buffer? In lots of cases one could just use a single buffer and write to the top or the bottom half according to the value in the DMA address pointer.

2) AIUI, the DMA flips the CT bit (bit 19 in CR) at the end of each transfer and I think it does it when the transfer count reaches zero. So if one stops the transfer before the count reached zero, am I right that bit will never get flipped? That seems to be what I found, and I am now flipping it manually, and ensuring a) the DMA is disabled and b) it is done via a copy of CR and not by doing RMW on CR.

9 REPLIES 9
TDK
Guru

1) Double buffer mode is helpful if you want to change the location of the buffer mid-transmission, which you can't do with single buffer mode. Otherwise it's effectively equivalent.

2) Correct. Not sure what you mean by "copy of CR". How is that different than a RMW?

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

I keep a copy of the DMA CR throughout the code, so all writing to the CR is based on that copy. It was pointed out by Jan that sometimes bits don't read back correctly.

Thanks for the other info.

> It was pointed out by Jan that sometimes bits don't read back correctly.

I don't believe DMA_SxCR ever gives incorrect values when read. It's certainly specified in the RM as read and write.

Perhaps @Community member​ can expand. Is this errata or miscommunication or something else?

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

Talking about TIM ISR, I pointed out that TIMx_SR - as well as some other registers which are accessed from "both sides" i.e. "by software and by hardware", - are not to be RMW'd (including bit-banding).

In the particular case of control-register of the dual-port DMA stream, probably this does not apply, even if there are such bits there (EN bit actually reads from an internal flag which is delayed after the write; DBM is a bit being changed by hardware), but in this particular case there are hardware interlocks (EN for DBM, and the TC status bit for EN) which appear to effectively exclude any unexpected issue due to RMW.

I don't say RMW is universally bad, but that it should be used with caution. I personally find, that in most cases, I am able to simply write a constant, as I *know* what the content of the register is supposed to be.

JW

I'd agree with what JW is saying in the linked note. However, in this case it's not applicable as the SxCR register is of type rw, not rc_w1 or rc_w0, nor does it have any side effects from being read. No need to be paranoid that "bits don't read back correctly".

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

Am I right in that if I flip the CT bit, and reload the transfer count, while DMA is disabled, in effect **I** am determining which buffer the DMA will be reading from when it is re-enabled, and the DMA will never flip this bit by itself? Or if it did flip it (because it saw a zero byte count) this will in any case be overriden by me flipping the bit in the CR register copy and then writing that into CR? It seems to be working but obviously is hard to debug because all this activity is on-chip.

This is the ISR which is otherwise working perfectly:

/*
* TIM1 input compare event interrupt handler. Used by waveform period measurement function.
* Process TIM1 CC1IE (CH1 capture) interrupt, generated by CH1 capture of CNT into CCR1, which
* is generated from a +ve edge on PE9.
* There was a problem with jitter in the first two samples after the TIM1 interrupt occurs, which
* is probably related to the zeroing of TIM4 count which is itself necessary to get the subsequent
* output to have the first sample output with the proper constant width.
* Much time was spent on it and the best fix was the 320/SPC lenghtening of the TIM4 ARR, ensuring
* that TIM4 runs just a little slower than the "exact" time, so the last sample of the cycle ends
* *after* the end of the cycle, reliably. This hack has been tested with SPC=32/64/128/256.
*
*
*/
 
void TIM1_IC_CaptureISR(void)
{
 
	// Check wavegen (basically TIM4) is enabled and TIM1 has seen a wave capture
 
	if ( ( (TIM4->CR1 & 1)==1 ) && (TIM1->CCR1>0) && wavegen_set )
	{
 
		//TIM4->PSC = isr_tim_pre;				// This is replaced by TIM4->EGR=1 below
		//TIM4->CNT = 0;
 
		// Update period of wave gen derived from input reference (period=TIM1->CCR1)
		// This doesn't need doing often; it is only to track variations in the input frequency but
		// here we do it every cycle because it doesn't produce any jitter etc.
		// The full formula if pclk1 != pclk2 is wave_tc = (pclk1*2L*(ccr1/((pclk2*2)/32)))/SPC
		// The simpler one if pclk1=pclk2 (as we have here) is (ccr1*32)/SPC
		// 32 is the prescaler in the measuring code (chosen at 32 to support 50Hz)
 
		TIM4->ARR = (((  TIM1->CCR1 * 32L )/isr_num_values)) + (320/isr_num_values);
 
		// Reset DMA pointers and transfer count
 
		dma_s5_cr_copy &= ~1;					// disable DMA (to access its cfg regs) & avoiding RMW
		DMA1_Stream5->CR = dma_s5_cr_copy;
		//while ( (DMA1_Stream5->CR & 1) ==1) {}
 
		hang_around_us(1);						// wait for DMA to stop (delay not actually needed)
 
		DMA1->HISR = 0x0F40;					// 1111 0100 0000 - clear pending int flags (per RM)
 
		DMA1_Stream5->NDTR = isr_num_values; 	// buffer size
		DMA1_Stream5->M0AR = isr_buffer0;		// base of table 0
		DMA1_Stream5->M1AR = isr_buffer1;		// base of table 1
 
		// The CT bit gets flipped when the transfer count reaches zero, but here we may be
		// restarting DMA just before it got to zero, so we force it manually. It can be
		// flipped here because DMA is disabled. It gets flipped on each ISR regardless of which state
		// the DMA would have set it to, and determines the source for the next DMA transfer.
 
		dma_s5_cr_copy ^= (1<<19);				// flip CT bit
		DMA1_Stream5->CR = dma_s5_cr_copy;
 
		dma_s5_cr_copy |= 1;					// re-enable DMA
		DMA1_Stream5->CR = dma_s5_cr_copy;
 
		// Reset TIM4 prescaler+counter chain, to avoid 1-sample-width jitter on output
		// This also forces a TRGO event which does a DAC update
 
		TIM4->EGR=1;
 
	}
 
	TIM1->SR = 0;								// Clear CC1IF & UIF - clear pending interrupt
 
}

PHolt.1
Senior III

I think when one posts any source code, the changes of getting a reply tend to be zero : - )

Anyway I think the answer must be YES, based on a lot of hacking I did.

The double buffer mode is actually very tricky. If there is any accidental synchro between the cycle period and the table updating, you get really weird effects. You can easily get one table updated and the other one never updated, which will obviously trash the waveform being generated. A table update at say 50Hz will trash any generated waveform which is a multiple of 50Hz. But if you update the table at 49Hz, it all looks perfect. And it is easy, with an RTOS etc, to end up with some sync which you never expected. The product can run for hours or months and then it will totally break as some component elsewhere drifts. I have left the double buffer mode in there but put in a #define to fill both buffers with the same data and do it regardless of which buffer is in use and regardless of how far up the buffer the DMA pointers are. It produces no artefacts. Perhaps a good solution is to rig up another RTOS task which is explicitly set up to run at say 1/3 of the wavegen period so accidental sync is impossible (?). Or use the 32F4 random number generator : - )

Yes, if you set CT, you are determining where it goes. DMA flips the bit when the transfer is complete, as the RM states.

The RM also covers how to use double buffer mode properly in "10.3.9 Double buffer mode". Obviously if you don't update things in time, things will not work correctly.

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