💡 划重点

  • 这是一个放在办公桌上的小型硬件:用 RP2040 做主控,OLED 显示番茄钟倒计时与坐/站状态,震动电机在久坐或番茄结束时提醒你站起来。两个按键完成开始、暂停、切换坐姿和重置。整机成本控制在 55 元内,USB 供电,不联网也能用,适合长时间伏案、又不想被手机分心的人。

需求洞察

目标用户是每天在工位连续工作 6 小时以上的程序员、设计师、文案和运营人员。真实痛点不是“不知道番茄工作法”,而是知道却做不到:手机番茄钟容易被消息打断,普通计时器只响一声,久坐时腰背已经僵硬才想起来该站。更麻烦的是,很多人一进入专注状态就忘记换姿势,等到肩颈酸痛才被动休息。

这个项目值得做,原因有三点。第一,提醒必须“无感但有效”,震动比声音更适合开放办公室,不会打扰同事。第二,把番茄钟和站姿管理合并,专注 25 分钟后自动提示站立休息,比单独装两个 App 更省心。第三,RP2040 成本低、生态好,OLED 和按键都是常见模块,适合作为硬件编程入门到进阶的完整项目。

方案设计

感知层:两个轻触按键。KEY_A 短按开始/暂停番茄钟,长按重置当前轮次;KEY_B 短按切换“坐姿/站姿”状态,长按跳过当前休息。系统用 time.ticks_ms() 记录时间,用状态机判断专注、短休、长休。虽然没有姿态传感器,但通过按键手动标记坐/站,成本低且足够可靠。

控制层:RP2040 双核。core0 负责按键扫描、计时状态机和久坐逻辑;core1 可负责 OLED 刷新与震动编码,避免刷新时漏掉按键。番茄钟默认专注 25 分钟、短休 5 分钟,每 4 轮进入 15 分钟长休。坐姿连续超过 20 分钟,触发一次“起立提醒”,震动两短一长。

执行层:0.96 寸 SPI OLED 显示状态、剩余时间、轮次和坐/站图标。震动电机通过 NPN 三极管驱动,GPIO 只给基极信号,避免电流超限。震动编码区分事件:短震表示切换坐姿,双短震表示久坐提醒,长震表示番茄结束。

日志/联网层:当前通过 USB 串口输出日志,格式为“轮次、专注分钟、久坐次数”,电脑端可用串口助手保存。预留 UART 和 I2C 排针,后续可加蓝牙模块或温湿度传感器,但为了控制成本和隐私,首版不联网。

物料清单

物料 数量 用途 估算单价 合计
RP2040 开发板 1 主控,USB 供电与串口日志 18 元 18 元
0.96 寸 OLED SPI 128x64 1 显示倒计时、轮次、坐/站 9 元 9 元
1027 扁平震动电机 1 无感提醒 2.5 元 2.5 元
S8050 三极管 2 驱动震动电机 0.15 元 0.3 元
1k 电阻 2 三极管基极限流 0.05 元 0.1 元
1N4148 二极管 2 电机续流保护 0.1 元 0.2 元
6x6 轻触按键 2 开始/暂停、坐/站切换 0.3 元 0.6 元
洞洞板 5x7 1 焊接固定 1.5 元 1.5 元
杜邦线、排针、焊锡 1 套 接线与焊接 4 元 4 元
外壳/亚克力/3D 打印 1 桌面支架 8 元 8 元
USB 数据线 1 供电与串口 5 元 5 元
总计 49.2 元

接线与结构

OLED 使用 SPI0:CS=GP13,DC=GP14,RST=GP12,SCK=GP18,MOSI=GP19。给定 CS=13、DC=14 保持不变;新增 RST=12 是为了让 OLED 可靠复位,同时避开按键和震动引脚。震动电机接 GP15,经 1k 电阻接 S8050 基极,发射极接 GND,集电极接电机负极,电机正极接 3.3V,电机两端反并联 1N4148。KEY_A 接 GP6,KEY_B 接 GP7,另一端接 GND,GPIO 内部上拉。

