TMRpcm wav playback library

victor_pv
Tue Jun 30, 2015 8:48 pm
I needed to be able to play some sounds and decided to give a shot to porting this library, which reproduces audio in the Arduino using the timers in PWM output mode.
https://github.com/TMRh20/TMRpcm/wiki

I am almost done porting it, although I am removing a bunch of options that are very specific to AVR, but the function call are going to be the same with the same functionality and hopefully good compatibility with different wav files. I have never used the library in Arduino but have heard some audio with pwm before and is good enough for what I need.
I am using interrupts at first, and probably will use DMA later to free up CPU.
I’ll make the port available once finished. I am not planing for 16 bit quality, but should be fairly easy to implement just using 2 CC for each output, and combining them with some resistors. Anyway I’ll use a whole timer, so using 1 or 2 outputs per channel just means using 2 more pins.


victor_pv
Thu Jul 02, 2015 1:35 pm
Just a quick update for anyone that read this thread, I have the library compiling without any errors now.
I need to wire a filter and speaker and test it out.

RogerClark
Fri Jul 03, 2015 4:02 am
Victor,

Thanks for letting the community know how this is going. I imagine the playback quality should be better than on AVR as the sample rate etc should be higher


victor_pv
Sat Jul 04, 2015 9:26 pm
Finally got it working except for the systick callback that should be loading the data from the sd card to the buffer.
I think I can’t properly access the sdcard from a function called from an ISR. It may have to do with using interrupts for the SPI port, I may test it later without DMA &interrupts.

Anyway, I moved away from using the class. I removed all the class stuff, and a lot of conditional compiling. For me it works better this way, at least at the moment it works.
I can play different wav files. The quality seems just fair for what it is, plus I am not properly filtering the noise out, I didn’t have a small enough capacitor around.
It plays with only 8 bit of resolution, in stereo. Next step is test out using 2 compare channels for each audio channel, to achieve 16bits of resolution, and adding a proper RC low pass filter.
I made a rudimentary amplifier with a BS170, and that works good enough.
Once I have tested it a bit more and have polished the code I’ll share it.

The audio playing part should be usable to play any audio, so it may come handy for very small sounds stored in flash, or like I’m using it now, for wav files stored in an SD-Card.

I think the quality can be on par with the ESP8266 mp3 playing library with a proper noise filter.


RogerClark
Sat Jul 04, 2015 9:33 pm
Hi Victor,

Re:ISR

I read something the other day about interrupt priorities on ARM.

Each ISR input is allocated a priority, the lower the number the higher the priority, i.e. 0 = highest priority.
Interrupts of higher priority can interrupt code already running in a lower priority interrupt.

