💡 划重点
- 本项目用 RP2040 做主控,把“番茄钟计时”“久坐提醒”“站姿/离座提示”三件事合并到一台桌面小设备上。
- 感知层只有按键与时间基准,不依赖摄像头,隐私友好、成本低、误报可控。
- 执行层用 OLED 显示剩余专注时间与状态,用震动电机做无声提醒,适合开放办公区。
- 整机物料成本控制在 55 元以内,接线采用 SPI OLED(CS=13、DC=14)、震动电机=15、按键=6/7。
- 软件采用“状态机 + 非阻塞计时”,并提供可编译的 PlatformIO/Arduino 骨架,方便二次修改。
需求洞察
目标用户是每天在工位连续坐 6 小时以上的办公室人群,尤其是程序员、设计师、运营与文字工作者。真实痛点不是“不知道要站起来”,而是三件事叠加:第一,专注时容易忘记时间,一坐就是一两个小时;第二,手机番茄钟会带来消息干扰,打开手机后注意力被带走;第三,普通闹钟提醒太吵,开放工位里不适合外放,戴耳机又容易听不到。
为什么值得做:番茄工作法本身强调 25 分钟专注 + 5 分钟休息,但多数人只执行了“计时”,没有执行“离开座位”。把计时与姿态提醒合并,可以让休息动作变得明确:专注结束,设备震动,屏幕提示“起身 3 分钟”;回到座位按一下按键,进入下一轮。这个动作足够轻,不会打断工作流,却能把久坐切成可管理的小段。用 RP2040 做这件事的性价比也很高:双核、USB 原生、GPIO 充足,后续加蜂鸣器、温湿度或无线模块都留有余量。
方案设计
感知层:两个轻触按键分别接 GPIO6 与 GPIO7。GPIO6 作为“开始/确认”键,GPIO7 作为“跳过/复位”键。设备没有摄像头和红外传感器,姿态提醒不判断真实坐姿,而是用“连续专注时长 + 休息确认”来间接管理站姿行为:专注满 25 分钟必须离座休息,休息满 3 分钟才能开启下一轮,避免“假休息”。
控制层:RP2040 运行 Arduino 框架,主循环维护一个四状态状态机:IDLE(待机)、FOCUS(专注)、BREAK(休息)、PAUSED(暂停)。计时使用 millis() 非阻塞差值,OLED 每 500 毫秒刷新一次,避免整屏闪烁。长按 GPIO6 两秒进入暂停,短按恢复。
执行层:0.96 寸 SPI OLED 显示状态、剩余时间与轮次;震动电机通过 NPN 三极管或 MOSFET 驱动,GPIO15 输出高电平触发,震动 1.5 秒作为一次提醒。专注结束与休息结束使用不同震动节奏:专注结束为“长震两下”,休息结束为“短震三下”。
日志/联网层:本项目不增加联网模块,以控制成本与隐私风险。RP2040 通过 USB 串口输出每轮开始、结束、跳过事件,电脑端可用串口助手记录当日番茄轮次;后续如需联网,可把串口日志换成 Wi-Fi 模块或蓝牙,不影响现有接线。
物料清单
| 物料 | 数量 | 用途 | 估算单价(元) |
|---|---|---|---|
| RP2040 开发板(兼容 Pico) | 1 | 主控 | 19.9 |
| 0.96 寸 SPI OLED(SSD1306) | 1 | 状态与倒计时显示 | 12.5 |
| 震动电机(扁平 coin 型) | 1 | 无声提醒 | 3.5 |
| NPN 三极管 S8050 + 1kΩ 电阻 | 1 套 | 电机驱动 | 0.8 |
| 轻触按键 6×6 | 2 | 开始/跳过 | 0.6 |
| 面包板或洞洞板 | 1 | 搭建 | 5.5 |
| 杜邦线 | 12 根 | 接线 | 3.0 |
| USB-C 数据线 | 1 | 供电与烧录 | 6.0 |
| 外壳(3D 打印/塑料盒) | 1 | 桌面放置 | 4.5 |
| 合计 | 56.3 |
合计略高于 55 元时,可去掉外壳改用现有纸盒,或选用更便宜的散装杜邦线,即可压到 55 元以内。若批量采购,RP2040 与 OLED 单价还会下降。
接线与结构
采用题目给定接线,仅补充电源与驱动细节:
- OLED SPI:CS=GPIO13,DC=GPIO14,RST 接 GPIO12(可微调,用于硬复位),SCLK=GPIO10,MOSI=GPIO11,VCC=3.3V,GND=GND。
- 震动电机:GPIO15 → 1kΩ 电阻 → S8050 基极;电机一端接 3.3V,另一端接集电极;发射极接 GND;电机两端反并联一只肖特基或 1N4148 续流二极管。
- 按键:GPIO6 与 GPIO7 分别接按键一端,另一端接 GND,软件启用内部上拉,按下为低电平。
- 结构:OLED 固定在面板上方,按键放在面板下方左右两侧,震动电机贴在桌面或外壳底部。整机由 USB-C 供电,放在显示器下方不占鼠标区。
微调说明:RST 使用 GPIO12 是为了让 OLED 上电稳定;若模块只有 4 针,可把 RST 接开发板 RESET 或 3.3V,代码中对应初始化参数保持默认即可。
核心代码
以下为 PlatformIO + Arduino 框架骨架,可直接编译烧录到 RP2040:
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
U8G2_R0, /*clock=*/10, /*data=*/11, /*cs=*/13, /*dc=*/14, /*reset=*/12);
const int BTN_START = 6;
const int BTN_SKIP = 7;
const int MOTOR = 15;
enum State { IDLE, FOCUS, BREAK, PAUSED };
State state = IDLE;
unsigned long lastTick = 0;
unsigned long remainMs = 0;
int roundCount = 0;
bool lastStart = HIGH, lastSkip = HIGH;
void motorPulse(int times, int onMs, int offMs) {
for (int i = 0; i < times; i++) {
}
}
void drawUI() {
oled.clearBuffer();
const char* label = "IDLE";
if (state == FOCUS) label = "FOCUS";
if (state == BREAK) label = "BREAK";
if (state == PAUSED) label = "PAUSE";
oled.drawStr(0, 12, label);
char buf[24];
snprintf(buf, sizeof(buf), "Round: %d", roundCount);
oled.drawStr(0, 28, buf);
unsigned long sec = (remainMs + 999) / 1000;
snprintf(buf, sizeof(buf), "%02lu:%02lu", sec / 60, sec % 60);
oled.drawStr(0, 60, buf);
oled.sendBuffer();
}
void setup() {
pinMode(MOTOR, OUTPUT);
digitalWrite(MOTOR, LOW);
oled.begin();
Serial.begin(115200);
drawUI();
}
void loop() {
bool s = digitalRead(BTN_START);
bool k = digitalRead(BTN_SKIP);
if (lastStart == HIGH && s == LOW) {
if (state == IDLE) {
state = FOCUS; remainMs = 25UL * 60 * 1000; roundCount++;
} else if (state == PAUSED) {
state = FOCUS;
} else if (state == BREAK) {
state = FOCUS; remainMs = 25UL * 60 * 1000; roundCount++;
}
}
if (lastSkip == HIGH && k == LOW) {
if (state == FOCUS) {
state = BREAK; remainMs = 3UL * 60 * 1000;
motorPulse(2, 400, 200); // 专注结束:长震两下
} else if (state == BREAK) {
state = IDLE; remainMs = 0;
}
}
lastStart = s; lastSkip = k;
if (state == FOCUS || state == BREAK) {
unsigned long now = millis();
if (now - lastTick >= 1000) {
lastTick = now;
if (remainMs > 1000) remainMs -= 1000;
else {
if (state == FOCUS) { state = BREAK; remainMs = 3UL * 60 * 1000; motorPulse(2, 400, 200); }
else { state = IDLE; remainMs = 0; motorPulse(3, 120, 120); }
}
drawUI();
}
}
delay(10);
}
调试步骤
- 只接 OLED,烧录后确认屏幕点亮并能显示 “IDLE”;若花屏,检查 CS、DC、RST 与 SPI 线序。
- 接入两个按键,用串口打印键值,确认 GPIO6 与 GPIO7 上拉有效、按下为低电平。
- 接入震动电机与三极管,先单独测试
motorPulse(1, 300, 0),确认震动强度与电流不导致开发板复位。 - 把专注时长临时改为 10 秒、休息改为 5 秒,完整走一轮状态切换,确认 OLED 与震动节奏正确。
- 恢复 25 分钟/3 分钟后连续跑两轮,检查长时间运行下计时漂移与屏幕刷新是否正常。
- 最后装入外壳,把电机贴在外壳底部,确认桌面共振可感知但噪音低。