You know those strange rules your parents make up when you’re a kid that stick with you into adulthood? The ones that if you stopped to really consider them you’d wonder “why the hell do I still follow that?” Like not drinking pop in the morning…does the time really matter? Well, growing up Mom had a rule that if you were sick enough to stay home from school, you were sick enough to stay home in the evening too. And if you were sick enough to stay home from school on a Friday, you were sick enough to stay at home the *entire* weekend! I would drag my sniffling, coughing, feverish butt to school on Friday before I’d ever ruin an entire weekend trapped at home. I still follow this “rule”, even though Mom isn’t around to enforce it. Unfortunately this week I’ve had a horrible cold that is kicking my a$$. I attempted to head into work on Friday, but 5 minutes into getting ready I was already too worn out to head downstairs and crashed back into bed. While I’m *sure* they missed me (riiiiight?!), my coworkers probably appreciated not getting this Valentine’s Day gift.
So for my self-imposed weekend in, I decided to finish up one of my electronics projects. Technically it isn’t a “weekend” hack because I’ve been working on it for a few weeks off and on, but I’m not one to break from the established norm, so a weekend hack it shall be. One of my project teams at work has several “that was easy”-type buttons on the meeting table, with the running gag of pressing them at key moments to promote a little fun with the project manager. We started brainstorming additional phrases that we “needed” buttons for, and decided (as a team!) we needed our own sound board with phrases just for the team, like “that wasn’t really easy”, and “less typing, more talking” (inside joke). Cue the Mr. Burns hands, and…excellent…I have a new project idea: the sound board!
I created a few ground rules before starting the design for the sound board:
- It had to be “hackable” so the team could easily change up the sounds on the board, preferably without needing to reprogram it. That basically eliminated memory-only sound players.
- It had to have multiple buttons, and each had to make a unique sound.
- It had to be portable (so it needed to have its own power source).
- It had to be easy to use, to enable comedic timing.
I created three “iterations” of the project, using different boards, extensions, and languages. I searched for simple mp3 sound files on Freesound to use as examples. And I raided my existing electronics hoard for components.
Iteration 1: Circuit Playground Express
This iteration was by far the easiest to implement because the CPE has *everything* you need right on the board itself. I used the CPE Bluefruit that came with the last Adabox, but I didn’t use anything specific to that version, so a regular CPE would work too. The fritzing diagram could not be simpler:

