I2S example code

Post your cool example code here.
victor_pv
Posts: 954
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Postby victor_pv » Sat Feb 11, 2017 8:28 pm

GrumpyOldPizza wrote:
victor_pv wrote:Thanks, I have been having a look at both the Arduino and your implementation. Are you using the STM drivers for the lowest level? or you wrote your own?
I'll see if this coming week I have some time to read how the i2s peripheral works in the F1 and start writing.
I'll have to ask you some things about the DMA callback functions, but I will when I get to start working on that.

On a side note, I think a similar way of managing the callback should work for the SPI DMA transfers in the F1 core, currently we block instead of using callbacks.


Own driver layer. See https://github.com/GrumpyOldPizza/ardui ... 4xx/Source

The STM32L4 core does use DMA callbacks like this for SPI as well. Actually exposed via the SPI class interface:

Code: Select all

bool SPIClass::transfer(const void *txBuffer, void *rxBuffer, size_t count, void(*callback)(void));


Difference is that there is a per call callback, so you can thread throu a sequence of callbacks. For I2S you really just need a "refill" callback.


OK, I have started writing the i2s library for the F1. I read the SAMD version and yours. At first I was going to use the doublebuffer library from the SAMD, to take all that out of the library code, but at the end I decided to use yours as a base, and keep that part. Do you mind that if I reuse your code for managing the buffers, pointers to the buffers etc?
All the stuff managing the peripheral, setting up the DMA, etc, is all different, but managing the double buffers is what's copied from yours, including the variable names.

BTW, I am impressed with your core. I don't know how much time and effort you put on it, and how experienced you are, but looks like you put quite some thought to it, I wish I was even close...

victor_pv
Posts: 954
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Postby victor_pv » Sun Feb 12, 2017 3:19 pm

GrumpyOldPizza wrote:
victor_pv wrote:Madias, did you ever turn the i2s code into a library?
I am getting back to work on the stm32 and just ordered some pt8211.
I want to clean up the wav player code and turn it to use one of those.


ArduinoZero (MKRZERO) added a I2S class now that is also supported by a ArduinoSound libary:

https://github.com/arduino/ArduinoCore- ... /src/I2S.h

https://www.arduino.cc/en/Reference/I2S
https://www.arduino.cc/en/Reference/ArduinoSound

It might make sense to not reinvent the wheel there and use at least the I2S interface as a base. I discarded my own homegrown interface and added this I2S class to the STM32L4 code. There was only one modification needed to support the MCK output. This STM32 SPI/I2S peripheral would work great with that class interface. N.b. that this "I2S" class is not a kitchensink approach, but the minimum support to get most hardware on the other end of the I2S interface to work.


Thomas, I may be wrong, but I think I have found a difference in the behavior of your library and Arduino's one.
They provide for a blocked write() for a single sample, while you dont provide for such, your's always go to the DMA buffer if there is space, but if there is no space in the buffer it will not block waiting for it. So the arduino sketches that write samples one by one, if they do so faster than the device is transmitting, would drop a lot of samples.

For example the Arduino simpleTone sketch:

Code: Select all

void loop() {
  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * sample;
  }

  // write the same sample twice, once for left and once for the right channel
  I2S.write(sample);
  I2S.write(sample);

  // increment the counter for the next sample
  count++;
}

User avatar
GrumpyOldPizza
Posts: 166
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Postby GrumpyOldPizza » Wed Feb 15, 2017 3:06 pm

victor_pv wrote:
GrumpyOldPizza wrote:
victor_pv wrote:Madias, did you ever turn the i2s code into a library?
I am getting back to work on the stm32 and just ordered some pt8211.
I want to clean up the wav player code and turn it to use one of those.


ArduinoZero (MKRZERO) added a I2S class now that is also supported by a ArduinoSound libary:

https://github.com/arduino/ArduinoCore- ... /src/I2S.h

https://www.arduino.cc/en/Reference/I2S
https://www.arduino.cc/en/Reference/ArduinoSound

It might make sense to not reinvent the wheel there and use at least the I2S interface as a base. I discarded my own homegrown interface and added this I2S class to the STM32L4 code. There was only one modification needed to support the MCK output. This STM32 SPI/I2S peripheral would work great with that class interface. N.b. that this "I2S" class is not a kitchensink approach, but the minimum support to get most hardware on the other end of the I2S interface to work.


