Okay, okay. I admit. This isn’t the minimum viable Arduino project. That’s the blinkey-light demo. But this comes pretty close. It’s the minimum viable Arduino project that is actually useful. This project stems from two things:
First, these super sweet aluminum buttons I picked up a few weeks ago.
Second, the fact that the Aeropress coffee maker needs to be plunged after 30 seconds, and it’s really annoying and error-prone to set a 30-second timer. Oops, I’m tired and I hit zero too many times. That’s a 3-minute (3:00) timer. I typed in 3-0 correctly, but hitting the start button didn’t quite register. Siri misheard. Siri took 10 seconds to respond about a 30 second timer.
So I went extra. I designed and built a dedicated 30-second timer around the Adafruit Trinket M0. This is basically an inexpensive Arduino-alike (it also can do Circuit Python if that’s your thing). It has a handful of GPIO pins, some of which can be PWM analog outputs. The electronic design of this timer is dead-simple. It’s a button wired to ground on Pin 0 (using an internal pull-up resistor) and a piezoelectric buzzer wired to Pin 1. I’d hoped to do interesting things with the buzzer by feeding it PWM-powered analog-ish power, but it seems to behave in a very binary fashion. Either it has enough power to get over the threshold to make noise, or it doesn’t. A pictographic schematic looks a bit like this:
At first, I’d planned to put it in a small laser-cut wooden box. My Glowforge broke after cutting a cardboard prototype, so I shelved that idea. (I’ve since fixed it!) I instead drafted something quick on OpenSCAD to print on the Ultimaker (scad file, stl file). I think this worked out a little better because of the integrated clip for the buzzer, plus it’s a single unibody piece and not glued-together panes of wood or acrylic.



The Trinket board has a USB connector. In both designs, the board is flush with the side opening, exposing the USB, for power. This has a side effect in that the light from the Neopixel RGB LED on the Trinket shines out the slot and onto the kitchen counter.
Yes, this could have been battery powered, but then it would have been a more complex project: LiPo battery charging circuit, some way to indicate the battery needs charging, code that puts the processor in deep sleep, to be woken up by interrupt when the button is pressed. I won’t even mention tolerances of the Trinket’s clock and whether delay(1000) is really truly 1 second. This was meant to be a “couple hours on a weekend afternoon” project. You have to plug it in. It might be a few hundred milliseconds in error. Corners have been cut.
The code itself is also simple, though I added a few flourishes. I don’t drive the buzzer at annoying levels. It’s a little trill to let you know you’ve hit the button, and a quick burst of chirps at the end to let you know 30 seconds has elapsed. It blinks the Neopixel LED every second as a visual indicator that it’s counting. It has a power-on test of the LED (which helped me to see I originally had the Green and Red values reversed).
I didn’t feel it worth throwing on Github, so here’s the code inline:
#include <Adafruit_DotStar.h>
const int PIN_BUTTON = 0;
const int PIN_BUZZER = 1;
//const int PIN_LED = 13;
Adafruit_DotStar strip(1, 7, 8, DOTSTAR_BGR);
void setup()
{
pinMode(PIN_BUTTON, INPUT_PULLUP);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(PIN_BUZZER, LOW);
strip.begin();
strip.setBrightness(255);
colorTest();
}
void colorTest()
{
unsigned int COLORS[] = {
strip.Color(0, 0, 0),
strip.Color(255, 0, 0),
strip.Color(0, 0, 0),
strip.Color(0, 255, 0),
strip.Color(0, 0, 0),
strip.Color(0, 0, 255),
strip.Color(0, 0, 0)
};
digitalWrite(13, HIGH);
for (int i = 0; i < 7; i++)
{
strip.setPixelColor(0, COLORS[i]);
strip.show();
delay(100);
}
digitalWrite(13, LOW);
}
void loop()
{
const int TIMER_DURATION = 30;
//const int TIMER_DURATION = 5;
waitForButton();
startBeep();
for (int seconds = 0; seconds < TIMER_DURATION; seconds++)
{
strip.setPixelColor(0, strip.Color(0, seconds % 2 == 0 ? 0 : 255, 0));
strip.show();
delay(1000);
}
endBeep();
strip.setPixelColor(0, strip.Color(0, 0, 0));
strip.show();
}
void waitForButton()
{
int previousButtonState = HIGH;
int lastDebounceTime = 0;
int debounceDelay = 30;
while (true)
{
int buttonState = digitalRead(PIN_BUTTON);
digitalWrite(13, buttonState == HIGH ? LOW : HIGH);
if (buttonState != previousButtonState)
{
lastDebounceTime = millis();
previousButtonState = buttonState;
}
if ((millis() - lastDebounceTime) > debounceDelay)
{
if (buttonState == LOW)
{
return;
}
}
}
}
void startBeep()
{
digitalWrite(PIN_BUZZER, HIGH);
delay(40);
digitalWrite(PIN_BUZZER, LOW);
}
void endBeep()
{
for (int i = 0; i < 3; i++)
{
digitalWrite(PIN_BUZZER, HIGH);
delay(20);
digitalWrite(PIN_BUZZER, LOW);
delay(250);
}
}
And that’s it. The timer sits in the kitchen and is easy for a sleepy, pre-caffinated Brian to hit in the morning.