cancel
Showing results for 
Search instead for 
Did you mean: 

Subtle Q on 32F4 dual DAC (32 bit data feed) mode

PHolt.1
Senior III

I am finding that with DAC1 triggered from TIM5, DAC2 can be set either for the same trigger (the doc implies that) or for no trigger ("software" trigger). Both work.

I would have expected software trigger / no trigger to work on DAC2 because the 32-bit mode feeds both DACs directly, so DAC2 should not care what caused the load to it.

Obviously they can't both trigger the DMA transfer! But ST might have put in logic to prevent DAC2 running unless its trigger is set the same as DAC1. And they could both trigger the DMA (although it would be pointless).

Entire code is below but my Q is around the init code for the two DACs. There is a bug in the code insertion on this forum and it clips a big chunk out but it is just a long comment and you can see the actual code.

void KDE_config_wave_gen( uint32_t *buffer, uint32_t num_values, uint16_t tim_pre, uint32_t tim_tc)
{
 
	// We get here with both DACs enabled from main.c, and outputting that init value
 
	TIM4->CR1 &= 0xfffe;				// disable TIM4 (DAC trigger, DAC then triggers DMA)
 
	// set both DAC outputs to Vref/2
	KDE_DAC_set_value(1, 2048);
	KDE_DAC_set_value(2, 2048);
 
	// DMA1_S5C5 EN=0 - disable DMA1 just in case (also needed at any time to write the address etc)
 
	DMA1_Stream5->CR &= 0xfffffffe;
	hang_around_us(10);					// this should wait on EN changing to 0, but is ok here
 
	// Config DAC1 for TIM5 trigger. DAC2 doesn't need anything because it gets loaded with the int32.
 
	DAC_ChannelConfTypeDef sConfig = {0};
	DAC_HandleTypeDef KDE_dac;
 
	KDE_dac.Instance = DAC;
	HAL_DAC_Init(&KDE_dac);
 
	// DAC1 config - gets fed with the DMA data (low 16 bits)
 
	sConfig.DAC_Trigger = DAC_TRIGGER_T5_TRGO;
	sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
	HAL_DAC_ConfigChannel(&KDE_dac, &sConfig, DAC_CHANNEL_1);
 
	// DAC2 config - gets fed with the DMA data (high 16 bits)
 
	sConfig.DAC_Trigger = DAC_TRIGGER_SOFTWARE;			// doc asks for DAC_TRIGGER_T5_TRGO for DAC2!
	sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;	// but either works.
	HAL_DAC_ConfigChannel(&KDE_dac, &sConfig, DAC_CHANNEL_2);
 
	// The following appears to do a spurious data transfer to both DACs, zeroing them
 
	DAC->CR = (1u << 12)				// DMAEN1=1 - DAC1 DMA1 enabled
			 |(5u << 3)					// TSEL1=5 - DAC1 trigger source Timer 4 (Timer 4 TRGO event)
			 |(1u << 2);				// TEN1=1 - DAC1 trigger enabled
 
	// set both DAC outputs to Vref/2 again, due to the above (there is a very short spike)
 
	KDE_DAC_set_value(1, 2048);
	KDE_DAC_set_value(2, 2048);
 
	// Timer 4 config - triggers both DACs
 
	RCC->APB1ENR |= (1u << 2);			// TIM4EN=1 - TIM 4 clock enable
	TIM4->CR1 = (1u << 7);				// Auto reload register (sample period) is buffered
	TIM4->CR2 = (2u << 4);				// master mode select for TRGO on update event
	TIM4->SMCR=0;						// disable slave mode, etc
	TIM4->PSC = tim_pre;				// prescaler for above
	TIM4->ARR = tim_tc;					// time constant
 
	// Config DMA1 Stream 7 Channel 7 to feed both DACs concurrently
 
	RCC->AHB1ENR |= (1u << 21);			// DMA1EN=1 - DMA1 clock enable
	hang_around_us(1);					// give it a chance to wake up
 
	DMA1->HIFCR = (0x03du << 8);		// clear Stream 5 interrupt flags
	DMA1_Stream5->CR = (0x07u << 25)	// DMA1, Stream5 channel 7 is the DAC
					  |(0u << 23)		// mburst single transfer
					  |(0u << 21)		// pburst single transfer
					  |(1u << 18)		// double buffer mode on
					  |(2u << 16)		// priority  = high
					  |(2u << 14)		// msize = 32 bits
					  |(2u << 11)		// psize = 32 bits (DAC using double register)
					  |(1u << 10)		// minc = 1
					  |(0u << 9)		// pinc = 0
					  |(1u << 8)		// circular mode buffering
					  |(1u << 6)		// memory to peripheral
					  |(0u << 5)		// dma is flow controller
					  |(0u << 4)		// no interrupts TCIE
					  |(0u << 3)		// no interrupts HTIE
					  |(0u << 2)		// no interrupts TEIE
					  |(0u << 1)		// no interrupts DMEIE
					  ;					// DMA is not yet enabled
	DMA1_Stream5->NDTR = num_values;					// buffer size
	DMA1_Stream5->PAR = (uint32_t) (&DAC1->DHR12RD);	// destination (DAC) address, 32 bit DAC mode
	DMA1_Stream5->M0AR = (uint32_t) buffer;				// both buffers are the same address
	DMA1_Stream5->M1AR = (uint32_t) buffer;				// code may alter this once the system is running
 
	DMA1_Stream5->CR |= 1;				// enable DMA to DACs
	TIM4->CR1 |= 1;						// start the timer
 
}

