New library for PWM playback from SD cards: SimpleSDAudio

AllanB:
Hi

I'm exited about this library. I might have a troublesome SD Card module.
I keep getting an error code: 49. I cant find out what code 49 is.
If I use Arduino's standard SD example listfiles, I get a correct list of files on the SD card. The SdFat library from Google Code Archive - Long-term storage for Google Code Project Hosting. also read the SD Card with the same wiring. So the wiring must be correct. And I use SdPlay.setSDCSPin(10); as pin 10 is my CS pin.
Then I commented the line: SPSR |= (1 << SPI2X);
Same result.

My sd module is this one http://www.lipoly.de/index.php?main_page=product_info&products_id=213383

I really hope someone can with this problem.

EDIT ----
It was not the SD Card Module but the SD Card itself. I tried with another one. Now it works.

Just my two cents, I had this same error and was to fix it by reformatting the sd as FAT. You may be able to do the same with your sd, but don't use the quick format or the card will give the same error.

Hi, I will try answer some of your questions:

@timberwolf9:
Why the argument -r 64000? If you use a 16 MHz Arduino, it should be 16 MHz / 256 = 62500. But anyway, the sound is just a little bit to slow then...

@bratan & timberwolf9:
I will tell you some internal details about my library: The library uses a ring buffer of 1024 bytes when no work buffer is set manually. As the buffer is filled in one sector blocks of 512 bytes each, the buffer became a ping-pong buffer in most cases. With 512 bytes, at 8-Bit/Mono/62.5kHz that is about 8ms time, because the data reading from sd-card also takes some time, your observation of 5ms max delay between worker()-calls is very realistic.

On mega-arduinos is still a lot of RAM availible, so you can try to increase the audiobuffer by calling setWorkBuffer(...) before calling init(). With an increased buffer you will get more time between worker-calls.

By using the function isUnderrunOccured() you can detect if you even had a slight buffer underrun. But beware, I think this function returns true on every first call after calling play().

I also thought about calling the worker-function automatically in a timer interrupt. This should be possible, but only if that interrupt is set to non-blocking to guarantee the main audio interrupt the maximum priority. Maybe I will try to implement such in a next version of my lib, so that calling worker() is not necessary then anymore.

@AllanB:
Error code 49, which is 0x31, indicates that something is wrong with the bootsector on that cards. Maybe there is more than one partition on the card or something else is wrong. That explains why other cards work for you.

@bratan:
Yes, rising the baud-rate should solve issues with print-functions, as then the worker-calls are called more often.

To your question about other Audio-Playback-Shields: As far as I have seen it, all other audio-shields contain a dedicated processor that reads the audio-data from storage and feed it to an audio-DAC. For a good sound the DAC must be feed with low jitter at the audio rate of the audio material, otherwise you may recognize audio distortions like slowdowns or stuttering. With a dedicated processor it doesn't matter what your Arduino does while audio is playing, but my library is for all those who don't want to spend extra money to such a thing. SimpleSDAudio get the most out of the AVR's by pushing the Arduino processor strongly to its limits - that's the price you have to pay if you want nice audio from that little thing.

And one detail on top: The magic trick of getting a good sound out of the AVR's PWM is that mainly PWM-frequency matters more than anything else. It is the sampling rate of >32kHz that make the big difference between this lib and the well known bad telephone-like audio-playbacks often sampled at only 8 kHz of other approaches. With PWM frequency as high as 31/62kHz a low-pass is often not necessary. And you only need more than 8-bit if you want to listen to something that has not been as fucking dynamic compressed as most of the actual radio stuff nowadays is.

Tuttut:
Hi, I will try answer some of your questions:

@timberwolf9:
Why the argument -r 64000? If you use a 16 MHz Arduino, it should be 16 MHz / 256 = 62500. But anyway, the sound is just a little bit to slow then...

@bratan & timberwolf9:
I will tell you some internal details about my library: The library uses a ring buffer of 1024 bytes when no work buffer is set manually. As the buffer is filled in one sector blocks of 512 bytes each, the buffer became a ping-pong buffer in most cases. With 512 bytes, at 8-Bit/Mono/62.5kHz that is about 8ms time, because the data reading from sd-card also takes some time, your observation of 5ms max delay between worker()-calls is very realistic.

