1. 项目背景
久坐提醒是健康类嵌入式应用中一个很典型的场景。传统方案通常依赖按键、压力传感器、摄像头或 BLE 设备判断用户是否在座,但这些方案要么需要额外结构设计,要么涉及隐私问题。
本项目尝试使用 DFRobot C4002 毫米波人体存在传感器,结合 ESP32-S31-Korvo 开发板,实现一个适合书房、办公室场景的离席/久坐提醒器。
工程目标包括:
- 通过 USB-TTL 转接器读取 C4002 数据;
- 判断人体是否处于座位区域;
- 在 LCD 屏幕显示当前状态、目标距离、久坐时间和倒计时;
- 久坐超过阈值后点亮板载 RGB LED;
- 支持离席重置和久坐时间统计。
项目仓库结构基于 ESP-IDF 工程组织,目标芯片为 esp32s31。
2. 硬件环境
使用的主要硬件如下:
- ESP32-S31-Korvo 开发板;
- DFRobot C4002 毫米波人体存在传感器;
- USB-TTL 转接器,例如 CH340/CH34x;
- ESP32-S31-Korvo 板载 800x480 RGB LCD;
- ESP32-S31-Korvo 板载 WS2812 RGB LED。
C4002 通过 USB-TTL 转接器连接到 ESP32-S31 的 USB Host 接口。这个连接方式先通过 ESP-IDF 官方 CDC Host 示例验证过,可正常识别 CH340 设备并读取串口数据。
之所以采用 USB-TTL 转接器,而不是直接使用普通 UART GPIO,是因为 ESP32-S31-Korvo 开发板的大量 I/O 已经分配给 RGB LCD、音频、按键、摄像头、SD 卡等板载外设,开发板没有额外引出足够方便使用的空闲 I/O 接口来连接 C4002。相比重新飞线或改动板载外设,使用 USB Host 接 USB-TTL 转接器更稳定,也更适合做可复现的开发板示例。
构建目标设置为:
- idf.py --preview set-target esp32s31
复制代码
因为当前使用的 ESP-IDF 分支中,esp32s31 仍属于 preview target,所以后续 idf.py 命令也需要带 --preview。
3. USB CDC Host 接入 C4002
C4002 模块本质上通过 UART 输出数据。由于开发板侧使用 USB Host,因此采用 USB-TTL 转接器接入。
工程依赖以下 USB VCP/CDC 组件:
- dependencies:
- usb_host_cdc_acm: "^2.3"
- usb_host_ch34x_vcp: "^2.2"
- usb_host_cp210x_vcp: "^2.2"
- usb_host_ftdi_vcp: "^2.1"
复制代码
程序启动后会安装 USB Host 和 CDC ACM Host 驱动,并监听新设备连接:
- const cdc_acm_host_driver_config_t driver_config = {
- .driver_task_stack_size = 4096,
- .driver_task_priority = USB_HOST_PRIORITY + 1,
- .xCoreID = 0,
- .new_dev_cb = new_dev_cb,
- };
-
- cdc_acm_host_install(&driver_config);
复制代码
当 CH340 转接器插入后,日志类似:
- USB CDC connected VID=0x1A86 PID=0x7523
- CDC device opened at 115200 8N1
复制代码
C4002 默认波特率为 115200,使用 8N1。
4. C4002 数据帧解析
开发初期先按 ASCII 文本方式兼容解析,例如:
- state=presence,presence_dist=1.20
- state=motion,motion_speed=0.34
- state=none
复制代码
但实际测试中发现,C4002 通过当前配置输出的是二进制帧。抓到的数据帧以如下头部开始:
复制代码
根据日志分析,帧格式大致为:
- FA F5 AA A5:帧头;
- 后 2 字节:小端整帧长度;
- 最后 2 字节:前面所有字节的加和校验;
- 偏移 12:目标状态;
- 偏移 24、25:目标距离,单位厘米,小端格式。
状态字段当前按如下方式解释:
复制代码
解析代码中会校验帧头、长度和 checksum。距离字段解析后转换为米,用于 LCD 和日志显示。
5. 为什么必须结合距离判断
最开始只根据 C4002 的 Static Presence 和 Motion 状态判断用户是否久坐。测试后发现,这样会有一个明显问题:
用户离开座位但仍在房间内,C4002 仍可能检测到人体存在。此时传感器状态仍是 Static Presence 或 Motion,单纯依赖状态无法判断“在座”还是“离开座位”。
因此后续加入距离判断:
复制代码
只有当目标距离在这个范围内时,才认为用户处于座位区域:
- sample->in_seat_zone = distance_cm >= CONFIG_C4002_SEAT_MIN_DISTANCE_CM &&
- distance_cm <= CONFIG_C4002_SEAT_MAX_DISTANCE_CM;
复制代码
当距离超过 1m,例如日志中的:
- target_dist=1.50m
- seated=no
复制代码
即使状态仍为 Static Presence,也不再累计久坐时间。
6. 久坐计时逻辑的迭代
这个项目中最容易出错的是计时逻辑。
早期逻辑中,进入 session 后使用:
- static_s = now - static_start_us;
复制代码
这会导致一个问题:如果用户短暂离开座位区域,但还没有达到 away reset 时间,程序虽然显示 seated=no,但内部提醒判断仍可能继续按 wall-clock 时间增长。
例如:
- static=7s
- target_dist=1.56m
- seated=no
- ...
- REMINDER | Static presence for 15s
复制代码
这明显不符合预期,因为用户已经离开座位区域,倒计时应该暂停。
最终修正为“实际在座累计时间”模型:
- int64_t seated_interval_start_us;
- int64_t seated_accumulated_us;
复制代码
逻辑如下:
- seated=yes 时开始或继续累计;
- seated=no 时暂停累计;
- 重新回到座位区域后继续累计;
- 超过 away reset 时间后重置 session;
- 久坐提醒只在 in_seat_zone=true 时触发。
这样就能实现:
- 0.96m 内累计到 7 秒
- 离开到 1.56m 后倒计时暂停
- 回到 1m 内后从 7 秒继续
- 累计到 15 秒才提醒
复制代码
7. 板载 RGB LED 控制问题
开发中遇到过一个典型硬件映射问题。
一开始根据资料误以为板载可寻址 RGB LED 在 GPIO8,但实际烧录后 LED 没有反应。后续查看 ESP32-S31-Korvo 出厂 demo 的 BSP,发现:
- #define BSP_LED_WS2812 GPIO_NUM_37
复制代码
而 GPIO8 是 LCD 的 RGB 数据线:
- #define BSP_LCD_DATA0 GPIO_NUM_8
复制代码
所以正确的 WS2812 引脚是 GPIO37。
出厂 demo 使用 led_strip 组件和 RMT 驱动 WS2812,颜色格式为 GRB。当前工程中采用 RMT 自定义 encoder 控制板载灯:
- 上电短暂红灯自检;
- 久坐提醒触发后点亮红灯;
- 离座 reset 后熄灭红灯。
8. LCD 显示实现
ESP32-S31-Korvo 带有 800x480 RGB LCD。出厂 demo 中 LCD 参数如下:
- 分辨率:800 x 480
- 颜色格式:RGB565
- 数据宽度:16-bit RGB
- PCLK:26 MHz
复制代码
关键 GPIO:
- DATA0 GPIO8
- DATA1 GPIO9
- ...
- DATA15 GPIO36
- PCLK GPIO40
- DE GPIO43
- HSYNC GPIO44
- VSYNC GPIO45
复制代码
最开始尝试直接引入完整出厂 BSP,但它会连带引入 Camera、Video、Audio、SD 等依赖,其中 esp_video 在当前环境下编译遇到 sys/socket.h 缺失问题。
因此最终采用“最小 LCD 初始化”方案:
- 只移植 RGB LCD GPIO 和时序参数;
- 使用 esp_lcd_new_rgb_panel() 初始化 RGB panel;
- 使用 esp_lvgl_adapter 注册到 LVGL;
- 不引入完整 BSP。
LCD 初始化核心代码类似:
- esp_lcd_rgb_panel_config_t panel_config = {
- .timings = {
- .pclk_hz = 26 * 1000 * 1000,
- .h_res = 800,
- .v_res = 480,
- .hsync_pulse_width = 1,
- .hsync_back_porch = 40,
- .hsync_front_porch = 20,
- .vsync_pulse_width = 1,
- .vsync_back_porch = 10,
- .vsync_front_porch = 5,
- .flags.pclk_active_neg = true,
- },
- .data_width = 16,
- .in_color_format = LCD_COLOR_FMT_RGB565,
- .flags.fb_in_psram = true,
- };
复制代码
9. PSRAM 配置问题
启用 LCD 后第一次运行遇到:
- lcd_rgb_panel_alloc_frame_buffers: no mem for frame buffer
- esp_lcd_new_rgb_panel: alloc frame buffers failed
复制代码
原因是 LCD 帧缓冲太大。800x480 RGB565 单帧约 750KB,内部 RAM 无法承载,必须启用 PSRAM。
出厂 demo 中也开启了 PSRAM:
- CONFIG_SPIRAM=y
- CONFIG_SPIRAM_SPEED_250M=y
- CONFIG_SPIRAM_XIP_FROM_PSRAM=y
复制代码
启用后启动日志中可以看到:
- esp_psram: Found 16MB PSRAM device
- esp_psram: Speed: 250MHz
- esp_psram: Adding pool of 15680K of PSRAM memory
复制代码
LCD 初始化也正常:
- RGB LCD registered to LVGL adapter
- LCD UI initialized
复制代码
10. LCD 界面内容
LCD 界面主要面向调试和实际使用,显示内容包括:
- 状态图标;
- 当前状态;
- 是否在座;
- 目标距离;
- 久坐提醒倒计时;
- 当前静止在座时间;
- 当前 session 时间;
- 总静止在座统计时间。
状态图标设计为简单字符:
- ?:未知
- O:离座或无目标
- S:在座
- M:运动
- !:久坐提醒
复制代码

