💡 划重点

  • 主控采用 ESP32,利用其 Wi-Fi、低功耗与丰富 GPIO,成本可控。
  • 用人体传感器判断是否在座,OLED 显示倒计时,蜂鸣器做声音提醒,舵机做“小旗”物理反馈。
  • 采用“空闲—学习—休息”三状态机,避免一直响、一直转的干扰。
  • 目标物料成本约 37 元,控制在 40 元以内。
  • 结构可用纸板、亚克力边角料或 3D 打印件搭建,适合放在学习桌角落。
  • 代码保留串口日志与 Wi-Fi 扩展接口,后续可加数据上报,不影响当前成本。

需求洞察

目标用户是居家学习的中小学生,以及需要长时间伏案写作业、看书的大学生。真实痛点不是“没有计时器”,而是手机计时器容易变成干扰源:一拿起手机就想看消息;普通闹钟只会响,缺少“我是否坐在桌前”的判断;家长反复催促又容易引发对抗。桌面作业提醒机器人要解决的是“开始难、坚持难、久坐不自觉”三个问题。

它值得做,原因有三点。第一,提醒发生在桌面现场,不依赖手机,减少分心。第二,人体传感器让提醒具备上下文:人离开就暂停,不把空座计时算成学习。第三,舵机小旗和蜂鸣器形成轻量物理反馈,比屏幕弹窗更有存在感,却比大声训斥更温和。成本控制在 40 元内,也让它具备批量制作和课堂实践价值。

方案设计

感知层由 HC-SR501 人体红外传感器完成在座检测,输出数字信号;ESP32 内部定时器负责学习 25 分钟、休息 5 分钟的节拍。控制层运行三状态机:空闲、学习、休息,并加入“连续 5 分钟无人则退出学习”的规则,避免空座累计。执行层包括 0.96 寸 OLED、SG90 舵机、有源蜂鸣器:OLED 显示状态与剩余时间,舵机带动小旗抬起或落下,蜂鸣器在状态切换时短鸣。日志/联网层先用串口输出每次状态切换与学习时长,ESP32 预留 Wi-Fi 能力,后续可通过 NTP 校时或 MQTT 上报,不增加当前硬件成本。

物料清单

物料 数量 用途 估算单价 合计
ESP32 开发板 1 主控、计时、联网扩展 15 元 15 元
0.96 寸 OLED I2C 1 显示状态与倒计时 6 元 6 元
SG90 舵机 1 驱动小旗做物理提示 5 元 5 元
HC-SR501 人体传感器 1 检测是否在座 6 元 6 元
有源蜂鸣器模块 1 状态切换声音提醒 1 元 1 元
杜邦线、扎带、纸板 1 批 接线与结构固定 4 元 4 元
合计 37 元

接线与结构

OLED 使用 I2C:SDA 接 GPIO21,SCL 接 GPIO22,VCC 接 3.3V,GND 共地。PIR 输出接 GPIO18,VCC 接 5V,GND 共地。舵机信号接 GPIO13,电源接 5V,GND 共地;SG90 启动电流较大,若出现复位,建议用独立 5V 电源并共地。蜂鸣器 I/O 接 GPIO27,采用低电平触发或高电平触发需按模块标注调整,代码默认高电平触发。以上均沿用建议接线,无需改脚。

结构上,纸板做 L 形底座,OLED 固定在正面,PIR 朝向座位,舵机装在侧面并粘一根轻质小旗,蜂鸣器留在底座内。舵机臂行程只做 0 到 90 度,避免堵转。

核心代码


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>

#define SDA_PIN 21
#define SCL_PIN 22
#define PIR_PIN 18
#define SERVO_PIN 13
#define BUZZ_PIN 27

Adafruit_SSD1306 display(128, 64, &Wire;, -1);
Servo flagServo;

enum State { IDLE, STUDY, BREAK };
State state = IDLE;
unsigned long stateStart = 0;
unsigned long lastShow = 0;
unsigned long noMotionStart = 0;
const unsigned long STUDY_MS = 25UL * 60UL * 1000UL;
const unsigned long BREAK_MS = 5UL * 60UL * 1000UL;
const unsigned long AWAY_MS = 5UL * 60UL * 1000UL;

void beep(int times) {
  for (int i = 0; i < times; i++) {
  }
}

void showState() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.setCursor(0, 20);
  unsigned long elapsed = millis() - stateStart;
  if (state == IDLE) {
    display.print("IDLE");
  } else if (state == STUDY) {
    unsigned long left = (elapsed < STUDY_MS) ? (STUDY_MS - elapsed) / 1000 : 0;
    display.print("S "); display.print(left / 60); display.print(":");
    if (left % 60 < 10) display.print("0");
    display.print(left % 60);
  } else {
    unsigned long left = (elapsed < BREAK_MS) ? (BREAK_MS - elapsed) / 1000 : 0;
    display.print("B "); display.print(left / 60); display.print(":");
    if (left % 60 < 10) display.print("0");
    display.print(left % 60);
  }
  display.display();
}

void setup() {
  Serial.begin(115200);
  pinMode(PIR_PIN, INPUT);
  pinMode(BUZZ_PIN, OUTPUT);
  digitalWrite(BUZZ_PIN, LOW);
  flagServo.attach(SERVO_PIN);
  flagServo.write(0);
  Wire.begin(SDA_PIN, SCL_PIN);
  display.clearDisplay();
  display.display();
  stateStart = millis();
}

void loop() {
  bool pir = digitalRead(PIR_PIN);
  unsigned long now = millis();

  if (state == IDLE && pir) {
    state = STUDY; stateStart = now; noMotionStart = 0;
    flagServo.write(90); beep(1);
    Serial.println("ENTER STUDY");
  } else if (state == STUDY) {
    if (pir) noMotionStart = 0;
    else if (noMotionStart == 0) noMotionStart = now;
    if (noMotionStart && now - noMotionStart > AWAY_MS) {
      state = IDLE; stateStart = now; flagServo.write(0); beep(2);
      Serial.println("AWAY -> IDLE");
    } else if (now - stateStart >= STUDY_MS) {
      state = BREAK; stateStart = now; flagServo.write(0); beep(2);
      Serial.println("ENTER BREAK");
    }
  } else if (state == BREAK && now - stateStart >= BREAK_MS) {
    state = IDLE; stateStart = now; beep(1);
    Serial.println("BREAK END");
  }

  if (now - lastShow > 500) {
    lastShow = now; showState();
  }
}

调试步骤

先只接 OLED,扫描 I2C 地址,确认 0x3C 能显示。再单独接 PIR,串口观察有人和无人时的电平变化,调节模块延时与灵敏度,避免小幅动作误触发。接着接蜂鸣器,确认高电平触发是否正常;若常响,改为低电平触发。最后接舵机,先空载测试 0 到 90 度,确认电源不跌落;若 ESP32 复位,改用独立 5V 并共地。整机联调时观察串口日志,检查学习 25 分钟、休息 5 分钟、离座 5 分钟退出是否符合预期,并根据桌面距离微调 PIR 角度。

设计图

桌面作业提醒机器人结构flow
1
感知输入
→
2
控制处理
→
3
执行反馈
→
4
数据上报