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