基于ATtiny85的脉搏血氧仪与光电容积描记器(PPG)设计

一、系统概述

基于ATtiny85微控制器(8位AVR架构,8KB Flash,512B RAM,6个I/O口),实现低成本、低功耗的脉搏血氧仪与光电容积描记器(PPG)。通过红光(660nm)与红外光(940nm)LED照射手指,利用光敏传感器采集透射/反射光信号,经信号调理、滤波、特征提取后,计算心率(HR) 和血氧饱和度(SpO2),支持LED状态指示与低功耗待机,适用于家用健康监测、运动生理研究等场景。

二、系统架构与硬件设计

1. 系统架构

graph TD A[手指] -->|红光/红外光| B[LED发射器 红+红外] B -->|透射/反射光| C[光敏传感器 光敏二极管] C -->|模拟信号| D[ATtiny85 ADC采集] D -->|信号处理| E[心率/SpO2计算] E -->|结果| F[状态指示 LED] E -->|结果| G[低功耗待机] H[电源 3V纽扣电池] -->|供电| D H -->|供电| B H -->|供电| F

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,结合掉电模式+定时唤醒实现长续航。

posted @ 2026-04-06 17:29  风一直那个吹  阅读(12)  评论(0)    收藏  举报