13 REPLIES 13

> I may not have made my Q clear. It concerns whether DAC2 needs any special config (trigger from a timer, or request a DMA) when the 32 bit mode is being used.

The Q is absolutely clear, the answer is, as so often, in careful reading of RM.

I've hinted previously:

>> What's content of DAC_CR.TEN2? (read: read its description in RM).

If DAC_CR=0x1102D, DAC_CR.TEN2 is zero, then according to its description:

0: DAC channel2 trigger disabled and data written into the DAC_DHRx register are

transferred one APB1 clock cycle later to the DAC_DOR2 register

In other words, whatever is the way how a value is written to the upper half of dual-data-holding-register (i.e. including DMA), it will eventually end up in the output-register.

The difference (maybe drawback) compared to setting where DAC2 would be triggered by the same trigger as DAC1 (i.e. if TEN2=1 and TSEL2 = TSEL1) is, that values to DAC2 are output almost immediately after DAC writes to the holding register, whereas to DAC1 are output when the trigger arrives. In short, DAC2 and DAC1 are outputs would be phase-shifted by roughly the trigger (TIM4 update) period.

JW

> But why:

> sConfig.DAC_Trigger = DAC_TRIGGER_T5_TRGO;

> Looks like you choose timer 5 as trigger, but you set up timer 4?

That's obviously evolutionary code, overriden later by direct write to DAC_CR.

DAC has only one relevant register (DAC_CR) to be configured in the vast majority of cases.

JW

PHolt.1
Senior III

Well spotted; I was really getting worried there 🙂 Yes I can see those original configs, using the HAL code, are basically superfluous except for the DAC_OUTPUTBUFFER_ENABLE which I can also do with 2 bits in the DAC CR.

The reason I am mixing HAL funcs with direct register writes is because the project had been worked on by two other people. The first one did the DAC funcs about a year ago (and used Cube MX to generate the code), and the 2nd one did the waveform generation project but he doesn't use Cube or HAL and does everything directly but he got it working on an ST development board which had a completely different conguration to my board. I paid him for the job but could not use it, other than as an example to follow, which I did.

Sounds like TSEL should be 5 for both DACs (the equivalent of DAC_TRIGGER_T4_TRGO for both), although at 400Hz the difference in timing will be totally negligible.

I did see the comment in the RM about DAC2 and the very slightly different clock delay but it doesn't actually state what the DAC2 config should be.

My next step will be to sync the output to an input reference waveform, whose period is measured with TIM1. That all works great (another thread I posted here) and the plan is to read TIM1 CNT (in a critical code section), disable the above DMA, change the pointers to get the sync, and re-enable DMA.