@Tuttut
"-r 64000" gives reasonably good audio on my arduino uno, it may be my setup that has gone wonk, but when converting using the rate you gave, the audio was always really high pitched and fast moving. So I played around with the terminal options for sox and found that to be a sweet spot where the audio tone and play speed are warm and spot on.

If I read the internals correctly, we could get away with performing micro task once the audio is playing. 704 is not a lot of RAM to play with, but for listening to the network and flashing a light, it may be enough. (704 is more than enough for flashing a light...I would think)

Also, Tuttut, I ran into the 49 error code on my card until I did a FAT format. Once that was done, everything else went smooth as butter.

Tuttut:
I also thought about calling the worker-function automatically in a timer interrupt. This should be possible, but only if that interrupt is set to non-blocking to guarantee the main audio interrupt the maximum priority. Maybe I will try to implement such in a next version of my lib, so that calling worker() is not necessary then anymore.

That would be awesome! :slight_smile:

Tuttut:
To your question about other Audio-Playback-Shields: As far as I have seen it, all other audio-shields contain a dedicated processor that reads the audio-data from storage and feed it to an audio-DAC. For a good sound the DAC must be feed with low jitter at the audio rate of the audio material, otherwise you may recognize audio distortions like slowdowns or stuttering. With a dedicated processor it doesn't matter what your Arduino does while audio is playing, but my library is for all those who don't want to spend extra money to such a thing. SimpleSDAudio get the most out of the AVR's by pushing the Arduino processor strongly to its limits - that's the price you have to pay if you want nice audio from that little thing.

Thanks for the info!
I think MP3 decoder shield might have such processor, but not WaveShield. From description they only have DAC with some basic low pass filters.
BTW, I'm just trying to understand how things work, not in any way implying anything bad about your library. In fact I think it's brilliant work, nobody else did anything like this with no external hardware (other than SD card)!

You are right, WaveShield doesn't have an additonal processor so the AVR has to cope with every sample. In the end, the external DAC mainly give the convenience that you don`t have to convert the sample rate of the audio file. I think WaveShield perfektly fits the hole between my radical simple hardware library and shields that came with additional controller.

Thanks for this library. It works like a dream.
For the fun of it, are there any way to speed up or slow down the speed. You know, making Schwarzenegger sound like Mickey mouse?
/Allan

I saw a video on YouTube where it appears someone has this working on the Due. Has anyone tried it on the Teensy 3.0 yet? I'd imagine the Due and Teensy3 could decode an actual WAV file..

update:
Sorry to mislead.. after some more tests.. I see this is really just an IDE 1.0 thing..

I tried to load another sketch (blink).. to my +3.3v/8MHz internal clock board.. and it gave same errors..

I opened IDE v.23 and the blink program loaded just fine... without error..

(the only problem now is.. SimpleSDAudio doesnt work with any IDE under 1.0....right?)

thanks!

Hi Tuttut
I would love to be able to read a text-file from the SD-Card to read some default settings to initialize my music-box. Would that be possible to add to the library without too much effort?
/Allan

Hi,

@xl97: I don't know if my lib works under Version below 1.00, but I think other people tried it as well and got it running, but I am not sure. Maybe you can try, within the last version of my lib I included at least something that picks other Arduino-default libs for versions below 1.0, but I never tested it.

@AllanB: My lib uses a own SD card implementation, but you could use this also to access other files on card. It is based on 3 layers: l0 does the hardware access to sd-card - you only have to change this if you want to use this lib on a different architecture. l1 gives access to sectors (sector read/write), whereas l2 gives access to filesystem. Using SD_L2_SearchFile you can find the first cluster of a file on card. Use then SD_L2_Cluster2Sector to get the appropriate sector that belongs to the file and finally read the sector using SD_L1_ReadBlock. Analyse the SimpleSDAudio.cpp to see how it works. It's not hard, but very low level...

hi Tuttut-

dont mind me!..

although it would be great if it did work with IDE v23 (and someone posted a 'how to')....

my realy problem is with IDE 1.0 (and not your lib at all)..

I made a minimal/breadboard hardware set-up (+3.3v @ 8MHz internal clock)... purpose built to use your lib for the project.

However.. my '3.3v/8MHz breadboard bootloader chip does NOT work with IDE 1.0 out of the box..

so Im reading up on how to make it 'compliant'.. (something to with boards.txt edits... and maybe another step as well?)

When I couldnt get the BLINK sketch to upload either.. I knew it was board/IDE problem.

