Author Topic: I2S to SPI DAC with STM32F429  (Read 7001 times)

0 Members and 1 Guest are viewing this topic.

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
I2S to SPI DAC with STM32F429
« on: July 02, 2020, 04:17:00 pm »
Hi All,

I'm writing data to a 24 bit register of a 4 channel AD5664r DAC from an STM32F429 discovery board which I am using to produce sine waves and slower saw tooth ramps. The full loop lasts approximately 200000 bytes which gives sufficient resolution over all waveforms.

I am getting a lot of jitter on the sine waves of about 2.5hz at approx 400Hz sine wave frequency. Ideally I'd like below 0.1Hz but I've read that this won't be easy to achieve over SPI. I have already tried using DWT->CYCCNT timer and also a timer interrupt but the performance was still poor. 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.

I started looking into DAC's for audio and realised that i2s protocol is the way to go for low jitter. I've been using the HAL which I appreciate many experienced coders hate 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 for native i2s support 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 or if I need to replace it with a 4 channel i2s compliant part? They would be overkill for my needs though as it's not for audio purposes. 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 bodge.

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



« Last Edit: July 02, 2020, 06:10:13 pm by matt09 »
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: us
  • Yes, I do this for a living
Re: I2S to SPI DAC with STM32F429
« Reply #1 on: July 02, 2020, 06:07:35 pm »
I started looking into DAC's for audio and realised that i2s protocol is the way to go for low jitter.

For audio, it's not so much that I2S is the solution to jitter problems. Audio converters have a high-frequency (figure 12.288 MHz or 24.576 MHz) clock which drives the sigma-delta modulator. This clock must be low jitter. The two other I2S clocks, BCLK (which is the shift register clock) and LRCLK (which is the frame clock indicating left or right sample, and this runs at the sample rate) are usually divided down from that modulator clock because they must be synchronous to it.

How you can convince your STM32's synchronous serial ports to work in I2S mode, I don't know.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14447
  • Country: fr
Re: I2S to SPI DAC with STM32F429
« Reply #2 on: July 02, 2020, 06:12:19 pm »
The STM32F4 series fully supports I2S AFAIK. Sorry I don't have a code example right now at disposal, but read the reference manual. Also take a look at the provided Cube projects as there are examples for this.

As to the mode, there won't be any difference between I2S or any other access mode in terms of performance.
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #3 on: July 03, 2020, 02:38:44 pm »
Thanks for the replies, the STM32f429 has native support for i2s and is supported in CubeMX with the HAL.

I have already looked at the cube projects but there is only one which covers audio playback for a different evaluation board. It will certainly be useful when it comes to implementation but at the moment I need to understand what I am trying to do it feasible and achievable with the HAL. As I understand I need to use free protocol mode on the STM32.

Really my questions boil down to:

Can i2s be configured to be compatible with a standard SPI device, and is it possible using HAL under the free protocol mode?
Would DSP mode work and could I use it with just one extra clock to allow toggling of WS?
Or is there something else I'm missing that would be easier or more suitable?

I thought using i2s compared to SPI would result in far less jitter because everything is tied to a fixed sample rate defined by WS?

Thanks
« Last Edit: July 03, 2020, 02:47:08 pm by matt09 »
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: us
  • Yes, I do this for a living
Re: I2S to SPI DAC with STM32F429
« Reply #4 on: July 03, 2020, 04:18:14 pm »
I thought using i2s compared to SPI would result in far less jitter because everything is tied to a fixed sample rate defined by WS?

Again -- the important clock in an audio converter is the high-speed modulator (or master, or MCLK) clock.

The I2S interface is just the data transport.

But to ensure that the converter gets the data properly, the I2S signals (two clocks and one data for a DAC) must be synchronous to the master clock, and this is why BCLK and LRCLK are commonly divided down from MCLK.

