最小可行Arduino项目:爱乐压计时器
Minimum Viable Arduino Project: Aeropress Timer

原始链接: https://netninja.com/2025/12/01/minimum-viable-arduino-project-aeropress-timer/

## 30秒爱乐压计时器 这个项目源于制作爱乐压咖啡时计时不准确的困扰。为了避免依赖容易出错的手机计时器,制作者使用Adafruit Trinket M0(一个小型、兼容Arduino的板子)构建了一个专用的30秒计时器。 这个设备非常简单:一个按钮触发计时器,激活一个蜂鸣器,先发出一个短促的初始蜂鸣,然后在30秒后发出连续的鸣叫声。集成的Neopixel LED每秒闪烁一次,以视觉方式指示倒计时。该设计优先考虑功能而非精致,选择了一个3D打印的外壳(在Glowforge出现问题后),并使用USB连接供电——牺牲电池寿命以换取简洁性。 代码包括基本的去抖动、启动LED测试以及可调节的蜂鸣模式。虽然并非完美(计时可能略有偏差),但它是一个实用的解决方案,可以实现一致的爱乐压冲泡,即使在咖啡因起作用之前也能轻松使用。制作者有意跳过了电池供电和GitHub托管等功能,以保持它作为一个快速的周末项目。

一个黑客新闻的讨论围绕着一个“最小可行Arduino项目”——一个爱乐压计时器。这个项目引发了关于最佳冲泡时间的讨论,一位评论员提到了James Hoffmann推荐的2分钟浸泡时间。 用户们争论了项目中提到的30秒下压时间,解释表明这与控制压力和过滤有关,以避免不想要的咖啡油和细粉。另一些人指出,冲泡时间并非至关重要,强调研磨度、水温和搅拌是关键的萃取因素——有些人甚至更喜欢浸泡几分钟。 关于该项目的实用建议包括使用电池供电,可能重复使用电子烟电池,并利用微控制器睡眠模式以延长电池寿命。一位用户甚至建议设计一个电路,在未使用时完全切断电源。总体情绪倾向于认为该计时器对于保持一致性很有用,尽管有些人质疑精确计时是否有必要。
相关文章

原文

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.

联系我们 contact @ memedata.com