I2S example code

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

Re: I2S example code

Post by 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: 1258
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Post by 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: 174
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Post by 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: 1258
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Post by 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: 174
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Post by 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: 1258
Joined: Mon Apr 27, 2015 12:12 pm

Re: I2S example code

Post by 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: 174
Joined: Fri Apr 15, 2016 4:15 pm
Location: Denver, CO

Re: I2S example code

Post by 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;
}

ChrisMicro
Posts: 161
Joined: Fri Mar 24, 2017 4:51 pm
Location: Germany

Re: I2S example code

Post by ChrisMicro » Thu Apr 20, 2017 8:11 am

There is a nice I2S amplifier from Adafruit:
https://learn.adafruit.com/adafruit-max ... mp/pinouts

It is possible to use it with an Arduino Zero
https://www.arduino.cc/en/Tutorial/Ardu ... vePlayback

I tried it with the STM32F103 BluePill but it didn't work. Probably the timing is more critical than with the other I2S DAC proposed here in this thread.

Code: Select all


#include <SPI.h>

#define DACSAMPLINGRATE_HZ    16000
#define TIMER_INTTERUPT_US ( 1000000UL / DACSAMPLINGRATE_HZ / 2 ) // division by 2 because of two channels

#define WS   PA3
#define BCK  PA5
#define DATA PA7
#define testpin PC13

int sine[] = {0x800, 0x832, 0x864, 0x896, 0x8c8, 0x8fa, 0x92c, 0x95e, 0x98f, 0x9c0, 0x9f1, 0xa22, 0xa52, 0xa82, 0xab1, 0xae0,
              0xb0f, 0xb3d, 0xb6b, 0xb98, 0xbc5, 0xbf1, 0xc1c, 0xc47, 0xc71, 0xc9a, 0xcc3, 0xceb, 0xd12, 0xd39, 0xd5f, 0xd83,
              0xda7, 0xdca, 0xded, 0xe0e, 0xe2e, 0xe4e, 0xe6c, 0xe8a, 0xea6, 0xec1, 0xedc, 0xef5, 0xf0d, 0xf24, 0xf3a, 0xf4f,
              0xf63, 0xf76, 0xf87, 0xf98, 0xfa7, 0xfb5, 0xfc2, 0xfcd, 0xfd8, 0xfe1, 0xfe9, 0xff0, 0xff5, 0xff9, 0xffd, 0xffe,
              0xfff, 0xffe, 0xffd, 0xff9, 0xff5, 0xff0, 0xfe9, 0xfe1, 0xfd8, 0xfcd, 0xfc2, 0xfb5, 0xfa7, 0xf98, 0xf87, 0xf76,
              0xf63, 0xf4f, 0xf3a, 0xf24, 0xf0d, 0xef5, 0xedc, 0xec1, 0xea6, 0xe8a, 0xe6c, 0xe4e, 0xe2e, 0xe0e, 0xded, 0xdca,
              0xda7, 0xd83, 0xd5f, 0xd39, 0xd12, 0xceb, 0xcc3, 0xc9a, 0xc71, 0xc47, 0xc1c, 0xbf1, 0xbc5, 0xb98, 0xb6b, 0xb3d,
              0xb0f, 0xae0, 0xab1, 0xa82, 0xa52, 0xa22, 0x9f1, 0x9c0, 0x98f, 0x95e, 0x92c, 0x8fa, 0x8c8, 0x896, 0x864, 0x832,
              0x800, 0x7cd, 0x79b, 0x769, 0x737, 0x705, 0x6d3, 0x6a1, 0x670, 0x63f, 0x60e, 0x5dd, 0x5ad, 0x57d, 0x54e, 0x51f,
              0x4f0, 0x4c2, 0x494, 0x467, 0x43a, 0x40e, 0x3e3, 0x3b8, 0x38e, 0x365, 0x33c, 0x314, 0x2ed, 0x2c6, 0x2a0, 0x27c,
              0x258, 0x235, 0x212, 0x1f1, 0x1d1, 0x1b1, 0x193, 0x175, 0x159, 0x13e, 0x123, 0x10a, 0xf2, 0xdb, 0xc5, 0xb0,
              0x9c, 0x89, 0x78, 0x67, 0x58, 0x4a, 0x3d, 0x32, 0x27, 0x1e, 0x16, 0xf, 0xa, 0x6, 0x2, 0x1,
              0x0, 0x1, 0x2, 0x6, 0xa, 0xf, 0x16, 0x1e, 0x27, 0x32, 0x3d, 0x4a, 0x58, 0x67, 0x78, 0x89,
              0x9c, 0xb0, 0xc5, 0xdb, 0xf2, 0x10a, 0x123, 0x13e, 0x159, 0x175, 0x193, 0x1b1, 0x1d1, 0x1f1, 0x212, 0x235,
              0x258, 0x27c, 0x2a0, 0x2c6, 0x2ed, 0x314, 0x33c, 0x365, 0x38e, 0x3b8, 0x3e3, 0x40e, 0x43a, 0x467, 0x494, 0x4c2,
              0x4f0, 0x51f, 0x54e, 0x57d, 0x5ad, 0x5dd, 0x60e, 0x63f, 0x670, 0x6a1, 0x6d3, 0x705, 0x737, 0x769, 0x79b, 0x7cd,
             };