You will probably want to run your STM32's port in slave mode. Some audio converters are capable of running in master or slave mode. In the former, the converter is the BCLK and LRCLK source. In the latter, something else is providing the clocks.
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: I2S to SPI DAC with STM32F429
« Reply #5 on: July 03, 2020, 04:20:24 pm »
Hi All,

I'm writing data to a 24 bit register of a 4 channel AD5664r DAC from an STM32F429 discovery board which I am using to produce sine waves and slower saw tooth ramps. The full loop lasts approximately 200000 bytes which gives sufficient resolution over all waveforms.

I am getting a lot of jitter on the sine waves of about 2.5hz at approx 400Hz sine wave frequency. Ideally I'd like below 0.1Hz but I've read that this won't be easy to achieve over SPI. I have already tried using DWT->CYCCNT timer and also a timer interrupt but the performance was still poor. 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.


 To have low jitter, you need transfer cycles to be initiated by hardware - timer. No interrupt for each byte, but you may use interrupt for blocks 2k - 16k bytes.
 Two options:
1. timer -> SPI transfer - > DMA linked memory array to spi;
2. timer -> DMA linked memory array -> spi SPI transfer;
 
 Personally, I prefer second variant.  Having timer as a master on transmitter side, allow 4 channels PWM pins be configured to drive any non standard SPI protocol, for example latch pin, sync, two channel switch that peripheral may need .
 Forget I2S, dummy interface designed for one specific task - stereo audio,

Arduino code, I tested with max5717:
Code: [Select]
  dac_Pins();

  TIM3_Config();
  if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  Serial.print(F("\n\tTim3 config done."));
  delay(100);

  TIM2_Config();
  Serial.print(F("\n\tTim2 config done."));
  delay(100);

  htim2.hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = TransferComplete;
  htim2.hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback = TransferError ;
  htim2.hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = TransferHalfComplete;
 
  if (HAL_DMA_Start_IT(htim2.hdma[TIM_DMA_ID_UPDATE], (uint32_t) out, SPI2_Write, (2 *OUT_BUFF)) != HAL_OK)
  {
    Error_Handler();
  }
Study HAL_DMA_Start_IT().
 

Offline fcb

  • Super Contributor
  • ***
  • Posts: 2117
  • Country: gb
  • Test instrument designer/G1YWC
    • Electron Plus
Re: I2S to SPI DAC with STM32F429
« Reply #6 on: July 03, 2020, 04:31:53 pm »
I might be missing something here - the AD5664R is not a native 'audio' DAC, it doesn't have it's own clock generator etc..

Jitter (in the truest sense of the phrase) will be largely dependent on the _SYNC quality fed to the IC.

However from what you describe you might want to look at you are generating the sine wave data, it should be super easy to get a pure tone with negliable wobble/jitter at almost any frequency, can you describe how you are generating the DAC data?
https://electron.plus Power Analysers, VI Signature Testers, Voltage References, Picoammeters, Curve Tracers.
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #7 on: July 03, 2020, 04:52:08 pm »

But to ensure that the converter gets the data properly, the I2S signals (two clocks and one data for a DAC) must be synchronous to the master clock, and this is why BCLK and LRCLK are commonly divided down from MCLK.

You will probably want to run your STM32's port in slave mode. Some audio converters are capable of running in master or slave mode. In the former, the converter is the BCLK and LRCLK source. In the latter, something else is providing the clocks.

This is why I thought it was the best solution for low jitter because MCLK defines the sample rate for all signals though division. This is what sets it apart from SPI. I'm not sure why you are saying to run the port in slave mode unless you mean to use a more accurate external clock source? The AD5664R is just a simple DAC and cannot be a master since it isn't capable of clock generation.
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #8 on: July 03, 2020, 05:00:33 pm »
I might be missing something here - the AD5664R is not a native 'audio' DAC, it doesn't have it's own clock generator etc..

Jitter (in the truest sense of the phrase) will be largely dependent on the _SYNC quality fed to the IC.

