简介:面向血氧饱和度(SpO2)与脉搏波监测等生物医疗场景,TI AFE4400模拟前端的驱动源码包适合嵌入式开发者、医疗电子工程师及可穿戴设备研发人员直接调用,能够快速完成芯片初始化并开展光电信号采集。rar压缩包内共2个文件,包含一个C源文件和一个头文件,整体仅4KB;C文件中针对AFE4400的寄存器配置、采样率与增益调整、数据读取及信号处理接口做了实现,并留有脉搏波分析算法扩展空间,头文件则集中声明函数原型、常量和数据结构,保证代码模块化与可集成性。目前已有1018人学习下载,特别适合在健康监护、运动监测等便携设备中落地血氧测量功能。对需要从底层驱动入手理解AFE4400工作流程、缩短原型验证周期并优化低功耗采集逻辑的开发者,这套源码具有直接的参考价值。
1. 接上指夹式探头就能出波形:AFE4400血氧模拟前端的信号链路与选型逻辑
在血氧模块的开发中,最耗时间的环节通常不是算法,而是模拟前端。探头输出的光电流只有微安级别,环境光、工频干扰和运动伪影全部叠加在这个小信号上,直接用 MCU 的 ADC 采样,波形很难稳定。AFE4400 把 LED 驱动、跨阻放大器、环境光消除和高分辨率 ADC 集成在一起,MCU 只需要通过 SPI 读写寄存器,就能拿到红光和红外两个通道的原始 PPG(光电容积脉搏波)数据。AFE44x0.c 和 AFE44x0.h 正是这套方案与上层算法之间的桥梁,前者完成初始化、配置和数据采集,后者给出寄存器定义与函数声明。对于做便携血氧仪、健康手环,或者参加 2024 年 TI 杯模拟电子系统专题赛医疗信号采集方向的学生,这套驱动源码都可以直接作为开发起点。
2. SPI 寄存器映射与 AFE44x0 驱动初始化
2.1 命令字格式与读写函数实现
AFE4400 以 4 线 SPI 从机方式与 MCU 通信。寄存器地址占 6 位,命令字节的最高两位用于区分读写方向。AFE44x0.c 里的读写函数虽然不长,但命令字的拼装方式如果不注意,很容易出现“写进去读不出来”的问题。
读寄存器的典型实现如下:
uint32_t AFE44x0_ReadReg(uint8_t reg) { uint8_t tx[4] = {0}; uint8_t rx[4] = {0}; uint32_t value = 0; tx[0] = reg & 0x3F; // bit7 和 bit6 都为 0,表示读操作 tx[1] = tx[2] = tx[3] = 0; // 读命令时数据线写入任意值 AFE44x0_CS_LOW(); SPI_TxRxBlock(tx, rx, 4); // 同时收发 4 个字节 AFE44x0_CS_HIGH(); value = ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | (uint32_t)rx[3]; return value; }写寄存器则是把命令字节的 bit6 置 1,后三个字节放置 24 位寄存器数据:
void AFE44x0_WriteReg(uint8_t reg, uint32_t data) { uint8_t tx[4]; tx[0] = (reg & 0x3F) | 0x40; // bit6 置 1,表示写操作 tx[1] = (uint8_t)((data >> 16) & 0xFF); tx[2] = (uint8_t)((data >> 8) & 0xFF); tx[3] = (uint8_t)(data & 0xFF); AFE44x0_CS_LOW(); SPI_TxBlock(tx, 4); AFE44x0_CS_HIGH(); }逻辑说明:AFE4400 的 24 位寄存器数据通过 32 位 SPI 帧传输,第一个字节是命令字,后三个字节才是有效数据。读操作时,芯片在收到完整命令字之后才把寄存器内容放到 MISO 线上,因此rx[0]只是一个时钟占位,真实数据从rx[1]开始拼接。如果直接拼接rx[0],读出的数值会整体左移 8 位,表现为寄存器值呈 256 倍放大。
参数说明:reg的取值范围是 0x00 到 0x3F;data虽然以 32 位整数传入,但驱动内部只保留低 24 位。SPI 速率建议控制在 4MHz 以下,过高的 SCLK 会导致上升沿采样不稳定,尤其是在飞线连接探头和数据线较长的情况下。片选信号必须覆盖完整的 4 字节传输周期,不能按字节分别拉低拉高,否则芯片会把每个字节误判为一条新命令。
2.2 控制寄存器的初始化顺序与参数设定
AFE4400 的寄存器分组可以粗略分成四类:控制类、LED 驱动类、时序类和测量结果类。TI 的官方驱动在初始化时并不建议一上来就开启所有功能,而是先让芯片进入待机状态,把时钟、LED 电流和时序配好之后,再切回测量模式。
下面这段初始化序列是我基于 AFE44x0.c 的常见做法整理出的顺序,具体数值需要对照硬件版本调整:
void AFE44x0_Init(void) { /* 1. 软复位,让寄存器回到默认状态 */ AFE44x0_WriteReg(0x00, 0x000001); DelayMs(2); /* 2. 关闭所有测量通道,避免配置过程中产生无效数据 */ AFE44x0_WriteReg(0x00, 0x000000); AFE44x0_WriteReg(0x02, 0x000000); /* 3. 选择内部振荡器,使能红光和红外两个 LED 通道 */ AFE44x0_WriteReg(0x02, 0x000855); /* 4. 配置 LED 电流,红光和红外先按同一档位 */ AFE44x0_WriteReg(0x0F, 0x008080); /* LED_CNTRL1:红光电流设定 */ AFE44x0_WriteReg(0x10, 0x008080); /* LED_CNTRL2:红外电流设定 */ /* 5. 配置采样率,PRPCOUNT 决定每秒测量次数 */ AFE44x0_WriteReg(0x1C, 39999); /* 目标约 100Hz,按 4MHz 时钟估算 */ /* 6. 打开片上平均,降低单次采样随机噪声 */ AFE44x0_WriteReg(0x1D, 0x000004); /* N_AVG:连续 4 次平均 */ /* 7. 切回正常测量模式 */ AFE44x0_WriteReg(0x00, 0x000000); }逻辑说明:第 2 步把 CONTROL2 清零是为了防止 LED 在寄存器还没有配置完时提前点亮,否则红光和红外的电流比例不一致,直接影响后面算 SpO2 的 R 值。第 5 步中的 PRPCOUNT 是测量周期计数值,实际计算式在数据手册中给的是内部时钟、PRPCOUNT 和 N_AVG 三者共同决定最终采样率,因此在修改采样率时,这几处寄存器要联动调整,不能只改一个。
参数说明:LED 电流寄存器的高字节和低字节分别控制红光与红外通道的电流档位,0x008080表示两路电流一致。如果使用反射式探头,红光电流通常要调大 30% 到 50%,因为红光在皮肤组织中的穿透深度与红外相差较大。增益和电流参数修改后,必须重新观察波形,不能照抄开发板的值。
2.3 寄存器地址速查与常见初始化误区
为了不再频繁翻数据手册,我把初始化阶段最常用的寄存器整理成下表:
| 地址 | 寄存器名 | 功能 | 初始化时的建议 |
|---|---|---|---|
| 0x00 | CONTROL0 | 芯片工作模式与软复位 | 先软复位,配置完成后切回测量模式 |
| 0x02 | CONTROL2 | LED 输出使能与时序 | 上电后先清零,最后再使能 |
| 0x0F | LED_CNTRL1 | 红光 LED 电流 | 按探头类型设定,建议初始值偏低 |
| 0x10 | LED_CNTRL2 | 红外 LED 电流 | 保持与红光通道同档位 |
| 0x1C | PRPCOUNT | 采样周期控制 | 决定 PRF,直接影响脉搏波分辨率 |
| 0x1D | N_AVG | 片上平均次数 | 过高会降低有效采样率 |
| 0x22 | DIAG | 诊断模式寄存器 | 出现异常时优先检查 |
初始化时最容易犯的一个错误是在芯片还没有离开复位状态时就写入时序寄存器。另一个常见问题是 SPI 命令字拼装方向出错,导致地址不对齐,初始化代码执行完后再去读寄存器,返回的一直是 0xFFFFFF。遇到这种情况,先用逻辑分析仪抓 MISO 返回数据,看第一个有效数据字节是否出现在第二个 SPI 时钟段之后,这是定位读写问题的捷径。
3. 从原始 ADC 数据到 SpO2:滤波、比值计算与查表
3.1 读取红光和红外通道的原始采样值
AFE4400 完成一次测量后,红光和红外的 PPG 原始数据分别存放在 LED1_VALUE、LED2_VALUE 寄存器中。读取代码只需要调用前面实现的读寄存器函数:
#define AFE44x0_REG_LED1_VALUE_MSB 0x0C /* 红光结果,按 24 位读取 */ #define AFE44x0_REG_LED2_VALUE_MSB 0x0D /* 红外结果 */ void AFE44x0_GetRawSample(uint32_t *red, uint32_t *ir) { *red = AFE44x0_ReadReg(0x0C); /* 红光通道原始值 */ *ir = AFE44x0_ReadReg(0x0D); /* 红外通道原始值 */ }逻辑说明:红光和红外通道的 ADC 结果是 24 位无符号数,数值大小与光照强度、探头透射率以及跨阻放大器增益直接相关。在暗环境下读数偏低,在强光下读数可能接近满量程。如果读出的红光值长期低于 1000 而红外值接近满量程,说明红光 LED 电流明显不足,或者探头这两个波长的光强比例本身就不合适。
参数说明:这里的 0x0C 和 0x0D 是 AFE4400 中实际存储测量结果的寄存器地址。关于这部分,AfE44x0.h 中通常会重新定义为宏,建议在上层代码里统一使用宏名,不要直接写地址。传感器数据在读取过程中要保持片选信号稳定,读一半时被中断,可能会导致寄存器地址自增位置错乱。
3.2 脉搏波预处理:去直流和带通滤波
计算 SpO2 前,先要把 PPG 信号中的直流分量(DC)和交流分量(AC)分开。DC 分量是组织、静脉血和皮肤对光的吸收,AC 分量才是动脉搏动引起的容积变化。对连续采集到的红光和红外样本,直流分量一般用几十个采样点滑动平均得到:
#define FILTER_WINDOW 32 static uint32_t g_red_buf[FILTER_WINDOW]; static uint32_t g_ir_buf[FILTER_WINDOW]; static uint8_t g_buf_idx; void AFE44x0_FilterDC(uint32_t red, uint32_t ir, uint32_t *red_dc, uint32_t *ir_dc) { uint32_t sum_red = 0, sum_ir = 0; int i; g_red_buf[g_buf_idx] = red; g_ir_buf[g_buf_idx] = ir; g_buf_idx = (g_buf_idx + 1) % FILTER_WINDOW; for (i = 0; i < FILTER_WINDOW; i++) { sum_red += g_red_buf[i]; sum_ir += g_ir_buf[i]; } *red_dc = sum_red / FILTER_WINDOW; *ir_dc = sum_ir / FILTER_WINDOW; }逻辑说明:这个滑动平均实现的是低通滤波器效果,窗口长度决定了最低保留频率。32 个点对应采样率 100Hz 时,截止频率大约是 1.5Hz,可以保留 0.5Hz 到 1.5Hz 的脉搏主波,同时把呼吸引起的低频基线漂移压下去。窗口选得太短,DC 分量会跟着脉搏跳动;选得太长,DC 分量的更新滞后,运动时 AC/DC 比值会失真。
参数说明:窗口长度需要根据实际采样率缩放。如果 PRPCOUNT 设置成 50Hz 采样率,窗口就要改成 16 点,否则滤波器的截止频率会偏移。得到 DC 分量之后,用red - *red_dc就是 AC 分量,这部分会作为后续峰值检测的输入。
3.3 计算 R 值与 SpO2 查表实现
SpO2 的核心计算量是 R 值,它定义成归一化后的红光和红外 AC/DC 比值之比:
R = (AC_red / DC_red) / (AC_ir / DC_ir)实际代码中先要检测一段完整脉搏波里的峰谷幅值,AC 分量取波峰和波谷差值的峰值,DC 分量则取同一时间段内的平均值:
float AFE44x0_ComputeR(float ac_red, float dc_red, float ac_ir, float dc_ir) { float ratio = 0.0f; if ((dc_red < 50.0f) || (dc_ir < 50.0f)) { return 0.0f; /* DC 过低,信号通道异常 */ } if ((ac_red / dc_red) > 0.1f) { return 5.0f; /* AC 过大,视为运动伪影 */ } ratio = (ac_red / dc_red) / (ac_ir / dc_ir); return ratio; }逻辑说明:ac_red / dc_red其实是调制深度,正常情况下脉搏波的调制深度在 1% 到 10% 之间。红光通道的调制深度显著高于红外通道。当运动伪影导致红光的 AC/DC 超过 10% 时,说明这段数据已经不适合参与计算,直接返回 5.0 这个异常值让上层过滤掉。
参数说明:dc_red低于 50 这个阈值针对的是 24 位 ADC 的典型量程。如果换用了不同增益配置,这个阈值也要相应调整。R 值正常范围在 0.4 到 1.0 之间,超出该范围的数据都应该丢弃,而不是直接参与 SpO2 拟合。
得到 R 值后,最通用的做法是用经验公式或者查表:
static const float spo2_table[] = { 100.0f, 98.0f, 96.0f, 94.0f, 92.0f, 90.0f, 85.0f, 80.0f }; uint8_t AFE44x0_CalcSpO2(float R) { float spo2; if (R <= 0.4f) spo2 = 100.0f; else if (R >= 1.0f) spo2 = 80.0f; else { /* 线性拟合区间的近似公式 */ spo2 = 110.0f - 25.0f * R; } if (spo2 > 100.0f) spo2 = 100.0f; if (spo2 < 80.0f) spo2 = 80.0f; return (uint8_t)spo2; }逻辑说明:110 - 25 * R是最早 Masimo 和 Nellcor 论文里常用的近似线性关系,在 R 值 0.4 到 1.0 区间内误差可以接受。不同血氧探头和不同肤色人群的散射特性不同,实际产品需要用血氧模拟器标定出自己的拟合曲线,把理想化的线性公式替换成多点插值表。
参数说明:表中每一点的数值也应该来自实测校准,不能直接照搬文献。特别是在血氧值低于 90% 时,线性公式误差会变大,此时查表法明显优于公式计算。在实际开发过程中,我先用这个近似公式把整条信号链路跑通,再做标定,这是效率比较高的分工方式。
3.4 运动伪影的简单抑制方法
运动伪影一直是血氧测量的难点,但不能一上来就上复杂的自适应滤波。先用差分阈值剔除明显异常点,是成本最低的一层保护:
#define MAX_PPG_DELTA 3000 /* 相邻样点最大差值 */ uint8_t AFE44x0_CheckArtifact(uint32_t red_current, uint32_t red_prev) { int32_t delta; delta = (int32_t)red_current - (int32_t)red_prev; if (delta < 0) delta = -delta; return (delta > MAX_PPG_DELTA) ? 1 : 0; }逻辑说明:人体脉搏波在 100Hz 采样率下,相邻两个点的变化幅度是有限度的。当手指抖动或者探头松脱时,光路突变会让相邻样点出现几千甚至上万计的跳变,用一个阈值就能拦下这类明显异常。它拦截不了慢速的伪影,但对高频抖动有比较直接的过滤效果。
参数说明:阈值MAX_PPG_DELTA的选取依赖增益配置,增益越高,阈值越大。建议先用串口打印正常呼吸时的最大差值,再乘以 2 到 3 作为安全阈值。这样不会误滤掉真实波形,也能挡住大部分瞬态干扰。
4. TI 的 DC 诊断流程与联调排错技巧
4.1 用 DC 诊断流程快速判断模拟通路是否正常
TI FAE 在支持血氧项目时,第一件事往往不是看算法,而是带着开发者做一遍 DC 诊断。AFE4400 的 0x22 是诊断寄存器(DIAG),它可以把内部测试电流注入到跨阻放大器输入端,通过读取 ADC 输出判断模拟通路是否有断裂或者短路:
void AFE44x0_DCDiagnostic(void) { uint32_t diag_val; /* 进入诊断模式,注入直流测试电流 */ AFE44x0_WriteReg(0x22, 0x000001); DelayMs(1); diag_val = AFE44x0_ReadReg(0x22); if ((diag_val & 0xFF) > 0x00) { /* 模拟通路存在异常,优先检查光电二极管焊接 */ } /* 退出诊断模式 */ AFE44x0_WriteReg(0x22, 0x000000); }逻辑说明:TI 的 DC 诊断流程核心思路是不依赖探头,用芯片内部的直流源模拟光电二极管信号,进而确认 AFE4400 的跨阻放大器和 ADC 链路是否完好。如果诊断读数与正常开发板偏差超过 5%,就要怀疑是虚焊、PCB 漏电或者探头连接问题,而不是继续调算法。
参数说明:诊断模式的注入电流档位由 0x22 寄存器的控制位决定,不同档位对应不同强度的直流信号。验证完诊断点之后,再切回正常模式读取 LED1_VALUE 和 LED2_VALUE,两次读数结合起来就能把故障定位在“芯片内部”还是“外部探头”。
4.2 常见异常现象与调整路径
我整理了联调中比较高频的几个问题,对应的处理方式如下表:
| 现象 | 可能原因 | 优先处理路径 |
|---|---|---|
| 红光通道读数长期为 0 | LED 电流未使能 | 检查 CONTROL2 寄存器 |
| 红色和红外通道都饱和 | 增益过高或探头遮挡 | 降低 GAIN_SLOT1/2 |
| 波形幅值波动大 | 探头与皮肤贴合压力不稳 | 检查探头机械结构 |
| 算出的 SpO2 恒为 99 | 红外通道 AC 未参与计算 | 确认采样时隙配置 |
遇到饱和问题时,先降低跨阻增益,不要先调 LED 电流。增益降低后信号噪声会变大,此时可以补充片上平均次数。调整的过程中每次只改一个参数,等波形稳定后再改下一个。
4.3 寄存器参数沉淀与量产前校验
完成联调后,把最终确定的一组寄存器值保存为一个配置结构体,上电后一次性写入。量产前建议增加一个寄存器回读校验步骤,把写入值和回读值逐位比较,避免因为 SPI 时序余量不足导致个别设备配置丢失。回读校验函数可以直接复用第 2 章的AFE44x0_ReadReg,对比通过之后再进入测量模式。这样既能保留调试阶段的调优结果,也能在产线上快速筛掉有问题的芯片和焊接点。
本文还有配套的精品资源,点击获取