HardwareTimer timer(3);

#define PIN_PORT GPIOC // testpin
#define PIN_PORT1 GPIOA // WS pin
#define PIN_BIT 5
#define PIN_BIT1 3

#define SD_MODE_PIN PB1

void setup() {
  pinMode(SD_MODE_PIN, OUTPUT);
  digitalWrite(SD_MODE_PIN, LOW); // off
  Serial.begin(9600);
  delay (500);

  SPI.begin(); //Initialize the SPI_1 port.
  SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
  SPI.setDataMode(SPI_MODE0); //Set the  SPI_2 data mode 0
  //SPI.setClockDivider(SPI_CLOCK_DIV4); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  //SPI.setClockDivider(SPI_CLOCK_DIV16); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  SPI.setClockDivider(SPI_CLOCK_DIV128); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  //SPI.setClockDivider(SPI_CLOCK_DIV256); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock

  pinMode(WS, OUTPUT); //Set the Word Select pin (WS) as output.
  pinMode(testpin, OUTPUT);
  SPI.setDataSize (SPI_CR1_DFF); // setting SPI data size to 16Bit

  // timer3 setup
  timer.pause(); // Pause the timer while we're configuring it
  timer.setPeriod( TIMER_INTTERUPT_US ); // // Set up period in microseconds


  timer.setChannel1Mode(TIMER_OUTPUT_COMPARE); // Set up an interrupt on channel 1
  timer.setCompare(TIMER_CH1, 1);  // Interrupt 1 count after each update
  timer.attachCompare1Interrupt(timer3_irq);
  timer.refresh();  // Refresh the timer's count, prescale, and overflow
  timer.resume(); // Start the timer counting

  delay(1000);
  digitalWrite(SD_MODE_PIN, HIGH); // on
  while (1);

}


void loop() {

}

void timer3_irq(void)
{
  static uint32_t channel = 0;
  static uint8_t index = 0;
  channel ^= 1;
  if (channel)
  {
    // digitalWrite(WS, LOW);  //Select RIGHT Audio channel
    gpio_write_bit(PIN_PORT1, PIN_BIT1, LOW);
    SPI.write(sine[index++]);
  } else
  {
    // digitalWrite(WS, LOW);  //Select LEFT Audio channel
    gpio_write_bit(PIN_PORT1, PIN_BIT1, HIGH);
    SPI.write(sine[index]);
  }
}
// http://www.stm32duino.com/viewtopic.php?f=18&t=519#p5195
/**
    I2S example code (modified by Matthias Diro: some sinus melody - 16bit transfer, setting up on timer3)

    Description:
    This I2S example creates a Sine waveform on the RIGHT Audio channel of PT8211
    and a Sawtooth waveform on the LEFT Audio channel.

    This is a very simple how-to-use an external I2S DAC example (DAC = Digital to Analog Converter).

    Created on 27 Aug 2015 by Vassilis Serasidis
    email:  avrsite@yahoo.gr

    Connections between PT8211 DAC and the STM32F103C8T6
    WS    <-->  PA3
    BCK   <-->  PA5 <-->  BOARD_SPI1_SCK_PIN
    DIN   <-->  PA7 <-->  BOARD_SPI1_MOSI_PIN

*/