However from what you describe you might want to look at you are generating the sine wave data, it should be super easy to get a pure tone with negliable wobble/jitter at almost any frequency, can you describe how you are generating the DAC data?

First I tried the following:

Code: [Select]
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
 
Code: [Select]
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)
}

Both of these methods result in significant jitter on the output waveforms. 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 and had huge delays between packets, (fast once it's going but slow to get going). 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. I think the problem is that HAL_SPI_Transmit and its associated output pins are not tried to any sample rate which is why I thought i2s would fix my problem.

Thanks
« Last Edit: July 03, 2020, 05:02:33 pm by matt09 »
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #9 on: July 03, 2020, 05:43:39 pm »
To have low jitter, you need transfer cycles to be initiated by hardware - timer. No interrupt for each byte, but you may use interrupt for blocks 2k - 16k bytes.
 Two options:
1. timer -> SPI transfer - > DMA linked memory array to spi;
2. timer -> DMA linked memory array -> spi SPI transfer;

 Study HAL_DMA_Start_IT().

Thanks for the post MasterT. If I'm honest I don't quite understand your proposed options, please could you explain them in a little more detail?
I will have a look at HAL_DMA_Start_IT() in the meantime.

Thanks
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: us
  • Yes, I do this for a living
Re: I2S to SPI DAC with STM32F429
« Reply #10 on: July 03, 2020, 08:51:50 pm »

But to ensure that the converter gets the data properly, the I2S signals (two clocks and one data for a DAC) must be synchronous to the master clock, and this is why BCLK and LRCLK are commonly divided down from MCLK.

You will probably want to run your STM32's port in slave mode. Some audio converters are capable of running in master or slave mode. In the former, the converter is the BCLK and LRCLK source. In the latter, something else is providing the clocks.

This is why I thought it was the best solution for low jitter because MCLK defines the sample rate for all signals though division. This is what sets it apart from SPI. I'm not sure why you are saying to run the port in slave mode unless you mean to use a more accurate external clock source? The AD5664R is just a simple DAC and cannot be a master since it isn't capable of clock generation.

There are some important differences between DACs meant for audio and those meant for other purposes (control, trimpot replacement, whatnot).

The AD5664R is a resistor-ladder DAC. It gets loaded with a digital word and you get an output voltage from it. The DAC output updates on the 24th falling edge of the clock, but only if the command you shifted in says to do so. SYNC has nothing to do with it -- all that is required is that SYNC is low for the shift to be allowed. This means that any jitter in the output is wholly dependent on the regularity of your shift clock, and specifically whether the last falling edge occurs on a regular periodic basis which is your sample rate. Good luck with that.

Another thing to note is that the AD5664R doesn't have any output reconstruction filter at all. The DAC output is a zero-order hold; that is, the classical "stair steps" that the know-nothings associate with digital audio. The correct low-pass filtering must be applied to get something even remotely resembling the sine wave you want. The point here is that the AD5664R is not going to give you a clean sine wave output.

An audio DAC uses oversampling techniques and sigma-delta modulation to generate a proper continuous-time signal from the samples. The reconstruction filter is built into the converter. As long as you are feeding samples to it the DAC will give you an output.  A sine wave will look like a sine wave, not stair steps.

I've said that the modulator clock determines the jitter performance. That is the point. Some microcontrollers that have I2S modes for their synchronous serial ports are also capable of generating the proper modulator clock for the desired sample rate. Whether the jitter performance of that clock meets your requirements is up to you to decide. If your micro can generate MCLK, it should also be able to use that clock as the source for the dividers that generate BCLK and LRCLK and drive your data line. In this case, the micro is the I2S master and the DAC is set up as the slave, with all clocks as an input.

If your micro cannot generate the MCLK, you need an external oscillator at that frequency. (They are common.) In this case, it is easiest to choose a DAC that can operate in I2S master mode; that is, given an input MCLK and you set up the part for a desired sample rate, it will generate the BCLK and LRCLK that you give to your micro, which is set up in slave mode. The micro will drive the I2S data line synchronously with BCLK. Note! The micro in this case does not need MCLK! All it needs are the BCLK and LRCLK to know how to frame the bits and shift them out.

Anyway.

Given that you are generating a tone, you should consider ditching the AD5664R and use an audio DAC instead.
 
The following users thanked this post: newbrain

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8168
  • Country: fi
Re: I2S to SPI DAC with STM32F429
« Reply #11 on: July 03, 2020, 09:32:53 pm »
If you use SPI ADCs/DACs which use the serial clock as their sampling clock, the jitter performance indeed depends on the SPI timing. It may be impossible to be able to trig an SPI through timers / DMA to perform repeated, cycle-accurate transfer cycles. I gave up trying that, and wrote a cycle-accurate assembly routine instead to drive a 2MSPS SPI ADC, and this solution was surprisingly painless. It was on M7; on M4, it's easier because the instruction timings are more predictable. SPI peripheral itself seems cycle accurate and repeatable, even when accessed through the complex M7 bus matrix, so didn't need to bitbang individual bits, just access the SPI TX/RX registers at proper timings.

Of course, doing other things at the same time requires some careful planning; instead of looping some nops to wait, do something useful, but you need to count cycles here again.

This is close to how people did program video games decades ago. Sometimes it's just too tedious but not necessarily so. You can do it in C, then lock the object file down against compiler version variations once you have verified with scope what you need, but with optimizations enabled, the changes you make sometimes seem to make quite random changes to the timing so it might be just easier to write in asm. If unfamiliar, this is a good excuse to hone your skills!
« Last Edit: July 03, 2020, 09:35:07 pm by Siwastaja »
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: I2S to SPI DAC with STM32F429
« Reply #12 on: July 03, 2020, 10:23:10 pm »
To have low jitter, you need transfer cycles to be initiated by hardware - timer. No interrupt for each byte, but you may use interrupt for blocks 2k - 16k bytes.
 Two options:
1. timer -> SPI transfer - > DMA linked memory array to spi;
2. timer -> DMA linked memory array -> spi SPI transfer;

 Study HAL_DMA_Start_IT().

Thanks for the post MasterT. If I'm honest I don't quite understand your proposed options, please could you explain them in a little more detail?
I will have a look at HAL_DMA_Start_IT() in the meantime.

Thanks


 Things a little bit more complicated, than I thought, F429 SPI supports 8 & 16 bits only.

I've programmed H743zi to interface with 18-bits ADC, 1.5 msps from analog device. To do precise clock, I switched SPI to slave 18-bits mode and generate CS, SCLK by 2 timers. Primary timer drives CS and define sampling rate, GPIO-PWM pin was connected to ADC CS & SPI-CS on same micro. Secondary timer - gated mode - was set to provide 18 SCLK pulses at 100 MHz in time frame specified by 1.5 msps sampling rate. GPIO-PWM pin timer-2 drives sclk ADC & SPI.  Data line adc - spi as usual.

Now to F429, I think it still would be possible to get jitter free spi transfer, with some limits that sampling rate can not be any value, but biased to spi internal clock divider.

Overall picture:
1. two timers - master & gated slave, master provides sync, gated timer generates 3 pulses on each gate timing. There are app note AN4776 TIM Cookbook.
2. Configure SPI as master 8 bits, DMA.
3. Timer-2 initiates DMA transfer, memory->SPI .
4. Data in memory array needs to be organize in 3-bytes words, to stay in correct order all the time.

 Definitely good oscilloscope is a must, to debug all hardware linkage.
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4414
  • Country: dk
Re: I2S to SPI DAC with STM32F429
« Reply #13 on: July 03, 2020, 10:45:19 pm »
maybe use a hardware timer to send the 24th clock edge?
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: I2S to SPI DAC with STM32F429
« Reply #14 on: July 03, 2020, 11:50:26 pm »
maybe use a hardware timer to send the 24th clock edge?
The issue with SPI, it doesn't support 24-bits mode. Max 16-bits, than software intervention required. Even with DMA, SPI likely to miss a few clock cycles to retrigger input buffer back .
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4414
  • Country: dk
Re: I2S to SPI DAC with STM32F429
« Reply #15 on: July 04, 2020, 12:58:40 am »
maybe use a hardware timer to send the 24th clock edge?
The issue with SPI, it doesn't support 24-bits mode. Max 16-bits, than software intervention required. Even with DMA, SPI likely to miss a few clock cycles to retrigger input buffer back .

on timer interrupt bit bang 23 bits, switch the clock pin to timer compare output so the 24th clk is timed by the timer 
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: us
  • Yes, I do this for a living
Re: I2S to SPI DAC with STM32F429
« Reply #16 on: July 04, 2020, 02:08:35 am »
If you use SPI ADCs/DACs which use the serial clock as their sampling clock, the jitter performance indeed depends on the SPI timing.
This is an R2R DAC. It updates on the 24th falling clock edge.
 

Offline fcb

  • Super Contributor
  • ***
  • Posts: 2117
  • Country: gb
  • Test instrument designer/G1YWC
    • Electron Plus
Re: I2S to SPI DAC with STM32F429
« Reply #17 on: July 04, 2020, 05:24:33 pm »
maybe use a hardware timer to send the 24th clock edge?
The issue with SPI, it doesn't support 24-bits mode. Max 16-bits, than software intervention required. Even with DMA, SPI likely to miss a few clock cycles to retrigger input buffer back .
3x 8 bit mode then?  Or as langwadt suggests, use a timer to generate the critical edge.





https://electron.plus Power Analysers, VI Signature Testers, Voltage References, Picoammeters, Curve Tracers.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8168
  • Country: fi
Re: I2S to SPI DAC with STM32F429
« Reply #18 on: July 04, 2020, 05:39:29 pm »
If you use SPI ADCs/DACs which use the serial clock as their sampling clock, the jitter performance indeed depends on the SPI timing.
This is an R2R DAC. It updates on the 24th falling clock edge.

So if you output any continuous waveform and not just change near-DC values every now and then, the AC performance depends on the jitter between the 24th edges of the subsequent samples.

Assuming three 8-bit writes (if the SPI has internal FIFO, this could be as easy as three writes to the peripheral with no delays inbetween) produce continuous clocking of 24 bits just like it was in (non-existent) 24 bits mode - this is the behavior I have always seen with STM32 SPI implementations - then the only thing left is to trigger the SPI transfer at accurate intervals. And, of course, you need to drive the nCS because the SPI-is-managing-the-nCS-pin mode cannot be used; it wants to pull nCS high between the bytes, which is utterly stupid and against the whole idea of SPI, where the nCS is supposed to be the command/message flow control, not "this is a word" flow control. But ST clearly does not understand this need.

This is why you should, at PCB design stage, always try to find an nSS pin which also has a timer output capability so that when your exceptations of STM32 SPI providing any kind of meaningful SPI nSS hardware management fails, you can cope. If you need peripheral-driven automation, that is. Sometimes software-driven is good enough.
« Last Edit: July 04, 2020, 05:44:51 pm by Siwastaja »
 

Offline Bassman59

  • Super Contributor
  • ***
  • Posts: 2501
  • Country: us
  • Yes, I do this for a living
Re: I2S to SPI DAC with STM32F429
« Reply #19 on: July 05, 2020, 02:48:26 am »
If you use SPI ADCs/DACs which use the serial clock as their sampling clock, the jitter performance indeed depends on the SPI timing.
This is an R2R DAC. It updates on the 24th falling clock edge.

So if you output any continuous waveform and not just change near-DC values every now and then, the AC performance depends on the jitter between the 24th edges of the subsequent samples.

Assuming three 8-bit writes (if the SPI has internal FIFO, this could be as easy as three writes to the peripheral with no delays inbetween) produce continuous clocking of 24 bits just like it was in (non-existent) 24 bits mode - this is the behavior I have always seen with STM32 SPI implementations - then the only thing left is to trigger the SPI transfer at accurate intervals. And, of course, you need to drive the nCS because the SPI-is-managing-the-nCS-pin mode cannot be used; it wants to pull nCS high between the bytes, which is utterly stupid and against the whole idea of SPI, where the nCS is supposed to be the command/message flow control, not "this is a word" flow control. But ST clearly does not understand this need.

This is why you should, at PCB design stage, always try to find an nSS pin which also has a timer output capability so that when your exceptations of STM32 SPI providing any kind of meaningful SPI nSS hardware management fails, you can cope. If you need peripheral-driven automation, that is. Sometimes software-driven is good enough.
None of this addresses the need for an output reconstruction filter, which he needs unless he likes stair-steps in his sine wave. Hence, use an audio DAC.
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #20 on: July 06, 2020, 06:03:47 pm »
Hi all,

Many thanks for all your contributions, I appreciate the time you've all spent on this. I should explain that I am using these waveforms to drive MEMS mirrors into resonance, signals are currently post filtered with active Bessel filters and amplified with a quad HV opamp. The sample rate is already sufficient that the Bessel filters aren't really needed due to the low mechanical frequency response of the MEMS mirror. It won't "see" these steps in the wave. Jitter is very important though because the mirrors have a high Q factor, thus it causes an error in the angular displacement.

There are two things I had not considered, that is phase jitter caused by the CLK signal to the Bessel filters and possible jitter caused by storing the sine array in external SDRAM on the 429 discovery board. I did a test earlier where I analysed the signal pre Bessel filter with the array stored in the internal SRAM but the jitter did not improve.


It gets loaded with a digital word and you get an output voltage from it. The DAC output updates on the 24th falling edge of the clock, but only if the command you shifted in says to do so. SYNC has nothing to do with it -- all that is required is that SYNC is low for the shift to be allowed. This means that any jitter in the output is wholly dependent on the regularity of your shift clock, and specifically whether the last falling edge occurs on a regular periodic basis which is your sample rate. Good luck with that.


Understood, I hadn't read this properly. Thanks for all the other info you provided, at the moment I am going to pursue MasterT's options with the AD5664 but will come back on this if those options fail.

Things a little bit more complicated, than I thought, F429 SPI supports 8 & 16 bits only.

I've programmed H743zi to interface with 18-bits ADC, 1.5 msps from analog device. To do precise clock, I switched SPI to slave 18-bits mode and generate CS, SCLK by 2 timers. Primary timer drives CS and define sampling rate, GPIO-PWM pin was connected to ADC CS & SPI-CS on same micro. Secondary timer - gated mode - was set to provide 18 SCLK pulses at 100 MHz in time frame specified by 1.5 msps sampling rate. GPIO-PWM pin timer-2 drives sclk ADC & SPI.  Data line adc - spi as usual.

Now to F429, I think it still would be possible to get jitter free spi transfer, with some limits that sampling rate can not be any value, but biased to spi internal clock divider.

Overall picture:
1. two timers - master & gated slave, master provides sync, gated timer generates 3 pulses on each gate timing. There are app note AN4776 TIM Cookbook.
2. Configure SPI as master 8 bits, DMA.
3. Timer-2 initiates DMA transfer, memory->SPI .
4. Data in memory array needs to be organize in 3-bytes words, to stay in correct order all the time.

 Definitely good oscilloscope is a must, to debug all hardware linkage.

Many thanks for your post MasterT, I will give your method a go next. The sampling rate is no issue and can be any value, so long as it is sufficiently high. 8 bit per sine cycle is sufficient and I am currently above this. The HAL_SPI_transmit_DMA function was so slow to get going it pushed me down to 6 bit resolution. I will have to see if I can speed this up first while keeping the data valid. I already have my data in 3 byte words for the test solutions I posted here.

I will let you know how I get on with this solution.

Matt

« Last Edit: July 06, 2020, 06:13:36 pm by matt09 »
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #21 on: July 16, 2020, 05:38:32 pm »
Things a little bit more complicated, than I thought, F429 SPI supports 8 & 16 bits only.

I've programmed H743zi to interface with 18-bits ADC, 1.5 msps from analog device. To do precise clock, I switched SPI to slave 18-bits mode and generate CS, SCLK by 2 timers. Primary timer drives CS and define sampling rate, GPIO-PWM pin was connected to ADC CS & SPI-CS on same micro. Secondary timer - gated mode - was set to provide 18 SCLK pulses at 100 MHz in time frame specified by 1.5 msps sampling rate. GPIO-PWM pin timer-2 drives sclk ADC & SPI.  Data line adc - spi as usual.

Now to F429, I think it still would be possible to get jitter free spi transfer, with some limits that sampling rate can not be any value, but biased to spi internal clock divider.

Overall picture:
1. two timers - master & gated slave, master provides sync, gated timer generates 3 pulses on each gate timing. There are app note AN4776 TIM Cookbook.
2. Configure SPI as master 8 bits, DMA.
3. Timer-2 initiates DMA transfer, memory->SPI .
4. Data in memory array needs to be organize in 3-bytes words, to stay in correct order all the time.

 Definitely good oscilloscope is a must, to debug all hardware linkage.

Hi Master T,

I spent some time modifying the spi driver to get DMA transfers quicker, I can now get 10 bit resolution on a 385hz sinewave in "open loop" mode without any delays, which is plenty good enough. Clearly it will be a bit less with a fixed sample rate.

I've tried to implement what you said but I'm still a little unsure on the overall picture. I've configured TIM1 CH1 as PWM output and TIM2 as a gated slave. I've put the DMA transmit line in the TIM2 interrupt handler. At the moment it runs once outputting one 24 bit word and stops. I tried resetting the counter using TIM2->CNT = 0; but it didn't work.

I looked though AN4776 but there is no mention of gated mode. I have found limited information on it in other pdfs notes. I have included my test code below. I would really appreciate any help to get your method working.

Many thanks,

Matt

Code: [Select]
void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);

  HAL_SPI_Transmit_DMA(&hspi1, &data[i], 3);
  i+=3;
}





