Lab: Playing .WAV Files from an Arduino using I2S and SPI

Introduction

Playback of digital sound files is a popular use of microcontrollers. The Inter-IC Sound (I2S) protocol makes this possible. In this lab, you’ll learn how to use the I2S bus on the Arduino Nano 33 IoT in combination with the SPI bus to read a .wav sound file from a microSD card and play it on an I2S-compatible amplifier.

What is I2S?

I2S, the Inter-IC Sound protocol, is a serial protocol used for connecting digital audio devices. I2S allows you to transmit Pulse-Code Modulated (PCM) audio data between integrated circuits, like a microcontroller and a digital amplifier. The Arduino site offers a brief introduction to I2S, and Wikipedia has a good definition page on I2S as well. Though it’s not the only means of transmitting digitized audio from one component to another, it is a popular one.

Don’t confuse I2S with I2C, the inter-integrated circuit protocol. Both are synchronous serial protocols, but with different purposes. You can learn about I2C in these labs.

This will not work on the Uno. It will only work on the Nano 33 IoT and the MKR series Arduino boards, which have an I2S bus built in. The Arduino I2S and the ArduinoSound libraries support I2S.

The material in this lab is adapted from Tom Igoe’s SoundExamples page.

What You’ll Need to Know

To get the most out of this Lab, you should be familiar with the basics of programming an Arduino microcontroller. If you’re not, review the Digital Input and Output Lab, and perhaps the Getting Started with Arduino guide. You should also understand asynchronous serial communication and how it differs from synchronous serial communication. You should also know how to communicate with a microSD card from an Arduino using the SPI protocol.

Things You’ll Need

Figures 1-8 list the components you will need.

Photo of an Arduino Nano 33 IoT module. The USB connector is at the top of the image, and the physical pins are numbered in a U-shape from top left to bottom left, then from bottom right to top right.
Figure 1. An Arduino Nano 33 IoT.
Three 22AWG solid core hookup wires. Each is about 6cm long. The top one is black; the middle one is red; the bottom one is blue. All three have stripped ends, approximately 4 to 5mm on each end.
Figure 2. 22AWG solid core hookup wires.
A short solderless breadboard with two rows of holes along each side. There are no components mounted on the board. The board is oriented sideways so that the long rows of holes are on the top and bottom of the image.
Figure 3. A short solderless breadboard.
A photo of a microSD card breakout board
Figure 4. A MicroSD breakout board and MicroSD card.
Photo of an I2S Audio amp breakout board, model UDA1334
Figure 5. I2S Audio amp. The UDA1334 breakout board from Adafruit has been tested with this lab.
Photo of an I2S Audio Amp, the MAX98357A amp breakout board from Sparkfun
Figure 6. I2S Audio Amp. The MAX98357A amp breakout board from Sparkfun has been tested with this lab. Wires have been soldered to the Audio out + and – pins to make it easier to connect. Click to see the full part.
Photo of a stereo mini phono jack
Figure 7. 3.5mm audio jack. If you use the MAX98357A amp, you’ll need a jack as well.
Photo of an 8 ohm speaker
Figure 8. If you’re using the MAX98357A amp, you can use a speaker as an alternative for the phono jack.

The Circuit

The circuit for this lab consists of:

  • the microcontroller
  • a microSD card breakout
  • an I2S audio amp breakout.

You’ll also need a microSD card reader for your personal computer.

The lab has been tested with two different amps, the UDA1334 breakout board from Adafruit, and the MAX98357A breakout board from Sparkfun. Both work just the same, but the Sparkfun amp needs an additional component, a 3.5mm audio hack.You’ll need to attach wires to connect to a stereo mini jack, as shown here, or you’ll need to solder a speaker onto the + and – holes of the breakout board. The Adafruit board has a built-in jack.

Connect the SD Card Breakout Board

The microSD card in this lab is for storing the .wav files that you want to play. You can playback from multiple files with the libraries used in this project.

SD cards use the  Serial Peripheral Interface (SPI) protocol to communicate with microcontrollers and other computers. SPI is a synchronous serial protocol that supports two-way communication between a controller device such as a microcontroller and a peripheral device like an SD card reader. All SPI devices have a common set of connections:

  • Serial Data In (SDI) connection, on which the controller sends data to the peripheral devices.
  • Serial Data Out (SDO) connection, on which the peripheral devices send data to the controller.
  • a Serial Clock  (SCLK) connection, on which the controller sends a regular clock signal to the peripheral devices.
  • one or more Chip Select (CS)  connections, which the controller uses to signal the peripheral devices when to listen to incoming data and when to ignore it.

The SDO of a controller connects to the SDI of a peripheral, and vice versa.

