cancel
Showing results for 
Search instead for 
Did you mean: 

Reducing DAC jitter

SSmit.0
Associate II

Hi,

I'm writing data to a 24 bit register of an AD5664 DAC from an STM32F429 discovery board which I am using to produce sinewaves and slower sawtooth ramps. The full loop lasts approximately 200000 bytes which gives sufficient resolution over all waveforms.

At the moment I am getting quite a lot of jitter on the sinewaves of about 2.5hz at approx 400Hz sinewave frequency. Ideally I'd like below 0.1Hz if possible via this method.  

I have streamlined the loop to be as small as possible but the jitter is still similar to my previous much longer loop. I've tried HAL_SPI_Transmit, HAL_SPI_Transmit_IT which was too slow to reach 400Hz, DMA didn't produce data that worked with the DAC for some reason. I'm not sure it would help much though. I've stripped quite a lot of code out from the HAL_SPI_Transmit function which has greatly increased my effective resolution for a given freq, but not so much the jitter. I also tried using a one pulse timer for the NSS/SYNC line but it actually seemed to perform worse.

Is there a way to reduce the jitter further? I understand there will be quite a large number of instruction cycles involved so perhaps I am quite close to what can be achieved for a relatively complex operation. 

for(p=0;p<200000;p+=3) 
{
GPIOG->BSRR = 0x10000000;		// NSS/SYNC low
//if (hspi1.State != HAL_SPI_STATE_BUSY_RX)  //Commented out as working without 
HAL_SPI_Transmit(&hspi1,&first[p],3,0); // Send a 24 bit packet as 3x8 bytes
GPIOG->BSRR = 0x1000;			// NSS/SYNC high 
**Accurate 15uS delay code here**	
}

I need to change between different modes where the sinewave DAC channel changes to a sawtooth, etc which is why I'm not just smoothing out square waves or using an external generator IC. Any help would be greatly appreciated. 

Thanks,

16 REPLIES 16
RMcCa
Senior II

I'm pretty sure you need to use a timer to establish a constant sample rate. Use the timer update interrupt to start dma spi transmission. Setting a flag in the isr can signal the main code that it is time to compute the next sample. If you use direct digital synthesis (dds) you can generate any waveform at up to 1/2 the sample rate.

TDK
Guru

Put the HAL_SPI_Transmit inside of a timer interrupt handler and set that timer to trigger at the desired rate.

You could also use DWT->CYCCNT timer as a more accurate clock source. You don't really want to delay 15us, what you want to do is to start HAL_SPI_Transmit at fixed intervals, so check for that condition instead.

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

Many thanks for the replies, just to clarify TDK, I assume you mean use DWT->CYCCNT as the timer for triggering the interrupt? Not the SPI clock or NSS line?

Thanks

TDK
Guru

No, just wait right before HAL_Transmit for the right time, then increment it for the next wait. No interrupts in this case. Something like:

static uint32_t next_trigger = DWT->CYCCNT;
while (next_trigger - DWT->CYCCNT < 0x80000000U) {
}
next_trigger += ...;
HAL_Transmit...

 But using a timer interrupt instead would have slightly less jitter.

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

Thanks for the clarification. I tried the code below but the performance was similar, I think I still need a for loop around it for accessing the array which may negate some accuracy of using a timer.

for(p=0;p<191400;p+=3) 
{
	while (next_trigger - DWT->CYCCNT < 0x80000000U) {}
	next_trigger +=500;
	GPIOG->BSRR = 0x10000000;
	HAL_SPI_Transmit(&hspi1,&first[p],3,0);
	GPIOG->BSRR = 0x1000;
	p+=3;
}

Next I tried 

void TIM8_TRG_COM_TIM14_IRQHandler(void)
{
 HAL_TIM_IRQHandler(&htim14);
 
 GPIOG->BSRR = 0x10000000;
 HAL_SPI_Transmit(&hspi1,&first[p],3,0);
 GPIOG->BSRR = 0x1000;
 p+=3;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if(p==191400)		// ensure loop after end of sequence
 p=0;		
}
static void MX_TIM14_Init(void)
{
 htim14.Instance = TIM14;
 htim14.Init.Prescaler = 150;
 htim14.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim14.Init.Period = 1;
 htim14.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 if (HAL_TIM_Base_Init(&htim14) != HAL_OK)
}

Again, the performance is similar to all other methods. Is there something else I'm doing wrong? I haven't tried SPI_Transmit_DMA yet since it wasn't producing valid data, I'm not sure the results would be much of an improvement though. 

Thanks

TDK
Guru

> next_trigger +=500;

500 ticks is not a lot. My guess is the HAL_Transmit_SPI takes longer than this. I didn't realize you were transmitting so quickly. 500 ticks over 15us is only 33MHz. Why isn't your system clock higher?

I don't think the HAL functions are going to work for you if you need this tight timing. Better off using registers directly and disabling interrupts during the code. I don't think you're going to get better accuracy than using DWT->CYCCNT, with optimized code with a software-only solution. With hardware, you could hook up two slave SPI peripherals to provide SCK and MOSI from their MISO pins, and then drive those peripherals with a constant PWM signal. That would be zero jitter (aside from system clock jitter). Maybe there is something else I'm not thinking of.

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

And again a perfect job for the wonderful and flexible SAI peripheral and again everyone ignores it... Just look at RM0090 section 29.7. Couple it with DMA and you've got a jitter free fully hardware solution.

> Is there something else I'm doing wrong?

Yes - you are using HAL! Drop that crap, start writing real code and enjoy the real performance.

SSmit.0
Associate II

Hi

Thank you for your responses. TDK, my maximum possible frequency with 8-bit resolution is only 670Hz via the SPI loop without any delay between words. The while (next_trigger - DWT->CYCCNT < 0x80000000U) {} simply acts to slow the loop down so only 500 cycles / approx a few us at 180MHz is needed before the output frequency from the DAC has dropped to 400Hz.

Piranha thank you for your suggestion, I had not heard of i2s before. I appreciate CubeMX isn't ideal coding but I'm on a big development project with changing requirements, of which the coding is a small part. It appears possible to configure SAI within CubeMX so this would be a good start.

I have done some reading on the protocol. I'm not sure if it can be configured to be compatible with my SPI DAC? The main problem is that it is designed for audio channels / slots which are addressed by toggling the FS line over alternate whole words. The AD5664r requires a short toggle between words as per standard SPI, as attached.

With standard i2s I would need to have total of 25 clocks, with one extra clock for transition of WS. Or, toggle WS within less than 1/2 sck cycle and still reliably register the first bit of a word. The may work at low speed but it would be a poor attempt.

DSP mode looks more suitable but would I need to be sending extra padding bits at end of each word to allow time for WS to change? Or is it possible to set the WS period equal to data length in this mode? This would be ideal as looks like the protocol would assign just 1 extra clock cycle to allow toggling of WS while briefly forcing the data line to 0. I.e it would achieve 25 clocks with one used for WS. I have attached some documentation I read referring to this.

Many thanks

0693W000001rKXtQAM.png

0693W000001rKZ6QAM.png

SSmit.0
Associate II

Hi,

I'd greatly appreciate some advice on the above, I've done some reading on the protocol but need a few pointers.

Thanks