利用现成元器件设计低成本脉搏血氧仪
作者:Stephen Evanczuk /Digi-Key
脉搏血氧仪可测量外周血氧饱和度 (SpO2),该指标反映了心肺系统向身体提供富氧血液的效率。运动员使用 SpO2 测量值来衡量他们在锻炼中的努力程度,但这种测量在新冠肺炎流行期间变得更加重要。医护人员很关注 SpO2 的下降,因为它是引起新冠肺炎的 SARS-CoV-2 病毒对肺组织造成损伤的预警信号。
对于被要求在家中隔离的症状轻微的感染者来说,如果有一台低成本脉搏血氧仪随时可以使用,那么将有助于判断病情并获得及时就医所需的警告信息。
本文简要讨论了新冠肺炎的症状和监测 SpO2 的需求。然后向开发人员展示如何使用 Microchip Technology 数字信号控制器 (DSC) 和一些附加器件,设计一个低成本的脉搏血氧仪,从而针对新冠肺炎感染后期的症状,为家庭用户提供预警。
新冠肺炎和测量血氧饱和度的需求
由于 SARS-CoV-2 病毒的破坏作用,新冠肺炎会表现出各种症状。对于医护人员来说,一个特别令人担忧的症状与肺组织损伤有关,它将导致呼吸系统受损,摄氧量减少。尽管医生会逐一使用胸部 X 光检查和计算机断层 (CT) 扫描来确认这一阶段的新冠肺炎,但他们往往会使用 SpO2 测量值作为早期指标。
通过由患者动脉抽取样本并分析血气水平,可直接确定动脉血氧饱和度 (SaO2),而 SpO2 测量是一种非侵入式的替代测量方法。虽然某些情况下可能需要直接测量动脉血气,但 SpO2 测量已被证明可以提供可靠的 SaO2 估计值。也许最重要的是,在家即可使用光学脉搏血氧仪进行测量,而测量结果跟临床环境一样可靠。
光学脉搏血氧仪利用脱氧血红蛋白 (Hb) 和氧合血红蛋白 (HbO2) 的光吸收差异来测量 SpO2。在富含氧气的肺内,红细胞中的血红蛋白最多可与四个氧分子迅速形成可逆性结合,从而生成 HbO2,该分子对波长为 940 nm 的光吸收率高于波长为 660 nm 的光(图 1)。

因为 HbO2 在血氧分压较低时会变为 Hb,所以 SpO2 可以用简单的公式来确定:
SpO2 = HbO2 / (HbO2 + Hb)
血液中 Hb 和 HbO2 的相对浓度则可通过测量对波长为 660 nm 和 940 nm 的光吸收情况来确定。脉搏血氧仪利用血氧分压、血红蛋白氧结合状态和光吸收差异之间的关系,可提供可靠的 SpO2 测量。
典型脉搏血氧仪的主要子系统
典型脉搏血氧仪设计包括三个主要子系统:
- 光传输子系统,包括模拟开关和驱动器,以及可发出红光(波长为 660 nm)和红外线 (IR)(波长为 950 nm)的发光二极管 (LED)。一些系统还包括绿光(波长为 530 nm)光源,可配合光电容积描记法 (PPG) 使用,通过监测皮肤血管容积的变化来确定心率。
- 光检测子系统,包括光电二极管、信号调节链和模数转换器 (ADC)。
- DSC 或微控制器,用于协调光传输和光检测子系统,并由测得的数据计算 SpO2。
无论是将这些采用光学封装结构的器件用于透射式测量,还是反射式测量,设计人员都只需要添加相对较少的元器件就可实现低成本的脉搏血氧仪设计,并且能够为家庭用户提供相关信息,针对是否需要专业医护人员进一步评估提出建议。在基于 Microchip Technology 的 DSPIC33FJ128GP802 DSC 构建的设计示例中,使用微控制器的集成外设来控制红光和红外光 LED 对皮肤的照射,然后将调节后的光电二极管输出信号数字化(图 2)。