The SPI pin numbers are the same numbers for the Uno and Nano 33 IoT, as follows:

  • SDO – pin 11
  • SDI – pin 12
  • SCK – pin 13
  • CS – pin 10

The MicroSD card reader/writer shown in Figure 4 is from Sparkfun. It has level-shifting circuitry built-in to adjust for either 3.3- or 5-volt operation. They have a second model which is shown in Figure 9 was the model for the breadboard drawing in Figure 10. Both will work with this lab. There are many other models on the market though. Here’s an Adafruit model. Here’s a Pololu model. Here’s a DFRobot model. All of them will communicate with your microcontroller in basically the same way, though, using the SPI pin connections.

Most SD card readers have a card detect (CD) pin as well, that changes state when the card is inserted or removed. It’s optional, but it can be useful to make sure you have a card in the reader. It’s not used in this example. You will see differing names for the SPI pins as the names as manufacturers modernize their naming conventions. As a result, different breakout boards may have different labels. Make sure to match up the pin functions, not just the pin numbers. Figure 9 shows several models, and you can see that the pin naming conventions and pin positions differ from one model to the next.

Photo of four different microSD breakout boards.
Figure 9. Different microSD breakout boards have different arrangements of the pins and different naming conventions. Shown here, from left to right: A Pololu MicroSD card breakout board; An Adafruit one; a Sparkfun one; and a DFRobot one.

Connect your Arduino to the SD card reader as shown in Figure 10 and 11. If you’re using the Sparkfun SD card reader/writer, the pins are on the on the left side of the board, and they’re numbered, from top to bottom, as follows:

  • Vcc – voltage in. Connects to microcontroller voltage out
  • CS – Chip select. Connects to microcontroller CS (pin D10 on the Nano/Uno)
  • DI – SPI data in. Connects to microcontroller SDO (pin D11 on the Nano/Uno)
  • SCK – SPI clock.. Connects to microcontroller SCLK (pin D13 on the Nano/Uno)
  • DO – SPI data out. Connects to microcontroller SDI (pin D12 on the Nano/Uno)
  • CD – card detect. Not connected in this example
  • GND – ground. Connects to microcontroller ground
Breadboard drawing of a microSD card reader attached to an Arduino
Figure 10. Schematic drawing of a microSD card reader attached to an Arduino using SPI connections
Schematic drawing of a microSD card reader attached to an Arduino
Figure 11. Schematic drawing of a microSD card reader attached to an Arduino using SPI connections

Connect the I2S Amplifier

The I2S amplifier takes the digitized audio signal from your microcontroller and converts it to an analog audio signal that can play on analog speakers or headphones. The connections for an I2S bus are:

  • Serial clock (SCK) or Bit Clock (BCLK) – This is the line that carries the clock signal
  • Frame Select (FS), also called Word Select (WS or WSEL), or Left-Right Clock (LRC) – This determines left and right channels
  • Data, also called Digital Out (DOUT) or Digital In (DIN) depending on the application – This is the data signal itself.

The controlling device sends the clock signal, just like in other synchronous serial protocols like I2C and SPI.

Connect the I2S amp to your Arduino as follows:

  • BCLK connects to A3 of the Nano 33 IoT board
  • LRC connects to A2 of the Nano 33 IoT board
  • DIN connects to D4 (SDA Pin) of the Nano 33 IoT board
  • Vin connects to 3.3V
  • GND connects to ground
  • + connects to the left and right sides of a 3.5mm audio jack
  • – connects to the center pin of a 3.5mm audio jack

This wiring is shown in Figures 12 and 13 with a MAX98357 I2S audio amplifier and audio jack. You have to attach a 3.5mm audio jack to the amplifier separately. The photo in Figure 6 shows a jumper wire cut in half and soldered to the + and – terminals of the audio amplifier to make it easier to connect to a 3jack via a breadboard.

Breadboard view of an Arduino Nano 33 IoT connected to a microSD card and MAX98357A I2S amp  and audio jack
Figure 12. Breadboard view of an Arduino Nano 33 IoT connected to a microSD card and MAX98357A I2S amp and audio jack as described above.
Schematic view of an Arduino Nano 33 IoT connected to a microSD card and MAX98357A I2S amp and audio jack
Figure 13. Schematic view of an Arduino Nano 33 IoT connected to a microSD card and MAX98357A I2S amp and audio jack as described above.

The connections for the Adafruit UDA1334 I2S amp are similar, but the pins have different names:

  • BCLK connects to A3 of the Nano 33 IoT board
  • WSEL connects to A2 of the Nano 33 IoT board
  • DIN connects to D4 (SDA Pin) of the Nano 33 IoT board
  • Vin connects to 3.3V
  • GND connects to ground

The UDA1334 amp has a built-in 3.5mm audio jack, so there’s no need to wire a separate jack as there is with the previous amp.