However, that being said… I didnt think SPI uses an ISR (or does your code use a SPI ISR ?


victor_pv
Sun Jul 05, 2015 5:11 am
RogerClark wrote:Hi Victor,

Re:ISR

I read something the other day about interrupt priorities on ARM.

Each ISR input is allocated a priority, the lower the number the higher the priority, i.e. 0 = highest priority.
Interrupts of higher priority can interrupt code already running in a lower priority interrupt.

However, that being said… I didnt think SPI uses an ISR (or does your code use a SPI ISR ?


RogerClark
Sun Jul 05, 2015 6:18 am
Victor

Ideally, we need to move away from blocking functions if possible.

Well blocking functions are OK, but they don’t give us the full benefit of DMA, i.e they give slightly faster transfers, but they don’t give us the ability that the processor can go off and do other things while the DMA is transferring.

And Ideally, we could run multiple separate SPI DMA’s on different channels.

e.g. I built a test rig with an SD card and also a VS1053 MP3 decoder, where the SD is on SPI1 and the MP3 decoder is on SPI2 – however at the moment there is no point in using 2 SPI channels as its impossible to transfer from SD at the same time as its transferring to MP3.

Also if I had a UI on this (which I don’t at the moment), it would probably have issues, as most of the time, the code is having to wait for either SD transfer to complete or MP3 transfer to complete – so I’d need to write code that polls for UI stuff


mrburnette
Sun Jul 05, 2015 1:11 pm
RogerClark wrote:Victor

Ideally, we need to move away from blocking functions if possible.

Well blocking functions are OK, <…>

Also if I had a UI on this (which I don’t at the moment), it would probably have issues, as most of the time, the code is having to wait for either SD transfer to complete or MP3 transfer to complete – so I’d need to write code that polls for UI stuff


victor_pv
Sun Jul 05, 2015 2:00 pm
I did some tests with CoOS. I believe I included that with one of the ports of CoOS (there are 2, the old one I found, and the latest version I ported by carrying over the changes in the old one).
Well, that example goes to test whether using some RTOS function in the DMA while loop, so the RTOS inmediately releases the CPU to another task, provides any gain.
I don’t remember if I implemented it by using a delay, which is fact causes a task switch in the RTOS, or a Semaphore, which has the same effect, or I even used a new taskswitch function that I found on a forums and I implemented, I would have to look back at it to confirm.

But the result of that test was that having the DMA block in a while loop without any additional “cpu releasing” command, or having it release the CPU for another task had very little or no effect as far as DMA for screen access.

As Ray points out, using the RTOS already takes care of giving CPU to other tasks when you block. I noticed when I looked the ESP8266 mp3 player that they use FreeRTOS, so the ESP can be doing all it’s networking stuff, and at the same time running a customer program easily.
I agree with Ray that we should be using it more. I am completely new to it, and still have to learn much, but I already have written a couple of examples to test things out and have been impressed with it.
Recently I wrote a test sketch that measures the CPU usage while running the same Cubes sketch, that rotates 2 cubes in the screen fairly fast.
CPU usage was around 28% only by using a fast DMA driver for the screen.

Next I need to test the wav player under CoOS or FreeRTOS, and I plan on measuring CPU usage for it. My intention is to make the wav player DMA driven.
The bad part is that the DMA channels are shared between different peripherals, so you need to carefully plan which peripherals will use DMA and which will not.

About the SPI DMA blocking, I remember we talked about implementing a callback mechanism and not block, but we decided to block because most current drivers toggle CS and other pins as soon as the SPI call returns, and they would need to be heavily rewritten to manage non blocking SPI access. But with an RTOS we dont need to rewrite anything, just start the DMA and switch task, and let the RTOS scheduler decide what to run next.

Still should be easy to provide for a blocking and a non-blocking option, so when you write a driver for something you can decide if the non-blocking option may work and plan around it from the start.

To encourage anyone to use RTOS, I will publish somewhere the sketch that measures CPU usage and shows it in the screen.
I plan on using it all the time when using an RTOS and perhaps only remove it once the project is finished, but during development I think is a great help.

EDIT: Video showing the cpu usage, next comes adding the wav playing to it, The Black Keys sound awesome even with 8bits ;)
https://www.youtube.com/watch?v=ObeDcXQ7SzA


mrburnette
Sun Jul 05, 2015 10:40 pm
victor_pv wrote:I did some tests with CoOS.
https://www.youtube.com/watch?v=ObeDcXQ7SzA

RogerClark
Sun Jul 05, 2015 10:48 pm
Guys,

Its interesting about using RTOS to overcome blocking on DMA transfers.

I must admit, I’m a bit old school when it comes to Real Time OS’s on embedded devices. In days gone by, the microprocessors didn’t really have enough grunt to handle an RTOS very well.

However as we now have 128kb to play with and more RAM and more cpu cycles, I guess it makes it more practical


victor_pv
Sun Jul 05, 2015 11:44 pm
CPU usage goes up roughly to 53% by playing a wav file at the same time.
That’s about 25% for the sd reading and moving data to the timer registers.

Now I have found the hard way that the SDFat library does not like cpu intensive ISRs.
It happens both under CoOS and without CoOS, with a normal single task sketch loading from SD card, and the timer ISR moves the data to the timers.
Well, the symptoms are that the SDCard access slows down to a crawl, and the file doesn’t play right any more, it rotates the same buffer a few times before another one is loaded.
When I tried the same in CoOS, I see the CPU usage jump to 100%, and sometimes it even affects the screen. I am not sure what causes it, but I have set the SDFat code to not use DMA, and still happens, so is not the library DMA.
Then I have tried disabling the Timer ISR, and setting the buffer loading one to keep reloading the buffers even though nothing empties it, and then it does not happen any more. So it seems to be the heavy processing in the Timer ISR that causes it.
The TMRpcm library does data conversion between 16bit and 8bit, and mono to stereo, and even volume adjustments all in the timer ISR, which seems to be too much. I measured almost 10% cpu usage in the timer ISR, which seems way too much.
So next I am going to change the wav timer ISR part to just dump the buffer to the registers, and move all the data processing to the same task that loads the buffers. So it will read a 512Bytes buffer, process it, and write it in a new buffer for the timer to dump straight to the registers.
That will help me reach another goal, that’s using DMA in circular mode for dumping the audio buffer to the timer CCR. That way I don’t even need a timer ISR anymore. Reduces CPU usage, and reduces the effect of the ISR in the SDFat library, win-win :)

