10 - SPO2 血氧传感器应用
本章节介绍 Pico-G1 扩展板上的 MAX30100 血氧传感器应用示例 —— spo2_sensor。该应用演示了如何使用 I2C 接口读取 MAX30100 传感器的红外光和红光数据,计算心率和血氧饱和度,并在 TFT 屏幕上实时显示健康数据。这是学习光学传感器和信号处理的进阶示例。
应用源码位于 SDK 目录 source/app/10_spo2_sensor/,提供了完整的 I2C 传感器读取和信号处理实现。
1 应用概述
1.1 功能特性
- 光学传感器读取:通过 I2C 接口读取 MAX30100 的 IR/R 红光数据
- 心率计算:基于红外信号峰值检测计算心率(BPM)
- 血氧估算:基于红光/红外光比值估算血氧饱和度(SpO2)
- FIFO 缓冲:使用传感器内部 FIFO 存储采样数据
- INT 中断支持:支持数据就绪中断检测
1.2 技术参数
| 参数 | 值 |
|---|---|
I2C 接口 | I2C3(/dev/i2c-3) |
从地址 | 0x57(部分模块为 0x5A) |
采样频率 | 50Hz |
ADC 分辨率 | 16-bit |
心率范围 | 30~220 BPM |
血氧范围 | 70~100% |
刷新频率 | 200ms(可配置) |
1.3 测试用例列表
| index | 名称 | 测试指令 | 预期现象(成功) | 失败可能原因 |
|---|---|---|---|---|
| 1 | 基础读数 | ./spo2_sensor | TFT显示心率和血氧,控制台输出数据 | I2C连接失败、地址错误 |
| 2 | 手指测试 | 手指正确放置传感器 | 显示心率60-100 BPM,血氧95-100% | 手指未正确放置、压力不当 |
| 3 | 运动测试 | 测量后轻微运动 | 心率上升5-20 BPM,血氧基本稳定 | 传感器松动、信号干扰 |
1.4 目录结构
source/app/10_spo2_sensor/
├── Makefile # 构建脚本
├── main.c # 主程序
├── max30100.c # MAX30100 驱动实现
├── max30100.h # MAX30100 驱动头文件
├── spo2_algorithm.c # 血氧算法实现
├── spo2_algorithm.h # 血氧算法头文件
├── i2c_hal.c # I2C HAL 层实现
├── i2c_hal.h # I2C HAL 层头文件
├── spi_hal.c # SPI HAL 层实现
├── spi_hal.h # SPI HAL 层头文件
├── st7789.c # ST7789 驱动实现
├── st7789.h # ST7789 驱动头文件
├── font8x16.h # 8×16 ASCII 点阵字库
└── README.md # 说明文档2 硬件连接说明
2.1 引脚定义
| 信号 | 板上 GPIO | 控制 / 节点 | 说明 |
|---|---|---|---|
| SCL | GPIO4_1 | I2C3 → /dev/i2c-3 | 与 OLED/MPU6050 共享 I2C3 总线 |
| SDA | GPIO4_2 | I2C3 → /dev/i2c-3 | 共享总线 |
| INT | GPIO4_4 | 中断输入 | 数据就绪中断(可选) |
| VCC | 3.3V | — | MAX30100 工作电压 1.8~3.3V |
| GND | GND | — | 地线 |
2.2 硬件电路
MAX30100 接线图:
Pico-G1 MAX30100 Module
┌───────────┐ ┌──────────────┐
│ │ │ │
│ GPIO4_1 ──┼────── SCL ───┤ SCL │
│ │ │ │
│ GPIO4_2 ──┼────── SDA ───┤ SDA │
│ │ │ │
│ GPIO4_4 ──┼────── INT ───┤ INT │
│ │ │ │
│ 3.3V ───┼─────────────┤ VCC │
│ │ │ │
│ GND ───┼─────────────┤ GND │
└───────────┘ └──────────────┘医疗级别声明
此示例仅为学习演示,不适用于医疗诊断。专业医疗设备需要严格的校准和认证。
3 编译与部署
3.1 编译应用
export PATH=$PATH:<SDK>/tools/linux/toolchains/arm-gcc12.2.0-linux-uclibceabi/bin
cd <SDK>/source/app/10_spo2_sensor
make3.2 运行应用
scp spo2_sensor root@<板端IP>:/usr/bin/
ssh root@<板端IP> '/usr/bin/spo2_sensor'3.3 预期输出
控制台输出
/mnt # ./spo2_sensor
[max] pad 复用:I2C3(4_1/4_2)->func2,GPIO4_4->func5
[max] pad 0x100C0010 -> 0x00001002
[max] pad 0x100C0014 -> 0x00001002
[max] 初始化 SPI 屏(/dev/spidev2.0)...
[spi] pad 0x100C0028 -> 0x00001004
[spi] pad 0x100C002C -> 0x00001004
[spi] pad 0x100C0030 -> 0x00001000
[spi] pad 0x100C0020 -> 0x00001005
[spi] pad 0x100C001C -> 0x00001005
[spi] opening /dev/spidev2.0 ...
[spi] spidev opened, fd=3
[spi] spidev mode/bits/speed set (MODE3/8b/24MHz)
[spi] chardev request DC @ /dev/gpiochip4 line 5 ...
[spi] chardev request RES @ /dev/gpiochip4 line 4 ...
[spi] chardev request CS @ /dev/gpiochip5 line 1 ...
[spi] chardev-verify: DC=0(expect0) RES=1(expect1) CS=1(expect1) ==> OK(chardev 真驱动了引脚)
[spi] spi_hal_init done
[tft] init: SLPOUT
[tft] init: SLPOUT +120ms ok
[tft] init: config cmds ok
[tft] init: DISPON
[tft] init: DISPON ok
[tft] init: clear-flush start
[tft] flush #1 start
[tft] flush #1 done
[tft] init: clear-flush done
[max] 初始化 MAX30100(/dev/i2c-3 addr 0x57)...
[max] PART_ID=0x11 REV_ID=0x05
[max] @ /dev/i2c-3 addr 0x57 (rev=0x05, INT=disabled, polling only)
[max] 采集中,50 SPS;采样线程 100ms/次,屏刷 200ms/次,Ctrl+C 退出。TFT 屏幕显示