thanks!

Hi xl97,

even it's off-topic, one of my next project I am working on is an special Arduino board with library for very low power applications. I already got my own bootloader for mega328 with 8 MHz internal clock (but 32kHz crystal-calibration) to work - I placed a boards.txt in a folder under /hardware/<my_arduino_board_name>. Compared to your file, it contains two lines that seems to be important for compilation:

gcduino168.build.core=arduino:arduino
gcduino168.build.variant=arduino:standard

Maybe that info will help you...

cool!

cant wait to see it..

Im using a TQFP 3.3v/8MHz minimal design (no regulator).. running off coin cell..

whole pcb is round and smaller than 1".. (plus has SD socket on bottom!).. has no room! lol

I just fired up IDE 1.0

and added this line to my breadboard board.txt file: (at the end)

atmega328bb.build.variant=arduino:standard

that seemed to be all I needed.. compiled and uploaded sketches.. works fine now.

I have a question about your diagrams for filters out the audio feed..

in some/many demos.. you say just use a 100Ohm resistor from D9 output.. before the speaker?..
and in some diagrams.. you have a 100uF cap ( - side to D9) between D9 and speaker..

My D10 is already fixed for the SD CS pin... so I cant use both D9 and D10 for better output..

but would like to learn more/better understanding about low-pass filters..etc and what can be done to (sorta) reduce the static/noise on the single D9 output approach..

I know a proper filter and amp would be best.. but there wont be any of that on this 'current' project

thanks!!

Since I've decided to use the 1284P and 644P for the benefit of the extra RAM, FLASH and EEPROM, I've been testing various things to see if everything will work. Obviously the 1284P isn't an official Arduino MCU so it requires some customisation to set everything up.

One thing I was worried about was SimpleSDAudio due to the use of the specific PWM pins and Timers. This turned out to be well founded and so I spent this evening investigating and trying to work a solution.

The other issue is that there are a few variants of pin mapping in use. I'm using the Bobuino variant that puts SPI across pins 10,11,12,13 and RX0/TX0 on D0/D1, to keep some consistency with Uno. There is also a 'standard' mapping that is the same as the Sanguino for the 644P

I managed to get it working in 1.01 and then moved to 1.02 and tried the same fix. So...

I have edited SimpleSDAudioDefs.h and added a new MCU entry for the 1284P and 1284, with both Bobuino and standard pin mappings. I also edited the 644P/644 entry to provide Bobuino pin mapping option, as I will use this with any 644 I use.

I also noticed that only the 168/328 entry has been updated to the Stereo 16 bit usage at the moment. Although I added the additional pins for PWM3 and PWM4. I've left them commented out as my knowledge doesn't extend to confirming if the register entries below it are correct for the 1284.

On the 1284 the OC0A and OC0B are on PB3 and PB4, rather than PD6 and PD5, as per the 168/328. The PWM1 and PWM2 are the same as 168/328 and certainly my 1284 works fine in 8 bit fullrate mono and 8 bit fullrate stereo now. The PWM4 conflicts with SPI SS again, as per the 328.

I wonder if it would be possible to incorporate this into the library direct to save it having to be applied by hand on updates? Maybe you could check if the 'QUAD' options are correct or need some amendement before possibly enabling.

I added the 1284 entry above the 644/Sanguino entry. Remove any comments by me as you see fit. They are just there to highlight what I've actually altered.