Anyway, this is a video of the Wav player running on CoOS at the same time it rotates the cubes, still plenty of CPU left for other stuff
https://www.youtube.com/watch?v=dIfgIt6bNJM


victor_pv
Wed Jul 08, 2015 9:06 pm
Ray, you are a master of practical applications.
I want to make a simple nice application showing the features of an RTOS, with display and wav player.

I can take care of the sound playing, and we have RTOS and display drivers working fine, but I have never written any kind of menu system.
Do you know of any library that can manage that, or do can you point me to some example?

I want to make some simple to just show off the features, so rather than having 2 pointless cubes rotating in the screen, have the list of wav files in a folder and provide a menu that you can scroll up and down, and press play with another key. That simple, no need for something super fancy, nice clean interface, perhaps just highlight in a different color on what option of the menu you are.

BTW I solved the problems I described before with sdfat slowing down. I found I had left a bunch of debugging serial.prints enabled, disabled them and problem solved. The gain on that is that it pushed me to try sdfat on SPI2, so I did the modifications in the SPI library to do DMA transfers in SPI2, and I got rid of the interrupts to manage the DMA transfers.


mrburnette
Thu Jul 09, 2015 12:53 am
victor_pv wrote:Ray, you are a master of practical applications.
I want to make a simple nice application showing the features of an RTOS, with display and wav player.
I can take care of the sound playing, and we have RTOS and display drivers working fine, but I have never written any kind of menu system.
Do you know of any library that can manage that, or do can you point me to some example?

victor_pv
Thu Jul 09, 2015 4:00 pm
mrburnette wrote:victor_pv wrote:Ray, you are a master of practical applications.
I want to make a simple nice application showing the features of an RTOS, with display and wav player.
I can take care of the sound playing, and we have RTOS and display drivers working fine, but I have never written any kind of menu system.
Do you know of any library that can manage that, or do can you point me to some example?

mrburnette
Thu Jul 09, 2015 5:46 pm
victor_pv wrote:
<…>
I think I’ll try to just present a list of wav files in the screen, and use short press to scroll to the next one, long press to play, long press to stop.
That would make a $10 wav player ;)

victor_pv
Thu Jul 09, 2015 7:35 pm
Thanks for all the advises. Using the button that way, I only need 1 button for many things, and the mini already includes one button, so one less component I need to add ;)

