Many a project requires some sort of sound output. This has been quite difficult to add to a project with a microntroller until recent years. I remember hand soldering the Adafruit WAV shield just to be able to get the Arduino to play some files off an SD card.
Enter the ESP32, the powerful chip which comes packed with a host of incredibly useful peripherals, 2 ADCs, 2 DACs, I2C, SPI and so on. The one were most interested in though for sound processing is the I2S peripheral.
What exactly is I2S? (Inter-IC Sound)
Pronounced eye-squared-ess, it is an electrical serial bus interface standard used for connecting digital audio devices together
We can use the I2S to handle MP3s and WAV files right out of the box. The ESP32 contains two I2S peripherals. These peripherals can be configured to input and output sample data via the I2S driver.
Since the M5Stack has a built in speaker we don't need to worry about wiring one up. Alternatively though if you want to output the sound into an amp for instance you could hook up an audio jack to pin 25, the pin which one of the Dac's and the speaker are both wired up to.
First things first with the code we need to initialize the I2S peripheral, and we can do that like so:
from machine import I2S, SDCard, Pin
from m5stack import *
import os, uos
from wav import wave
#initialize the I2S device
i2s = I2S( mode = I2S.MODE_MASTER | I2S.MODE_TX | I2S.MODE_DAC_BUILT_IN,
rate = 16000,
bits = 16,
channel_format = I2S.CHANNEL_ONLY_RIGHT,
data_format = I2S.FORMAT_I2S_MSB)
Above we imported os and uos. These modules both deal with file directories in MicroPython. We need them to load the WAV files from your sd card or the internal flash of the device. Generally a simple os.mountsd() command should suffice to mount the sd, however I have experienced problems with this working reliably. If you have issues like me, this line usually fixes things.
sd = SDCard(slot = 2, sck = Pin(23), miso = Pin(33), mosi = Pin(19), freq = 10000000)
Since both the screen and the SD reader on M5Stack both use SPI, I wonder if there is some conflict. Moving on, we now need to create a function to load the WAV file into the buffer and write it to the I2S, we do so like this:
#create a function to play the wav
def wav_player(fname):
wav = wave.open(fname)
i2s.set_dac_mode(I2S.DAC_RIGHT_EN)
i2s.sample_rate(wav.getframerate())
i2s.bits(wav.getsampwidth() * 8)
i2s.nchannels(wav.getnchannels())
i2s.volume(20)
while True:
data = wav.readframes(1024)
if len(data) > 0:
i2s.write(data)
else:
wav.close()
break
Next we need to mount the SD if we are intending to play WAV files off the SD storage. This is generally recommended so as to save precious flash space.
try:
uos.mountsd(sd, '/sd')
except:
#os.mountsd()
pass
As I mentioned earlier I had very mixed results from using uos or os modules to mount the SD, feel free to comment out either one if one works better for you.
Finally we can call the function. We need to pass it the file path of the file we wish to play. By default there is a WAV file in the res folder of the M5Stacks flash that you can use to test. If you want to load your own WAV file from the SD card then make sure you enter the correct path and name of your file. We can use the button pressed functions to play each individual WAV file.
while True:
if btnA.wasPressed():
wav_player('/flash/res/mix.wav')
i2s.stop()
if btnB.wasPressed():
wav_player('/sd/Noise.wav')
i2s.stop()
As of yet I have only used very small WAV files, so I haven't tested to see whether there are any specific requirements for WAV encoding or max size. Hope you found this guide useful. Any issues or questions please leave them down in the comments section.
Comments