Code: [Select]
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;

TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);

int i;
uint8_t data[260];

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();

  for (i=0;i<sizeof data;i++)
{
data[i] = i;  // dummy data to check on scope (NOT ADC data)
}

  i=0;
 
  HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
  HAL_TIM_Base_Start_IT(&htim2);
 
while(1)
{
}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if(i==258) // ensure loop after end of sequence 258 chosen as divisible by count up of 3
{
i=0;
}
}
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 180;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Activate the Over-Drive mode
  */
  if (HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_SPI1_Init(void)
{
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }

}

static void MX_TIM1_Init(void)
{

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1000;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 100;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_DISABLE;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 100;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);

}

static void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 150;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 10;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);

}

static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();

}


« Last Edit: July 20, 2020, 12:28:12 pm by matt09 »
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: I2S to SPI DAC with STM32F429
« Reply #22 on: July 16, 2020, 10:04:31 pm »
Unfortunately, I can't test your code, as I don't have F429 in my possession. All necessary information about timer's modes you can find in reference manual for this particular cpu.
https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf
STM32CubeMX helps a lot to generate a code, get one from ST official web-site.
CubeMX skips some functions, like startPWM, but I see that is something you implemented already.
I could suggest, to split all software into sub-blocks: timers, spi, dma and debug each part separately. Combine them if you have two peaces error free.
 If no oscilloscope available, I would play with a timers in "slow-motion" - setting speed down to seconds with prescallers, and use a led to indicate a pattern, blink-blink-blink- pause, repeat.
 

