PWM Audio with reading data from SD card

Hi all,

Currently I'm working on this Arduino/Nanode project where we want to play a collection of WAV-files stored on a SD-card, with PWM on clock OCR0.

  • I'm able to play the PWM perfectly, starting from the sketch from Michael Smith on the Arduino website: Arduino Playground - PCMAudio
  • I'm able to read the SD-card correctly and convert the data to 8bit integers that look correct when I print them to the serial window.

The problem I have is when I feed in these integers to the PWM value of the clock.
As I said, when I'm using the original PWM Audio file with my own WAV-file converted to a .h-file (through wav2c) it works and it sounds good. When I'm reading the SD-card it shows me the correct values. It shows correct when I'm reading the WAV-files direct and also (what I'm trying in my latest version posted here) as I convert them to text-files and read these. When I'm feeding in the integers from the text file, I hear a horn-like sound, like if the PWM uses the wrong values to output.

I'm guessing the problem is somewhere in the casting of the data into the byte data the Atmega uses. But I don't have any clue where to look or how to solve it. I noticed that the original file uses unsigned char's where I'm using uint_t8. I tried to cast them but it's not working.

Does anyone has some experience in this? Or any clue how I could possible solve this?

Many thanks for you help and time!

Jeroen

PS: Below is the piece of my code where I read through the text files and convert them to integers. They always consist of 3 characters; value 21 for example is printed as 021 in the file, and seperated with a comma which the script skips with the 4th myFile.read()

    myFile = SD.open(FileName);

    char sampleTMP[4];

    sampleTMP[0] = (myFile.read());
    sampleTMP[1] = (myFile.read());
    sampleTMP[2] = (myFile.read());
    sampleTMP[3] = 0;
    myFile.read();

    unsigned char ss;
    ss = atoi(sampleTMP);

    Serial.println(ss, DEC);

    OCR0A = ss;
    OCR0B = ss;

It's not clear from your code if it's exactly what you're using -- I'm guessing not because there's only one write to the OCR0A register. So, there is some kind of loop. Do you do a Serial.println() inside this loop? Because that would really screw up your timing. And how exactly do you do the timing of the samples to match the sample rate inside the WAV file?

I don't really see a problem with the atoi() call or how you're converting the 4-character digits into numbers. Maybe there's something in the other part of your code that's not correct (can you post it?)

--
The Rugged Audio Shield: Line In, Mic In, Headphone Out, microSD socket, potentiometer, play/record WAV files

Hi!

Thank you for the quick reply. well yes, the Serial.println() is in the loop now, just to clear for my what happens, but I understand your timing comment. In order to match it I actually want to read the WAV files directly, but for as we're producing them ourselves, I know that they are 16bit 16MHz.

Off course I can post the whole code, I only wanted to be short, but I understand that the whole picture helps understanding it, so here it is:

Thank you!

/*  Plays 8 or 16-bit audio on pin 5 and 6 of using pulse-width modulation (PWM).
 For Arduino or Nanode with ATmega 328 at 16 MHz.
 It is being triggered by a Sharp IR-sensor and reads from a library of WAV-files
 stored on an on-board SD-card. Then it sends a wireless RFM12 signal to the 
 'mother-station' which is connected to broadcast information trough the internet.
 
 Uses two timers: timer 0 and 2. Takes over Timer 0 to generate a 16 kHz clock,
 while Timer 2 holds pin 5 and 6 high for 0-255 ticks out of a 256-tick cycle,
 depending on the sample value, stored on the SD-card.
 
 References used:
 - Based on the original file by Michael Smith found on: 
 http://www.arduino.cc/playground/Code/PCMAudio
 - http://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf
 
 Modified by Jeroen Janssen, 2012, contact: post@jeroenjanssenarchitectuur.nl
 More info about the project: http://www.publicinterventions.org    
 */

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <SD.h>

#define SAMPLE_RATE 16000

File myFile;

const int sounddata_length = 912;
unsigned char sounddata_data[sounddata_length];

int speakerPin = 6;
int speakerPin2 = 5;

volatile uint16_t sampleCount;
uint8_t sample;

byte lastSample;

// This is called at 16000 Hz to load the next sample.

ISR(TIMER1_COMPA_vect) {

  if (sampleCount >= sounddata_length) {
    if (sampleCount == sounddata_length + lastSample) {
      stopPlayback();
    }
    else {

      // Ramp down to zero to reduce the click at the end of playback.

      OCR0A = sounddata_length + lastSample - sampleCount;
      OCR0B = sounddata_length + lastSample - sampleCount; 
    }
  }
  else {

    myFile = SD.open("Impact.h");
    unsigned char ss;
    while (myFile.available()) {
      char sampleTMP[4];

      sampleTMP[0] = (myFile.read());
      sampleTMP[1] = (myFile.read());
      sampleTMP[2] = (myFile.read());
      sampleTMP[3] = 0;
      myFile.read();

      ss = atoi(sampleTMP);
      //Serial.println(ss, DEC);
    }

    OCR0A = ss;
    OCR0B = ss;
  }

  ++sampleCount;
}


void setupPlayback()
{
  pinMode(speakerPin, OUTPUT);
  pinMode(speakerPin2, OUTPUT); 

  digitalWrite(speakerPin, LOW);
  digitalWrite(speakerPin2, LOW);
}