倒计时逻辑需要特别注意:
- 在座时倒计时减少;
- 离开座位区域时倒计时暂停;
- 回到座位区域后继续;
- 达到阈值后显示提醒状态。

11. 当前默认配置
当前工程默认配置如下:
- 久坐提醒阈值:15 秒
- 重复提醒间隔:300 秒
- 离座重置时间:10 秒
- 座位有效距离:30cm ~ 100cm
- LCD 显示:开启
- 板载 RGB LED:GPIO37
复制代码
配置文件主要包括:
- sdkconfig.defaults
- main/Kconfig.projbuild
- main/idf_component.yml
复制代码
建议将 sdkconfig.defaults 上传仓库,便于其他开发者复现关键配置。
12. 构建与烧录
代码仓库地址:
- https://gitee.com/zealsoft/c4002_sedentary_reminder
复制代码
构建命令:
- cd D:\esp\master\esp-idf\projects\c4002_sedentary_reminder
- idf.py --preview set-target esp32s31
- idf.py --preview build
- idf.py --preview flash monitor
复制代码
13. 仓库上传建议
建议上传:
- CMakeLists.txt
- README.md
- README_zh.md
- sdkconfig.defaults
- dependencies.lock
- main/
- .gitignore
复制代码
建议忽略:
- build/
- managed_components/
复制代码
managed_components 是 ESP-IDF Component Manager 自动下载的依赖,不需要上传。build 是构建产物,也不应上传。
如果希望完全复现当前开发环境,也可以上传 sdkconfig。但更通用的做法是上传 sdkconfig.defaults,让用户通过 idf.py set-target 和默认配置生成自己的 sdkconfig。
14. 总结
这个项目看起来只是一个“久坐提醒器”,但实际开发中涉及了不少嵌入式工程细节:
- USB Host CDC 设备枚举;
- C4002 二进制帧解析;
- 毫米波存在状态与距离联合判断;
- 防止房间内其他位置误判为在座;
- WS2812 引脚与 LCD 引脚冲突排查;
- ESP32-S31-Korvo RGB LCD 初始化;
- LVGL adapter 接入;
- PSRAM 帧缓冲配置;
- 久坐计时逻辑从 wall-clock 改为实际在座累计。
最终实现的效果是:系统能够通过 C4002 判断用户是否处于座位区域,在 LCD 上显示状态和倒计时,并在实际久坐达到阈值后点亮红灯提醒用户起身活动。
|