What do you think of something like:
Print welcome screen (nice BMP of course, that’s one advantage of using an sdcard).
If button pressed within welcome screen time out, present a short diagnostic menu.
Diagnostics include checking if the sdcard is present and displaying the information of it, and running a short speaker test with a tone.
Button press again bring you to player screen.
Player screen displays all the tracks available, highlight the first one.
If nothing is presses, start playing with that one, and when it’s over go to the next one and so on.
If the user press the button for say 2 seconds, start playing the track.
If the user press the button for 2 seconds while playing stop.
If the user press the button for short while playing, highlight the next track, but nothing else.
If the user press the button for 2 seconds, play that track.

So basically I need one pointer to the track it is playing right now, and one pointer to the track highlighted, which can go down to the next one in a cyclic manner.
An I need an array with all the wav diles discovered. The pointers to the playing track and highlighted track are just index to that array.

I think this is clean and simple enough, doesn’t need addional hardware beyond the maple mini, display with sdcard, and speaker. (probably a mosfet, capacitor an resistor for a rudimentary amplifier and low pass filter). And it should fit in the flash left (takes about 40KB now, but removing the cubes should free up some) and in the RAM left (have about 4 or 5KB left, but removing the cubes should free up some). I am using 2 x 1K buffers for the timers, plus a 512bytes one for loading the data from the sdcard, and a bunch of stacks for each task. Lots of that can be reduced or go away.
I am also planning to add a DMA option to the timer CCR update, so that will free up cpu cycles.


mrburnette
Thu Jul 09, 2015 11:40 pm
victor_pv wrote:Thanks for all the advises. Using the button that way, I only need 1 button for many things, and the mini already includes one button, so one less component I need to add ;)

What do you think of something like:


victor_pv
Sun Jul 26, 2015 4:47 pm
mrburnette wrote:victor_pv wrote:Thanks for all the advises. Using the button that way, I only need 1 button for many things, and the mini already includes one button, so one less component I need to add ;)

What do you think of something like:


RogerClark
Sun Jul 26, 2015 9:19 pm
Ultra Cool..

Thanks for sharing


mrburnette
Mon Jul 27, 2015 11:53 am
Very nice!

Surprised at the video speed with the sound decoding. Awesome.

Ray


victor_pv
Mon Jul 27, 2015 12:43 pm
mrburnette wrote:Very nice!

Surprised at the video speed with the sound decoding. Awesome.

Ray


RogerClark
Mon Jul 27, 2015 9:30 pm
Victor

I dont think you improve the speed of that code. So assuming you are DMAing the input buffer, the only speed improvement would in in the lib when it processes and plays the audio data


victor_pv
Tue Jul 28, 2015 1:37 am
Well, thanks for reminding me because I had forgotten to enable DMA in the sdfat library again after the last time I disabled it, and was not using DMA. That shaved a bit of CPU usage, but I discovered a small bug in the latest code for the SPI functions.
In dmaTransfer and the 2 dmaSend functions, we have this line:
uint8 b;

Now b is not always initialized, and can result in random number returned to the sdfat library, that affects on some cases (pretty random, some times it works, some times it crashes, some times it detects the sdcard but just can’t list the content).

At some point I forgot to initialize that to 0, so we just need to add a line next to it:
b = 0;

Or change the line to
uint8 b = 0;

It does not affect DMA operation in the tft library, cause that one doesn’t care about the returned value, but the sdfat library uses that value to detect if the transfer timed out without completing. With the random b values returned, it thinks the transfers did not complete some times when it actually did.

I won’t send a pull because we can’t merge automatically, so just in spi.cpp, functions dmaTransfer, dmaSend (for 8 bits) and the other dmaSend (for 16 bits), change that line to initialize b to 0.

About improving the output, as of right now it only checks if the sound is mono or stereo, and then dumps the value from the buffer to the timer register, but does not do any data conversion, that is done in the loop above when the data is read, so I don’t think I can improve the output part much unless I do that with DMA, which I have considered too, but I need to plan carefully cause I already use 2 DMA channels for SPI1, 2 mode for SPI2, and I plan on using 2 more for IR TX and IR RX, that 6 channels, so I need to plan which timers I will use for each thing, so I dont end up with 2 devices needing the same channel at the same time.

