Arduino WAV Player 1 Arduino WAV Player 2

This is a small project to read wav files from a micro SD card via an Arduino Nano and then play them directly to a loudspeaker. I’ll state upfront that this isn’t the easiest way to do this (there are MP3 modules and libraries), but I wanted to:

  1. Understand sound generation using a microcontroller from first principles
  2. Have some tools / ideas about how to generate sounds for any future projects

As an idea of the sort of thing I was thinking of - I’ve been thinking of a DIY “ring” doorbell. I already have a camera that can see anyone approaching the front door. A 433.92MHz receiver could detect when the doorbell is pressed (so no new doorbell required). A Raspberry Pi (i.e. the one with the camera) controlling it all and sending and receiving to my mobile phone. A lot more work than just buying a Ring doorbell - but a lot more satisfying!

To drive the loudspeaker I used a power MOSFET. Ideally I would use two (or maybe four) MOSFETs in a push pull arrangement. I was more interested in getting the software right however, so I didn’t spend a lot of time getting the hardware correct. [Figure 3 on this page][Class D Audio Amplifiers] would seem be the ideal solution, but I suspect that if I went down this route it would need four independently controllable PWM inputs - and having given this a bit of thought - I think this would be best done with an FPGA. That’s a project for another day! (but it would be pretty awesome). …or maybe I’ll just buy an audio amplifier module from Aliexpress :)

Hardware:

Arduino Loudspeaker Driver

…and an SD card module for the Arduino. They cost about 50p if you get them directly from China.


I realized early on that PWM was the easiest way to generate the sound - and after some investigation and calculations worked out that I could have 8 bit audio with a PWM running at 62.5KHz (16MHz clock divided by 256).

PWM code (62.5KHz):
 1inline void fastWriteD3(int value)
 2{
 3  if (value) PORTD |= 1 << 3;
 4  else PORTD &= ~(1 << 3);
 5}
 6
 7// Using Timer 2 B = pin D3, 62.5KHz i.e. no prescaler
 8void setupPWMTimer()
 9{
10  pinMode(3, OUTPUT);
11  //pinMode(11, OUTPUT);
12  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
13  TCCR2B = _BV(CS20);
14  OCR2B = 0x80;
15}
16
17void updatePWMTimer(uint8_t val)
18{
19  if (val == 0)
20  {
21    TCCR2A &= ~_BV(COM2B1);
22    fastWriteD3(0);
23  }
24  else if (val == 0xff)
25  {
26    TCCR2A &= ~_BV(COM2B1);
27    fastWriteD3(1);
28  }
29  else
30  {
31    TCCR2A |= _BV(COM2B1);
32    OCR2B = val;
33  }
34}

The next thing I needed was to play each sample. I probably could do one channel at 44KHz but decided to use 16KHz - which would mean the upper frequency limit would be 8KHz. Nowhere near HiFi, but easily good enough for what I was trying to achieve. So I created an interrupt routine that occurred 16,000 times a second with the following code:

Interrupt code (16KHz):
 1ISR(TIMER1_COMPA_vect)
 2{
 3  if (dataFirst != dataLast)
 4  {
 5    uint8_t val = dataBuffer[dataFirst++];
 6    if (dataFirst == DATA_BUFFER_SIZE)
 7    {
 8      dataFirst = 0;
 9    }
10    updatePWMTimer(val);
11  }
12  else if (!paused)
13  {
14    noData = true;
15  }
16}
17
18// Set timer1 interrupt at 16kHz
19void setup16KHzTimer()
20{
21  TCCR1A = 0;
22  TCCR1B = 0;
23  TCNT1  = 0;               //initialize counter value to 0
24  OCR1A = 1000;             // = (16*10^6) / (1 * 16000) - 1 (must be <65536)
25  TCCR1B |= (1 << WGM12);   // turn on CTC mode
26  TCCR1B |= (1 << CS10);    // Set CS10 bit for no prescaler
27  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
28  sei();
29}

Every time the interrupt occurs one sample of the WAV file should be sent to the PWM. You can see this in the ISR.

I haven’t included the SD card reading code here (because it’s not the reason for this article), but the entire sketch can be be found [on this github repo][Arduino Code Github Repo].


And this is what it sounds like:

There seems to be lots of noise in the background of both tracks - but it was raining very hard outside when I recorded them - so it might be that.

I created the WAV files using Audacity - 8 bit @ 16,000 samples per second - which works out to about 1 MByte / minute. I’ll be the first to admit it’s not HiFi quality - but it’s not bad considering how it’s being generated (well I think anyway :))

What I learnt

I did some tests and found that I could read the data from the SD card about 7 times faster than I needed it.

Therefore I should be able to do stereo (although I’m not sure what the point would be) but I would need another PWM channel.

References