porting the SAMD's new I2S library to other platforms

399 views
Skip to first unread message

Thomas Roell

unread,
Dec 12, 2016, 12:41:28 PM12/12/16
to Developers
Backgound. I am doing a STM32L4 Arduino Core for a few STM32L4xx platforms (github: https://github.com/GrumpyOldPizza/arduino-STM32L4, kickstarter (our newest creations): https://www.kickstarter.com/projects/2109048010/arduino-programmable-cortex-m4f-development-boards). Of course I need to add I2S/SAI support. Ran across the newly checked in I2S library and since I don't see a good reason to have yet another I2S library that is incompatible I have a few design questions and requests for extensions.

First off congrats to Sandeep coming up with this excellent concept.

Off we go to the questions:

(1) I need to add MCK output. Some CODECs do not have their own clocks, and hence need to derive that from MCK. That of course is only relevant to master mode. The various CODECs document the configuration of MCK as ratio between MCK and LRCK (I2S_FS/I2S_WS, pick your name for the same pin ;-)). Hence I would propose the following API modification:

    int begin(int mode, long sampleRate, int bitsPerSample, int ratio = 0);


If "ratio" is 0, then MCK is disabled, if it's non-zero, the MCK output is enabled. Typical values for "ratio" are 128, 192, 256, 384, 512. STM32L4 supports 128, 256 & 512. SAMD would support anything between 64 and 1024. nRF52 does 128, 192, 256, 384, 512. Hence I2S.begin() would return an error if a "ratio" cannot be supported by hardware.


(2) On STM32L4 on can link 2 SAI peripherals together to form a composite peripheral that supports I2S_MCK/I2S_SCK/I2S_FS/I2S_SDO/I2S_SDI, basically switching from halfduplex to fullduplex. nRF52 has a similar setup as well. This needed to be selectable for.both master and slave mode. Hence I would propose similar to the HardwareSerial "config" idea to merge that into "mode" via:

   I2S_MODE_DUPLEX


(3) "bitsPerSample" seems to describe right now only the memory layout for the DMA engine. However even the example code dealing with an I2S microphone runs into the issue that the real data is 24 bits only. I'd suggest to adopt the following semantics (based upon what common CODECs do):

    32       32 bit data, MSB is bit 31
    24       32 bit data, MSB is bit 23
    20       32 bit data, MSB is bit 19
    16       16 bit data, MSB is bit 15
    10       16 bit data, MSB is bit 9
    8         8 bit data, MSB is bit 7

It's then the task of any given implementation to setup the internal shifting/reformatting logic so that "mode" + "bitsPerSample" get mapped to the proper output stream. However a single sample on the I2S port still takes up 64 SCK cycles. There does not seem to be any good reason to add a more convoluted scheme.


(4) I kind of like and do not like the idea of a doubled buffered scheme over a ringbuffer scheme. If each buffer has 512 bytes, you can store 128 24 bit samples. So there is some latency before you can process the first sample on the receive path, as you have to wait for your buffer to be filled. For one that latency needs to be exposed to the user, which means I2S_BUFFER_SIZE should be moved into I2S.h for visibility. However if you need a smaller buffer size for reducing the read latency, you'd need a knob for that. Hence I would propose the following new API:

    I2S.threshold(int numSamples);

This would force the code to swap buffers after "numSamples" on the read path, if "numSamples" is larger than a MCU dependent minimum values and smaller than the I2S_BUFFER_SIZE allows.


(5) Right now the library is defined in terms if half-duplex. However other than throu the callback there is no way for the application to determine whether a transmit is fully done, and a switch to receive mode would not block. I would suggest to add this API:

   I2S.done();

This would return "false" if there is any unsent sample (other DMA or in the HW FIFO), and hence a switch to receive mode would block. N.b I2S.flush() is then really a wait till I2S.done() is true.


- Thomas

Sandeep Mistry

unread,
Dec 12, 2016, 3:26:58 PM12/12/16
to Developers
Hi Thomas,

Thanks for the initial feedback and for getting the discussion going!

See inline responses below:


On Mon, Dec 12, 2016 at 10:41 AM, Thomas Roell <grumpyo...@gmail.com> wrote:
Backgound. I am doing a STM32L4 Arduino Core for a few STM32L4xx platforms (github: https://github.com/GrumpyOldPizza/arduino-STM32L4, kickstarter (our newest creations): https://www.kickstarter.com/projects/2109048010/arduino-programmable-cortex-m4f-development-boards). Of course I need to add I2S/SAI support. Ran across the newly checked in I2S library and since I don't see a good reason to have yet another I2S library that is incompatible I have a few design questions and requests for extensions.

First off congrats to Sandeep coming up with this excellent concept.

It was great team effort! Cristian Maglie, Tom Igoe, and Arturo Guadalupi all provided their valuable input!
 

Off we go to the questions:

(1) I need to add MCK output. Some CODECs do not have their own clocks, and hence need to derive that from MCK. That of course is only relevant to master mode. The various CODECs document the configuration of MCK as ratio between MCK and LRCK (I2S_FS/I2S_WS, pick your name for the same pin ;-)). Hence I would propose the following API modification:

    int begin(int mode, long sampleRate, int bitsPerSample, int ratio = 0);


If "ratio" is 0, then MCK is disabled, if it's non-zero, the MCK output is enabled. Typical values for "ratio" are 128, 192, 256, 384, 512. STM32L4 supports 128, 256 & 512. SAMD would support anything between 64 and 1024. nRF52 does 128, 192, 256, 384, 512. Hence I2S.begin() would return an error if a "ratio" cannot be supported by hardware.

That proposal sounds good for now, let's see if anyone else on the mailing list has any other ideas. We will also need to consider the case for using the MCK as an input signal in slave mode.

May I ask which specific codecs you are looking to support?

The "maker friendly" I2S devices we tested with for the initial release include:
 

(2) On STM32L4 on can link 2 SAI peripherals together to form a composite peripheral that supports I2S_MCK/I2S_SCK/I2S_FS/I2S_SDO/I2S_SDI, basically switching from halfduplex to fullduplex. nRF52 has a similar setup as well. This needed to be selectable for.both master and slave mode. Hence I would propose similar to the HardwareSerial "config" idea to merge that into "mode" via:

   I2S_MODE_DUPLEX

Just to confirm, there is only a single set of MCK, SCK and FS pins? Do both peripherals need to have the same rate and bits per sample? Could duplex mode be entered when I2S.read(...) and I2S.write(...) are both called?

Do you have any specific use cases in mind for this setup? Also, a rough idea for user sketch in this scenario would be great.

 
(3) "bitsPerSample" seems to describe right now only the memory layout for the DMA engine. However even the example code dealing with an I2S microphone runs into the issue that the real data is 24 bits only. I'd suggest to adopt the following semantics (based upon what common CODECs do):

    32       32 bit data, MSB is bit 31
    24       32 bit data, MSB is bit 23
    20       32 bit data, MSB is bit 19
    16       16 bit data, MSB is bit 15
    10       16 bit data, MSB is bit 9
    8         8 bit data, MSB is bit 7

It's then the task of any given implementation to setup the internal shifting/reformatting logic so that "mode" + "bitsPerSample" get mapped to the proper output stream. However a single sample on the I2S port still takes up 64 SCK cycles. There does not seem to be any good reason to add a more convoluted scheme.

For now, we've decided to keep things simple and only support 32, 16, and 8 bits per sample.

The microphone is nice enough to shift in 0's for the LSB which makes dealing with basic audio analysis with signed math much easier.

Again, outside of the mic I mentioned that uses 24-bit are you targeting any particular devices that need to use those bits per sample?
 

(4) I kind of like and do not like the idea of a doubled buffered scheme over a ringbuffer scheme. If each buffer has 512 bytes, you can store 128 24 bit samples. So there is some latency before you can process the first sample on the receive path, as you have to wait for your buffer to be filled. For one that latency needs to be exposed to the user, which means I2S_BUFFER_SIZE should be moved into I2S.h for visibility. However if you need a smaller buffer size for reducing the read latency, you'd need a knob for that. Hence I would propose the following new API:

    I2S.threshold(int numSamples);

This would force the code to swap buffers after "numSamples" on the read path, if "numSamples" is larger than a MCU dependent minimum values and smaller than the I2S_BUFFER_SIZE allows.