Offline matt09Topic starter

  • Regular Contributor
  • *
  • Posts: 194
  • Country: gb
Re: I2S to SPI DAC with STM32F429
« Reply #23 on: July 20, 2020, 01:33:28 pm »
Unfortunately, I can't test your code, as I don't have F429 in my possession. All necessary information about timer's modes you can find in reference manual for this particular cpu.
https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf
STM32CubeMX helps a lot to generate a code, get one from ST official web-site.
CubeMX skips some functions, like startPWM, but I see that is something you implemented already.
I could suggest, to split all software into sub-blocks: timers, spi, dma and debug each part separately. Combine them if you have two peaces error free.
 If no oscilloscope available, I would play with a timers in "slow-motion" - setting speed down to seconds with prescallers, and use a led to indicate a pattern, blink-blink-blink- pause, repeat.

Hi MasterT

Thanks for your help. I actually posted the code to check if I am doing your method correctly as you suggested, rather than debugging code. Once I know the method is correct I will investigate problems.
I'm not sure exactly how everything should be configured for your method.

I have attached an image of my CubeMX configuration which produced the code, to check parameters such as trigger source are correct?


When you said "3. Timer-2 initiates DMA transfer, memory->SPI ."
I assume you did mean this?

Code: [Select]
void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);

  HAL_SPI_Transmit_DMA(&hspi1, &data[i], 3);
  i+=3;
}