Once you’ve connected the MicroSD card and amplifier, your circuit is ready.

Format the MicroSD card

Your SD card needs to be formatted as FAT32 or FAT16. The SD Card Lab has notes on how to do this. There are also instructions on the Arduino site on formatting your card, as well as the Adafruit site. If you’re formatting on MacOS, you can use the DiskUtility app, but you must format your disk as MS-DOS (FAT). You should test the SD card for reading and writing first.

Your filename needs to correspond to 8.3 naming, so it should be no more than 8 characters long, with the extension .wav at the end.

Make A .WAV file

The ArduinoSound library can only play audio files formatted as .wav files, because these are uncompressed audio files. The .wav file must be formatted as stereo, signed 16-bit, 44100Hz. There’s a good tutorial on the Arduino site on how to do this using the free audio editing software Audacity. Once you’ve made your file, copy it to the SD card.

Program the Microcontroller

The ArduinoSound library comes with a and example called WavePlayback to get you started. Here are a few of the important parameters you should know about:

You’ll make an instance of the SDWaveFile class to read the file from the SD card. From that, you can get the file duration, sample rate, bits per sample, channels, current time, and more. It’s useful to print out one of these properties, like the file duration, when you open it, to see that things are working.

The AudioOutI2S class gives you control over playback. You can check if the output can play, and you can play, pause, loop, resume, stop the playback. You can also check whether the file is playing or paused and you can set the volume.

Import the Library

Download the ArduinoSound library on your Arduino IDE. You can find it in the Library Manager of the IDE (Sketch Menu -> Include Library -> Manage Libraries, search for “ArduinoSound”). At the start of your sketch, import the libraries and set up a variable to hold the SDWaveFile instance like so:

#include <SD.h>
#include <ArduinoSound.h>
#define I2S_DEVICE 1   // this enables the I2C bus

// filename of wave file to play
// file name must be 8 chars (max) .3 chars
const char filename[] = "MUSIC.WAV";

// variable representing the Wave File
SDWaveFile waveFile;
// timestamp for printing the current time:
long lastPrintTime = 0;

Initialize the SD card

In the setup, you need to check that the components are working. If any of them fail, it’s a good idea to stop and notify the user. Since there’s no user interface in this basic sketch, you’ll use the Serial Monitor.

First, check that the SD card works:

void setup() {
  // Open serial communications:
  Serial.begin(9600);
  // wait for serial  monitor to open:
  while (!Serial);

  // setup the SD card.
  Serial.print("Initializing SD card...");
  if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    while (true); // do nothing
  }
  Serial.println("SD card is valid.");

Open the .wav File

Once you know the SD card is good, open the file as a .wav file and check that it can play:

// create a SDWaveFile
  waveFile = SDWaveFile(filename);

  // check if the WaveFile is valid
  if (!waveFile) {
    Serial.print("There is no .wav file called ");
    Serial.println(filename);
    while (true); // do nothing
  }
  // print the file's duration:
  long duration = waveFile.duration();
  Serial.print("Duration = ");
  Serial.print(duration);
  Serial.println(" seconds");

  // check if the I2S output can play the wave file
  if (!AudioOutI2S.canPlay(waveFile)) {
    Serial.println("unable to play wave file using I2S");
    while (true); // do nothing
  }

Set the Volume

Once you know the file’s good to play, set the volume and start playing. The AudioOutI2S.volume() function takes levels from 0-100. This will end the setup() function:

 // set the playback volume:
  AudioOutI2S.volume(80);
  // start playback
  Serial.println("playing file");
  AudioOutI2S.play(waveFile);

What To Do While While Playing

While the file is playing, you don’t need to do anything. It will play regardless of what you are doing in the loop() function. However, you can check to see if it is playing or paused, and you can check the current time. These can be useful for user interface actions like responding to a play/pause button, changing the volume, and so forth.

void loop() {
  if (millis() - lastPrintTime > 1000) {
    Serial.print(waveFile.currentTime());
    Serial.println( " seconds");
    lastPrintTime = millis();
  }
  if (!AudioOutI2S.isPlaying()) {
    Serial.println("File has stopped");
    while (true); // do nothing
  }
  if (AudioOutI2S.isPaused()) {
    Serial.println("File is paused");
  }
}

With that much code, you’ve got enough to play a file. You can find the complete sketch at this link. Upload this sketch to your Nano and connect a headphone or speaker via a cable to the 3.5mm audio jack. Once you open the Serial Monitor, the setup messages will print to let you know things are working, and then current time in seconds will print until the song is done. Then the sketch will stop and do nothing.

The AudioSound library gives you a lot of capability to build .wav file playback devices. Try building a simple audio player with play/pause/rewind buttons. Add a button to skip to the next track. Or try building a sample player that lets you play short files like instrument notes.