It seems from the datasheet
http://www.st.com/content/ccc/resource/ ... 161566.pdf ( p.13 )
that only high density devices with equal or more 256k have I2S interfaces.
The BluePill has unfortunately only 64K and therefore not I2S.

Am I right?

The SAMD21G18 which is found on the ARDUINO Zero has a built in I2S interface.
To check, if my chip is not damaged I just connected it to the Arduino Zero, flashed the simple tone and it worked:
https://www.arduino.cc/en/Tutorial/I2SSimpleTone

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

Re: I2S example code

Post by victor_pv » Thu Apr 20, 2017 4:10 pm

ChrisMicro wrote:There is a nice I2S amplifier from Adafruit:
https://learn.adafruit.com/adafruit-max ... mp/pinouts

It is possible to use it with an Arduino Zero
https://www.arduino.cc/en/Tutorial/Ardu ... vePlayback

I tried it with the STM32F103 BluePill but it didn't work. Probably the timing is more critical than with the other I2S DAC proposed here in this thread.

Code: Select all


#include <SPI.h>

#define DACSAMPLINGRATE_HZ    16000
#define TIMER_INTTERUPT_US ( 1000000UL / DACSAMPLINGRATE_HZ / 2 ) // division by 2 because of two channels

#define WS   PA3
#define BCK  PA5
#define DATA PA7
#define testpin PC13

int sine[] = {0x800, 0x832, 0x864, 0x896, 0x8c8, 0x8fa, 0x92c, 0x95e, 0x98f, 0x9c0, 0x9f1, 0xa22, 0xa52, 0xa82, 0xab1, 0xae0,
              0xb0f, 0xb3d, 0xb6b, 0xb98, 0xbc5, 0xbf1, 0xc1c, 0xc47, 0xc71, 0xc9a, 0xcc3, 0xceb, 0xd12, 0xd39, 0xd5f, 0xd83,
              0xda7, 0xdca, 0xded, 0xe0e, 0xe2e, 0xe4e, 0xe6c, 0xe8a, 0xea6, 0xec1, 0xedc, 0xef5, 0xf0d, 0xf24, 0xf3a, 0xf4f,
              0xf63, 0xf76, 0xf87, 0xf98, 0xfa7, 0xfb5, 0xfc2, 0xfcd, 0xfd8, 0xfe1, 0xfe9, 0xff0, 0xff5, 0xff9, 0xffd, 0xffe,
              0xfff, 0xffe, 0xffd, 0xff9, 0xff5, 0xff0, 0xfe9, 0xfe1, 0xfd8, 0xfcd, 0xfc2, 0xfb5, 0xfa7, 0xf98, 0xf87, 0xf76,
              0xf63, 0xf4f, 0xf3a, 0xf24, 0xf0d, 0xef5, 0xedc, 0xec1, 0xea6, 0xe8a, 0xe6c, 0xe4e, 0xe2e, 0xe0e, 0xded, 0xdca,
              0xda7, 0xd83, 0xd5f, 0xd39, 0xd12, 0xceb, 0xcc3, 0xc9a, 0xc71, 0xc47, 0xc1c, 0xbf1, 0xbc5, 0xb98, 0xb6b, 0xb3d,
              0xb0f, 0xae0, 0xab1, 0xa82, 0xa52, 0xa22, 0x9f1, 0x9c0, 0x98f, 0x95e, 0x92c, 0x8fa, 0x8c8, 0x896, 0x864, 0x832,
              0x800, 0x7cd, 0x79b, 0x769, 0x737, 0x705, 0x6d3, 0x6a1, 0x670, 0x63f, 0x60e, 0x5dd, 0x5ad, 0x57d, 0x54e, 0x51f,
              0x4f0, 0x4c2, 0x494, 0x467, 0x43a, 0x40e, 0x3e3, 0x3b8, 0x38e, 0x365, 0x33c, 0x314, 0x2ed, 0x2c6, 0x2a0, 0x27c,
              0x258, 0x235, 0x212, 0x1f1, 0x1d1, 0x1b1, 0x193, 0x175, 0x159, 0x13e, 0x123, 0x10a, 0xf2, 0xdb, 0xc5, 0xb0,
              0x9c, 0x89, 0x78, 0x67, 0x58, 0x4a, 0x3d, 0x32, 0x27, 0x1e, 0x16, 0xf, 0xa, 0x6, 0x2, 0x1,
              0x0, 0x1, 0x2, 0x6, 0xa, 0xf, 0x16, 0x1e, 0x27, 0x32, 0x3d, 0x4a, 0x58, 0x67, 0x78, 0x89,
              0x9c, 0xb0, 0xc5, 0xdb, 0xf2, 0x10a, 0x123, 0x13e, 0x159, 0x175, 0x193, 0x1b1, 0x1d1, 0x1f1, 0x212, 0x235,
              0x258, 0x27c, 0x2a0, 0x2c6, 0x2ed, 0x314, 0x33c, 0x365, 0x38e, 0x3b8, 0x3e3, 0x40e, 0x43a, 0x467, 0x494, 0x4c2,
              0x4f0, 0x51f, 0x54e, 0x57d, 0x5ad, 0x5dd, 0x60e, 0x63f, 0x670, 0x6a1, 0x6d3, 0x705, 0x737, 0x769, 0x79b, 0x7cd,
             };
