Playing short audio samples on an Arduino using some PWM tricks from the PCM library.
Here’s a quick demo..
Microcontrollers typically don’t have a lot of on-board memory, so the idea of playing audio samples is generally out of reach without somehow solving the storage issue (or delving into synthesised sound).
But even with the limited memory of an Arduino (32kb flash in most cases), it is possible to handle short samples.
The PCM Library
The PCM library plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
It uses two timers (Timer 1, 2) to modulate a 62.5kHz carrier with the 8-bit sound information (which adjusting pulse-width).
Because the library uses the two timers, these canot also be used for other purposes at the same time. Specifically:
- prohibits PWM for Arduino pins 9 and 10 (Timer 1)
- prohibits PWM for Arduino pins 3 and 11 (Timer 2)
Encoding Sound Samples
I used a simple ruby program - sample_encoder.rb - to generate the C header file with the necessary meomry definition.
It can read WAV files in various format and output the necessary 8-bit samples. It uses the wavefile ruby gem to interpret the WAV file format.
At the moment, it cannot re-sample the source WAV file - so it must be an 8kHz file to avoid pitch-shift.
I grabbed a few sample files and used audacity to re-sample the source at 8kHz and sneure the clips were under the ~4sec limit that can fit into available flash memory.
Setting up to run the encoder. A Gemfile is provided, so bundler can install the necessary pre-requisites:
$ cd encoder $ bundle install
To run the encoder, simply invoke with a WAV file parameter. The resulting header file will be sent to standard out, which can be piped to a header file for compilation like this:
$ ./sample_encoder.rb ../sounds/phone.wav > ../sample_phone.h
The sample to be programmed is selected by ensuring the correct include definition is included in the SimpleSamplePlayer.ino script.
There are many other ways of preparing the audio sample - see the original post for an iTunes/processing option.
This script requires is a speaker (or audio in) connected to pin 11. With a speaker, it’s a good idea to include a series resistor to avoid overloading the Arduino pins. I’ve used 100Ω. In practice, things will work just fine without a resistor … until a sample wants to peg the output high, which risks pulling more current that the Atmega chip can handle.
I’ve included a push-button on pin 2, which is configured with built-in pull-up resistor (INPUT_PULLUP). On a FALLING interrupt (i.e. when you press the button), the script plays the audio sample.
I’ve provided two encoded samples:
- sample_phone.h - a phone ring-tone (the example I demonstrate in the video)
- sample_pop.h - a “pop” sound effect
- sample_arduino_duemilanove.h - a phrase, as used in the original example
One just needs to include the corresponding header file in the main script.