I won’t spend time going through setting up the CPE, getting libraries working etc (all of that is available on Adafruit’s site), but after I figured out a driver problem on my computer (#@!% Windows), it was pretty easy to get this working. Here’s code.py:
from adafruit_circuitplayground import cp
import board
import touchio
import neopixel
touch_pad1 = board.A2
touch_pad2 = board.A3
touch_pad3 = board.A4
touch_pad4 = board.A5
touch1 = touchio.TouchIn(touch_pad1)
touch2 = touchio.TouchIn(touch_pad2)
touch3 = touchio.TouchIn(touch_pad3)
touch4 = touchio.TouchIn(touch_pad4)
cp.pixels.brightness = 0.1
while True:
if touch1.value:
cp.pixels[8] = (255, 0, 0)
cp.play_file("001.wav")
cp.pixels[8] = (0, 0, 0)
elif touch2.value:
cp.pixels[9] = (255, 255, 0)
cp.play_file("002.wav")
cp.pixels[9] = (0, 0, 0)
elif touch3.value:
cp.pixels[0] = (0, 255, 0)
cp.play_file("003.wav")
cp.pixels[0] = (0, 0, 0)
elif touch4.value:
cp.pixels[1] = (0, 0, 255)
cp.play_file("004.wav")
cp.pixels[1] = (0, 0, 0)
How it works: when you touch capacitive pad A2, the Neopixel nearest the pad turns red, the file 001.wav is played, and when it’s done playing the same neopixel turns off. Pretty easy.
I liked this version because:
- It was really quick to get it up and running. The more I work with CircuitPython, the more I’m loving it.
- The CPE sports a JST battery input so it can be mobile with 3xAAA’s or a little lithium battery.
- The final product could be the prototype! No need to solder anything.
- The team could hack the prototype by plugging the CPE into their computer, copying over files, and even modifying the code.
But it also had a few downsides:
- The CPE can’t play mp3 files, so I had to use Audacity to convert the files to wav. Unfortunately the sound quality isn’t very good.
- The touch buttons are neat, but a bit finicky inside the case I have. Maybe my fingers are just too big, I dunno, but I usually had to fiddle with the pads a few times before they’d play. The comedic timing of a well placed “that was easy” would be ruined if you had to press the pad several times, or if you accidentally pressed the wrong one.
- It was too easy! I was hoping something a bit more challenging / complicated / satisfying for this project.
Iteration 2: Music Maker Featherwing
Using yet another Adabox’s contents, I pulled out my music maker featherwing, a Feather M0 Adalogger, and a few extra components, including some lovely rainbow buttons. Here’s the fritzing diagram and a picture of the breadboard prototype:


I switched to using the Arduino IDE for this prototype and into the more familiar-to-me Arduino / Processing / C++ language. Again, the code is pretty simple — I cheated a bit and just adjusted the example “feather_player” of the Adafruit VS1053 library. To make the code a bit more readable (and modifiable!), I created arrays for the buttons, LEDs, and files. To add more sounds, increase the NUM_BUTTONS, define some more pins for the buttons and LEDs, and add them to the corresponding arrays.
// include SPI, MP3 and SD libraries
#include <SPI.h>
#include <SD.h>
#include <Adafruit_VS1053.h>
#define RED_BUTTON A4
#define YELLOW_BUTTON A2
#define GREEN_BUTTON 11
#define BLUE_BUTTON 12
#define RED_LED A5
#define YELLOW_LED A3
#define GREEN_LED A1
#define BLUE_LED 13
#define NUM_BUTTONS 4
int buttons[NUM_BUTTONS] = {RED_BUTTON, YELLOW_BUTTON, GREEN_BUTTON, BLUE_BUTTON};
int leds[NUM_BUTTONS] = {RED_LED, YELLOW_LED, GREEN_LED, BLUE_LED};
char* files[NUM_BUTTONS] = {"red.mp3", "yellow.mp3", "green.mp3", "blue.mp3"};
#define VS1053_RESET -1 // VS1053 reset pin (not used!)
#define VS1053_CS 6 // VS1053 chip select pin (output)
#define VS1053_DCS 10 // VS1053 Data/command select pin (output)
#define CARDCS 5 // Card chip select pin
#define VS1053_DREQ 9 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);
void setup() {
for(int i = 0; i < NUM_BUTTONS; i++) {
pinMode(buttons[i], INPUT_PULLUP);
pinMode(leds[i], OUTPUT);
}
if (! musicPlayer.begin()) { // initialise the music player
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
while (1);
}
musicPlayer.sineTest(0x44, 500); // Make a tone to indicate VS1053 is working
if (!SD.begin(CARDCS)) {
Serial.println(F("SD failed, or not present"));
while (1); // don't do anything more
}
Serial.println("SD OK!");
// list files
// Set volume for left, right channels. lower numbers == louder volume!
musicPlayer.setVolume(10,10);
musicPlayer.useInterrupt(VS1053_FILEPLAYER_TIMER0_INT);
}
void loop() {
for(int i = 0; i < NUM_BUTTONS; i++) {
if(digitalRead(buttons[i]) == LOW) {
playSound(i);
break;
}
}
}
void playSound(int soundNumber) {
// turn on the sound's LED
turnOnLED(soundNumber);
musicPlayer.playFullFile(files[soundNumber]);
// turn off the sound's LED
turnOffLED(soundNumber);
}
void turnOnLED(int ledNumber) {
digitalWrite(leds[ledNumber], HIGH);
}
void turnOffLED(int ledNumber) {
digitalWrite(leds[ledNumber], LOW);
}
One important thing to note in the design is that some of the pins on the Adalogger are taken up by the music maker, and you can’t use them for additional input / output. From the code, you can tell that pins 6, 10, 5, and 9 are needed by the featherwing.
The breadboard prototype worked, but it didn’t exactly meet my project goals of being portable. Breadboards are great for proving an idea, but I don’t think the team would want something with loose wires on the project desk, even considering the geek factor. So I grabbed a prototype board, created a layout, and soldered it together. Much thanks to Collin’s labs for the advice! I used stacking headers on the board so I could reuse the speaker and the feather in other projects, but figured I could spare the buttons, LEDs, and resistors so those are permanently on the prototype. Here’s a top, side, and back view:

I haven’t soldered much with prototype boards, so it took me awhile to figure out the layout. The planner that I am, I opened Fritzing, did a “top view” and “back view” of the layout (keeping the orientation the same to not have to logically reverse everything in my head), then used paint to horizontally flip the back view of the layout. I used both pictures to guide me while soldering.

I liked this version of the sound board because:
- It too was easy to put together and code. I didn’t have to fight with drivers and I’m much more familiar with Arduino, so I completed the breadboard version even faster than the CPE version.
- The sound quality was much better than the CPE, and it could play mp3’s so I didn’t have to convert the sample files I had.
- It’s hackable! The music maker featherwing has a built in micro SD reader so the team can remove the micro SD card, adjust the mp3’s to their liking (as long as they followed the file naming scheme), and pop the card back into the reader. And if they really wanted to hack it, they could still plug in the feather and reprogram it.
- The Feather M0 Adalogger board has a JST battery input too, but it only works with the lithium rechargeable batteries because it has a charger built into it.
And yet I still wasn’t happy with it:
- The feather plugs into stacking headers, and the featherwing plugs into the feather, so the prototype was very tall.
- The feather is meant to be a multi-use prototyping board, so using it for a simple project like this is really overkill. Add in the powerful featherwing, and it felt like using a bulldozer to swat a fly.
- Because the featherwing takes up several of the microcontroller’s pins, it would be difficult to extend the device (as designed) to add buttons.
- You can’t turn it off to save power. The feather doesn’t have an on / off switch, so unless your power source has a built-in switch, while the battery is plugged in, the device will always be on, and that can quickly drain the battery.
- It was still too easy.
Iteration 3: ATtiny85
The ATtiny85 chip is in the same (extended) microcontroller family as the ATmega328P, the microcontroller that drives the much loved Arduino Uno R3. It’s smaller, has less memory, and fewer input / output pins, but it’s cheap and just as easy to work with. In fact, I initially used an Arduino Uno for this iteration, and other than needing to watch the memory and the number of pins, I found switching to the ATtiny85 as simple as switching the board in the IDE.
While researching ideas for this version, I stumbled across a neat little module called the DFPlayer Mini that has a built in micro SD card and plays mp3s without the need for complex codec wrangling. I found a cheap version on Amazon and decided to give it a try. It has an unusual library that requires adherence to specific protocols (including naming and copying files over in the order you want them played), but once I figured out the different modes and how to call them, I was impressed with the little module’s abilities. I used the “play folder” mode which let me specify a folder number and a file number on the micro SD card. This worked, but I had to follow an exact naming structure — annoying, but workable:

I looked around to see if anyone else had used the ATtiny85 and the DFPlayer mini, and stumbled across a few helpful sites. I’m going to list them all, because each of them provided some help in solving one or more issues I had during this iteration:
- http://www.technoblogy.com/show?QBB
- https://www.circuito.io/app?components=9442,336411,8300591
- https://arduino.stackexchange.com/questions/49807/dfplayer-noise-researched-tried-and-bep-bep-bep-bep-bep
- https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299
- https://www.youtube.com/watch?v=iT7_CG2hvuA
The first issue to solve was the number of pins on the ATtiny. It only has 8 pins total. Minus one for each power and ground, that left 6 pins for controlling the player module and input / output from the buttons / LEDs. I faced a similar problem with Battleship, so I used the same solution, and used an I/O port extender (MCP23008). While using only 2 pins on the microcontroller, I could read / write up to 8 additional pins — enough for my 4 buttons and their corresponding LEDs. I used a similar layout as iteration 2, and created the breadboard prototype. Here’s the Fritzing diagram and a picture of the breadboard:


I had a few problems while ironing out the design:
- The MCP23008 didn’t work with the ATtiny85 the same way as the UNO. I found I had to put pull up resistors (4.7k) between SDA and SCL to power before it would detect button presses.
- The player would work for a few button presses, but after a specific file played the second time, it would just make a high pitch buzzing noise and wouldn’t let me press any more buttons. I tried adding 1k resistors on TX / RX between the microcontroller and the player and connecting *both* ground pins of the player, but it only temporarily helped and eventually the problem would be back. I eventually solved it by introducing a 100ms delay in the setup code between initializing the module and setting the volume. I think the module setup hadn’t completed before I called the volume control and so defaulted the volume to max. The problematic file was louder than the rest, and it seems the player overloaded.
- When first turned on, the speaker emits a faint “pop” sound. I didn’t find the cause (though several forums described similar problems) but decided it wasn’t a big deal so left it. Hey it’s not a bug, it’s a feature to let you know the device is ready!
- I wanted to turn on the LED only while the song was playing. This proved tricky with the DFPlayer library, until I found the handy checkPlayer method in the documentation, which acts as a poor man’s event listener. One of the types you can detect is the player has finished playing. When that event is detected, I clear all the LEDs and let the user press another button again.
- The ATtiny isn’t plugged into a computer like the Arduino for power, so you have to provide it power. For the breadboard, I used a breakout module from Adafruit and a 3xAAA battery holder.
Here’s the code. Note that it needs a few additional libraries installed (namely Adafruit’s MCP23008, SoftwareSerial, and the DFRobotDFPlayerMini). All of the libraries can be installed using the Arduino IDE library manager.
#include <Adafruit_MCP23008.h>
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#define RX 3
#define TX 4
Adafruit_MCP23008 mcp;
#define MCP_ADDR 0
#define NUM_BUTTONS 4
#define RED_LED 7
#define RED_BUTTON 6
#define YELLOW_LED 5
#define YELLOW_BUTTON 4
#define GREEN_LED 3
#define GREEN_BUTTON 2
#define BLUE_LED 1
#define BLUE_BUTTON 0
int buttons[NUM_BUTTONS] = {RED_BUTTON, YELLOW_BUTTON, GREEN_BUTTON, BLUE_BUTTON};
int leds[NUM_BUTTONS] = {RED_LED, YELLOW_LED, GREEN_LED, BLUE_LED};
int songs[NUM_BUTTONS] = {1, 2, 3, 4};
SoftwareSerial softwareSerial(RX, TX);
DFRobotDFPlayerMini dfPlayer;
boolean isPlaying = false;
void setup() {
mcp.begin(MCP_ADDR);
for(int i = 0; i < NUM_BUTTONS; i++) {
mcp.pinMode(leds[i], OUTPUT);
mcp.pinMode(buttons[i], INPUT);
mcp.pullUp(buttons[i], HIGH);
}
pinMode(RX, INPUT);
pinMode(TX, OUTPUT);
softwareSerial.begin(9600);
dfPlayer.begin(softwareSerial);
dfPlayer.setTimeOut(500);
delay(100);
dfPlayer.volume(20);
delay(100);
dfPlayer.pause();
}
void loop() {
if(dfPlayer.available()) {
checkPlayer(dfPlayer.readType(), dfPlayer.read());
}
for(int i = 0; i < NUM_BUTTONS; i++) {
if(mcp.digitalRead(buttons[i]) == LOW) {
playSound(i);
break;
}
}
}
void checkPlayer(uint8_t type, int value) {
switch(type) {
case DFPlayerPlayFinished:
soundStopped();
break;
}
}
void soundStopped() {
isPlaying = false;
for(int i = 0; i < NUM_BUTTONS; i++) {
turnOffLED(i);
}
delay(100);
}
void playSound(int soundNumber) {
if(isPlaying) {
return;
}
turnOnLED(soundNumber);
isPlaying = true;
dfPlayer.playFolder(1, songs[soundNumber]);
dfPlayer.start();
}
void turnOnLED(int ledNumber) {
mcp.digitalWrite(leds[ledNumber], HIGH);
}
void turnOffLED(int ledNumber) {
mcp.digitalWrite(leds[ledNumber], LOW);
}
I used Sparkfun’s Tiny AVR Programmer (I recommend this thing to anyone using the ATtiny85!) and the Arduino IDE to compile and upload the code to the chip. Some advice though — make sure you orient the ATtiny85 the proper way when using the programmer (dot up!). Otherwise, you too will smell burning chips and need to sacrifice an innocent IC. But at least I only had to learn this lesson once.
After ironing out the breadboard version, I again soldered together a prototype. I wanted to be able to reuse the ICs, DFPlayer module, and speaker and so used stacking headers and DIP socket adapters. I also used a JST 2 pin connector for the battery. Here’s the Fritzing layout, and pictures of the top, side, and back:


This was my favorite iteration because:
- There were little problems to make it challenging enough with troubleshooting, yet it still fit in as a weekend project. Other than the DFPlayer module, I had all the components on hand, and all were relatively inexpensive. I think the micro SD card was probably the most expensive part.
- The prototype is small (smaller than a credit card), and compact (about an inch tall) so that makes it a nice size for the team’s table. It’s portable with the 3xAAA battery holder.
- It’s hackable by replacing the files on the micro SD card, as long as you follow the naming conventions the library requires.
- Adding more buttons to the device is easy-ish. Instead of the 8 port I/O extender (MCP23008) you could use the 16 port I/O extender (MCP23016). It has a slightly different pinout, but connecting it to the ATtiny85 would be the exact same.
And yet it too had problems:
- You can’t turn it off unless your power source has an on / off switch built-in. Luckily my battery holder had such a switch, but the design would be improved with its own on / off button, or, even better, only used power when one of the buttons was pressed.
- The sound isn’t as nice as iteration 2. The sound might be better with a volume control, but that might bring back the overloading issues for loud audio files. An amplifier might also help.
- You can change the files on the micro SD card, but it isn’t as hackable as the other two iterations. Once the ATtiny85 chip is programmed, you can’t change it without reprogramming it again.
“That was easy!”
All in all, this was a fun, simple side project. I hope the team likes it and uses it for (mostly) good. I’m now going back to bed to be well enough to return to work on Tuesday (yeah, stayed home on a Friday of a long weekend, great timing stupid cold). See you at the next weekend hack!