I2S emulation using SPI (this is not I2C)

04 Nov 2009 . Edited: 04 Nov 2009

hi,

any idea how to use the SPI at 2,116,800 bit per second sending 6 consecutive bytes representing left 24 bit + right 24 bit Digital Audio to go into CS4331 Audio DAC ? I'm trying getting SPI emulating I2S. Please note this thread is not related to I2C. I2S is interfacing audio codecs like CS4331 and CS5331. LRCK is the frame sync signal, at 44.1 Khz.

void LRCK_in() { // interrupt service routine starts here 
    SPI device(p5, p6, p7); //mosi, miso, clk 

    //mbed needs configuring SPI here otherwise "device unknown" error 
    void format (int bits = 8, int mode = 0); 
    void frequency (int hz = 2116800); //44.1 KHz * 48 bits myled2 = 1; 

    // set oscilloscope sync 
    int rx_ch1_byte1 = device.write(0x55); 
    myled2 = 0; 
    // reset oscilloscope sync 
    int rx_ch1_byte2 = device.write(0x01); 
    int rx_ch1_byte3 = device.write(0x02); 
    int rx_ch2_byte1 = device.write(0x04); 
    int rx_ch2_byte2 = device.write(0x08);
    int rx_ch2_byte3 = device.write(0x55); 
} // interrupt service routine ends here

As you can see, I'm trying to send a fixed pattern (55-01-02-04-08-55 hex). The above routine to be called upon receiving the LRCK interrupt at 44.1 KHz on pin16.

int main() { // code at reset 
    PwmOut LRCK_emu(LED1); 
   // rough LRCK emulation (this pin must be physically connected to the interrupt pin) 
   void period_us(int = 22); //22 us period (instead of 22,67573696...) 
   LRCK_emu = 0.50; //50% duty cycle 
   InterruptIn event(p16); // define external interrupt pin 
   void PullUp(PinMode pull); // add a pullup 
   event.rise(&LRCK_in); // define polarity and define interrupt vector 
   while(1) { // application main_loop starts here 
       myled2 = 1; 
       wait(0.025); 
       myled2 = 0; 
       wait(0.025); 
   } // application main_loop ends here 
}

A) Setting up SPI at the beginning of each interruption is weird, but needed. This will increase the interrupt latency. How to setup SPI once for all just after reset, without mbed compiler reporting a "device unknown" error ?

B) The first byte may be sent, but I doubt the next 5 bytes are going to be sent, unless mbed is using an undocumented buffering scheme. But wait a minute : anyway, upon execution, the succession of "device.write" calls is way too fast for getting any meaningful data into the receiving variables (rx_ch1_byte2 untill rx_ch2_byte3). It confirms this approach is completely wrong, isn't ?

C) One should send the first byte, still using the LRCK interrupt, but the five remaining bytes should be sent using a second interrupt source, generating an interrupt each time a byte just got sent by the SPI block. How to setup such a "SPI_byte_sent" interrupt ?

Steph

04 Nov 2009

Hi Stephane,

Stephane Cnockaert wrote:
any idea how to use the SPI at 2,116,800 bit per second sending 6 consecutive bytes representing left 24 bit + right 24 bit Digital Audio to go into CS4331 Audio DAC ? I'm trying getting SPI emulating I2S.
I'm not sure if you are aware, but the mbed actually has I2S hardware on it. We just didn't show the interfaces on the basic pinout. If you are trying to talk to an I2S device, this would definitely be the way forward.

We don't have an mbed class for it yet, but I don't think it is too hard to get going. You could perhaps have a look at the datasheet for the LPC1768, and see if you can get something working.

Simon

04 Nov 2009

Hi again,

I've now untangled the code in the original post (I used the "Insert Code" toolbar option, and then just re-organised it a little so it is sensible to read), and can see the answers to a few of your questions. It is mainly some simple changes.

Stephane Cnockaert wrote:
A) Setting up SPI at the beginning of each interruption is weird, but needed. This will increase the interrupt latency. How to setup SPI once for all just after reset, without mbed compiler reporting a "device unknown" error ?

In your code, you currently create the SPI device within a function:

void LRCK_in() { // interrupt service routine starts here 
    SPI device(p5, p6, p7); //mosi, miso, clk 
    //mbed needs configuring SPI here otherwise "device unknown" error 
    :
    :
What this means is that the SPI device will only exist whilst the function does; it is created when the function is called, and destroyed when the function returns. This will happen every time the function is called, which is not what you are after in this case.

What you really want to do is just create the SPI device as part of startup, by making it just a global object:

SPI device(p5, p6, p7); //mosi, miso, clk 

void LRCK_in() { // interrupt service routine starts here 
    :
    :

Here you can see the SPI device sits on its own. Before main is called, it will get created.

Also in the interrupt you are trying to setup the SPI device:

void LRCK_in() { // interrupt service routine starts here 
    void format (int bits = 8, int mode = 0); 
    void frequency (int hz = 2116800); //44.1 KHz * 48 bits
    :

Here, the syntax is slightly wrong in a few places. I can see how you got to it from the API documentation so we might be able to improve that. The API documentation provides the standard way to specify methods and functions, but the way you actually call them must interpret that.

For example, format and frequency are "methods" (functions) of the SPI object, so you need to call it on a specific SPI object using the object-dot-method notation. And the function arguments (the numbers you pass) just require the value.

Also, you again probably only want to do this once, so may be best to do this setup in main. For example:

int main() {
    device.format(8, 0); // bits, mode 
    device.frequency(2116800); //44.1 KHz * 48 bits 
    :
Hope this makes some of the programming syntax more clear.

Simon

04 Nov 2009

hi Simon,

is there a template within mbed.org, listing the methodology to be followed for creating some new APIs ? Can you point something, as an advice ?

One new API would enable the LPC chip talking with Audio DACs , ADCs and CODECs using the generic I2S interface that's in the LPC chip.
Another new API could be getting the SPI buffered, for sending and receiving data formatted in 6 bytes with minimal overhead (subsequent bytes to be transmitted using the TX_empty interrupt at byte level).

Any advice appreciated.

By the way if you could streamline the presentation of all existing mbed APIs, using the same conventions and same typography everywhere, with commented examples used both as function and as object, mbed could become a practical way of learning and practising C++. The attractiveness would increase. It's Rapid Prototyping coupled to self-training and gaining expertize in a highly demanded, highly regarded area.

Will mbed.org have the resources for arbitrating between APIs and streamlining everything ? Will mbed.org generate a forest of different APIs created by different people, for the same purpose ? Let me explain : imagine now we have 10 different people proposing a genuine I2S mbed API. How will you manage this ? Publishing all 10 nearly-redundent APIs or selecting only one ?

I'm not against mbed, far away from this idea. I like the idea of getting mbed APIs to be used by everybody, in any context, with or without an underlying operating system. There are so many small projects where an off-the-shelf OS is overkill, and where a simple sequencer within the "do while" main_loop is an optimal approach. I like mbed, especially if, unlike M$ with .Net Micro, you are not raising a new tax on microcontrollers.  If you intend launching an universal "mbed" debug pod, compatible with most 32-bit microcontrollers (ARM-based, plus some others like Microchip and Renesas), then no doubt you represent the future - and you will get banking for an extra-fast expansion. Do you intend signing up other chip makers than NXP, and also, maybe, other 32-bit architectures than ARM ?

Steph

05 Nov 2009 . Edited: 05 Nov 2009

Hi Stephane,

Stephane Cnockaert wrote:
is there a template within mbed.org, listing the methodology to be followed for creating some new APIs ? Can you point something, as an advice ?

No there is not, but this is something I'd like to create soon. Do you have any ideas on what would be a useful form? Maybe a by-example walkthrough building up a realworld API, or a more theoretical one?

Stephane Cnockaert wrote:
Another new API could be getting the SPI buffered, for sending and receiving data formatted in 6 bytes with minimal overhead (subsequent bytes to be transmitted using the TX_empty interrupt at byte level).

This API improvement is something we're looking at now. In the case of the SPI, what sort of functionality are you looking for?

To make it more tangible, what would be really interesting would be some pseudocode showing how you'd like to work with the SPI object (or I2S object). i.e write some code that uses the SPI class with any additional made-up functionality you like, in a real world example. This is a really good way to test thinking and iterate an API design to find something which is natural to implement, but more importantly, natural to use.

If you put together some pseudocode examples, we can iterate some of the ideas and perhaps build a few prototype objects to play with to test out the ideas.

Simon