实现低成本脉搏血氧仪硬件设计
在此设计中,DSC 使用外部 Microchip Technology 的 MCP4728 数模转换器 (DAC) 将单独的 MBT2222 晶体管置于所需电平,从而以所需的亮度驱动各个 LED。为了对每个 LED 的“开启”序列进行精确定时,DSC 使用其中两个脉冲宽度调制 (PWM) 输出来控制 Analog Devices 的 ADG884 模拟开关(图 3)。



Microchip 提供的脉搏血氧仪固件包带有一个样例程序,演示了如何使用 DSC 执行这些照射控制和数据转换序列。在这里,该程序使用一对 DSC 定时器(Timer2 和 Timer3)来实现中断驱动方法,这两个定时器分别用于红外光 LED 和红光 LED 各自的“开启”序列。这样,每个定时器又为 DSC 的两个输出比较 (OC) 模块(OC1 和 OC2)提供时基,这两个模块分别用于控制红外光LED 和红光 LED 的模拟开关。
如清单 1 所示,该软件首先初始化 Timer2 和 Timer3,以设置所需的照射周期时长并启用中断。作为其初始化序列的一部分,OC1 和 OC2 模块使用 DSC 的可重映射引脚 (RP) 功能连接到各自的输出引脚。然后,初始化序列设置照射占空比,并选择关联定时器用作时基。
//********************************************************************************************************* // Initialize Timer 2 - IR light //********************************************************************************************************* T2CON = 0x0020; // Stop 16-bit Timer2, 1:64(40MhzFosc) Prescale, Internal clock (Fosc/2) TMR2 = 0x00; // Clear timer register PR2 = 1250; // Load the period value, OCxRS <= PRx, 4ms period = (1/(Fosc/2))*1000*64*PR2 = (1/(40000000/2))*1000*64*1250 IPC1bits.T2IP = 2; // Set Timer2 Interrupt Priority Level IFS0bits.T2IF = 0; // Clear Timer2 Interrupt Flag IEC0bits.T2IE = 1; // Enable Timer2 Interrupt //********************************************************************************************************* // Initialize Timer 3 - Red light //********************************************************************************************************* T3CON = 0x0020; // Stop 16-bit Timer3, 1:64(40MhzFosc) Prescale, Internal clock (Fosc/2) TMR3 = 0x00; // Clear timer register PR3 = 1250; // Load the period value, OCxRS <= PRx, 4ms period = (1/(Fosc/2))*1000*64*PR2 = (1/(40000000/2))*1000*64*1250 IPC2bits.T3IP = 2; // Set Timer3 Interrupt Priority Level IFS0bits.T3IF = 0; // Clear Timer3 Interrupt Flag IEC0bits.T3IE = 1; // Enable Timer3 Interrupt //********************************************************************************************************* // Initialize Output Compare 1 module in Continuous Pulse mode, OC1 controls IR LED switch //********************************************************************************************************* RPOR6bits.RP13R = 0b10010; // RP13/RB13 tied to OC1 (IR) OC1CONbits.OCM = 0b000; // Disable Output Compare 1 Module OC1R = 0; // Write the duty cycle for the first PWM pulse, 24=8MHzFosc(50us), 30=40MHzFosc(50us), 600=40MHzFosc(1ms) OC1RS = duty_cycle; // Write the duty cycle for the second PWM pulse, OCxRS <= PRx, 499=8MHzFosc(1ms), 623=40MHzFosc(1ms), 1246=40MHzFoc,2msPeriod, 4984=40MHzFoc,8msPeriod, 280=450us D/C@40MHzFoc,2msPeriod,switch OC1CONbits.OCTSEL = 0; // Select Timer 2 as output compare time base //********************************************************************************************************* // Initialize Output Compare 2 module in Continuous Pulse mode, OC2 controls Red LED switch //********************************************************************************************************* RPOR6bits.RP12R = 0b10011; // RP12/RB12 tied to OC2 (Red) OC2CONbits.OCM = 0b000; // Disable Output Compare 2 Module OC2R = 0; // Write the duty cycle for the first PWM pulse, 24=8MHzFosc, 30=40MHzFosc, 600=40MHzFosc(1ms) OC2RS = duty_cycle; // Write the duty cycle for the second PWM pulse, OCxRS <= PRx, 499=8MHzFosc(1ms), 623=40MHzFosc(1ms), 1246=40MHzFoc,2msPeriod, 4984=40MHzFoc,8msPeriod, 280=450us D/C@40MHzFoc,2msPeriod,switch OC2CONbits.OCTSEL = 1; // Select Timer 3 as output compare time base
复制代码- OC2 生成一个连续脉冲并发送至模拟开关,开启红光 LED
- DSC 开始执行 _T3Interrupt ISR(清单 2)
void __attribute__((__interrupt__, no_auto_psv)) _T3Interrupt(void) //Read Red DC & AC signals from AN0 & AN1{ int delay; unsigned char i; Read_ADC_Red = 1; CH0_ADRES_Red_sum = 0; CH1_ADRES_Red_sum = 0; for (delay=0; delay<200; delay++); //2000=delayed 256us before read ADC // LATBbits.LATB14 = 1; // for debugging for (i=0; i<oversampling_number; i++) { //Acquires Red-DC from Channel0 (AN0) AD1CHS0bits.CH0SA = 0x00; // Select AN0 AD1CON1bits.SAMP = 1; // Begin sampling while(!AD1CON1bits.DONE); // Waiting for ADC completed AD1CON1bits.DONE = 0; // Clear conversion done status bit CH0_ADRES_Red_sum = CH0_ADRES_Red_sum + ADC1BUF0; // Read ADC result //Acquires Red-AC from Channel1 (AN1) AD1CHS0bits.CH0SA = 0x01; // Select AN1 AD1CON1bits.SAMP = 1; // Begin sampling while(!AD1CON1bits.DONE); // Waiting for ADC completed AD1CON1bits.DONE = 0; // Clear conversion done status bit CH1_ADRES_Red_sum = CH1_ADRES_Red_sum + ADC1BUF0; // Read ADC result } CH0_ADRES_Red = CH0_ADRES_Red_sum / oversampling_number; FIR_input_Red[0] = CH1_ADRES_Red_sum / oversampling_number; #ifdef Sleep_Enabled if (CH0_ADRES_Red<=74 && CH1_ADRES_Red>=4000) //if spo2 probe is not connected, 74=60mV, 4000=3.2V { goto_sleep = 1; } else if (CH0_ADRES_Red > Finger_Present_Threshold) //if no finger present then goto sleep { goto_sleep = 1; } else #endif { // LATBbits.LATB14 = 0; // for debugging for (delay=0; delay<500; delay++); //1000=delayed 256us before read ADC // LATBbits.LATB14 = 1; // for debugging //Acquires Red-DC baseline from Channel0 (AN0) AD1CHS0bits.CH0SA = 0x00; // Select AN0 AD1CON1bits.SAMP = 1; // Begin sampling while(!AD1CON1bits.DONE); // Waiting for ADC completed AD1CON1bits.DONE = 0; // Clear conversion done status bit Baseline_ambient = ADC1BUF0; Baseline_Upper_Limit = Baseline_ambient + DCVppHigh; Baseline_Lower_Limit = Baseline_ambient + DCVppLow; Meter_State = Calibrate_Red(); } // LATBbits.LATB14 = 0; // for debugging OC2RS = duty_cycle; // Write Duty Cycle value for next PWM cycle IFS0bits.T3IF = 0; // Clear Timer3 Interrupt Flag }
复制代码检查了此探头状态之后,ISR 会对环境光水平进行采样,并使用此更新值相应地调整基线窗口限值。利用这些调整后的限值,函数 Calibrate_Red() 可增加或减少连接红光 LED 驱动器的 DAC 输出,使亮度保持在 Baseline_Lower_Limit 和 Baseline_Upper_Limit 之间。
T2 定时器中断服务例程使用相同的基本设计模式,但不包括对 sleep_enabled 和环境光水平测量值的检查。
配置了定时器、输出比较和 ISR 后,该示例软件的主例程将执行一个简短的初始化序列,并启动 Timer2 和 Timer3。此时,代码进入主循环,等待接收 ISR 处理的数据。待获得红光和红外光数据后,这些值将由一个数字有限脉冲响应 (FIR) 滤波器进行处理,最后调用例程来计算 SpO2 和心率(清单 3)。
//********** Enable OC1 & OC2 ouputs for IR & Red LED's on/off switch ********** OC2CONbits.OCM = 0b101; // Select the Output Compare 2 mode, Turn on Red LED T3CONbits.TON = 1; // Start Timer3 for (delay=0; delay<2200; delay++); OC1CONbits.OCM = 0b101; // Select the Output Compare 1 mode, Turn on IR LED T2CONbits.TON = 1; // Start Timer2 goto_sleep = 0; first_reading = 0; while (1) { if (goto_sleep) { [lines clipped] Sleep(); // Put MCU into sleep Nop(); } } //--------- Main State Machine starts here --------- if (RedReady && IRReady) { RedReady = 0; IRReady = 0; // LATBbits.LATB14 = 1; //for debugging FIR(1, &FIR_output_IR[0], &FIR_input_IR[0], &BandpassIRFilter); FIR(1, &FIR_output_Red[0], &FIR_input_Red[0], &BandpassRedFilter); CH1_ADRES_IR = FIR_output_IR[0]; CH1_ADRES_Red = FIR_output_Red[0]; [lines clipped] if (Detection_Done) { //Max & Min are all found. Calculate SpO2 & Pulse Rate SpO2_Calculation(); //calculate SpO2 Pulse_Rate_Calculation(); //calculate pulse rate [lines clipped] } /***************************************************************************** * Function Name: SpO2_Calculation() * Specification: Calculate the %SpO2 *****************************************************************************/ void SpO2_Calculation (void) { double Ratio_temp; IR_Vpp1 = fabs(IR_Max - IR_Min); Red_Vpp1 = fabs(Red_Max - Red_Min); IR_Vpp2 = fabs(IR_Max2 - IR_Min2); Red_Vpp2 = fabs(Red_Max2 - Red_Min2); IR_Vpp = (IR_Vpp1 + IR_Vpp2) / 2; Red_Vpp = (Red_Vpp1 + Red_Vpp2) / 2; IR_Vrms = IR_Vpp / sqrt(8); Red_Vrms = Red_Vpp / sqrt(8); // SpO2 = log10(Red_Vrms) / log10(IR_Vrms) * 100; // if (SpO2 > 100) // { // SpO2 = 100; // } // Using lookup table to calculate SpO2 Ratio = (Red_Vrms/CH0_ADRES_Red) / (IR_Vrms/CH0_ADRES_IR);
复制代码SpO2 设计优化选项
虽然本文介绍的设计为低成本脉搏血氧仪提供了一个有效的解决方案,但其他器件可能会提供进一步的优化。例如,开发人员可以使用 Microchip Technology 的 DSPIC33CK64MP102 DSC 集成的运算放大器,则无需添加外部 MCP6002 双运算放大器器件。
然而,在实现这种经过修改的脉搏血氧仪设计时,由于 DSC 有所不同,开发人员需要重写以上软件包的一些关键部分。
例如,不同于 DSPIC33FJ128GP802 DSC 中的 Timer2/Timer3 功能,DSPIC33CK64MP102 DSC 提供了一组多用途定时器模块,因此需要开发人员提供自己的解决方案,以实现本文清单中所述的某些功能。即便如此,工作原理依然不变,开发人员至少可以使用 Microchip Technology 示例软件包所示的设计模式来指导自己的定制软件设计。
总结
血氧饱和度测量可提供重要的呼吸功能指标,在新冠肺炎疫情期间已成为重要的健康管理工具。使用简单的光学方法,脉搏血氧仪提供了可靠的外周血氧饱和度 (SpO2) 估计值,可满足疫情期间对平价健康监测解决方案的特别需求。
如上所述,DSC 与一些基本元器件相结合,为实现低成本的脉搏血氧仪提供了有效的硬件基础,能够提供可靠的 SpO2 测量值,从而针对是否需要寻求进一步医疗护理以诊治不断加重的新冠肺炎感染,为用户提供必要信息。