I2S example code

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

Re: I2S example code

Post by victor_pv » Thu Sep 10, 2015 12:15 am

madias wrote:Ok, I was reading the RTOS reference and looked at some examples.
In the library folder there are two RTOS libs: FreeRTOS and FreeRTOS821. I think the 821 is the newer one that I should use? (Sadly I didn't found a hint in the forum)
Howto DMA and RTOS-tasks (my consideration):
In the DMA IRQ, which triggers when buffer is empty (I deleted the half empty option) I should set a handler/trigger to an RTOS-task (call it: RTOS_audioengine)
So the RTOS_audinoengine fires up fill up and fill up the buffer?

Should I do that within the DMA IRQ with:
xQueueReceiveFromISR ?
edit or
So the RTOS_audioengine is first priority.
The rest of the tasks should be: TFT (low priority), MIDI (middle-high), human-interface (middle-low), modulations ADSR, LFO's (middle)....

As you use a single buffer, Semaphore should work fine.
The Queues are more useful if you have several items to give, like imagine you set up 2 buffers, you can place them in a Queue with size for 2 objects, such as the pointer to the buffer start address.
Then when DMA finish sending a buffer the DMA ISR could picks one from the queue, and place the one it just used in the queue.

But for 1 single buffer, I would probably just use a semaphore.
Then in the function that needs to fill the buffer it just waits until the semaphore has been set as free and fills the buffer and take it. And the DMA ISR only takes care of giving the Semaphore back.

If you decide to use the half transfer and transfer complete interrupts, then you can split the buffer in two halves as I said before, and then you could use a Queue for 2 objects.

The ESP8266 MP3 player that someone linked a while back, uses a queue for the sending buffers from the task that decodes the MP3 to the task that plays them. That one is written for FreeRTOS if I remember right, but you can have a look.
I haven't personally used Queues yet, so can't give you much more advise.

EDIT: to add more details.
Both with queues and semaphores, you can set the timeout value waiting for it to be free. If you set it to infinite, then that task will seat not taking any CPU time until the semaphore is free or the Queue has an object to be taken. I used semaphores extensively I use mutexes to write to the screen, so only one tasks can write to the screen at any given time. Kind of like semaphores, just slightly different.

So I would do something like this.
1.-Declare semaphore.
2.-The task that writes the buffers, RTOS_audioengine, takes the semaphore, fills the buffer, and loops back to try to take the semaphore again, to just seat there because it wasn't released yet.

3.-The DMA ISR will eventually trip, and will release the semaphore, and return.
Next, RTOS_audioengine will be called because the semaphore was released, and will repeat step 1.

In the time between that task filling the buffer and the DMA emptying it, all the other tasks will take time to run depending on their priorities.

If a higher priority task is in a loop checking some something like reading a value from a pin or something that is not a CoOS thing, like queue, semaphores, mutex, etc it will block any code with a lower priority, because CoOS doesn't know it is waiting, so it will let it run.
So any time you need to wait for something, either insert a vDelay, or use mutex, semaphore, etc. You can see a bit of that in how I read the button, that has a bunch of delays before re-reading the button again. That gives time to any other task to run.

Your priorities look good to me, but the human-interface, unless you inssert a some delays like I did, should have a lower priority than MIDI, otherwise someone pressing a button may cause some Midi packets to be lost.

Posts: 813
Joined: Mon Apr 27, 2015 11:26 am
Location: Vienna, Austria

Re: I2S example code

Post by madias » Thu Sep 10, 2015 9:03 am

Dear Victor,
thanks for your inputs!
I'll play a little bit with Coos and RTOS to get the right "feeling" with it.
As a next step, I'll design a flow chart of all tasks and the osc-model. Some things are vague to me, like building a LFO:
I'll use about 8-12 LFO's (each Voice (4) has 2-3 indepenent LFO's, so there are LFO_a[4], LFO_b[4] and LFO_c[4].
So I built that on my TIVA with a timer. LFO's are very simple: Frequency up to 200Hz (or lower), they only scan through a table, like sine[256]. So I gave all of them into a timer_ISR (with for (byte voicenr=0;voicenr<=MAX_VOICES;voicenr++) because the routine is always the same) . For the speed I setup a counter variable (0...256) and the table triggers one step if the counter variable reached the value. With RTOS it might be better to give the LFO function into a separate task or should I build 4 tasks (for each voice) and instead of the counter variable I set up a RTOS_delay... many things to try out :)
Another thing:
I I occupied this thread so I'll od further posting on: http://www.stm32duino.com/viewtopic.php?f=19&t=533

Posts: 18
Joined: Thu Oct 08, 2015 5:44 am

Re: I2S example code

Post by jbforrer » Fri Oct 30, 2015 5:07 pm


I have previously interfaced a Wolfson WM8751 CODEC to a F4 Discovery board for a SDR project. Works great but that was using CMSIS libraries and Eclipse-based IDE.

Now trying now to interface a 320AIC23B CODEC to a Blue Pill using STM32Duino IDE.
I2C is used to configure the CODEC ... that works fine, i.e. clock signals appears OK on the scope when the CODEC is initialized. I seem to have some issue related to I2S.

In my setup, the CODEC hardware is MASTER providing BitClock (PA5) and WS (PA3) to the STM32 I2S pins.
It appears PA5 may be configured as output and stays LOW, at least in SLAVE mode.
Am I missing something that needs to be set up besides ...

SPI.beginSlave(); //Initialize the SPI_1 port (STM32 is SLAVE).
SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI_2 data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV16); // Slow speed (72 / 16 = 4.5 MHz SPI_1 speed)
SPI.setDataSize (SPI_CR1_DFF); // setting SPI data size to 16Bit

Thanks much.

EDIT: Think I found an answer ... STM32F103C8T does not have I2S. I'll have to try something like STM32F103VET6 which as two I2S. Although one can get close to emulate I2S by clever SPI usage, timing becomes tricky ... true I2S relies on continuous bit streams. Best to use I2S DMA. Thought I'd share that tidbit.

Posts: 33
Joined: Mon Jun 01, 2015 6:16 pm

Re: I2S example code

Post by tj_style » Thu Nov 05, 2015 8:16 pm


On the first page is mentioned that PT8211 is using LSBJ (Least Significant Bit Justified) "japanese input format" or some people sid is EIAJ format.
I have TDA1545A, here is datasheet:

are that DAC are using same format like PT8211 ?
because I can't have working configuration for that DAC.
the sound is produced but with much noise.
I also have TDA1543, is I2s version DAC, and it work great using I2S_Standard_Phillips mode.


Posts: 813
Joined: Mon Apr 27, 2015 11:26 am
Location: Vienna, Austria

Re: I2S example code

Post by madias » Mon Nov 09, 2015 10:25 pm

I did a quick look into the datasheet and they should be nearly a drop in replacement for the PT8211 or the TDA1543.
Two things I can recommend:
1) Try out only 8 bit (I'd bought from china some TDA1543 and they are totally fakes and work only with 8-bit, tried it out on more than one system and different audio formats)
2) Try out different Audio formats, sadly I cannot remember where I've set the bit for that (just take a quicklook in all those STM32 - I2S guides, it should be easy to find, maybe I'd wrote it into code...)