void startPlayback(char* FileName)
{

  // Set up Timer 0 to do pulse width modulation on the speaker pin.
  // Set fast PWM mode  (p.108)

  TCCR0A |= _BV(WGM01) | _BV(WGM00);
  TCCR0B &= ~_BV(WGM02);

  // Do non-inverting PWM on pin OC0A (p.106)
  // On the Arduino this is pin 6.

  TCCR0A = (TCCR0A | _BV(COM0A1)) & ~_BV(COM0A0);

  // Do inverting PWM on pin OC0B (p.106)
  // On the Arduino this is pin 5.

  TCCR0A |= _BV(COM0B1) | _BV(COM0B0);

  // No prescaler (p.110)
  // !!! WATCH OUT: THE FUNCTIONS millis() AND delay() WILL ACT DIFFERENT millis(64000) == 1 SECOND NOW!

  TCCR0B = (TCCR0B & ~(_BV(CS02) | _BV(CS01))) | _BV(CS00);

  // Set initial pulse width to the first sample.

  myFile = SD.open(FileName);
  char sampleTMP[4];

  sampleTMP[0] = (myFile.read());
  sampleTMP[1] = (myFile.read());
  sampleTMP[2] = (myFile.read());
  sampleTMP[3] = 0;
  myFile.read();

  unsigned char ss;
  ss = atoi(sampleTMP);
  // Serial.println(ss, DEC);

  OCR0A = ss;
  OCR0B = ss;

  // Set up Timer 1 to send a sample every interrupt.

  cli();

  // Set CTC mode (Clear Timer on Compare Match) (p.136)
  // Have to set OCR1A *after*, otherwise it gets reset to 0!

  TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
  TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

  // No prescaler (p.137)

  TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

  // Set the compare register (OCR1A).
  // OCR1A is a 16-bit register, so we have to do this with
  // interrupts disabled to be safe.

  OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 16000 = 1000

  // Enable interrupt when TCNT1 == OCR1A (p.139)

  TIMSK1 |= _BV(OCIE1A);

  lastSample = 128;//pgm_read_byte(&sounddata_data[sounddata_length-1]);
  sampleCount = 0;

  sei();
}


void stopPlayback()
{

  // Disable playback per-sample interrupt.

  TIMSK1 &= ~_BV(OCIE1A);

  // Disable the per-sample timer completely.

  TCCR1B &= ~_BV(CS10);

  // Disable the PWM timer.

  TCCR0B &= ~_BV(CS00);

  myFile.close();

  digitalWrite(speakerPin, LOW);
  digitalWrite(speakerPin2, LOW);
}


void setup()
{
  Serial.begin(9600);

  pinMode(10, OUTPUT);

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  setupPlayback();
}


void loop()
{
  int sensorValue = analogRead(A1);
  //Serial.println(sensorValue);

  if (sensorValue >= 300){
    char file[] = "Impact.h";
    startPlayback(file);

  }
}

Whoa...something is not right here. You cannot call SD.open() in every call in the interrupt routine (ISR). The SD.open() call should happen only once in your program, when you open the file. From that point on there should only be calls to SD.read().

And the while (myFile.available()) in the ISR should be if (myFile.available()) else you will just burn through all samples in your file every time there is an interrupt.

--
The QuadRAM shield: add 512 kilobytes of external RAM to your Arduino Mega/Mega2560

Aiai... Off course! That are indeed two terrible mistakes. I'm actually very sorry to even ask the question...

That helps a lot, thanks!!

I changed it and it plays some sounds now. Not completely correct yet. I think that the reading of the text file, storing in an array and then converting into an integer takes too much time in the ISR. I changed it to reading a value from the wav-file direct, and that sounds closer to the original sound already, but not fully satisfying. A little bit more work needed I guess.

Is there perhaps another way of buffering a audio stream like this from an SD card, which someone has experience with already?

Our goal is to have a library of 24 sounds where the sketch picks one, according to the input read from the infrared receiver. I noticed that storing 500 char's in an array is around the maximum we can go on the flash memory of the Nanode, so I think we do have to use the external memory on the SD card. For as we want to have 100 fully independent units in the field, we're looking to use as much hardware solutions as possible.

Thank you for any suggestions!

Allright, a brief update,

If I play the sound once it sounds great! So thank you very much for the fresh look at the code! That always works!

If I have the function startPlayback in the void loop, and play a sound with every trigger by the IR I hear a lot of noise in between. I guess it has to do with input readings simultaneous with the ISR or something. How is this sequence going actually?

My question is, is it possible to pause the whole script, or at least the input readings while the play function or the ISR is running?

I tried to introduce a boolean toggle and use a while loop in the void loop:

while (isPlaying == true){
}

but then the sound is only played once and the rest of the code seems to be not running anymore...

Many thanks again!

Hi, I'm trying to make a project for shifting in frequency sounds that I read from an SD card, and for that I'm using CTC and the PWM. I saw that although our projects are different we are using quite similar things.

Here´s the thing and I hope you might help me, I can read the SD correctly and I can play sounds ok, but when I try to play the sounds reading them from the SD the something goes wrong because the sound starts to banish till practically disappear. This particularly happened when I include the SD.begin(), I'm not sure if I'm using the same pins for reading the SD as the same pins that the uC uses for the timer, did you have any similar problem or idea what might be going wrong?
In advance thank you for your answer.