Thomas, I may be wrong, but I think I have found a difference in the behavior of your library and Arduino's one.
They provide for a blocked write() for a single sample, while you dont provide for such, your's always go to the DMA buffer if there is space, but if there is no space in the buffer it will not block waiting for it. So the arduino sketches that write samples one by one, if they do so faster than the device is transmitting, would drop a lot of samples.

For example the Arduino simpleTone sketch:

Code: Select all

void loop() {
  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * sample;
  }

  // write the same sample twice, once for left and once for the right channel
  I2S.write(sample);
  I2S.write(sample);

  // increment the counter for the next sample
  count++;
}


Yes, you are correct. My bad. Need to fix that. Perhaps. The odd thing is that I2S.write(data, count) is always non-blocking, so it returns the number of samples written. Same goes for the return value of I2C.write(sample). Normally I'd suggest to add an APi along the line of I2S.nonBlocking(onoff), so you can control that behavioir ...

victor_pv
Posts: 954
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Postby victor_pv » Wed Feb 15, 2017 4:58 pm

GrumpyOldPizza wrote:
victor_pv wrote:
GrumpyOldPizza wrote:
ArduinoZero (MKRZERO) added a I2S class now that is also supported by a ArduinoSound libary:

https://github.com/arduino/ArduinoCore- ... /src/I2S.h

https://www.arduino.cc/en/Reference/I2S
https://www.arduino.cc/en/Reference/ArduinoSound

It might make sense to not reinvent the wheel there and use at least the I2S interface as a base. I discarded my own homegrown interface and added this I2S class to the STM32L4 code. There was only one modification needed to support the MCK output. This STM32 SPI/I2S peripheral would work great with that class interface. N.b. that this "I2S" class is not a kitchensink approach, but the minimum support to get most hardware on the other end of the I2S interface to work.


Thomas, I may be wrong, but I think I have found a difference in the behavior of your library and Arduino's one.
They provide for a blocked write() for a single sample, while you dont provide for such, your's always go to the DMA buffer if there is space, but if there is no space in the buffer it will not block waiting for it. So the arduino sketches that write samples one by one, if they do so faster than the device is transmitting, would drop a lot of samples.

For example the Arduino simpleTone sketch:

Code: Select all

void loop() {
  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * sample;
  }

  // write the same sample twice, once for left and once for the right channel
  I2S.write(sample);
  I2S.write(sample);

  // increment the counter for the next sample
  count++;
}


Yes, you are correct. My bad. Need to fix that. Perhaps. The odd thing is that I2S.write(data, count) is always non-blocking, so it returns the number of samples written. Same goes for the return value of I2C.write(sample). Normally I'd suggest to add an APi along the line of I2S.nonBlocking(onoff), so you can control that behavioir ...


The problem is the arduino examples that write 1 int at a time do not check for any return, they just write as fast as possible hoping for call to block.

I was thinking on the same, adding some other functions to control behaviour, but I was also thinking that to me it seems a waste of RAM space to declare a double buffer only to be used by the class, but then the program using it needs another chunk of buffer space. Would make more sense to pass the write call the pointer to a buffer and the size of such buffer, the class can keep a queue of pairs of pointers+size, and send them in order, and the call back to the user code can return the pointer and size that were just completed.
That would allow for user code that can use as many buffers as needed, of any size desired, and no wasted duplicate buffers, cause the space where the application writes is the one used by the DMA controller.

What do you think on something like that?

One other thing I noticed is the api doesn't provide for a method to control the clock polarity, even though the i2s peripheral can use both polarities.

User avatar
GrumpyOldPizza
Posts: 166
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Postby GrumpyOldPizza » Thu Feb 16, 2017 1:32 pm

The API is not perfect ;-)

Looking at all of this, it seems to be best to implement a switch between the I2S.write(sample) and I2S.write(data, size) way, so that the former one needs to check that all DMA has been finished before it could write directly. But then again writing directly is bad, because you cannot prebuffer. Suppose you want to read data from an SD card ... I think SAI on STM32L4 has perhaps 16 buffered samples, using the I2S periperipheral on STM32F1 you have only a 1 entry deep buffer.

Size of the buffer. Here is what I did for Uart.h:

Code: Select all

    void begin(unsigned long baudrate, uint16_t config);
    void begin(unsigned long baudrate, uint16_t config, uint8_t *buffer, size_t size);


So the normal begin() you reference the normal predefined rx-buffer. If you use the version with buffer/size, then your own buffer is used. Hence if you don't use the first variant the default rx_buffer will not be used, and hence does not end up wasting space. I suppose the same could be done for the I2S API, so that you can control the amount of data buffering that you want.

Your thought of having a data/size queue is what my own API has started with. I.e. you had a callback that said it wanted more data, or if read data is here, please provide me with a new buffer (not N+1, but N+2), to keep the stream going. The problem however is that you don't want to reinvent the wheel. I ran across this API, and for most use cases it seemed to be good enough. Moving to the idea of double-buffering and keeping the allocation logic on the implementation side is actually pretty smart, as this avoids convoluted checks as to whether a data/size buffer is valid (alignment, minimum size and such).

Ah, clock polarity and such is not an issue. The protocol selected defines it.

victor_pv
Posts: 954
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Postby victor_pv » Thu Feb 16, 2017 2:51 pm

GrumpyOldPizza wrote:The API is not perfect ;-)

Looking at all of this, it seems to be best to implement a switch between the I2S.write(sample) and I2S.write(data, size) way, so that the former one needs to check that all DMA has been finished before it could write directly. But then again writing directly is bad, because you cannot prebuffer. Suppose you want to read data from an SD card ... I think SAI on STM32L4 has perhaps 16 buffered samples, using the I2S periperipheral on STM32F1 you have only a 1 entry deep buffer.

Size of the buffer. Here is what I did for Uart.h:

Code: Select all

    void begin(unsigned long baudrate, uint16_t config);
    void begin(unsigned long baudrate, uint16_t config, uint8_t *buffer, size_t size);


So the normal begin() you reference the normal predefined rx-buffer. If you use the version with buffer/size, then your own buffer is used. Hence if you don't use the first variant the default rx_buffer will not be used, and hence does not end up wasting space. I suppose the same could be done for the I2S API, so that you can control the amount of data buffering that you want.

Your thought of having a data/size queue is what my own API has started with. I.e. you had a callback that said it wanted more data, or if read data is here, please provide me with a new buffer (not N+1, but N+2), to keep the stream going. The problem however is that you don't want to reinvent the wheel. I ran across this API, and for most use cases it seemed to be good enough. Moving to the idea of double-buffering and keeping the allocation logic on the implementation side is actually pretty smart, as this avoids convoluted checks as to whether a data/size buffer is valid (alignment, minimum size and such).

Ah, clock polarity and such is not an issue. The protocol selected defines it.


What I have done in my implementation for write(sample) is that it writes to the DMA buffer like you did, write(data, size), (totally agree writing directly is not the best idea), but checks the returned value, if it was not able to write the sample to the buffer, cause it was full, it blocks retrying.
That way I make sure any such write(sample) is successful, without having to check the returned value. I think that fits more with how the API is documented and how it's used in the Arduino examples. I'm sure there are cleaner ways of doing it, but this how I did, suggestions accepted ;)

Code: Select all

size_t I2SClass::write(int32_t sample)
{
    size_t sent = 0;
    while (sent < _width / 8)
    sent += write((const void*)(&sample + sent), (_width / 8));
    return sent;
}

User avatar
GrumpyOldPizza
Posts: 166
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Postby GrumpyOldPizza » Sat Feb 25, 2017 1:52 am

Victor, that took a while to get to that. Code has now been updated on github.com. I ended up using this code:

Code: Select all

size_t I2SClass::write(int32_t sample)
{
    if (_state != I2S_STATE_TRANSMIT) {
        if (!((_state == I2S_STATE_READY) || (_state == I2S_STATE_TRANSMIT))) {
            return 0;
        }
       
        _state = I2S_STATE_TRANSMIT;
    }

    while (!write((const void*)&sample, (_width / 8))) {
        armv7m_core_yield();
    }

    return 1;
}


Return to “Code snipplets”

Who is online

Users browsing this forum: No registered users and 2 guests