I have access to good oscilloscopes so this is no problem once I get it going.

Thanks again

« Last Edit: July 20, 2020, 10:04:57 pm by matt09 »
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: I2S to SPI DAC with STM32F429
« Reply #24 on: July 20, 2020, 10:28:59 pm »
Quote
I have attached an image of my CubeMX configuration which produced the code to check parameters such as trigger source are correct?
Seems not. Based on reply #21, TIM1 supposed to be a master, though plain config w/o any:
 trigger - non,
 slave mode - non,
 clock - internal
Prescaller = 1000? If for testing, otherwise I don't think you need a prescaller, TIM1 16-bits, enough down to 180/ 65536 = 2.746 kHz.
 I'd set two PWM channels, one ( 1-st, if don't mind ? ) is for NSS ( or CS - Chip Select - SPI /DAC frame driver ), another PWM ( 2-nd ? ) specifically  for slave timer TIM2 gating. Doing so, you will have an option to adjust NSS pulse to set HIGH a few microseconds later than TIM2 stops SPI transfer, to give a DAC some time to finish.
In CUBEMX its means to set :
Channel 1 - PWM generation on channel1
Channel 2 - PWM generation no output
Trigger TRGO parameters / trigger events select: OC2REF
 
Now TIM2:
Slave Mode - Gated mode
Trigger source - page 632  / 1749  doc- RM0090 Rev 18 table 98 - ITR0, seens TIM1 should be a gate master.
Clock source - internal.

Next - Counter period, obviously TIM2 needs 3x speed compare to TIM1, so if Counter TIM1 = 17'999 ( 10 kHz sharp ) than Counter TIM2 = 5'999

One PWM channel.

Quote
I assume you did mean this?

Code: [Select]
void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);

  HAL_SPI_Transmit_DMA(&hspi1, &data, 3);
  i+=3;
}

No, this is exactly what I'm advising not to use - interrupt driven transfer.
Should be:

Code: [Select]
  if (HAL_DMA_Start_IT(htim2.hdma[TIM_DMA_ID_UPDATE], (uint32_t) out, SPI2_Write, (2 *OUT_BUFF)) != HAL_OK)
  {
    Error_Handler();
  }

 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf