Working with I2S audio CODECs


Lee O'D

Recommended Posts

Hi there,

 

I'm starting a new project to provide basic audio mixing in the digital domain, using a bunch of 2 in / 2 out audio CODECs. They all seem to be very similar in spec, and typically use a 16 or 24 bit transport over I2S (though you can configure other protocols such as raw 16 or 24 bit frames). I would drive all the CODECs (probably 8 of them) from common system, bit and channel clocks, and just have individual pins for the data in and outs which should keep the pin count down. 

 

I have two main challenges to begin with, and I was wondering if anyone could offer some advice:

 

1. Providing a master I2S interface (with the CODECs running as slaves), there are a few example VHDL libraries out there (eg http://opencores.org/project,i2s_interface ), but if anyone has had success with any libraries out there, or used the other protocols to interface the CODECs if it's simpler?

 

2. Providing a synthesized clock of the right rate. Depending on the sample rate (44.1 or 48kHz) I would need to provide either 11.2896 or 12.288 MHz CODEC system clock, with the other clocks being divided/counted from that. Clearly these are odd rates to derive from the 32MHz crystal, and would ideally need to be fairly accurate. Any pointers / recommendations on how to start on this please on the Spartan 6, this has clearly been tackled for eg video clock generation?

 

Many thanks everyone

 

Lee

 

Link to comment
Share on other sites

I have been thinking about that too... currently I'm trying to decide between making a wing around the UDA1345 (http://www.nxp.com/documents/data_sheet/UDA1345TS.pdf) or the UDA1380 (http://www.nxp.com/documents/data_sheet/UDA1380.pdf). Both of those chips support 24bit... and in either case the wing will probably be a simple one with a single stereo input and a single stereo output, since more connectors probably won't fit on a single wing without using 'stacked' connectors like they do on motherboards. Since I2S is a relatively simple protocol (i.e. not differential) it will work on any port so you should be able to use up to 6 HD Audio Wings at time.

 

Did anyone else have any chips in mind? The main points I'm looking for are a stereo input, stereo output, 16-24bit, and for as low cost as possible. 

Link to comment
Share on other sites

These are pretty hard clocks to generate exactly. You need 32M*441/1250 = 11.2896M and 32M*48/125 = 12.288M to generate these clocks precisely. The problem is that on a Spartan 6 with a 32M osc, the PLL will max out with a x33 multiplier for a -3 step or a x31 multiplier for a -2 step (ref. DS162 page 57, FVCOMAX parameter).
 
The large divisors are not a problem, you can always add logic to further divide a clock generated by a DCM but the multipliers are the hard part as they don't have enough common denominators between them to reduce them to a value <32.
 
One solution would be a fractional clock divider, I can't remember where I saw one recently (was it you hamster?). Say for example in the case you need 11.2896M you need to multiply 32M by 0.3528 so instead you multiply by 9/25 so the mul/div values don't exceed the PLL ratings, generating a slightly faster clock and add the missing 0.0072 each clock cycle until you reach or exceed one clock period then you skip a clock cycle. This way in the long term you average the clock you want but you introduce jitter.
 

Arbitrary frequency digital clock generation is quite and interesting problem and if anyone has any interesting pointers please share :)

Link to comment
Share on other sites

Yeap - it might have been me - http://hamsterworks.co.nz/mediawiki/index.php/SPDIF_out It outputs a bit every 17 + 631/882 cycles of a 100MHz clock. It has a jitter of about 10ns (1/17th of a bit), but is well within spec. The important factor is that the amount of jitter is always going to be the period of the clock cycle.

 

 

Unless you are going for bonus points and use probably use DDR outputs to half the jitter on any signal going off the FPGA. 8-)

Link to comment
Share on other sites

Hi Lee,

Alex seems to have a better handle on this than me so I'll keep it general.

 