Posts: 33
Joined: Mon Jun 01, 2015 6:16 pm

Re: I2S example code

Post by tj_style » Sat Nov 14, 2015 1:18 pm

Hi Madias,

Yes, it's hardware issue. The first TDA1545A that I was used is the fake one.
Now I use ooriginal TDA1545A, is working great.
Only 1 issue left for playing 24bit files, I think I need to convert the stream to 16bit first.

Posts: 813
Joined: Mon Apr 27, 2015 11:26 am
Location: Vienna, Austria

Re: I2S example code

Post by madias » Sat Nov 14, 2015 8:44 pm

Sad to hear that you also bought a fake TDA DAC :( I think asia isn't the place to buy them (TDA).
about 24 bits to 16 bits:
Why not just:

Code: Select all

stream >>= 8;

Or you wanna put out via I2s the whole 24 bit range? (Shouldn't be a real problem, but unless we do high end recording / playback devices, 16 bit should be enough, because I2S on the STM32F1xx is really castrated (no real good matching divider for sampling rate,...))

Posts: 3
Joined: Wed Jan 20, 2016 2:51 pm

Re: I2S example code

Post by marksparks » Wed Jan 20, 2016 3:07 pm

Good to see the 14p eBay chips working.
Great project.
If you can help please with suggestion on similar UDA1330ATS Dac chip?
The datasheet says the WS must only change on negative edge of BCK, which of course cannot be exactly so with SPI, after DIN transfer; taking WS high/low - could be any state of clock?

But your chip has same requirement in datasheet timing, and it works!
Also, the 'Japanese LSB justified' is amazing clear wording - unlike UDA1330ATS head-scratching confusion. To me anyway.

They are same WS protocol, yes? The L/R data is transferred high speed and then other things can be done before next data transfer?
I want to use the UDA1330ATS on Atmega328 pro mini - need lower sps around 8ksps, therefore interpolating filter essential (10 bit values + dummy data made up to 16 bits).

Many thanks.
(122.78 KiB) Downloaded 41 times

User avatar
Posts: 7494
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia

Re: I2S example code

Post by RogerClark » Wed Jan 20, 2016 8:37 pm

Are you sure you cant use SPI On the falling edge.

SPI has multiple modes etc. You many want to double check whether you can use it after all

Posts: 3
Joined: Wed Jan 20, 2016 2:51 pm

Re: I2S example code

Post by marksparks » Wed Jan 20, 2016 9:52 pm

What I was looking at being a possible problem is that Chip Select CS can be taken LOW at an interval before sending the data via MOSI.
On an oscilloscope, you see the 8 bits as a burst in sync with clock for each byte. Then a gap and then another burst, etc.
But if WS has to go HIGH or LOW to direct data to the correct internal latch of the DAC at the falling clock edge...

Never mind, you seem to have been successful using a big gap at this point in the timing.
I think I can now see why this is so, because the clock has stopped between each byte. And DAC latch has no knowledge or care about this. Well, so long as it isn't a few hours or days between bytes. The WS state change has still been on the 'edge' of the clock, when the stm32 resumes to send the next SPI burst.

In your first example sketch posted. I tried calculating the SPI 4.5mHz clock prescale and your 16 bit words (1 left,1 right).
It takes 1uS to update table position, switch channels,etc. The rest of the time is to SPI.transfer() the 4 bytes?

So for my 8ksps, I need double this rate = 16kHz SPI CLK.

Many thanks, lots of useful info from your posts.

Post Reply