Yes, I agree something like this would be useful.

Do you think we could use the size argument of I2S.read(buffer, size) to determine the sample count threshold? Currently receive mode is not started until I2S.read(...) or I2S.available() are read.

 
(5) Right now the library is defined in terms if half-duplex. However other than throu the callback there is no way for the application to determine whether a transmit is fully done, and a switch to receive mode would not block. I would suggest to add this API:

   I2S.done();

This would return "false" if there is any unsent sample (other DMA or in the HW FIFO), and hence a switch to receive mode would block. N.b I2S.flush() is then really a wait till I2S.done() is true.

It would be useful for me to see a sketch of what you have in mind for this. Also, how would the hardware setup work - would someone be plugging and unplugging wires or toggling a switch?

The SAMD version currently does not block on I2S.flush(), something we should definitely consider changing.


Thomas, again these are an excellent set of questions. It would be really helpful for us to get a better picture of how you see users using the various features you mentioned above (including user sketches and hardware setups).

There is also a higher level ArduinoSound library available via the library manager now, that builds upon the new SAMD's I2S library. Tutorials and documentation for I2S and ArduinoSound should be up on the arduino.cc website later this week.



Sandeep

Thomas Roell

unread,
Dec 12, 2016, 4:33:39 PM12/12/16
to Developers
Comments embedded.


On Monday, December 12, 2016 at 1:26:58 PM UTC-7, Sandeep Mistry wrote:
Hi Thomas,

Thanks for the initial feedback and for getting the discussion going!

See inline responses below:


On Mon, Dec 12, 2016 at 10:41 AM, Thomas Roell <grumpyo...@gmail.com> wrote:
Backgound. I am doing a STM32L4 Arduino Core for a few STM32L4xx platforms (github: https://github.com/GrumpyOldPizza/arduino-STM32L4, kickstarter (our newest creations): https://www.kickstarter.com/projects/2109048010/arduino-programmable-cortex-m4f-development-boards). Of course I need to add I2S/SAI support. Ran across the newly checked in I2S library and since I don't see a good reason to have yet another I2S library that is incompatible I have a few design questions and requests for extensions.

First off congrats to Sandeep coming up with this excellent concept.

It was great team effort! Cristian Maglie, Tom Igoe, and Arturo Guadalupi all provided their valuable input!
 

Off we go to the questions:

(1) I need to add MCK output. Some CODECs do not have their own clocks, and hence need to derive that from MCK. That of course is only relevant to master mode. The various CODECs document the configuration of MCK as ratio between MCK and LRCK (I2S_FS/I2S_WS, pick your name for the same pin ;-)). Hence I would propose the following API modification:

    int begin(int mode, long sampleRate, int bitsPerSample, int ratio = 0);


If "ratio" is 0, then MCK is disabled, if it's non-zero, the MCK output is enabled. Typical values for "ratio" are 128, 192, 256, 384, 512. STM32L4 supports 128, 256 & 512. SAMD would support anything between 64 and 1024. nRF52 does 128, 192, 256, 384, 512. Hence I2S.begin() would return an error if a "ratio" cannot be supported by hardware.

That proposal sounds good for now, let's see if anyone else on the mailing list has any other ideas. We will also need to consider the case for using the MCK as an input signal in slave mode.

That is an interesting thought. Why would you want MCK as an input in slave mode (input to the MCU) ? All you care is really SCK/FS/SD. What purpose would MCK input serve in this scenario ? MCK seems to me to be a crutch to save on the external oscillator for a CODEC chip. But then again my exposure is limited.
 
May I ask which specific codecs you are looking to support?

CS43L22 (on the STM32L476 Discovery board for testing)
WM8731  (on the mikroe Audio Codec Proto board)
ICS43432 (on a simple breakout, https://www.tindie.com/products/onehorse/ics43432-i2s-digital-microphone/, probably the same you are looking at)

Out of those CS43L22 needs MCK as it has no own oscillator. WM8731 could be driven by MCK, but also via oscillator. The board I listed uses a crystal.
 

The "maker friendly" I2S devices we tested with for the initial release include:


Thanx for pointing out MAX98357A. In the datasheet it explains:

"MCLK Elimination The ICs eliminate the need for the external MCLK signal that is typically used for PCM communication. This reduces EMI and possible board coupling issues in addition to reducing the size and pin-count of the ICs."

So it seems they drive the DAC directly via BCLK, while higher quality DACs typically want to have a higher clock for that (well, MCK, which normally is 4x SCK (aehm, BCLK in MAX98357A terms).

 
 

(2) On STM32L4 on can link 2 SAI peripherals together to form a composite peripheral that supports I2S_MCK/I2S_SCK/I2S_FS/I2S_SDO/I2S_SDI, basically switching from halfduplex to fullduplex. nRF52 has a similar setup as well. This needed to be selectable for.both master and slave mode. Hence I would propose similar to the HardwareSerial "config" idea to merge that into "mode" via:

   I2S_MODE_DUPLEX

Just to confirm, there is only a single set of MCK, SCK and FS pins? Do both peripherals need to have the same rate and bits per sample? Could duplex mode be entered when I2S.read(...) and I2S.write(...) are both called?

Correct, shared MCK/SCK/FS pins. The problem with your idea to use I2S.read() + I2S.write() as trigger is that I'd like to bind the necessary pins at I2S.begin() time (and release them at I2S.end() time). All other communication type interface (Serial/Wire/SPI) have those semantics implied. So it would be more desirable to some add that ti I2S.begin().
 

Do you have any specific use cases in mind for this setup? Also, a rough idea for user sketch in this scenario would be great.

No sketch yet. Just what WM8731 can do. This chip/board has one shared SCK (named BLCK), and a pair of ADCLRCK/ADCDAT and DACLRCK/DACDAT. So you can drive an external speaker, and an analog microphone at the same time. Given the shared SCK, at the end of the day you'd end up naturally with a shared FS (ADCLRCK/DACLRCK).

Interestingly enough LPC1763 has exactly this mapping with a shared SCK and a RX_SDA/RX_WS & TX/SDA/TX_WS.

nRF52 has a MCK/SCK/WS/SDI/SDO composite setup to begin with.


So there is a lot of interesting hardware out there that uses pretty much exactly that setup of a fullduplex SDI/SDO scheme with shared clocking signals.

 (3) "bitsPerSample" seems to describe right now only the memory layout for the DMA engine. However even the example code dealing with an I2S microphone runs into the issue that the real data is 24 bits only. I'd suggest to adopt the following semantics (based upon what common CODECs do):

    32       32 bit data, MSB is bit 31
    24       32 bit data, MSB is bit 23
    20       32 bit data, MSB is bit 19
    16       16 bit data, MSB is bit 15
    10       16 bit data, MSB is bit 9
    8         8 bit data, MSB is bit 7

It's then the task of any given implementation to setup the internal shifting/reformatting logic so that "mode" + "bitsPerSample" get mapped to the proper output stream. However a single sample on the I2S port still takes up 64 SCK cycles. There does not seem to be any good reason to add a more convoluted scheme.

For now, we've decided to keep things simple and only support 32, 16, and 8 bits per sample.

Well, problem with that is that you cannot express right justified 24 bit data correctly. Please also keep in mind that I am not asking to extend the current SAMD implementation (which may or may not implement a specific feature) but how to extend the API for devices that offer different formats.
 

The microphone is nice enough to shift in 0's for the LSB which makes dealing with basic audio analysis with signed math much easier.

Again, outside of the mic I mentioned that uses 24-bit are you targeting any particular devices that need to use those bits per sample?

Right justified 24 bit data is the use case on CS43L22. If you get right justfied 24 bit data from you input file, then you don't need to convert it to left justified 32 bit data.

20 bit data is what you get from some software codecs, so again, if hardware can handle this directly, why involve additional software.

But to be honest other than "what the hardware can do", the sticky common case is 24 bit right justified data.

Pretty much all the hardware I had checked can do 16/24/32 bit (packed as 16bit and 32bit respectively). 8 bit seems to be rarely supported. Is there a specific use case for 8 bit that was the driving force ?
 
(4) I kind of like and do not like the idea of a doubled buffered scheme over a ringbuffer scheme. If each buffer has 512 bytes, you can store 128 24 bit samples. So there is some latency before you can process the first sample on the receive path, as you have to wait for your buffer to be filled. For one that latency needs to be exposed to the user, which means I2S_BUFFER_SIZE should be moved into I2S.h for visibility. However if you need a smaller buffer size for reducing the read latency, you'd need a knob for that. Hence I would propose the following new API:

    I2S.threshold(int numSamples);

This would force the code to swap buffers after "numSamples" on the read path, if "numSamples" is larger than a MCU dependent minimum values and smaller than the I2S_BUFFER_SIZE allows.

Yes, I agree something like this would be useful.

Do you think we could use the size argument of I2S.read(buffer, size) to determine the sample count threshold? Currently receive mode is not started until I2S.read(...) or I2S.available() are read.


You start reading when I2S.available() is called. So by then you'd need already the threshold. Also consider the case where somebody only works via callback (onReceive). So first the receive path get's tickled, and then in the receive callback you check how much data is there via I2S.available() and then read this amount. So you'd never end up setting a new threshold. Conversely if the user has a loop in the code that chunks with say 16 samples, they'd inadvertantly set a threshold. 

 
(5) Right now the library is defined in terms if half-duplex. However other than throu the callback there is no way for the application to determine whether a transmit is fully done, and a switch to receive mode would not block. I would suggest to add this API:

   I2S.done();

This would return "false" if there is any unsent sample (other DMA or in the HW FIFO), and hence a switch to receive mode would block. N.b I2S.flush() is then really a wait till I2S.done() is true.

It would be useful for me to see a sketch of what you have in mind for this. Also, how would the hardware setup work - would someone be plugging and unplugging wires or toggling a switch?

This was for a non audio device that was using I2S to transfer data (PCM coded, pairs of 32bit data items), so I2S_FS was really only used as sync pulse. In that case you'd want to make use of the half duplex. It's not really the much of a biggy, other than that you need this anyway for a proper I2S.flush(), so why not exposing it ? 

The other more interesting idea behind it is that the rest of the interface could be done wholly non-blocking. Only I2S.flush() and I2S.end() need to block. So adding I2S.done() would make it possible to use the whole API non-blocking.
 

The SAMD version currently does not block on I2S.flush(), something we should definitely consider changing.


Thomas, again these are an excellent set of questions. It would be really helpful for us to get a better picture of how you see users using the various features you mentioned above (including user sketches and hardware setups).

There is also a higher level ArduinoSound library available via the library manager now, that builds upon the new SAMD's I2S library. Tutorials and documentation for I2S and ArduinoSound should be up on the arduino.cc website later this week.


Thanx for pointing that one out.

Quickly summarizing, I guess the minimum things I'd like to see as something that I'd like to have for the specific piece of hardware (MCU) that I am supporting is:

(1) Add "ratio" to I2S.begin() in master mode to expose a MCK pin (which SAMD may or may not use for now). With a "ratio = 0" default that should be fairly transparent.

(2) Add 24 bit, so right justified 24 bit data can be expressed. Should also be transparent. SAMD doesn't support that yet, so it would just return 0.

Anyway, let me ask the other way around, how would you support the WM8731 use case, where you have a real CODEC with audio output as well as audio output, but a shared SCK ?
 



Sandeep

Thomas Roell

unread,
Dec 12, 2016, 7:42:48 PM12/12/16
to Developers
Couple of more comments, after studying the code on github (and way too many datasheets):

    enableClock(sampleRate * 2 * bitsPerSample);
    i2sd.setSlotSize(_deviceIndex, bitsPerSample);
    i2sd.setDataSize(_deviceIndex, bitsPerSample);

What I was suggesting is to decouple DataSize and SlotSize. Something more along the lines of:

    
    if (bitsPerSample <= 16) bitsPerSlot = 16;
    else                               bitsPerSlot = 32;

    enableClock(sampleRate * 2 * bitsPerSlot);
    i2sd.setSlotSize(_deviceIndex, bitsPerSlot);
    i2sd.setDataSize(_deviceIndex, bitsPerSample);


So "bitsPerSample == 24" would use really 32 bit slots.

However with all of that there is this problem on CS43L22. It always expects a LRCLK/SCK ratio of 64 in master mode, meaning that "bitsPerSlot" is always 32. So with the API in place you had to select always 32 bits and then to the 16/24/32 -> 32 conversion on the CPU side. 

WM8731 has similar restrictions, where it says "In Master mode, DACLRC and ADCLRC will be output with a 50:50 mark-space ratio with BCLK output at 64 x base frequency".

STGL5000 (Teensy Audio Board) has this SCLKFREQ field which is documented as: "Sets frequency of I2S_SCLK when in master mode (MS=1). When in slave mode (MS=0), this field must be set appropriately to match SCLK input rate. 0x0 = 64Fs 0x1 = 32Fs - Not supported for RJ mode". And MCK is required as input ...

PCM5102 actually supports SCK/FS ratios of 64/48 and 32. PCM5102 also wants to see MCK.

ES9023 states: "Run by the I2S bit clock, typically 64FS clock". ES9023 also wants to see MCK.

AK4558 has SCK/FS ratios of 128/64/32 in stereo (non-TDM) mode. AK4558 support MCK as well.

CS4272 does support MCK, and with that the SCK/FS ratios of 32 and 64. 


So it seems "bitsPerSample" in the I2S API is really "bitsPerSlot" and the application needs to then reformat ? My comments where assuming that one would always use "bitsPerSlot == 32" and then bitsPerSample would indicate how many data bits were transferred on this 32 bit slot. Also for DMA the byte count per sample would be rounded up to the next 8bit, 16bit & 32bit value. Perhaps this clears up some confusion.

Ideally the API would expose "bitsPerSample" and "bitsPerSlot", but that does buy little over just assuming what I outlined above for majority of cases.

What I am worried about after sifting throu all those datasheets is whether "bitsPerSample == 16" (CD Audio playback) works with "bitsPerSlot == 16" across the board or not.

With the current API, "bitsPerSample == 8" (and hence "bitsPerSlot == 8", what is the use case ? I cannot see any codec actually supporting that.

- Thomas

PS: Please forgive me if I misread the source code in any case.

Sandeep Mistry

unread,
Dec 15, 2016, 12:50:24 PM12/15/16
to Developers
Pretty much all the hardware I had checked can do 16/24/32 bit (packed as 16bit and 32bit respectively). 8 bit seems to be rarely supported. Is there a specific use case for 8 bit that was the driving force ?

No specific use case, we tested with SAMD board to board with 8-bits. It was a bit of a freebie to add 8-bit.

 
 
(1) Add "ratio" to I2S.begin() in master mode to expose a MCK pin (which SAMD may or may not use for now). With a "ratio = 0" default that should be fairly transparent.

Yes, that works.
 

(2) Add 24 bit, so right justified 24 bit data can be expressed. Should also be transparent. SAMD doesn't support that yet, so it would just return 0.

Would the data be "packed" in memory when reading writing (3 bytes per sample) - DMA transfers would remain 32-bit though? This might tie in the the bits per slot topic below.
 

Anyway, let me ask the other way around, how would you support the WM8731 use case, where you have a real CODEC with audio output as well as audio output, but a shared SCK ?

Do you have link to the data sheet for the WM8731? I can only find a device that uses I2C and SPI ...

Instead of having a half and full duplex mode, maybe I2S.begin(...) could just mux both the input and output pins?
 

So it seems "bitsPerSample" in the I2S API is really "bitsPerSlot" and the application needs to then reformat ? My comments where assuming that one would always use "bitsPerSlot == 32" and then bitsPerSample would indicate how many data bits were transferred on this 32 bit slot. Also for DMA the byte count per sample would be rounded up to the next 8bit, 16bit & 32bit value. Perhaps this clears up some confusion.
Ideally the API would expose "bitsPerSample" and "bitsPerSlot", but that does buy little over just assuming what I outlined above for majority of cases.

It could help distinguish how the data is packed in memory? 

 
What I am worried about after sifting throu all those datasheets is whether "bitsPerSample == 16" (CD Audio playback) works with "bitsPerSlot == 16" across the board or not. 

It really depends, we tried to keep things simple and stick to the "maker friendly" boards out there for the initial release. The Tindie I2S mic we tested needs a bits per slot of 32 to work, right now as you noticed we must use 32-bits per sample. Adding bits per slot would allow people to get 8-bit or 16-bit data from it - which is pretty nice.


Let me know if I missed any topics ... maybe a next step is to define an I2S .h file to summarize what we've discussed?

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.

Thomas Roell

unread,
Dec 15, 2016, 1:21:19 PM12/15/16
to Developers
Comments embedded.

- Thomas


On Thursday, December 15, 2016 at 10:50:24 AM UTC-7, Sandeep Mistry wrote:
Pretty much all the hardware I had checked can do 16/24/32 bit (packed as 16bit and 32bit respectively). 8 bit seems to be rarely supported. Is there a specific use case for 8 bit that was the driving force ?

No specific use case, we tested with SAMD board to board with 8-bits. It was a bit of a freebie to add 8-bit.

I see. Easy enough to do in general. Some older hardware from STM can only do 16 or 32 bit slot sizes. That's where I was coming from asking. If it's not needed (and there is no use case), then perhaps removing it might be a better idea. Less to test, and less compatibility issues.
 
 
(1) Add "ratio" to I2S.begin() in master mode to expose a MCK pin (which SAMD may or may not use for now). With a "ratio = 0" default that should be fairly transparent.

Yes, that works.
 

(2) Add 24 bit, so right justified 24 bit data can be expressed. Should also be transparent. SAMD doesn't support that yet, so it would just return 0.

Would the data be "packed" in memory when reading writing (3 bytes per sample) - DMA transfers would remain 32-bit though? This might tie in the the bits per slot topic below.

It would not be packet. DMA would be still 32 bits. Think about it this way:

DDDDDDDDDDDDDDDDDDDDDDDDxxxxxxxx     24 bit data with "bitsPerSample == 32"
xxxxxxxxDDDDDDDDDDDDDDDDDDDDDDDD     24 bit data with "bitsPerSample == 24"

  
Anyway, let me ask the other way around, how would you support the WM8731 use case, where you have a real CODEC with audio output as well as audio output, but a shared SCK ?

Do you have link to the data sheet for the WM8731? I can only find a device that uses I2C and SPI ...

 
Instead of having a half and full duplex mode, maybe I2S.begin(...) could just mux both the input and output pins?

Good questions. Most of the CODECs I have been looking at really have MCK/SCK/FS/SDIN/SDOUT. So if mux at I2S.begin() time you get either receive or transmit (unless I misinterpreted your suggestion), but not both in parallel. Suppose you have a audio communication setup and want to play back what the other side is saying, while at the same time record what you are saying. That setup is perfect for that. The key thing is that you need to make sure both receive and transmit has the same protocol/timing over the bus ...

The idea to put that into "mode" somehow came from the observation that not all I2S.h implementation will support that. If not, then this can be compile time checked (if a #define rather than an enum would be used). Same is however true for simply adding I2S_SUPPORTS_FULLDUPLEX as define, and then having a I2S.fullduplex(onoff) style API.
 
So it seems "bitsPerSample" in the I2S API is really "bitsPerSlot" and the application needs to then reformat ? My comments where assuming that one would always use "bitsPerSlot == 32" and then bitsPerSample would indicate how many data bits were transferred on this 32 bit slot. Also for DMA the byte count per sample would be rounded up to the next 8bit, 16bit & 32bit value. Perhaps this clears up some confusion.
Ideally the API would expose "bitsPerSample" and "bitsPerSlot", but that does buy little over just assuming what I outlined above for majority of cases.

It could help distinguish how the data is packed in memory? 

Correct, although I was more commenting on how within a 32bit word the 24 bits of data payload are fetched from. I am not suggesting a 3 byte data type.
 
What I am worried about after sifting throu all those datasheets is whether "bitsPerSample == 16" (CD Audio playback) works with "bitsPerSlot == 16" across the board or not. 

It really depends, we tried to keep things simple and stick to the "maker friendly" boards out there for the initial release. The Tindie I2S mic we tested needs a bits per slot of 32 to work, right now as you noticed we must use 32-bits per sample. Adding bits per slot would allow people to get 8-bit or 16-bit data from it - which is pretty nice.

I would recommend against exposing "bitsPerSlot" to the user. It's another thing to understand and of course to get wrong (why can I not do 20 for "bitsPerSlot" ?). Sifting throu all the relevant datasheets from NXP/Freescale/STM/TI/Novuton/Atmel, it seems all the Cortex-M based systems do support 16 & 32 for bitsPerSlot. Many support 8 bits for the slot size. But rarely any do 24 bit. So this is why I would not want to expose another parameter that introduces hardware dependencies.

So in reality the only "bitsPerSample" that should be added is 24, which should be defined with a slotsize of 32, and a dma fetch size of 32.

A value of 20 would be nice to have for a lot of interesting hardware, but that then would introduce compatibility issues left and right.
 
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Thomas Roell

unread,
Dec 18, 2016, 9:28:52 AM12/18/16
to Developers
I had not responded to one question about the next step. Right now I am implementing the requested changes in I2S.h and for STM32L4. So bear with me for a few days. Some ideas are best discussed if there is a proof of concept.

One thing I ran into on STM32L4 is the fact that the sample rates are effectively limited by the clock setup of the device:

8000
16000
24000
32000
48000
96000
(192000)
11025
22050
44100

Is there such a list for SAMD as well ? 

- Thomas

On Thursday, December 15, 2016 at 10:50:24 AM UTC-7, Sandeep Mistry wrote:
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Thomas Roell

unread,
Dec 20, 2016, 9:07:06 PM12/20/16
to Developers
Code for STM32L4 checked in as prototype on github.

(1) Added "masterClock = false" to I2S.begin(). No need for ratio, as all chips do 256.

(2) DId not add 10, 20 & 24 bit depths. The I2S API has signed int32_t / int16_t semantics for 32/16 bits per sample (see "int I2S::read()". Unclear how 24 bit should be mapped. STM32L4 cannot sign-extended ...

(3) Did not add threshold yet.


So now there is a 2nd implementation of the API available, without too many hitches.

- Thomas

Sandeep Mistry

unread,
Dec 23, 2016, 3:16:20 PM12/23/16
to Developers
Hi Thomas,

Great progress! Have you tried to use it with the ArduinoSound library?


(1) Added "masterClock = false" to I2S.begin(). No need for ratio, as all chips do 256.

Could you please clarify what you mean by "all chips"?
 
Is there such a list for SAMD as well ? 

The SAMD is much more flexible for the sample rate, you can specify a clock and divider.

 
Correct, although I was more commenting on how within a 32bit word the 24 bits of data payload are fetched from. I am not suggesting a 3 byte data type.

We should take a look at how wave files handle 24-bits per sample, I suspect it could be packed (3 bytes instead of 4), so something in the stack will have to adjust it to 32-bits during playback.


I would recommend against exposing "bitsPerSlot" to the user. It's another thing to understand and of course to get wrong (why can I not do 20 for "bitsPerSlot" ?). Sifting throu all the relevant datasheets from NXP/Freescale/STM/TI/Novuton/Atmel, it seems all the Cortex-M based systems do support 16 & 32 for bitsPerSlot. Many support 8 bits for the slot size. But rarely any do 24 bit. So this is why I would not want to expose another parameter that introduces hardware dependencies. 

Right, maybe bits to clock is more appropriate term? For example the ICS43432 mic is 24-bits, but requires a 32-bits to be clocked per sample, right now the library just treats it as a 32-bit and the mic sets the LSB 8-bits to 0's.

The main use case would be collecting from devices like this with a lower bit rate. So, for example I could get 8-bits or 16-bits per sample from the device without having to do any conversion in software.


Sandeep

To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.

Thomas Roell

unread,
Dec 27, 2016, 6:57:37 PM12/27/16
to Developers
I did try the ArduinoSound library and adjusted the STM32L4 so that all examples compile and work.

Most chips == NXP/FreeScale/STM microcontrollers + 16/32 slot size configurations in all CODECs I had looked at for up to 48kHz. Only 24 bit slots requires 192/384 as multiplier, but those slot sizes are pointless ...

- Thomas
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Reply all
Reply to author
Forward
0 new messages