First, sounds like you need to learn about clock management on Spartan 6. Each Spartan 6 has a number of digital clock managers (DCM's). These are special function blocks on the FPGA that allow clock signals to be manipulated in various ways. They can divide and multiply the 32MHz signal in interesting ways. See http://www.xilinx.com/support/documentation/user_guides/ug382.pdf

 

Given that 48kHz is an even integer fraction of 32MHz (4000/6) it shouldn't be too hard to pick an intermediate frequency, say 5.666MHz (1/6) and work from there. The CLKFX pin is probably what you're after. The Xilinx ISE deisgn software has a wizard to help configure the DCM in either VHDL or Verilog.

 

I'll be interested in what you come up with as I intend to implement a simple mixer as part of my Minimoog project.

 

Cheers,

Steve

 

PS. If it were up to me i'd start out by implementing with a 16 bit signal simply because the S6 hardware multipliers are 18x18 bits. Using a 24 bit word makes muliplication that little bit trickier.

Link to comment
Share on other sites

Hi Steve and Lee, what level of experience do you guys have with DSP in general?

 

I'm still trying to come to terms with how different DSP style filters and electronic filters are. The more I learn the more my assumptions get challenged.

 

For example, all analogue filters result in phase changes, as do most Digital Infinite Impulse Response filters, however you can run IIR "backwards in time", and get a close to an analogue two-pole (-6db / octave)  but with zero phase change.

 

I'm sure this could be leveraged into making an all digital  crossover for bi-amped speakers for hi-fi nuts (the sort who measure the distance from the woffer/tweeter speaker cones to the "sweet spot") - imaging that "zero crossover phase distortion", with tweakable frequency and roll-off...

Link to comment
Share on other sites

This won't help much in this particular case but I feel it's an appropriate time to mention that when using a Spartan 6, you don't always have to use a DCM for clock generation. Another option is to use a PLL_BASE which has slightly larger ranges for mul/div (mul = 1 to 64, div = 1 to 52). In addition it has 6 separate clock outputs each with it's own divisor in the range 1 to 128 as well as controls for phase and duty cycle for each output. This is a very flexible clock generation for when you need multiple clocks.

 

Here's an example usage from one of my own projects, where I need two single ended clocks and one differential clock and still have two unused outputs I could use for extra clocks. Note that to generate the differential clock I use two outputs with the same divisor but "invert" one of the outputs by specifying a 180 phase shift.

------------------------------------------------- generate all the system clocks required-----------------------------------------------inst_pll_base : PLL_BASEgeneric map (	BANDWIDTH          => "OPTIMIZED",	COMPENSATION       => "SYSTEM_SYNCHRONOUS",	CLKIN_PERIOD       => 20.00, -- Clock period (ns) of input clock on CLKIN	DIVCLK_DIVIDE      => 1,     -- Division factor for all clocks (1 to 52)	CLKFBOUT_MULT      => 12,    -- Multiplication factor for all output clocks (1 to 64)	CLKFBOUT_PHASE     => 0.0,   -- Phase shift (degrees) of all output clocks	REF_JITTER         => 0.100, -- Input reference jitter (0.000 to 0.999 UI%)	-- 12Mhz	CLKOUT0_DIVIDE     => 50,    -- Division factor for CLKOUT0 (1 to 128)	CLKOUT0_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT0 (0.01 to 0.99)	CLKOUT0_PHASE      => 0.0,   -- Phase shift (degrees) for CLKOUT0 (0.0 to 360.0)	-- 24Mhz	CLKOUT1_DIVIDE     => 25,    -- Division factor for CLKOUT1 (1 to 128)	CLKOUT1_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT1 (0.01 to 0.99)	CLKOUT1_PHASE      => 0.0,   -- Phase shift (degrees) for CLKOUT1 (0.0 to 360.0)	-- 120Mhz positive	CLKOUT2_DIVIDE     => 5,     -- Division factor for CLKOUT2 (1 to 128)	CLKOUT2_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT2 (0.01 to 0.99)	CLKOUT2_PHASE      => 0.0,   -- Phase shift (degrees) for CLKOUT2 (0.0 to 360.0)	-- 120Mhz negative	CLKOUT3_DIVIDE     => 5,     -- Division factor for CLKOUT3 (1 to 128)	CLKOUT3_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT3 (0.01 to 0.99)	CLKOUT3_PHASE      => 180.0, -- Phase shift (degrees) for CLKOUT3 (0.0 to 360.0)	-- Not used	CLKOUT4_DIVIDE     => 1,     -- Division factor for CLKOUT4 (1 to 128)	CLKOUT4_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT4 (0.01 to 0.99)	CLKOUT4_PHASE      => 0.0,   -- Phase shift (degrees) for CLKOUT4 (0.0 to 360.0)	-- Not used	CLKOUT5_DIVIDE     => 1,     -- Division factor for CLKOUT5 (1 to 128)	CLKOUT5_DUTY_CYCLE => 0.5,   -- Duty cycle for CLKOUT5 (0.01 to 0.99)	CLKOUT5_PHASE      => 0.0    -- Phase shift (degrees) for CLKOUT5 (0.0 to 360.0))port map (	CLKFBOUT => CLKFB,      -- General output feedback signal	CLKOUT0  => clkout0,	CLKOUT1  => clkout1,	CLKOUT2  => clkout2,	CLKOUT3  => clkout3,	CLKOUT4  => open,	CLKOUT5  => open,	LOCKED   => pll_locked, -- Active high PLL lock signal	CLKFBIN  => CLKFB,      -- Clock feedback input	CLKIN    => CLKIN,      -- Clock input	RST      => ext_reset   -- Asynchronous PLL reset);-- Distribute PLL clocks globallyinst_buf1 : BUFG port map (I => clkout0, O => clk_12M);inst_buf2 : BUFG port map (I => clkout1, O => clk_24M);inst_buf3 : BUFG port map (I => clkout2, O => clk_dvi_p);inst_buf4 : BUFG port map (I => clkout3, O => clk_dvi_n);

Also look up the DCM_CLKGEN primitive, mul range 2 to 256, div range 1 to 256

Link to comment
Share on other sites

Hi all,

 

Many thanks for all the info. Some great food for thought there.

 

So I had tried using the clock wizard in ISE, but this couldn't get close to the desired clock frequency (and the jitter seemed pretty big too). This was using PLL_BASE, would DCM be any more accurate?

 

Hamster - your code seems really interesting, using the same calculation, for me to generate a 12.288MHz clock (which is 48kHz x 256 as the ideal clock), then I'd get:

 

32 MHz -> 12.288 MHz
= 32000000 / 12288000 = 2.604166666666666
= 2 + 7424000 / 12288000
= 2 + 29/48
 
(NB I assume FPGA clock at 32MHz, obviously if I decided to run the rest of the system faster I would need to scale up the values here, eg 96MHz source came out as = 7 + 39 / 48...)

 

These values seem quite reasonable, do you think these values should work with your code (ie they are quite a lot smaller ratios than your SPDIF example)? I guess I will give it a go and see..

 

How can you estimate the amount of jitter - it is just +/- half the period of the source clock? Is this the period of the system clock (if I multiply up) or the physical crystal speed?

 

Very interesting thoughts anyway, I must admit generating clocks ourselves rather than using the inbuilt clock generation isn't something that had occurred to me!

 

The rest of the project (which will include EQ etc) will also be a challenge, but I'm starting small by just trying to get data in and out of the CODEC intact, then some simple summations etc - and go on from there.

 

Cheers

 

Lee

Link to comment
Share on other sites

Lee,

nothing says you have to use the 32MHz clock as your reference.

 

Viable alternatives could be to:

1) replace existing oscillator with one of a different frequency

2) add a second oscillator on one of the many broken out GCLK pins

 

Steve

Link to comment
Share on other sites

Hi Steve,

 

Yes I think for the final project implementation I would get a precise 12.288MHz crystal which are readily available, this was basically so I could get a basic prototype running with the parts I have to hand (Pap Pro, Wolfson 8569 CODEC, SSOP to DIL breakout board) to get moving on the HDL front. It should be easy later to swap out this temporary clock generator with a low jitter accurate one.

 

I'll have a go with Hamster's code, solder up the parts and see if I can get any sampling going!

 

Any thoughts on benefits of I2S vs right justified mode (the chip default is 16 bit right justified, I think this may be the simplest as it's just clocking data in/out MSB first,,,)?

 

Thanks

Link to comment
Share on other sites

Hi all,

 

So i've had a bit of time, managed to hack together the Wolfson 8569 onto a DIL breakout board and wire it up to receive clocks from the Papilio (C0=MCLK, C1=BCLK, C2=LRCLK for now). I used the PLL with 28x multiplier and 73 divider to get 12.274MHz MCLK (which is only 0.11% slow from 12.288MHz), and then used RTL to divide by 4 for the BCLK and by 64 for the LRCLK. For now i'm just looping the ADC digital out into the DAC in - getting the data into the FPGA will be the next step. I used hardware mode which means that I don't need to mess around with the SPI registers, and I can get 24 bit right justified out, which also means I don't need to implement I2S.

 

I measured all the clocks on my scope and they were looking correct (though I only have a single channel scope, so checking the phase wasn't possible). Annoyingly I got zero output from the chip. Having given up last night, I checked everything today and same result. Then I realised that I hadn't selected fast slew on the clock outputs. Strangely, it works 100% when I have fast slew on the MCLK and BCLK, but leave the LRCLK with standard slew rate - weird! I don't have a proper PCB etc so perhaps there's some jitter getting in or something.

 

Anyway, it sounds 100% with this configuration. I decided to use the onboard PLL, as when the design is done I will add a proper 12.288 or 24.576MHz crystal to an input pin and get all the clocks 100% accurate.

 

I'm still finding my feet a bit around ISE etc (haven't done VHDL/FPGAs for over a decade, back in those days it was XACTstep and XC4000s), and I'm not entirely sure how to set simulation up - so I'm quite pleased to have something working in hardware after half a day!

 

Cheers

 

Lee

Link to comment
Share on other sites

Hi Lee,

in ISE 14 you now toggle between implementation and simulation views in the top left panel. You can add test benches/fixtures (vhdl vs. verilog) to test on a per module basis. Perform a behavioral syntax check before running the simulation model. Running the model opens the iSim application window and with a little tweaking you will see nice graphs of your inputs and responses.

 

Cheers,

Steve

Link to comment
Share on other sites

Thanks Steve, I managed to get simulation running ok. Managed to get some issues with the clocks resolved once the simulation was online. Now sounding 100%

 

Next step is to get the data in, de-serialised and start doing things with it!

 

Lee

Link to comment
Share on other sites

Lee,

have you given any thought to turning your circuit idea into a wing? I for one would like to experiment with audio processing eg. mixing, filtering, effects. Having a board with several possible line, guitar, balanced mic inputs and several line outputs would be neat.

 

Steve

 

PS. Don't know what your background is but would be happy to work with you if PCB design isn't your thing.

Link to comment
Share on other sites

Sure, I'll share what I can when it's up and working. I will be making something vaguely wing form factor, depending on how much hardware I can fit onto the board (and whether i'll split off the analogue section to reduce noise).

 

Cheers

Link to comment
Share on other sites

  • 2 years later...

Hi,

 

...

For example, all analogue filters result in phase changes, as do most Digital Infinite Impulse Response filters, however you can run IIR "backwards in time", and get a close to an analogue two-pole (-6db / octave)  but with zero phase change.

...

 

AFAIK, this "IIR-backwards-in-time" idea originates from an exercise in a classic DSP textbook that has gone horribly wrong (Matlab's "filtfilt" has popularized the idea).

It's technically correct, but no one ever does this in real life except the guy who bounces his signal back and forth through the filter via two tape machines, once forward and once reversed... I haven't met him yet.

 

What's funny about it is that I can get linear phase using a totally ordinary FIR filter, for example designed using a so-called "Parks/McClellan" method.

As long as the filter's impulse response is symmetrical in time, its phase is linear, and it has constant group delay.

The only catch is, the delay is typically longer than in an IIR filter.

 

PS if anybody wonders about this "backwards-in-time" stuff:

The explanation is that time reversal results in a "mirror image" of the frequency response, same amplitude but opposite phase (complex conjugate).

Apply the filter once forwards and once backwards, and the phase shift cancels out, leaving only two times the amplitude response.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.