HardwareTimer timer(3);

#define PIN_PORT GPIOC // testpin
#define PIN_PORT1 GPIOA // WS pin
#define PIN_BIT 5
#define PIN_BIT1 3

#define SD_MODE_PIN PB1

void setup() {
  pinMode(SD_MODE_PIN, OUTPUT);
  digitalWrite(SD_MODE_PIN, LOW); // off
  Serial.begin(9600);
  delay (500);

  SPI.begin(); //Initialize the SPI_1 port.
  SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
  SPI.setDataMode(SPI_MODE0); //Set the  SPI_2 data mode 0
  //SPI.setClockDivider(SPI_CLOCK_DIV4); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  //SPI.setClockDivider(SPI_CLOCK_DIV16); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  SPI.setClockDivider(SPI_CLOCK_DIV128); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock
  //SPI.setClockDivider(SPI_CLOCK_DIV256); //  speed (72 / 4 = 18 MHz SPI_1 speed) PT8211 up to 20MHZ clock

  pinMode(WS, OUTPUT); //Set the Word Select pin (WS) as output.
  pinMode(testpin, OUTPUT);
  SPI.setDataSize (SPI_CR1_DFF); // setting SPI data size to 16Bit

  // timer3 setup
  timer.pause(); // Pause the timer while we're configuring it
  timer.setPeriod( TIMER_INTTERUPT_US ); // // Set up period in microseconds


  timer.setChannel1Mode(TIMER_OUTPUT_COMPARE); // Set up an interrupt on channel 1
  timer.setCompare(TIMER_CH1, 1);  // Interrupt 1 count after each update
  timer.attachCompare1Interrupt(timer3_irq);
  timer.refresh();  // Refresh the timer's count, prescale, and overflow
  timer.resume(); // Start the timer counting

  delay(1000);
  digitalWrite(SD_MODE_PIN, HIGH); // on
  while (1);

}


void loop() {

}

