💡 划重点

  • 本项目用 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 单价还会下降。

接线与结构

采用题目给定接线,仅补充电源与驱动细节:

微调说明: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);
}

调试步骤

  1. 只接 OLED,烧录后确认屏幕点亮并能显示 “IDLE”;若花屏,检查 CS、DC、RST 与 SPI 线序。
  2. 接入两个按键,用串口打印键值,确认 GPIO6 与 GPIO7 上拉有效、按下为低电平。
  3. 接入震动电机与三极管,先单独测试 motorPulse(1, 300, 0),确认震动强度与电流不导致开发板复位。
  4. 把专注时长临时改为 10 秒、休息改为 5 秒,完整走一轮状态切换,确认 OLED 与震动节奏正确。
  5. 恢复 25 分钟/3 分钟后连续跑两轮,检查长时间运行下计时漂移与屏幕刷新是否正常。
  6. 最后装入外壳,把电机贴在外壳底部,确认桌面共振可感知但噪音低。

设计图

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