PHolt.1
Senior III

Ok guys, apologies for the messy comments and fossil code. Here is the latest version. If anyone can spot anything wrong, I am "all ears" but it does work.

void KDE_config_wave_gen( uint32_t *buffer, uint32_t num_values, uint16_t tim_pre, uint32_t tim_tc)
{
 
	// We get here with both DACs enabled from main.c, and outputting that init value
	// which can be defined in config.ini under dac_init
 
	TIM4->CR1 &= 0xfffe;				// disable TIM4 (DAC trigger, DAC then triggers DMA)
 
	// DMA1_S5C5 EN=0 - disable DMA1 just in case (also needed at any time to write the address etc)
 
	DMA1_Stream5->CR &= 0xfffffffe;
	hang_around_us(10);					// this should wait on EN changing to 0, but is ok here
 
	// CR starts with BOFF1=BOFF2=0 (buffers enabled) and EN1=EN2=0 (DACs disabled)
	// Both DAC outputs fall to zero, presumably because they were disabled, so there
	// is a very short spike before wave generation starts.
 
	DAC->CR = (1u << 12)				// DMAEN1=1 - DAC1 DMA1 enabled
			 |(5u << 3)					// TSEL1=5 - DAC1 trigger source Timer 4 (TRGO event)
			 |(5u << 19)				// TSEL2=5 - DAC2 trigger source Timer 4 (TRGO event)
			 |(1u << 2)					// TEN1=1 - DAC1 trigger enabled
			 |(1u << 18);				// TEN2=1 - DAC2 trigger enabled
 
	// Timer 4 config
 
	RCC->APB1ENR |= (1u << 2);			// TIM4EN=1 - TIM 4 clock enable
	TIM4->CR1 = (1u << 7);				// Auto reload register (sample period) is buffered
	TIM4->CR2 = (2u << 4);				// master mode select for TRGO on update event
	TIM4->SMCR=0;						// disable slave mode, etc
	TIM4->PSC = tim_pre;				// prescaler for above
	TIM4->ARR = tim_tc;					// time constant
 
	// Config DMA1 Stream 7 Channel 7 to feed both DACs concurrently
 
	RCC->AHB1ENR |= (1u << 21);			// DMA1EN=1 - DMA1 clock enable
	hang_around_us(1);					// give it a chance to wake up
 
	DMA1->HIFCR = (0x03du << 8);		// clear Stream 5 interrupt flags
	DMA1_Stream5->CR = (0x07u << 25)	// DMA1, Stream5 channel 7 is the DAC
					  |(0u << 23)		// mburst single transfer
					  |(0u << 21)		// pburst single transfer
					  |(1u << 18)		// double buffer mode on
					  |(2u << 16)		// priority  = high
					  |(2u << 14)		// msize = 32 bits
					  |(2u << 11)		// psize = 32 bits (DAC using double register)
					  |(1u << 10)		// minc = 1
					  |(0u << 9)		// pinc = 0
					  |(1u << 8)		// circular mode buffering
					  |(1u << 6)		// memory to peripheral
					  |(0u << 5)		// dma is flow controller
					  |(0u << 4)		// no interrupts TCIE
					  |(0u << 3)		// no interrupts HTIE
					  |(0u << 2)		// no interrupts TEIE
					  |(0u << 1)		// no interrupts DMEIE
					  ;					// DMA is not yet enabled
	DMA1_Stream5->NDTR = num_values;					// buffer size
	DMA1_Stream5->PAR = (uint32_t) (&DAC1->DHR12RD);	// destination (DAC) address, 32 bit DAC mode
	DMA1_Stream5->M0AR = (uint32_t) buffer;				// both buffers are the same address
	DMA1_Stream5->M1AR = (uint32_t) buffer;				// code may alter this once the system is running
 
	DMA1_Stream5->CR |= 1;				// enable DMA to DACs
	DAC->CR |= (1<<16) | (1<<0);		// EN1=EN2=1 (enable both DACs)
	TIM4->CR1 |= 1;						// start the timer - this kicks off wave gen
 
}