3. 血氧饱和度监测
任务目标
利用MAX30102传感器(内置红光与红外光LED)采集人体手指透射光信号,通过比值法计算血氧饱和度(SpO₂),并在OLED屏幕上实时显示数值与脉搏波形状态。
硬件与依赖
- 主控:ESP32 / Arduino(I²C接口)
- 传感器:MAX30102脉搏血氧模块
- 显示:0.96寸SSD1306 OLED(I²C)
- 库依赖:
Wire.h、Adafruit_SSD1306、Adafruit_GFX、SparkFun_MAX3010x
核心原理
血氧饱和度通过红光(660nm)与红外光(880nm)的交流分量(AC,脉动部分)与直流分量(DC,组织吸收)比值计算:
R = (AC_red / DC_red) / (AC_ir / DC_ir)
经验公式:SpO₂ = 110 - 25 × R(线性近似,适用于R在0.4~1.0范围)
关键完整代码
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <SparkFun_MAX3010x.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
SFE_MAX30102 sensor;
const int SAMPLE_RATE = 100; // Hz
const int BUFFER_SIZE = 100; // 1秒数据窗口
float redAC[BUFFER_SIZE], irAC[BUFFER_SIZE];
float redDC[BUFFER_SIZE], irDC[BUFFER_SIZE];
int bufferIndex = 0;
void setup() {
Serial.begin(115200);
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init failed");
while(1);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
if (!sensor.begin()) {
display.setCursor(0, 0);
display.println("MAX30102 not found");
display.display();
while(1);
}
sensor.setup(SAMPLE_RATE, 4); // 100Hz采样,4mA LED电流
sensor.setPulseAmplitudeRed(0x0A);
sensor.setPulseAmplitudeIR(0x1F);
}
float calculateSpO2() {
// 计算AC/DC比值
float redMean = 0, irMean = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
redMean += redDC[i];
irMean += irDC[i];
}
redMean /= BUFFER_SIZE;
irMean /= BUFFER_SIZE;
float redACSum = 0, irACSum = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
redACSum += abs(redDC[i] - redMean);
irACSum += abs(irDC[i] - irMean);
}
float redACVal = redACSum / BUFFER_SIZE;
float irACVal = irACSum / BUFFER_SIZE;
if (redACVal < 50 || irACVal < 50) return -1; // 信号弱
float R = (redACVal / redMean) / (irACVal / irMean);
float spo2 = 110 - 25 * R;
return constrain(spo2, 70, 100);
}
void loop() {
if (sensor.check()) {
redDC[ bufferIndex] = sensor.getRed();
irDC[bufferIndex] = sensor.getIR();
bufferIndex = (bufferIndex + 1) % BUFFER_SIZE;
if (bufferIndex == 0) { // 每满1秒计算一次
float spo2 = calculateSpO2();
display.clearDisplay();
display.setCursor(0, 0);
display.print("SpO2: ");
if (spo2 > 0) {
display.print(spo2, 1);
display.println("%");
} else {
display.println("--");
}
// 绘制简易脉搏波形(最近50个红外AC值)
for (int i = 0; i < 50; i++) {
int idx = (bufferIndex + i) % BUFFER_SIZE;
int y = 40 - (irDC[idx] / 1000);
display.drawPixel(i * 2, y, SSD1306_WHITE);
}
display.display();
}
}
}
运行与验证
- 接线:MAX30102与OLED均接I²C(SDA→GPIO21,SCL→GPIO22),VIN→3.3V,GND→GND
- 编译上传:Arduino IDE选择ESP32开发板,安装上述库后编译上传
- 操作:将手指轻按传感器表面,保持稳定约3秒
- 通过条件:OLED显示SpO₂数值在95%~100%之间(健康成年人正常范围),且波形区域有随心跳起伏的脉动曲线;手指移开后数值显示"--"
常见问题
- 数值持续偏低或显示"--":手指按压过紧或过松,调整至波形幅度稳定;检查LED电流设置是否过低
- 波形无起伏:确认传感器贴紧皮肤且未受环境光直射;可尝试增大采样率至200Hz
- I²C地址冲突:MAX30102默认地址0x57,OLED为0x3C,若冲突需修改OLED地址