一、项目介绍和创意介绍
我参加的是方向二:可穿戴设备的生命体征实时采集方向。本项目名为"基于 XIAO nRF52840 Sense的智能健康穿戴手表",是一款集心率血氧监测、计步追踪、姿态识别与蓝牙无线传输于一体的可穿戴健康管理设备。
本项目的核心设计理念是"低功耗、高交互、专业化":通过有限状态机(FSM)管理三个页面(主页、心率血氧页、息屏页),结合LSM6DS3六轴传感器的姿态识别能力,实现了抬腕亮屏、放平息屏、久坐提醒等自然交互体验;同时利用MAX30102光学传感器的反射式血氧检测原理,配合滑动平均滤波算法和经验校准策略,让一枚小小的开发板具备了心率血氧仪的测量能力。此外,通过 BLE 蓝牙协议栈,手表可以将心率、血氧、步数和当前时间等数据与手机App 进行双向通信,真正实现"测量→传输→记录"的完整数据闭环。
二、使用的硬件介绍
1. Seeed Studio XIAO nRF52840 Sense(来自贸泽电子平台):搭载 Nordic nRF52840 芯片,ARM Cortex-M4 内核,内建 BLE 5.0蓝牙协议栈。板载 LSM6DS3 六轴IMU传感器和PDM数字麦克风。尺寸仅 21×17.5mm,极为适合可穿戴场景。
2. MAX30102/MAX30105 心率血氧传感器模块:集成红外LED(880nm)和红光LED(660nm),利用光电容积描记法(PPG)原理,通过检测皮肤下血管随心跳的透光率变化来计算心率与血氧饱和度。
3. ST7789 240×135 TFT LCD 屏幕:1.14英寸彩色IPS显示屏,SPI通信接口,色彩细腻,作为手表的交互界面。
4. 无源蜂鸣器:用于按键反馈、久坐报警等声音提示。
5. 轻触按键:连接至 XIAO 的 D6 引脚,配合内部下拉电阻,用于页面切换操作。
三、方案框图和项目设计思路
系统架构框图:

设计思路:
项目采用"有限状态机 (FSM) + 硬件中断 + 定时轮询"的混合架构。系统被划分为三个页面状态(主页 PAGE_HOME、心率页PAGE_HR、息屏页 PAGE_SLEEP),每个页面拥有独立的 run 函数。按键切换通过硬件中断实现,可以在任何时立即响应,解决了传统轮询式按键检测在阻塞算法中失灵的问题。
IMU 数据采集在主循环的每个周期都会执行,保证了姿态识别的实时性。心率血氧算法则只在心率页被激活时运行为了休眠省电,采用滑动窗口更新来提高心率准确性。BLE蓝牙广播和通知在主循环中以非阻塞方式执行,不影响传感器读取和 UI 刷新。
四、原理图和PCB介绍
为了方便演示,我画了一个简单的PCB。按键和蜂鸣器各使用一个gpio控制,OLED连接spi外设控制,血氧传感器使用一路iic通讯。
原理图如下:
PCB连接图:
五、软件流程图和关键代码介绍
软件流程图:

关键代码介绍:
(1) buttonISR()
中断服务函数在按键上升沿时被硬件瞬间调用,内部只做最少量的工作(修改页面状态、设置标志位),200ms的去抖动逻辑确保不会因按键抖动而误触发。
void buttonISR() {
unsigned long interruptTime = millis();
if (interruptTime - lastInterruptTime > 200) {
lastPage = currentPage;
currentPage++;
if (currentPage > 2) currentPage = 0;
needRedraw = true;
buttonPressed = true;
lastManualActionTime = interruptTime;
lastInterruptTime = interruptTime;
}
}
(2) IMU 姿态识别算法:
通过反复测试,确定当屏幕竖起面向面部时,地球重力加速度使Y轴接近 -1.0G,因此使用 y < -0.6 && abs(x) < 0.5 && z> -0.2 && z < 0.6 的组合条件来精准识别抬腕动作。放平息屏则通过 Z 轴接近 1.0G(屏幕平放)来判断。
float x = imu.readFloatAccelX();
float y = imu.readFloatAccelY();
float z = imu.readFloatAccelZ();
if (currentPage == PAGE_SLEEP) {
if (y < -0.6 && abs(x) < 0.5 && z > -0.2 && z < 0.6) {
lastPage = currentPage;
currentPage = PAGE_HOME;
needRedraw = true;
}
}
if (currentPage == PAGE_HOME) {
if (millis() - lastManualActionTime > 2000) {
if (z > 0.8 && abs(x) < 0.3 && abs(y) < 0.3) {
lastPage = currentPage;
currentPage = PAGE_SLEEP;
needRedraw = true;
}
}
}
(3) 心率血氧滑动窗口处理
使用100点循环缓冲区,首次进入时预填充全部数据,后续每次仅增量采样 25个新点,将旧数据前移。maxim_heart_rate_and_oxygen_saturation() 算法负责从红外/红光两路 PPG信号中提取心率和血氧值。
for (byte i = 25; i < 100; i++) {
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
for (byte i = 75; i < 100; i++) {
while (particleSensor.available() == false) {
particleSensor.check();
if(currentPage != PAGE_HR) return;
}
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
}
if (currentPage != PAGE_HR) return;
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
(4)UI 局部刷新机制: 为避免全屏重绘导致的闪屏问题,采用"静态框架一次绘制 + 动态数据局部覆盖"的策略。时间、步数、心率 、血氧数值各自维护上一次显示值的缓存,仅在数据变化时才用背景色矩形覆盖旧值后写入新值:
void updateStatus(const char* msg) {
tft.setTextSize(1);
tft.setTextColor(0x07E0, COLOR_CARD);
char buf[20];
sprintf(buf, "%-15s", msg);
tft.setCursor(140, 6);
tft.print(buf);
}
void updateData(int hr, int ox) {
tft.setTextSize(4);
char buf[10];
tft.setTextColor(COLOR_HR, COLOR_CARD);
tft.setCursor(20, 80);
if(hr == 0) tft.print("-- ");
else { sprintf(buf, "%-3d ", hr); tft.print(buf); }
tft.setTextColor(COLOR_SPO2, COLOR_CARD);
tft.setCursor(140, 80);
if(ox == 0) tft.print("-- ");
else { sprintf(buf, "%-3d ", ox); tft.print(buf); }
}
六、功能展示图及说明
页面 | 功能说明 | 交互方式 |
主页 | 显示当前时间(蓝牙同步)、实时步数 | 抬腕自动亮屏;放平自动息屏 |
心率血氧 | 实时显示心率和血氧饱和度数值,顶部状态栏显示传感器工作状 | 手指放置于传感器上,自动识别“无手指/调整手指/测量中”三种状态 |
息屏页 | 屏幕背光关闭,进入省电模式 | 抬腕自动唤醒,久坐自动弹窗报警 |
久坐提醒 | 红色弹窗显示"Time to Move!" + 蜂鸣器持续报警 | 步数不增加超阈值即触发 |
BLE 连接 | 手机 App可扫描到"M-Design Watch"设备,接收实时心率/血氧/步数数据 | 手机端发送时间同步指令 |
未上电硬件图:

主页展示:

心率血氧测量:

放平自动息屏:

久坐提醒:

可通过BLE 连接手机查看数据、修改时间:

七、设计中遇到的难题和解决方法
1.最初使用轮询式 checkButton() 函数,但在 100 次循环预填充传感器数据时,CPU被完全占用,按键检测被"饿死"。后来改用硬件中,按键信号可以在任何时刻强行打断CPU正在执行的任务,将页面切换逻辑放在 ISR 中直接完成才彻底解决问题。
2.MAX30102 在某些情况下会因重搏波(dicrotic notch)将一次心跳误判为两次,导致读数为实际值的两倍。我通过多次测试,得出经验:当原始值超过 95 BPM 时,乘以 0.7 进行修正。同时引入 5点滑动平均滤波,消除短暂的信号干扰。
八、对本次竞赛的心得体会
参加2026 M-Design设计竞赛,我第一次体验到了Seeed Studio XIAO nRF52840 Sense这款开发板,对比我之前使用的开发板,它小到极致,但是麻雀虽小五脏俱全,其他开发板该有的功能spi,iic,uart等功能他都具有,让我惊讶的是他板载了一个麦克风和imu,加上蓝牙功能,他的性能十分强大,让我收获远超预期。同时我也第一次接触使用Arduino进行开发,从最初"点亮屏幕、读取传感器数据"的基础目标,到最终实现抬腕亮屏、放平息屏、久坐提醒、BLE 蓝牙同步等交互功能,让我收获许多。