// ADDDED FOR ATMEGA1284P and 1284 COMPATIBILITY - M BEEBY
#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__)
// Mighty1284P & Bobuino

    // Standard pinmap, same as Sanguino
    //#define SSDA_PWM1_PIN 13 // OC1A
    //#define SSDA_PWM2_PIN 12 // OC1B
    //#define SSDA_PWM3_PIN 3 // OC0A PB3 - NOTE 328 IS DIFFERENT, ON PD6
    //#define SSDA_PWM4_PIN 4 // OC0B PB4 (collision with SPI SS!) - NOTE 328 IS DIFFERENT, ON PD5

    // Bobuino pinmap
    #define SSDA_PWM1_PIN 8 // OC1A
    #define SSDA_PWM2_PIN 30 // OC1B
    //#define SSDA_PWM3_PIN 7 // OC0A PB3 - NOTE 328 IS DIFFERENT, ON PD6
    //#define SSDA_PWM4_PIN 10 // OC0B PB4 (collision with SPI SS!) - NOTE 328 IS DIFFERENT, ON PD5
	
    
    // Output-compare settings
    #define SSDA_OC1L    OCR1AL
    #define SSDA_OC2L    OCR1BL
	//#define SSDA_OC3L    OCR0A
    //#define SSDA_OC4L    OCR0B
    #define SSDA_OC1H    OCR1AH
    #define SSDA_OC2H    OCR1BH
    
    // Register to backup and restore
    #define SSDA_OC_CR1_REG             TCCR1A
    #define SSDA_OC_CR2_REG             TCCR1B
	//#define SSDA_OC_CR3_REG             TCCR0A
    //#define SSDA_OC_CR4_REG             TCCR0B
    
    // Always: Prescaler = 1, Fast-PWM-Mode with 8-Bit
    #define SSDA_SINGLE_OC_ENABLE()     { TCCR1A = _BV(WGM10) | _BV(COM1A1); TCCR1B = _BV(WGM12) | _BV(CS10);}
    #define SSDA_DUAL_OC_ENABLE()       { TCCR1A = _BV(WGM10) | _BV(COM1A1) | _BV(COM1B1); TCCR1B = _BV(WGM12) | _BV(CS10);}
    #define SSDA_DUAL_OC_BRIDGING()     { TCCR1A |= _BV(COM1B0); }
	//#define SSDA_QUAD_OC_ENABLE()       { TCCR0A = _BV(WGM00) | _BV(WGM01) | _BV(COM0A1) | _BV(COM0B1); TCCR0B = _BV(WGM02) | _BV(CS00);}
    //#define SSDA_QUAD_SYNC()			{ TCNT1 = 0; TCNT0 = 0; }
    #define SSDA_OC_INT_DISABLE()       { TIMSK1 &= ~_BV(TOIE1); TIFR1 |= _BV(TOV1); }
    #define SSDA_OC_INT_ENABLE()        { TIMSK1 |=  _BV(TOIE1); TIFR1 |= _BV(TOV1); }

    #define SSDA_OC_INTERRUPT           TIMER1_OVF_vect

//------------------------------------------------------------------------------
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__)
// Sanguino

	//Original standard pin mapping
    #define SSDA_PWM1_PIN 13 // OC1A
    #define SSDA_PWM2_PIN 12 // OC1B
	
	// Bobuino pinmap - Added by M BEEBY
	//#define SSDA_PWM1_PIN 8 // OC1A
    //#define SSDA_PWM2_PIN 30 // OC1B
    
    // Output-compare settings
    #define SSDA_OC1L    OCR1AL
    #define SSDA_OC2L    OCR1BL
    #define SSDA_OC1H    OCR1AH
    #define SSDA_OC2H    OCR1BH
    
    // Register to backup and restore
    #define SSDA_OC_CR1_REG             TCCR1A
    #define SSDA_OC_CR2_REG             TCCR1B
    
    // Always: Prescaler = 1, Fast-PWM-Mode with 8-Bit
    #define SSDA_SINGLE_OC_ENABLE()     { TCCR1A = _BV(WGM10) | _BV(COM1A1); TCCR1B = _BV(WGM12) | _BV(CS10);}
    #define SSDA_DUAL_OC_ENABLE()       { TCCR1A = _BV(WGM10) | _BV(COM1A1) | _BV(COM1B1); TCCR1B = _BV(WGM12) | _BV(CS10);}
    #define SSDA_DUAL_OC_BRIDGING()     { TCCR1A |= _BV(COM1B0); }

    #define SSDA_OC_INT_DISABLE()       { TIMSK1 &= ~_BV(TOIE1); TIFR1 |= _BV(TOV1); }
    #define SSDA_OC_INT_ENABLE()        { TIMSK1 |=  _BV(TOIE1); TIFR1 |= _BV(TOV1); }

    #define SSDA_OC_INTERRUPT           TIMER1_OVF_vect

//------------------------------------------------------------------------------

@allenb - or anyone

have you made any head way getting the SD lib to have some file reading capabilities yet?

I too would like to be able to use same lib to grab a few params/vars from a text file to be used in a sketch that also uses the SimpleSDAudio lib for audio playback

I read the brief comment left by Tuttut about looking at the lib ..and I havent got that far into it yet..(and dont really have high hopes or figuring it out by myself anyways!) lol..

figured Id ask before I dive in next week

thanks!!

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: