💡 划重点

  • 核心痛点:久坐伏案导致腰椎劳损与下肢循环不畅,传统单一时长提醒难以改变行为惯性。
  • 方案核心:基于 RP2040 的番茄钟与站姿提醒二合一工位终端,25 分钟专注 + 5 分钟强制离座缓冲期,震动电机物理打断「沉浸式久坐」。
  • 硬件关键:0.96 寸 SPI OLED 实时显示倒计时/站立进度,GPIO 15 驱动震动电机形成「温柔而坚决」的体感提醒,按键 6/7 完成开始/跳过/重置。
  • 工程要点:独立按键扫描逻辑避免阻塞延时;OLED 采用帧刷新机制降低刷新噪声;震动电机采用 PWM 分级强度,根据用户响应动态调节震动时长。
  • 成本控制:核心物料总计 52.7 元,预留 2.3 元为线材与焊料损耗空间,严格控制在 55 元目标以内。

需求洞察

目标用户:每天连续伏案 6 小时以上的程序员、设计师、新媒体编辑与远程办公人群,年龄段集中在 22-40 岁,具备基础电子动手能力或愿意购买成品。

真实痛点与隐含需求:

  1. 时间失真:当人高度专注进入「心流」状态时,主观时间流速变慢,45 分钟后才察觉腰背僵硬。普通手机闹钟在锁屏界面容易被忽略,且与工作无关的通知混杂,降低警惕性。
  2. 提醒的仪式感缺失:电脑弹窗提醒极易被一键关闭,没有任何物理性的后果;而纯声音提醒在佩戴耳机时无效。
  3. 「站立」不等于「离开」:许多久坐提醒只是弹出「请站起来」,但用户可能站起来拿个文件又坐下,并未真正活动。本方案设计「双阶段确认」:震动后若未按下站立确认键,系统不启动下一轮番茄钟,以行为锚点强制驱动微运动。

为什么值得做:从健康经济学角度,腰椎间盘突出单次治疗成本远超 300 元;一支 55 元的工位硬件可连续使用 3 年以上,日均成本不到 5 分钱。RP2040 双核架构提供了后续扩展空间——未来可叠加低功耗蓝牙上报健康数据,不会因算力不足导致二次设计。

方案设计

感知层

控制层

执行层

日志/联网层

物料清单

物料 数量 用途 估算单价 小计
Raspberry Pi Pico 开发板(RP2040) 1 主控 12.5 元 12.5 元
0.96 寸 OLED SPI 模块(SSD1315) 1 计时显示与交互界面 16.8 元 16.8 元
微型扁平震动电机(1027 型) 1 体感提醒输出 3.5 元 3.5 元
S8050 三极管 + 1kΩ 电阻 1 套 震动电机驱动放大 0.6 元 0.6 元
6×6×5mm 轻触按键 2 开始确认/跳过重置 0.25 元 ×2 0.5 元
1kΩ/10kΩ 电阻排 若干 OLED 上拉/分压 0.4 元 0.4 元
103450 聚合物锂电池(1200mAh) 1 便携供电 9.5 元 9.5 元
TP4056 充电模块 1 锂电池充放电管理 2.4 元 2.4 元
自锁电源开关 1 整机电源控制 0.4 元 0.4 元
TYPE-C 线 1 充电与程序烧录(兼用) 1.2 元 1.2 元
亚克力/3D 打印外壳散件 1 工位固定与保护 4.5 元 4.5 元
杜邦线/飞线 若干 内部接线 0.4 元 0.4 元
合计 52.7 元

接线与结构

接线表(颜色示例仅作示意)

RP2040 GPIO 外设引脚 说明
GPIO 14 OLED DC(数据/命令) 拉高为数据,拉低为命令
GPIO 13 OLED CS(片选) 低电平有效
GPIO 10 (SPI0 SCK) OLED SCK 复用 SPI 时钟线
GPIO 11(SPI0 MOSI) OLED SDA 复用 SPI 数据线
GPIO 15 三极管基极(经 1kΩ 电阻) 震动电机 PWM 驱动
GPIO 6 按键 1 输入(启用内部下拉) 按下拉高,读取到高电平为确认
GPIO 7 按键 2 输入(启用内部下拉) 按下拉高,读取到高电平为跳过
3V3 OLED VCC,按键左端 供电
VSYS(经二极管) TP4056 OUT 锂电池供电入口
GND 公共地 所有模块共地

结构构思

采用 94×56×22mm 迷你桌面盒体,OLED 以 30° 仰角嵌入前壳便于视读,两枚按键置于侧面以免误触。震动电机使用 3M 双面胶贴附在底壳内侧下方,使其紧贴桌面——利用桌面作为共振腔增强震感传播,且避免电机旋转直接接触外壳产生异响。

核心代码

以下为 MicroPython 骨架代码(RP2040 官方 MicroPython 固件),展示状态机与定时器核心逻辑:


import time, ssd1315

# --- Pin 初始化 ---
oled_cs = Pin(13, Pin.OUT, value=1)
oled_dc = Pin(14, Pin.OUT)
spi = SPI(0, baudrate=20_000_000, polarity=0, phase=0, firstbit=SPI.MSB)
oled = ssd1315.SSD1315_SPI(128, 64, spi, oled_dc, oled_cs)

KEY_START = Pin(6, Pin.IN, Pin.PULL_DOWN)
KEY_SKIP = Pin(7, Pin.IN, Pin.PULL_DOWN)

motor_pwm = PWM(Pin(15, Pin.OUT))
motor_pwm.freq(100)

# --- 全局状态 ---
FOCUS_TIME = 25 * 60
STAND_TIME = 5 * 60

state = 'IDLE'
state_start_ms = 0

# --- 计时回调(基于单调时钟) ---
def show_ui():
    global state, state_start_ms
    now_ms = time.ticks_ms()
    elapsed = time.ticks_diff(now_ms, state_start_ms) // 1000
    remain = 0
    
    if state == 'FOCUS':
        remain = FOCUS_TIME - elapsed
    elif state == 'STANDING':
        remain = STAND_TIME - elapsed
    elif state == 'VIBRATE':
        remain = 0
    
    oled.fill(0)
    oled.text(state, 0, 0)
        mins = remain // 60
        secs = remain % 60
        oled.text(f"{mins:02d}:{secs:02d}", 20, 24)
    oled.show()

def check_state_transition():
    global state, state_start_ms
    now_ms = time.ticks_ms()
    elapsed = time.ticks_diff(now_ms, state_start_ms) // 1000
    
    if state == 'FOCUS' and elapsed >= FOCUS_TIME:
        state = 'VIBRATE'
        motor_pwm.duty_u16(52000)  # 开启强震
        state_start_ms = time.ticks_ms()
    elif state == 'VIBRATE':
        # 若按键站立确认
        if KEY_START.value() == 1 and elapsed > 1:
            motor_pwm.duty_u16(0)
            state = 'STANDING'
            state_start_ms = time.ticks_ms()
        # 若 30s 无响应则持续震动
        if elapsed > 30:
            motor_pwm.duty_u16(52000)
    elif state == 'STANDING' and elapsed >= STAND_TIME:
        state = 'IDLE'
        state_start_ms = time.ticks_ms()

def key_skip_handler(pin):
    global state, state_start_ms
    if state == 'VIBRATE':
        motor_pwm.duty_u16(0)
        state = 'IDLE'
    elif state == 'IDLE':
        state = 'FOCUS'
    else:
        state = 'FOCUS'
    state_start_ms = time.ticks_ms()

KEY_SKIP.irq(trigger=Pin.IRQ_RISING, handler=key_skip_handler)

# --- 主循环 ---
state_start_ms = time.ticks_ms()
while True:
    check_state_transition()
    show_ui()
    time.sleep_ms(100)

调试步骤

  1. 单元测试 OLED 显示:单独运行 I2C/SPI 扫描,确认 Screen 显示工程名与时间静态字符串。
  2. 振动通路验证:用 motor_pwm.duty_u16(32000) 测试是否振动,若无动作检查三极管基极电压是否 >0.7V。
  3. 按键电平测试:用 while True: print(KEY_START.value()) 确认按下时读到 1,排除悬空干扰。
  4. 状态机联调:将 FOCUS_TIME 临时改为 10s 进行加速循环测试,观察 UI 跳变与震动/停止时序。
  5. 整机功耗测流:正常显示时 26mA,震动 15s 时 120mA,使用 1200mAh 电池续行约 8 个番茄周期——若低于预期,检查 OLED 是否未进入睡眠而全亮。
  6. 人手实测反馈:第 25 分钟震动是否被感知——若感觉微弱,调高 PWM 占空比至 90%,或在电机下方垫高密度海绵增强贴合力。

设计图

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