结构上,RP2040 和洞洞板固定在底座内,OLED 倾斜 15 度面向使用者,震动电机用双面胶贴在桌面边缘或显示器下沿。USB 线从后方引出,避免遮挡屏幕。整体尺寸建议 8cm x 6cm x 3cm,可放入普通桌面角落。

核心代码

以下为 MicroPython 骨架,依赖 ssd1306 库。代码可编译,实际使用时按接线调整引脚。


import ssd1306, time

CS, DC, RST, VIB = 13, 14, 12, 15
KEY_A, KEY_B = 6, 7

spi = SPI(0, baudrate=10_000_000, sck=Pin(18), mosi=Pin(19))
oled = ssd1306.SSD1306_SPI(128, 64, spi, dc=Pin(DC), res=Pin(RST), cs=Pin(CS))
vib = PWM(Pin(VIB)); vib.freq(1000)
key_a = Pin(KEY_A, Pin.IN, Pin.PULL_UP)
key_b = Pin(KEY_B, Pin.IN, Pin.PULL_UP)

FOCUS, SHORT, LONG = 25*60*1000, 5*60*1000, 15*60*1000
state, remain, round_no, posture = 'IDLE', FOCUS, 0, 'SIT'
last = time.ticks_ms(); last_stand = time.ticks_ms()

def buzz(ms, duty=300):

def draw():
    oled.fill(0)
    oled.text("Pomodoro", 0, 0)
    oled.text("State: " + state, 0, 14)
    oled.text("Left: %d" % (max(remain, 0) // 1000), 0, 28)
    oled.text("Round: %d" % round_no, 0, 42)
    oled.text("Posture: " + posture, 0, 56)
    oled.show()

while True:
    now = time.ticks_ms()
        remain -= time.ticks_diff(now, last)
        if remain <= 0:
            if state == 'FOCUS':
                round_no += 1
                buzz(800)
                if posture == 'SIT':
                state = 'SHORT' if round_no % 4 else 'LONG'
                remain = SHORT if state == 'SHORT' else LONG
            else:
                state = 'FOCUS'; remain = FOCUS; buzz(500)
    last = now

    if posture == 'SIT' and state == 'FOCUS' and time.ticks_diff(now, last_stand) > 20*60*1000:
        last_stand = now

    if key_a.value() == 0:
        time.sleep_ms(30)
        if key_a.value() == 0:
            if state == 'IDLE':
                state = 'FOCUS'; last = time.ticks_ms()
                state = 'PAUSE'
            else:
                state = 'FOCUS'; last = time.ticks_ms()
            while key_a.value() == 0: pass

    if key_b.value() == 0:
        time.sleep_ms(30)
        if key_b.value() == 0:
            posture = 'STAND' if posture == 'SIT' else 'SIT'
            last_stand = time.ticks_ms(); buzz(100)
            while key_b.value() == 0: pass

    draw()
    time.sleep_ms(100)

调试步骤

  1. 先只接 RP2040 和 USB,确认电脑识别串口,刷入 MicroPython 固件。
  2. 连接 OLED,运行简单清屏和文字显示,确认 SPI 引脚 CS=13、DC=14、RST=12、SCK=18、MOSI=19 无误。
  3. 单独测试按键 GP6、GP7,用串口打印按下状态,确认内部上拉和去抖正常。
  4. 测试震动电机:GP15 输出短 PWM,观察是否震动;若太弱,检查三极管方向和 1N4148 续流。
  5. 烧录完整代码,观察 25 分钟计时是否准确,短休和长休切换是否正确。
  6. 模拟久坐:把 20 分钟阈值临时改为 10 秒,确认双短震触发。
  7. 整机联调:连续运行 2 小时,检查 OLED 是否闪屏、按键是否误触、震动是否干扰 USB 通信。

设计图

番茄钟工位站姿提醒结构flow
1
感知输入
→
2
控制处理
→
3
执行反馈
→
4
数据上报