A couple of years ago, I developed a simple digital audio recorder(DAR) for the Arduino Uno just as a bit of a side project for our Arduino Masterclass series, but it’s turned out to be one of the most popular projects on the APC website (we still get comments on it).
So today, we’re introducing the Mk. II, based around the larger Arduino Mega2560. It still only captures single-channel 8-bit audio, but it’ll handle sample rates up to 48kHz and now includes automatic filename incrementing.
A couple of requests from readers wanting a version that would work with the Arduino Mega2560 prompted this update, but it was already on the to-do list.
The Mega is quite a bit bigger than the Uno, but it has a couple of useful advantages. Apart from having four times the number of digital I/O pins, the Mega also has four times the RAM — 8KB to the Uno’s 2KB. It’s not much but that makes a big difference when capturing audio.
The Mk. I also only recorded to the filename ‘rec00000.wav’ — any new recording simply wiped the old one. This new Mk. II automatically increments the filename digits, starting with ‘rec0.wav’, ‘rec1.wav’, ‘rec2.wav’, up to ‘rec9999.wav’.
In other words, it now keeps everything you record, rather than wiping the one file each time.
Digital audio theory
Read it yet? You really should… Okay, let’s get cracking on the new version.
Getting the audio into the Arduino is the same as before — we’re using analog I/O pin A5 as the audio input into the Arduino’s analog-to-digital converter (ADC).
We’re also using the same divider or ‘prescaler’ trick, boosting the ADC clock from its default 125kHz up to 4MHz — that cranks up the sample rate from a sleepy 9.6kHz up to a possible 307kHz.
Now, obviously CD-audio sample rate is only 44.1kHz, but the extra speed gives us valuable time to do everything else we need.
To create recordings with more common sample rates, we code one of the Mega’s internal counters or ‘timers’ to trigger an interrupt at intervals the inverse of the desired sample rate, which you set in the source code prior to flashing to the Mega.
For example, for a 44,100Hz sample rate, we set the timer to fire every 1/44,100 seconds or approximately 22.7 microseconds — and at each interrupt, we get one audio sample.
We store the samples with a MicroSD card reader connected via the Mega’s SPI (Serial Peripheral Interface) port.
Like the Uno, the Mega has two SPI pin sets, but here, the MOSI pin is now 51, MISO is 50 and SCK is 52.
We use the excellent SdFat library to enable the Mega to write samples to a FAT32 formatted microSD card — but that’s after we’ve written the WAV header, which tells every media player how the file works.
However, the really tricky bit is interleaving the audio sampling with the file writing, and we do that with a technique called a ‘ring buffer’.
The idea behind the ring buffer is dividing a slab of memory into equal-sized buffers, so that, while we’re recording audio samples into one buffer, we’re writing the previous buffer to the microSD card.
The Mk. I had just 1KB of RAM divided into eight buffers, each 128 bytes, and as each one was filled, it would be written to storage while samples were captured into the next.
However, 128 bytes at a sample rate of 44.1kHz takes just 2.9 milliseconds to fill up — the whole 1KB buffer ring can only hold 23.2 milliseconds of audio at a 44.1kHz sample rate.
For the most part, the microSD card and SPI port operate fast enough to ensure a smooth flow of audio samples to the card, but every now and then, the microSD card will delay the write, causing a backlog.
If that backlog extends beyond the ring buffer capacity, you lose samples and get skips in the audio.
The two simple ways to fix that are to capture audio samples at a slower rate, or get a bigger ring buffer — but that needs RAM, which is why we’ve gone for the Mega with its 8KB of RAM.
We still need to handle other code variables, so as before, we only assign half the RAM (4KB) to the ring buffer, but with a ring of four buffers, instead of eight.
The result is instead of 128 bytes per buffer, we now have 1024 bytes — it’s still not ideal, but is certainly better.
By having the audio sample code triggered precisely by a timed interrupt, the remaining time between completion of each sample and the next trigger is used to write data to the card.
So in other words, audio sampling takes precedence and writing data to the card happens around that.
Line-level audio only
most common request we’ve had has been readers wanting to plug a microphone into the input and record it.
The problem is that microphones deliver an output signal of less than 10mV RMS (0.01 volts), whereas the Atmel chip’s analog input expects a signal voltage 100 times that.
The answer is a microphone preamplifier, which most microcontrollers — and all of the Arduinos — don’t have.
You can make one externally, but it takes more than we have space for here.
This new recorder can handle any type of line-level audio input, but 8-bit audio also comes with a low 48dB dynamic range, which means the background noise will be noticeable on quieter passages of music.
CD-quality, it’s not. But here’s the thing — an Arduino DAR may lack sample bit-depth and overall speed, but we’re using exactly the same techniques the real DARs use.
So as a learning exercise, you can’t go wrong.