基于ATtiny85的脉搏血氧仪与光电容积描记器(PPG)设计
一、系统概述
基于ATtiny85微控制器(8位AVR架构,8KB Flash,512B RAM,6个I/O口),实现低成本、低功耗的脉搏血氧仪与光电容积描记器(PPG)。通过红光(660nm)与红外光(940nm)LED照射手指,利用光敏传感器采集透射/反射光信号,经信号调理、滤波、特征提取后,计算心率(HR) 和血氧饱和度(SpO2),支持LED状态指示与低功耗待机,适用于家用健康监测、运动生理研究等场景。
二、系统架构与硬件设计
1. 系统架构
2. 核心硬件选型
| 模块 | 型号/参数 | 功能 |
|---|---|---|
| 主控 | ATtiny85(8MHz内部RC,6I/O) | 信号采集、滤波、心率/SpO2计算、低功耗管理 |
| LED发射器 | 红光(660nm, 5mm)+红外(940nm, 5mm) | 双波长光源,照射手指采集PPG信号 |
| 光敏传感器 | 光敏二极管(BPW34,响应光谱400-1100nm) | 将光信号转换为模拟电信号 |
| 信号调理 | 运放(LM358,增益100) | 放大光敏信号(mV级→V级,适配ATtiny85 ADC) |
| 状态指示 | 双色LED(红+绿) | 显示心率/SpO2状态(正常/异常) |
| 电源 | CR2032纽扣电池(3V, 220mAh) | 系统供电,支持低功耗待机(续航>72小时) |
3. 关键电路设计
(1)LED驱动与光敏采集电路
-
LED驱动:ATtiny85的PB0/PB1(PWM输出)通过1kΩ限流电阻驱动红光/红外LED,交替点亮(避免串扰),占空比50%,频率1kHz;
-
光敏信号调理:光敏二极管输出接LM358同相放大电路(增益100倍),输出经RC低通滤波(1kΩ+100nF,截止频率1.6kHz)后,接入ATtiny85的PB3(ADC3)(10位ADC,参考电压3V)。
(2)低功耗设计
-
电源管理:纽扣电池经肖特基二极管(1N5819) 降压至2.7V(保护电池),ATtiny85工作在1MHz低频模式(降低功耗);
-
外设控制:LED、运放仅在采集时供电(通过PNP三极管控制VCC),平时关闭,电流<1μA。
三、软件设计(C语言实现,基于AVR-GCC)
1. 开发环境
-
IDE:Atmel Studio 7
-
编译器:AVR-GCC 5.4.0
-
库:AVR Libc(标准库,含ADC、PWM、睡眠模式函数)
-
关键外设:10位ADC(PB3)、Timer0(PWM生成)、Timer1(采集时序控制)
2. 主程序流程
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <math.h> // 仅用整数运算替代浮点,实际用定点数
// 系统状态
typedef struct {
uint8_t is_measuring; // 测量标志(0:待机,1:采集)
uint16_t red_buffer[50]; // 红光信号缓冲区(50点,1秒@50Hz)
uint16_t ir_buffer[50]; // 红外信号缓冲区(50点)
uint8_t hr; // 心率(bpm)
uint8_t spo2; // 血氧饱和度(%)
} SystemState;
int main(void) {
// 1. 初始化
SystemState sys_state = {0};
ADC_Init(); // ADC初始化(PB3, 参考电压3V)
PWM_Init(); // PWM初始化(PB0=红光, PB1=红外, 1kHz)
Timer1_Init(); // Timer1初始化(50Hz采集频率)
LED_Init(); // 状态LED初始化(PB2=红, PB4=绿)
// 2. 主循环(低功耗待机+定时采集)
while (1) {
if (sys_state.is_measuring) {
// 2.1 采集PPG信号(红光+红外,各50点/秒,共1秒)
Collect_PPG_Data(&sys_state);
// 2.2 信号处理(滤波+特征提取)
Filter_Signal(sys_state.red_buffer, 50); // 移动平均滤波
Filter_Signal(sys_state.ir_buffer, 50);
sys_state.hr = Calculate_HR(sys_state.red_buffer, 50); // 心率计算
sys_state.spo2 = Calculate_SpO2(sys_state.red_buffer, sys_state.ir_buffer, 50); // SpO2计算
// 2.3 状态指示(正常:绿灯闪, 异常:红灯闪)
if (sys_state.hr > 50 && sys_state.hr < 120 && sys_state.spo2 > 95) {
LED_Green_Blink(2, 200); // 绿灯闪2次(200ms/次)
} else {
LED_Red_Blink(3, 200); // 红灯闪3次(200ms/次)
}
// 2.4 进入低功耗待机(10秒后再次采集)
sys_state.is_measuring = 0;
Enter_Sleep_Mode(10); // 睡眠10秒
} else {
// 待机模式(仅RTC唤醒)
Enter_Sleep_Mode(0); // 无限期睡眠,直到定时唤醒
}
}
}
3. 关键模块实现
(1)PPG信号采集(ADC+PWM驱动LED)
功能:交替点亮红光/红外LED,通过ADC采集光敏信号,存储至缓冲区。
// ADC初始化(PB3=ADC3,参考电压3V)
void ADC_Init(void) {
ADMUX = (1<<REFS0) | (1<<MUX1) | (1<<MUX0); // Vref=Vcc(3V), ADC3(PB3)
ADCSRA = (1<<ADEN) | (1<<ADPS1) | (1<<ADPS0); // 使能ADC,分频8(1MHz/8=125kHz)
}
// PWM初始化(Timer0,PB0=红光,PB1=红外,1kHz)
void PWM_Init(void) {
TCCR0A = (1<<WGM00) | (1<<WGM01) | (1<<COM0A1) | (1<<COM0B1); // 快速PWM,非反相
TCCR0B = (1<<CS01); // 分频8(8MHz/8/256=3.9kHz,调整OCR0x=128→1.95kHz,接近1kHz)
OCR0A = 128; // 红光PWM占空比50%
OCR0B = 128; // 红外PWM占空比50%
DDRB |= (1<<PB0) | (1<<PB1); // PB0/PB1输出
}
// 采集PPG数据(50点/秒,共1秒)
void Collect_PPG_Data(SystemState* state) {
for (uint8_t i=0; i<50; i++) {
// 1. 采集红光信号(PB0亮,PB1灭)
PORTB &= ~(1<<PB1); // 红外灭
PORTB |= (1<<PB0); // 红光亮
_delay_us(100); // 稳定时间
state->red_buffer[i] = ADC_Read(); // 读取ADC值(0-1023)
PORTB &= ~(1<<PB0); // 红光灭
// 2. 采集红外信号(PB1亮,PB0灭)
PORTB |= (1<<PB1); // 红外亮
_delay_us(100);
state->ir_buffer[i] = ADC_Read();
PORTB &= ~(1<<PB1); // 红外灭
_delay_ms(20); // 50Hz采集(1/50=20ms/点)
}
}
// ADC读取(10位)
uint16_t ADC_Read(void) {
ADCSRA |= (1<<ADSC); // 启动转换
while (ADCSRA & (1<<ADSC)); // 等待转换完成
return ADC; // 返回10位结果(0-1023)
}
(2)信号滤波(移动平均滤波,去除噪声)
功能:用5点移动平均滤波平滑PPG信号,减少运动伪影。
// 移动平均滤波(输入缓冲区,长度N)
void Filter_Signal(uint16_t* buffer, uint8_t len) {
uint16_t temp[50];
for (uint8_t i=2; i<len-2; i++) { // 边界不处理
temp[i] = (buffer[i-2] + buffer[i-1] + buffer[i] + buffer[i+1] + buffer[i+2]) / 5;
}
memcpy(buffer, temp, len*2); // 复制回原缓冲区
}
(3)心率计算(峰值检测法,整数运算优化)
功能:通过红光信号峰值检测,计算相邻峰值间隔(RR间期),换算为心率(bpm=60/RR间期)。
// 计算心率(输入红光信号缓冲区,长度N)
uint8_t Calculate_HR(uint16_t* buffer, uint8_t len) {
#define PEAK_THRESHOLD 30 // 峰值阈值(相对基线)
#define MIN_INTERVAL 20 // 最小RR间期(20点=0.4秒→150bpm)
#define MAX_INTERVAL 100 // 最大RR间期(100点=2秒→30bpm)
uint8_t peak_count = 0;
uint8_t last_peak_idx = 0;
uint16_t baseline = 0;
for (uint8_t i=0; i<len; i++) baseline += buffer[i];
baseline /= len; // 基线(平均信号)
for (uint8_t i=1; i<len-1; i++) {
// 峰值条件:大于邻点且超过阈值
if (buffer[i] > buffer[i-1] && buffer[i] > buffer[i+1] &&
(buffer[i] - baseline) > PEAK_THRESHOLD) {
if (peak_count == 0) {
last_peak_idx = i;
peak_count = 1;
} else {
uint8_t interval = i - last_peak_idx; // 间隔(点)
if (interval > MIN_INTERVAL && interval < MAX_INTERVAL) {
// 计算心率(bpm=60/(interval*0.4s))→60 * 2.5/interval=150/interval
uint8_t hr = 150 / interval; // 整数运算(0.4s/点=1/2.5秒/点)
if (hr > 30 && hr < 150) return hr; // 有效范围
}
last_peak_idx = i;
}
}
}
return 0; // 无有效峰值
}
(4)血氧饱和度(SpO2)计算(红光/红外AC/DC比值法)
原理:SpO2与红光(AC/DC)和红外(AC/DC)比值的对数差线性相关,公式:
\(SpO2=110−25×(R_{ratio})\)
其中 \(Rratio=\frac{(ACred/DCred)}{(ACir/DCir)}\)(AC为交流分量,DC为直流分量)。
实现:用整数运算替代浮点,缩放1000倍避免精度损失。
// 计算SpO2(输入红光/红外缓冲区,长度N)
uint8_t Calculate_SpO2(uint16_t* red, uint16_t* ir, uint8_t len) {
// 1. 计算DC分量(平均值)
uint16_t red_dc = 0, ir_dc = 0;
for (uint8_t i=0; i<len; i++) {
red_dc += red[i];
ir_dc += ir[i];
}
red_dc /= len;
ir_dc /= len;
// 2. 计算AC分量(峰峰值/2)
uint16_t red_max=0, red_min=1023, ir_max=0, ir_min=1023;
for (uint8_t i=0; i<len; i++) {
if (red[i] > red_max) red_max=red[i];
if (red[i] < red_min) red_min=red[i];
if (ir[i] > ir_max) ir_max=ir[i];
if (ir[i] < ir_min) ir_min=ir[i];
}
uint16_t red_ac = (red_max - red_min)/2;
uint16_t ir_ac = (ir_max - ir_min)/2;
// 3. 计算R_ratio(整数运算,缩放1000倍)
uint32_t r_ratio = ( (uint32_t)red_ac * ir_dc * 1000 ) / ( (uint32_t)red_dc * ir_ac + 1 ); // 避免除0
r_ratio /= 1000; // 还原比例
// 4. 计算SpO2(公式:SpO2=110-25*R_ratio,整数优化)
uint8_t spo2 = 110 - (25 * r_ratio) / 100; // 25*r_ratio/100=0.25*r_ratio
if (spo2 > 100) spo2 = 100;
if (spo2 < 70) spo2 = 0; // 无效值
return spo2;
}
(5)低功耗管理(睡眠模式+定时唤醒)
功能:通过Timer1定时唤醒,平时进入掉电模式(Power-down),电流<1μA。
// 进入睡眠模式(sec=0:无限期,>0:定时唤醒)
void Enter_Sleep_Mode(uint8_t sec) {
if (sec > 0) {
// 配置Timer1(1秒中断,CTC模式)
TCCR1B = (1<<WGM12) | (1<<CS12) | (1<<CS10); // 分频1024(8MHz/1024=7812.5Hz)
OCR1A = 7812; // 1秒中断(7812.5Hz/7812≈1Hz)
TIMSK |= (1<<OCIE1A); // 使能比较匹配中断
}
// 进入掉电模式
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sei(); // 允许中断
sleep_cpu(); // 进入睡眠
sleep_disable(); // 唤醒后执行
if (sec > 0) {
TIMSK &= ~(1<<OCIE1A); // 关闭Timer1中断
}
}
// Timer1比较匹配中断(1秒唤醒)
ISR(TIMER1_COMPA_vect) {
// 唤醒后标记需要采集数据
extern SystemState sys_state;
sys_state.is_measuring = 1;
}
参考代码 ATtiny85脉搏血氧仪和光电容积描记器 www.youwenfan.com/contentcnt/161018.html
四、系统测试与优化
1. 测试指标
| 参数 | 指标 | 测试方法 |
|---|---|---|
| 心率测量范围 | 50-120bpm(静息状态) | 对比医用ECG仪,误差±5bpm |
| SpO2测量范围 | 90-100%(正常范围) | 对比指夹式血氧仪,误差±2% |
| 采集时间 | 1秒/次(50点/秒) | 示波器测量LED驱动与ADC采样时序 |
| 待机功耗 | <1μA(掉电模式) | 万用表串联测量电池电流 |
| 续航时间 | >72小时(CR2032电池) | 连续采集(每小时1次),记录电池电压 |
2. 优化方向
-
抗干扰:增加硬件带通滤波(0.5-5Hz,覆盖心率频率),软件用中值滤波替代移动平均;
-
低功耗:LED驱动用低占空比(10%)+间歇采集(每10秒1次),进一步降低平均电流;
-
算法轻量化:用查表法替代除法(如心率计算中的150/interval),减少计算时间;
-
显示扩展:增加0.96寸OLED(I2C接口,需ATtiny85软件模拟I2C),显示实时波形与数值。
五、总结
本设计基于ATtiny85实现了低成本、低功耗的脉搏血氧仪与PPG,通过双波长LED+光敏传感器采集信号,移动平均滤波+峰值检测+AC/DC比值法计算心率/SpO2,结合掉电模式+定时唤醒实现长续航。

浙公网安备 33010602011771号