4 MAX30100 传感器原理
4.1 I2C 寄存器配置
主要寄存器:
| 寄存器地址 | 名称 | 说明 |
|---|---|---|
| 0x02 | MODE_REGISTER | 工作模式配置 |
| 0x03 | SPO2_REGISTER | SPO2 模式配置 |
| 0x04 | LED_REGISTER | LED 电流控制 |
| 0x05 | FIFO_REGISTER | FIFO 配置 |
| 0x06 | FIFOdatapointer | FIFO 数据指针 |
| 0x07 | FIFO_READ | FIFO 数据读取 |
4.2 FIFO 读写机制
MAX30100 内置 16 字节 FIFO 缓冲区:
// 配置 FIFO
void max30100_config_fifo(int i2c_fd)
{
uint8_t fifo_config = 0x00; // 全部采样平均值
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x05, &fifo_config, 1);
// 设置 FIFO 水位
uint8_t sample_avg = 0x01; // 每2个样本平均一次
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x05, &sample_avg, 1);
}
// 读取 FIFO 数据
void max30100_read_fifo(int i2c_fd, uint8_t *data, uint8_t len)
{
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x06, NULL, 0); // 设置 FIFO 指针
i2c_read_bytes(i2c_fd, MAX30100_ADDR, 0x07, data, len); // 读取数据
}4.3 数据格式
MAX30100 输出数据格式:
- IR 红外光:16位 ADC 值
- R 红光:16位 ADC 值
- 每帧数据:4字节(IR_L + IR_H + R_L + R_H)
5 心率血氧计算原理
5.1 心率计算算法
信号处理流程:
// 心率计算步骤
float calculate_heart_rate(uint16_t *ir_buffer, int buffer_size)
{
// 1. 计算基线和振幅
float baseline = calculate_baseline(ir_buffer, buffer_size);
float amplitude = calculate_amplitude(ir_buffer, buffer_size, baseline);
// 2. 设置检测阈值
float threshold = baseline + amplitude * 0.3f;
// 3. 检测峰值
int peak_count = 0;
float peak_intervals[16];
for (int i = 1; i < buffer_size - 1; i++) {
if (ir_buffer[i] > threshold &&
ir_buffer[i] > ir_buffer[i-1] &&
ir_buffer[i] > ir_buffer[i+1]) {
// 记录峰值间隔
if (peak_count > 0) {
peak_intervals[peak_count-1] = i * 0.02f; // 50Hz采样
}
peak_count++;
}
}
// 4. 计算平均间隔
float avg_interval = 0;
for (int i = 0; i < peak_count - 1; i++) {
avg_interval += peak_intervals[i];
}
avg_interval /= (peak_count - 1);
// 5. 计算心率
return 60.0f / avg_interval; // BPM
}5.2 血氧估算算法
比值计算法:
float calculate_spo2(uint16_t *ir_buffer, uint16_t *red_buffer, int buffer_size)
{
// 1. 计算 IR 和 R 的平均值
float ir_avg = 0, red_avg = 0;
for (int i = 0; i < buffer_size; i++) {
ir_avg += ir_buffer[i];
red_avg += red_buffer[i];
}
ir_avg /= buffer_size;
red_avg /= buffer_size;
// 2. 计算比值
float ratio = (red_avg / ir_avg);
// 3. 估算血氧(简化公式)
float spo2 = 110.0f - 30.0f * ratio;
// 4. 限制范围
if (spo2 > 100.0f) spo2 = 100.0f;
if (spo2 < 70.0f) spo2 = 70.0f;
return spo2;
}6 关键编程要点
6.1 MAX30100 初始化
int max30100_init(int i2c_fd)
{
// 复位传感器
uint8_t mode_config = 0x03; // 复位
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x02, &mode_config, 1);
usleep(10000); // 等待复位完成
// 配置 SPO2 模式
uint8_t spo2_config = 0x4F; // 50Hz采样,16位ADC
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x03, &spo2_config, 1);
// 配置 LED 电流
uint8_t led_config = 0x4F; // IR和R都是50mA
i2c_write_register(i2c_fd, MAX30100_ADDR, 0x04, &led_config, 1);
return 0;
}6.2 信号采集
#define BUFFER_SIZE 100
typedef struct {
uint16_t ir_buffer[BUFFER_SIZE];
uint16_t red_buffer[BUFFER_SIZE];
int buffer_index;
} signal_buffer_t;
void collect_samples(int i2c_fd, signal_buffer_t *signals)
{
uint8_t fifo_data[4];
// 读取 FIFO
max30100_read_fifo(i2c_fd, fifo_data, 4);
// 解析数据
uint16_t ir_value = (fifo_data[0] << 8) | fifo_data[1];
uint16_t red_value = (fifo_data[2] << 8) | fifo_data[3];
// 存储到缓冲区
if (signals->buffer_index < BUFFER_SIZE) {
signals->ir_buffer[signals->buffer_index] = ir_value;
signals->red_buffer[signals->buffer_index] = red_value;
signals->buffer_index++;
}
}6.3 数据校准
信号质量检测:
bool check_signal_quality(uint16_t *ir_buffer, int buffer_size)
{
// 计算信号变化范围
uint16_t min_val = 0xFFFF, max_val = 0;
for (int i = 0; i < buffer_size; i++) {
if (ir_buffer[i] < min_val) min_val = ir_buffer[i];
if (ir_buffer[i] > max_val) max_val = ir_buffer[i];
}
uint16_t amplitude = max_val - min_val;
// 信号幅度检查
if (amplitude < 100) {
printf("[SPO2] 信号强度不足\n");
return false;
}
return true;
}7 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 读数异常 | 手指未正确放置 | 确保手指完全覆盖传感器,适当压力 |
| 心率为 0 | 信号强度不足、检测算法失败 | 调整 LED 电流、检查环境光干扰、重新放置手指 |
| 血氧数值异常 | 比值计算错误、校准问题 | 重新校准传感器、确保手指静止 |
| 数据不更新 | FIFO 配置错误、I2C 通信失败 | 检查传感器寄存器配置、验证 I2C 连接 |
| 心率跳动大 | 手指移动、环境干扰 | 保持手指静止、避免强光直射 |
测量建议
为获得最佳测量效果:
- 手指放置:使用食指或中指,轻放在传感器上
- 保持静止:测量时保持手部稳定,避免移动
- 环境光线:避免强光直射传感器
- 测量时间:每次测量至少 5 秒钟
8 进阶功能
8.1 信号滤波
低通滤波器:
#define ALPHA 0.1f // 滤波系数
float low_pass_filter(float input, float prev_output)
{
return ALPHA * input + (1.0f - ALPHA) * prev_output;
}8.2 数据趋势分析
typedef struct {
float history[10];
int index;
} spo2_history_t;
void update_spo2_history(spo2_history_t *history, float spo2)
{
history->history[history->index] = spo2;
history->index = (history->index + 1) % 10;
// 计算趋势
float avg = 0;
for (int i = 0; i < 10; i++) {
avg += history->history[i];
}
avg /= 10.0f;
printf("[SPO2] 当前: %.1f%% 平均: %.1f%%\n", spo2, avg);
}8.3 蓝牙数据传输
// 发送数据到手机APP
void send_spo2_via_bluetooth(float heart_rate, float spo2)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "HR:%.0f,SPO2:%.1f", heart_rate, spo2);
// 通过BLE发送数据
bluetooth_send(buffer, strlen(buffer));
}