void timer3_irq(void)
{
  static uint32_t channel = 0;
  static uint8_t index = 0;
  channel ^= 1;
  if (channel)
  {
    // digitalWrite(WS, LOW);  //Select RIGHT Audio channel
    gpio_write_bit(PIN_PORT1, PIN_BIT1, LOW);
    SPI.write(sine[index++]);
  } else
  {
    // digitalWrite(WS, LOW);  //Select LEFT Audio channel
    gpio_write_bit(PIN_PORT1, PIN_BIT1, HIGH);
    SPI.write(sine[index]);
  }
}
// http://www.stm32duino.com/viewtopic.php?f=18&t=519#p5195
/**
    I2S example code (modified by Matthias Diro: some sinus melody - 16bit transfer, setting up on timer3)

    Description:
    This I2S example creates a Sine waveform on the RIGHT Audio channel of PT8211
    and a Sawtooth waveform on the LEFT Audio channel.

    This is a very simple how-to-use an external I2S DAC example (DAC = Digital to Analog Converter).

    Created on 27 Aug 2015 by Vassilis Serasidis
    email:  avrsite@yahoo.gr

    Connections between PT8211 DAC and the STM32F103C8T6
    WS    <-->  PA3
    BCK   <-->  PA5 <-->  BOARD_SPI1_SCK_PIN
    DIN   <-->  PA7 <-->  BOARD_SPI1_MOSI_PIN

*/

It seems from the datasheet
http://www.st.com/content/ccc/resource/ ... 161566.pdf ( p.13 )
that only high density devices with equal or more 256k have I2S interfaces.
The BluePill has unfortunately only 64K and therefore not I2S.

Am I right?

The SAMD21G18 which is found on the ARDUINO Zero has a built in I2S interface.
To check, if my chip is not damaged I just connected it to the Arduino Zero, flashed the simple tone and it worked:
https://www.arduino.cc/en/Tutorial/I2SSimpleTone
Chris you are right, only devices with at least 256KB of flash or more have the i2s interface.
There should be an easy way to simulate left justified i2s with the SPI peripheral and a timer, but I haven't tried it, but I think when I searched for ideas to do this found some TI AN describing something similar).
It goes like this:
-Can be used for 16 bit or 8 bit (data widths supported by the SPI peripheral)
-Configure the timer to overflow at 2x the frequency desired (for 2 channels). Configure one of the timer outputs to toggle, so every time the timer overflows a pin toggles, thats the WS pin.
-Now for the SPI you can use either interrupts of DMA:

If using interrupts:
-Attach ISR to timer overflow
-When the ISR get's called send 1 sample out the spi port (spi.write), alternating left and right.

If using DMA (a bit more involved to set, but lower cpu usage)
-Find what DMA channel is the one for the timer Update Event. Alternatively find a channel attached to a CC event for a timer channel, then set that channel CC to overflow at the timer reload value or lower, so it's tripped at the same time as the Update event.
-Configure the DMA channels to do transfers from the buffer to the spi DR register (Important, you set up the DMA channel used by the timer, not the SPI peripheral, since the timer is the one requesting the DMA transfers).
-Enable DMA requests from the timer.
Every time the timer updates due to Overflow, or the CC register match the counter and generates a CC event, the timer will request 1 transfer to the DMA channel from the buffer to the spi peripheral.
The spi should be configured to a speed at which can complete 1 byte or word transfer before the next overflow of the timer, but within a frequency accepted by the i2s DAC used.
Since the transfer is left justified (first bit goes with the first clock signal), the i2s DAC needs to support that format. The DAC should not care that there is no clock signal for a while after receiving input for 1 channel and receiving for the next, so the spi doesn't need to keep a continues flow of clock only, and only be able to complete the transfer for 1 sample before the timer overflows and switches channel.

ChrisMicro
Posts: 161
Joined: Fri Mar 24, 2017 4:51 pm
Location: Germany

Re: I2S example code

Post by ChrisMicro » Fri Apr 21, 2017 5:19 am

The MAX98357a seems to need a quite accurate timing.
The timing of the code example above ( timer interrupt triggers SPI ) seems to be not sufficient for this chip.
The bit sequence needs to start 1 bit delayed ( relating to the I2S specs ) which seems to me that it can not be done with DMA.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest