New library for PWM playback from SD cards: SimpleSDAudio

Hi,

here an example how to read a file with the very low-level SD-sector-access commands from SimpleSDAudio-Library:

/*
 Simple SD Audio example, prints content of TEST.TXT from SD card.
 
 This example shows how to use the SimpleSDAudio library embedded low-level file access.
 You need: 
 - An Arduino with ATmega368 or better
 - An SD-Card connected to Arduinos SPI port (many shields will do)
   -> copy TEST.TXT on freshly formated SD card into root folder
 
 See SimpleSDAudio.h or our website for more information:
 http://www.hackerspace-ffm.de/wiki/index.php?title=SimpleSDAudio
 
 created  19 Jan 2013 by Lutz Lisseck
*/
#include <SimpleSDAudio.h>
#include <sd_l2.h>
#include <sd_l1.h>

// Create static buffer (shared for all file access, because mega328 are quite low on RAM!)
#define BIGBUFSIZE (2*512)      // bigger than 2*512 is often only possible on Arduino megas!
uint8_t bigbuf[BIGBUFSIZE];

SD_L2_File_t TxtFileInfo;  

// helper function to determine free ram at runtime
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

void setup()
{
  uint8_t retval;
  
  // Open serial communications and wait for port to open:
  Serial.begin(9600); while (!Serial) {}

  // Setting the buffer manually for more flexibility
  SdPlay.setWorkBuffer(bigbuf, BIGBUFSIZE); 

  // Using F("...") to avoid wasting RAM  
  Serial.print(F("\nInitializing SD card..."));  
  
  // If your SD card CS-Pin is not at Pin 4, enable and adapt the following line:
  // SdPlay.setSDCSPin(10);
  
  if (!SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO)) {
    Serial.println(F("Initialization failed. Error code:"));
    Serial.println(SdPlay.getLastError());
    while(1);
  } 

  Serial.print(F("Free Ram: "));
  Serial.println(freeRam());

  Serial.print(F("Looking for TEST.TXT... "));
  // Search for file TEST.TXT in Rootdir (=cluster 0), search shortname files only (0x00,0x18)
  retval = SD_L2_SearchFile((uint8_t *)"TEST.TXT", 0UL, 0x00, 0x18, &TxtFileInfo);
  if(retval) {
    Serial.println(F(" not found on card! Error code: "));
    Serial.println(retval);
    while(1);
  } else {
   Serial.println(F("found.")); 
   Serial.print(F("First sector: "));
   Serial.print(TxtFileInfo.ActSector);
   Serial.print(F(" Size: "));
   Serial.println(TxtFileInfo.Size);
  }    

  while(TxtFileInfo.ActBytePos < TxtFileInfo.Size) {
    // read one sector
    retval = SD_L1_ReadBlock(TxtFileInfo.ActSector++, &bigbuf[0]);
    if(!retval) {
      uint16_t cnt = 0;
      // print sector content until 512 bytes or file length is reached
      while((cnt < 512) && (TxtFileInfo.ActBytePos < TxtFileInfo.Size)) {
        cnt++;
        TxtFileInfo.ActBytePos++;
        Serial.write(bigbuf[cnt]);
      }
    } else {
      Serial.println(F("Err read sector. Error code: "));
      Serial.println(retval);              
      while(1);
    }
  }
  Serial.println("done.");
}

void loop(void) {
}

I will include this example in later versions of the lib...
I had to edit this code in a way that the Audio-Lib shares the same buffer as that is used for sector-read because only that way it should fit to ATmega328 that is quite low on RAM.

Tuttut

Hi! We're trying to run the library with an Arduino Duemilanove and the Breakout Board for SD-MMC Cards. We´re having troubles when we try to run the Bare Minimun with debug and at the serial output we get an error code: 1

We've checked that our SD card is correctly wired and with the free RAM method we've got a 708...

So what does this mean? Sorry, English is not my native language...

We don't know why the code isn't running, if is the RAM or another point that we cannot see at this time

Can you help us?

Hi, error code 1 is a sign that there is something wrong with the SD-Card connection, RAM is no problem. Have you used level-shifters to ensure that the SD-card only get 3.3V? Otherwise you can damage the card. Are you using any kind of shield for the SD-card? You find also info about wiring the SD-card in the file SimpleSDAudio.h, this also explains how to build the level shifters.

miriel90:
Hi! We're trying to run the library with an Arduino Duemilanove and the Breakout Board for SD-MMC Cards. We´re having troubles when we try to run the Bare Minimun with debug and at the serial output we get an error code: 1

We've checked that our SD card is correctly wired and with the free RAM method we've got a 708...

So what does this mean? Sorry, English is not my native language...

We don't know why the code isn't running, if is the RAM or another point that we cannot see at this time

Can you help us?

sounds like problem with SD card set-up...

how is it set-up?

what breakout board are you using? Do you have any pics of the wiring/set-up?

I have used this with the following with 'success':

1.) Arduino Duemilanove 2009 board with a SeeedStudio SD shield (has bot SD and micro SD) http://www.seeedstudio.com/depot/sd-card-shield-p-492.html

2.) have created a custom 'Arduino' board, all SMD, with on-board microSD socket, this runs at +5v/16MHz.. with a lever shifter for the SD card (and +3.3v regulator)

3.) a BARE minimum 'Arduino' circuit... running at +3.3v native logic.. and INTERNAL 8MHz clock.. with on-board micro SD card.. (running direct connect, no lever shifter needed or any voltage divider)

and after working through some of MY mistakes.. all have worked great and as described.

Tuttut:
Hi, error code 1 is a sign that there is something wrong with the SD-Card connection, RAM is no problem. Have you used level-shifters to ensure that the SD-card only get 3.3V? Otherwise you can damage the card. Are you using any kind of shield for the SD-card? You find also info about wiring the SD-card in the file SimpleSDAudio.h, this also explains how to build the level shifters.

Yes we used level-shifters but we used a wiring configuration different from the wiring of the SimpleSDAudio.h... so we're going to change our wiring. We're using this shield SparkFun SD/MMC Card Breakout - BOB-12941 - SparkFun Electronics? for the SD-card and the configuration we've used previously is this:

D2 nothing
D3 Chip Select line (CS) needs pull down resistors
CMD pin 11, no pull down resistors
CD nothing
CLK clock connect to pin 13, needs pull down resistors
VCC 3.3V
GND ground
D0 pin 12, needs pull down resistors
D1 nothing
WP nothing

I found it in the same page of Sparkfun...

So, the D2 pin of the Breakout Board is the CS?

Thanks for your answers! :smiley:

I have set up my sd card and successfully played the example program (although for some reason it doesn't work with the two resistors)
My problem is how to produce my own files as I cannot understand exactly what I am supposed to convert my wav file to. I have downloaded the SOX program as suggested and haven't a clue how to use it. I downloaded a guide but find it totally incomprehensible. Please, is there a simple explanation for ancients like me with a shortage of grey cells !

hi-

in the library .zip file .. there is a directory called 'tools'..
(Datei:SimpleSDAudio V1.02.zip – Hackerspace Ffm) <-- latest version posted is v1.02

SimpleSDAudio\tools

Inside that you had three more directories:

Arduino with 8 MHz
Arduino with 16 MHz
sox_win
&
ReadmeSimpleSDAudio.txt

(the readme explains all of this)

so you take your /wav file of choice, that you want to convert..(the file format accepted/used by SimpleSDAudio is .afm)

if you need are using an 8MHz board/clock.. then you would open/go into the Arduino with 8MHz folder.. and drag your .wav file over the conversion you want done/format you want to end up with.

which is either FULL or HALF rate.. and either MONO or STEREO

if your board is running a 16MHz clock.. then you do the same in the Arduino with 16MHz directory...

after you drop the file on the correct .bat file you want.. you will be prompted to press any key from a command prompt (CMD) window..

this will created a folder called 'converted' in the directory you just dropped the .wav in.. and inside will be your new, ready to use .afm file.

That's brilliant - thanks very much - works a treat and with good sound. There will now be a period of silence whilst I figure out how to select and play specific files from a list. I presume that this is possible if not perhaps someone could tip me off to save me from hurting my brain !!

there is a demo provided whre it lists out all .files on the SD card..

and you can enter it in by name (in the serial monitor)..

and you can then P = play S= stop...etc..etc (its outlines the commands)

I bet if you tear that demo apart you'll be able to cut out those snippets you want

That's great, many thanks.. I'll have a crack at it

Been studying the demo sketch and can see roughly what bit is doing. I am trying to find out how to issue a serial command from within the program (rather than via the serial monitorto select and play a file. I'm afraid I am very new to Arduino and am perhaps taking on too much here but I just cant see how to do it.

Hi folks, good news from me: 8)
I've updated the library and now you don't need to call worker() anymore if you provide the SSDA_MODE_AUTOWORKER flag at initialization. I've also added a doorbell (with ding-dong-sound) example, the SD card file access example and edited the examples for autoworker-function. Also there is the fancy AbsoluteMinimum-Example to play the example-audio-file from card that contains just this:

/*
 SimpleSDAudio absolute minimum example, plays file EXAMPLE.AFM from root folder of SD card (CS on pin 4)
 through speaker/amplifier at pin 9 for ATmega328 / pin 44 for ATmega1280/2560.
 */
#include <SimpleSDAudio.h>
void setup()
{ 
  // SdPlay.setSDCSPin(10); // Enable if your SD card CS-Pin is not at Pin 4... 
  SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER);
  SdPlay.setFile("EXAMPLE.AFM"); 
  SdPlay.play();
}

void loop(void) {
}

Enjoy!

nice job..

(for those lazy.. here is the link to the lib: SimpleSDAudio – Hackerspace Ffm)

So using what you've posted.. all you need to do in the MAIN loop..

for a button 'check/press' is:

(dummy code:)

if(digitalread(button1) == LOW){
     SdPlay.setFile("EXAMPLE2.AFM"); 
     SdPlay.play();
}else if(digitalread(button2) == LOW){
     SdPlay.setFile("EXAMPLE3.AFM"); 
     SdPlay.play();
}

etc..etc..

no need to init or de-init anything?

the more demos showing all the functions and features make for popular usage!!..

I still have gotten to the 2/4 channel stuff I saw mention.. or how all that works! lol..

(side question and really more a general hardware questions.. is there any other things we can add to the single output on D9 to make it sound a bit better?.. I only have a 100uF cap right now.. no resistors any thing)..

I dont/wont have enough room to have a pot/wiper and a ton of extra components.. but if adding another cap or resistor or two will help improve.. Id like to try!).. I saw some examples use a resistor.. and some use only a cap..etc.

@Tuttut, did you see my post at #84 about use on the 1284P? Thanks.

Thanks once again .. I'll get to work on that. It's amazing how I can spend several hours getting nowhere and then you guys come up with a nice straightforward solution that leaves me wondering why I didn't think of that ! GRRRR. Anyway many thanks for the help, its greatly appreciated.

Hi,
@tack: have you looked inside the new version? I think I've put your stuff in, but it would be nice if you can try if also autoworker works on your platform as it needs additional stuff in those hardware-settings.

@xl97: You still need the init function that does a lot internally like setting up the file-system and configuring the audio ports. For Button example see the doorbell example that is also new in my library.

hello Tuttut

i just finished an analysis of pwm distortion, and then thought it would be cool to make a good and cheap wav player. and it turns out it is cool, and you have already done it. thanks for the work, i always enjoy finding projects like this. if you are open to suggestions, here are some things that my research has shown to make improvements in the audio quality.

  1. use 14b, phase correct pwm, at 62khz clock rate. you never really get the full 16b anyways, even with perfect resistors, due to distortions building up in the noise floor. also, perfect resistors are difficult to come by, even with trimming, due to the variability of the internal resistance of the arduino.

  2. use 3.9k / 499k resistors for the mixing. the output resistance is around 40ohms, so this keeps its error component low in comparison to the 3.9k.

  3. dont bother with the external logic chips, they have a lot of jitter in comparison to the arduino itself, and introduce phase modulation and associated distortions. also, the noise floor on the arduino i tested was -110dB, which was below the pwm noise floor for frequencies above 1kHz. with the usb disconnected, the arduino isnt that bad.

  4. you can try changing the depth / location of pwm to get further gains, but its probably not too significant for audio (as compared to pure tones), as the maximum amplitude is usually low anyways.

here are the writeups if youre interested:

distortion analysis:

dual pwms:

again, great work, im looking forward to trying it out.

I'm loving this, already using it in a solar-powered project.

Now, maybe I'm daydreaming, but I would love to be able to output AM Radio Modulation instead of audio. ]:smiley:

http://dangerousprototypes.com/2011/10/05/am-sofware-radio-using-arduino/

Code:
http://dangerousprototypes.com/forum/download/file.php?id=5107&sid=9ed56f436c83604b287f75ec73f11146

Hi g_u_e_s_t,
a warm welcome on the journey of pushing the audio quality from 1-bit (and 2-bit) outputs to new frontiers! Raising audio quality from such limited systems is a challenging task but I am amazed how far we get. Some years ago I also shared the opinion that without an additional DAC it would be hard to get audio out of an AVR that sound far better than telephone.

I read your writeups about PWM distortion and - at least - tried to understand them. I think you did a very good theoretical work, even I didn't understand everything yet. But I think you did a really great work toward raising audio quality out of such constrained systems and breaking actual known limits. Some time ago I started my journey by coding a high-order delta-sigma-modulator that converts audio-files to pulse-dense-modulated single bit-streams that I played back through the SPI port of the AVR at rates of up to 2 MBit/s. That way I moved most of the quantization noise out of the audible band, but for whatever reason, the quality on real-world hardware was worse than expected and because it is not possible to do mixing easily with pulse-width-modulated data I stopped my efforts. I wanted then to make something that makes good audio quality playback as easy as possible to use and with minimum hardware effort and started SimpleSDAudio.

With your research results I think we should try to get a step further in quality, but it's time to leave theoretic and try it at the real thing. Let's try if and when phase-correct PWM really sound better than fast PWM. Does it make sense for 2-bit systems to go from 16-bit to 14-bit (or even less) for better results? How about pre-filtering the input audio files to something like PWM-Freq/4? We will loose some high-frequency audio-information but maybe reduce perceived noise also.

You measured -110dB noise floor, I can't believe this really. When SimpleSDAudio stopped at a fixed PWM value, there is really no audible noise, but how about when it is accessing SD card and the AVR has to work harder? I thought the noise is much higher than but maybe I am wrong and that noise is really only from those artifacts you described - I have to try again...

Also interesting that Ti-paper about logic outputs. I wondered why it is so hard to drive speakers at reasonable volume, but with a 74AC14 it should be again worth a try to drive a speaker as this thing cost only a quarter Euro.

Keep on your good work! I look forward to see what happens if you apply your thoughts to real hardware...

@WilliamK: Interesting thing that AM modulation. BTW: hook up a laser-pointer or a focused LED to the digital audio output pin and receive it over quite a distance using a small solar-panel from garden-light connected to an amplifier - it works great.

hello Tuttut,

one more thing. you can pre-distort a wav file so that it plays back identical to the source material. the pwm transform is a known function, and although not invertible, it can be compensated for perfectly. i have yet to implement this, but i have read research papers on it, which prove it mathematically.