The loop above consumes about 20% of my cpu time :( if I read an 8bit wav, which goes straight to the output buffer with no processing, I see 20% less cpu time used than if I read a 16bit wav, which needs to use the loop.


RogerClark
Tue Jul 28, 2015 1:48 am
Victor

Just let me know what function its in, and I’ll update it manually


victor_pv
Tue Jul 28, 2015 1:55 am
RogerClark wrote:Victor

Just let me know what function its in, and I’ll update it manually


RogerClark
Tue Jul 28, 2015 2:01 am
I’ve directly edited the file in GitHub

https://github.com/rogerclarkmelbourne/ … rc/SPI.cpp

Can you take a quick look and confirm I did the correct ones


Vassilis
Tue Jul 28, 2015 8:38 am
wow! very cool!
well done victor

victor_pv
Tue Jul 28, 2015 1:13 pm
RogerClark wrote:I’ve directly edited the file in GitHub

https://github.com/rogerclarkmelbourne/ … rc/SPI.cpp

Can you take a quick look and confirm I did the correct ones


Vassilis
Tue Jul 28, 2015 3:19 pm
victor_pv wrote:Vassilis thanks, nothing close to a full fledge mp3 player but provides for producing more sounds than just a simple beep beep :)
At some point I want to try porting libmad or helix to our core, for mcus with 48Kb or more. There are already versions for the stm32f1, but using the std peripheral library. That could help eliminate 1 component in projects like your mp3 webplayer. and an RC board is the same price or cheaper than an mp3 decoder board. If you feel brave to deal with the stm32 DAC and timers, I can pass you the links with the working ports.

victor_pv
Tue Jul 28, 2015 4:29 pm
Vassilis wrote:victor_pv wrote:Vassilis thanks, nothing close to a full fledge mp3 player but provides for producing more sounds than just a simple beep beep :)
At some point I want to try porting libmad or helix to our core, for mcus with 48Kb or more. There are already versions for the stm32f1, but using the std peripheral library. That could help eliminate 1 component in projects like your mp3 webplayer. and an RC board is the same price or cheaper than an mp3 decoder board. If you feel brave to deal with the stm32 DAC and timers, I can pass you the links with the working ports.

Vassilis
Tue Jul 28, 2015 9:51 pm
I finally ordered the STM32F103VET6 board for testing it. It includes all the necessary features for mp3 decoding.
– 64 kB RAM
– 512 kB flash
– 2 x I2S
– 2 x DAC

and many more…


RogerClark
Tue Jul 28, 2015 10:16 pm
can you post a link to the board

thanks


Vassilis
Wed Jul 29, 2015 6:05 am
RogerClark wrote:can you post a link to the board

thanks


RogerClark
Wed Jul 29, 2015 6:10 am
OK

I have 2 or 3 of those


madias
Wed Jul 29, 2015 3:11 pm
keep in mind, that this stm32F103VEt6 board for nearly under 10USD has also some goodies:
Board has three SO8 chip factory where SPI flash 25q16, EEPROM 24C02 have been welded on , 485 IC welding on their own needs
(whatever a “485 IC” is….)
http://www.aliexpress.com/item/FREE-SHI … 80157.html
I ordered it a week ago, because I wanna also play with I2s :)

samghan20
Sun Aug 07, 2016 7:05 pm
Hi
witch TMRPCM library works in the stm32?

RogerClark
Sun Aug 07, 2016 11:31 pm
I don’t think anyone on this forum has ported that library

mrburnette
Mon Aug 08, 2016 2:48 pm
RogerClark wrote:I don’t think anyone on this forum has ported that library

RogerClark
Mon Aug 08, 2016 9:13 pm
Thanks Ray

golpesar132
Wed Sep 27, 2017 5:50 pm
Is there any simple example for this library and play wav??
What must be my wav file details??

Leave a Reply

Your email address will not be published. Required fields are marked *