简介:这是一份基于STM8S105微控制器与AFE4300模拟前端的完整工程源码,用于人体成分分析场景中的阻抗测量,面向嵌入式医疗电子开发者及相关专业学习者。工程采用虚拟SPI方式与AFE4300通信,涵盖初始化、测量时序、数据读取等关键环节,可直接作为体脂秤、健康检测设备开发的参考实现。压缩包共278个文件,大小约5.84MB,核心为C语言源文件与头文件,同时包含编译依赖、链接配置、目标文件及HEX烧录文件,便于在IAR环境下直接编译验证。目前已有382人学习下载。通过该资源,读者可以掌握STM8驱动AFE4300的完整软件流程,理解人体阻抗测量中的信号采集与数据处理思路,并借助附带的HEX文件快速开展实测。此外,压缩包内还包含工程文件、浏览信息与烧录脚本,目录结构清晰,便于查找不同模块的源码。
1. STM8S105、AFE4300 与 VirtualSPI 组成的人体成分阻抗测量链路
把一颗 AFE4300 接到 STM8S105 的任意 GPIO 上,用软件模拟 SPI 打通阻抗测量链路,是人体成分秤方案里常见的架构选择。STM8S105 负责 UI、按键和体脂算法,AFE4300 负责从电极发出交流激励并完成模拟解调,两者之间靠一组 4 线制 SPI 通信。
不用硬件 SPI 的原因通常是片内 SPI 已被 Flash 或屏占用,或者改板后只剩零星 IO。标题里的 ALLtestOK 说明这条链路已经通过全流程验证,读者真正需要复制的是这条验证路径:先通信、再标定、后读数。
2. AFE4300 阻抗测量原理与人体成分计算的算法入口
人体成分测量不是直接把电极贴上去读一个电阻值那么简单。AFE4300 这条链路上发生的事情是:芯片向人体注入小幅交流电流,同时测量电压,最后以复阻抗的形式把结果交给你。复阻抗的实部来自体液电阻,虚部来自细胞膜电容,两者合起来才能推算体脂率、水分率和肌肉量。理解这层模型,后面配置寄存器时才不会把频率和增益参数调反。
2.1 BIA 生物电阻抗:人体组织在交流电流下的等效电路
BIA(生物电阻抗分析)的基本前提是:人体组织对交流电流的响应是频率相关的。低频时细胞膜容抗很大,电流基本只走细胞外液;高频时容抗变小,电流可以穿膜进入细胞内液。经典等效电路是细胞外液电阻 Re 与「细胞膜电容 Cm 串联细胞内液电阻 Ri」并联,整体阻抗是一个复数,极坐标形式表现为幅值和相位角两个量。直角坐标下写作 R = mag·cos φ 与 X = mag·sin φ,其中 X 为负值表示容性。
体脂率本身不是测出来的,是用回归公式算出来的。通用做法是先算总体水 TBW = a·H²/R + b·W + c,H 是身高,W 是体重,a、b、c 是特定人群的回归系数;再用瘦体重含水率约 73% 的假设反推体脂率。不同厂商的算法差异几乎全在系数和测量条件上,这也是同一台样机换一套回归系数就能出不同结果的原因。单频体脂秤一般固定在 50 kHz 附近,因为这个频点对回归公式最稳定;多频机型会叠加低频和高频档位,把细胞外液和总水量分开估算。
2.2 四电极接法:体脂秤重复性的底线
二电极接法让激励和采样共用两个电极,接触阻抗直接串联进测量回路;四电极接法用 I+/I- 注入电流,V+/V- 独立采样电压。电压测量端几乎没有电流,接触压降近似为零,电极表面氧化、角质层差异和湿度扰动被大幅抑制。家用体脂秤的电极长期暴露在空气中,二电极方案读数一天一个样,四电极是保证复测一致性的底线,AFE4300 的四线引脚就是按这个架构设计的。
| 接法 | 电流与采样 | 接触阻抗影响 | 走线 | 适用场景 |
|---|---|---|---|---|
| 二电极 | 同一对电极既走电流又测电压 | 直接进入结果 | 2 根 | 便携评测 |
| 四电极 | 外侧激励、内侧采样 | 几乎隔离在电压回路外 | 4 根 | 体脂秤、人体成分分析仪 |
2.3 AFE4300 片内信号链:激励、解调、ADC
2.3.1 交流激励与电流幅度选择
AFE4300 用内部 DAC 生成正弦激励,而不是直流,原因有两个:直流会使电极极化并产生不确定偏置;只有交流才能激发出电容分量。激励幅度在消费类人体成分设备里通常控制在毫安以下,配合 50 kHz 频率人体基本无感。幅度过小信噪比差,过大会在电极接触面产生非线性,具体取值要结合电极面积和应用规范,工程上一般从中间档位起步,看重复性再决定加减。
2.3.2 正交解调:读 I/Q 而不是拿 ADC 硬算
AFE4300 的接收通道不是把正弦波采样后交给 MCU 做 DFT,而是在片内把接收信号与 0° 和 90° 两路参考信号相乘,低通滤波后得到 I 和 Q 两个准直流分量,再由片内 ADC 量化。MCU 不用跑复数运算,读两个寄存器就能还原幅值和相位。
/* 码值换算:k_mag 来自标准电阻标定,把解调器到 ADC 的增益误差一次吸收 */ float mag = k_mag * sqrtf((float)i * (float)i + (float)q * (float)q); float phase = atan2f((float)q, (float)i); float R = mag * cosf(phase); /* 等效电阻 */ float X = mag * sinf(phase); /* 容抗,通常为负 */参数说明:k_mag 是增益系数,它在标定阶段由已知电阻反推。纯电阻负载下 phase 应接近 0,如果明显偏离,说明线缆和电极引入了附加相移,需要在标定阶段补偿。验证这条换算最直接的办法是换几枚不同阻值的标准电阻,看算出的 R 是否线性。
3. STM8S105 的 VirtualSPI 实现:引脚初始化、时序与读写帧
3.1 硬件 SPI 被占用时,VirtualSPI 是常见解法
STM8S105 只有一个硬件 SPI 外设,当它被板上的 Flash、显示屏或射频模块占用时,AFE4300 只能另起炉灶。AFE4300 的 SPI 时钟不需要跑满,GPIO 翻转的速率完全够用,因此用软件逐 bit 搬数据就成了惯例。这种做法的额外好处是引脚任意,改板时 SCK/MOSI/MISO/CS 可以挪到任何空闲 IO,不受硬件 SPI 固定引脚的约束。
代价是时序要自己保证。GPIO 翻转如果被中断打断,波形会出现毛刺,我的做法是整帧放进关中断的临界区。24 bit 一帧耗时几十微秒量级,对 50 kHz 激励的测量节奏几乎没有影响,关中断期间 UART 丢一个字节,也比读出坏数据好处理。
3.2 引脚规划与端口寄存器初始化
3.2.1 引脚分配和 GPIO 配置
下面是一组常见分配:PD2=SCK、PD3=MOSI、PD4=MISO、PD5=CS,对应关系如下。
| 信号 | 引脚 | 方向 | 电平约定 |
|---|---|---|---|
| SCK | PD2 | 输出 | 空闲低电平,上升沿采样 |
| MOSI | PD3 | 输出 | 主发数据,CS 低期间有效 |
| MISO | PD4 | 输入 | 从机应答,三态时需上拉 |
| CS | PD5 | 输出 | 低有效,包住完整 24 bit 帧 |
初始化直接操作 STM8S105 的端口寄存器,不调用库函数,方便在 IAR 和 SDCC 之间移植。
/* VirtualSPI 引脚映射 */ #define VSPI_SCK (1 << 2) #define VSPI_MOSI (1 << 3) #define VSPI_MISO (1 << 4) #define VSPI_CS (1 << 5) void vspi_init(void) { PD_DDR |= VSPI_SCK | VSPI_MOSI | VSPI_CS; /* 三个输出 */ PD_DDR &= ~VSPI_MISO; /* MISO 输入 */ PD_CR1 |= VSPI_SCK | VSPI_MOSI | VSPI_CS; /* 推挽输出 */ PD_CR1 &= ~VSPI_MISO; /* MISO 浮空输入 */ PD_CR2 &= ~(VSPI_SCK | VSPI_MOSI); /* 限制斜率减 EMI */ PD_ODR |= VSPI_CS; /* CS 空闲高 */ PD_ODR &= ~VSPI_SCK; /* SCK 空闲低 */ }逻辑说明:PD_DDR 控制方向,PD_CR1 在输出模式下选推挽或开漏、在输入模式下选浮空或上拉;PD_CR2 控制输出斜率。MISO 配成浮空输入时,如果走线较长,建议硬件上加 10k 上拉;不打算加电阻就把 PD_CR1 对应 bit 置 1 启用内部上拉,但内部上拉阻值偏高,三态翻转期间沿速率会变慢。
3.2.2 单字节搬移函数
static uint8_t vspi_shift_byte(uint8_t tx) { uint8_t rx = 0; for (uint8_t i = 0; i < 8; i++) { if (tx & 0x80) PD_ODR |= VSPI_MOSI; else PD_ODR &= ~VSPI_MOSI; tx <<= 1; __no_operation(); /* 数据建立时间 */ PD_ODR |= VSPI_SCK; /* SCK 上升沿 */ __no_operation(); /* 保持时间 */ rx <<= 1; if (PD_IDR & VSPI_MISO) rx |= 1; /* 上升沿采样 MISO */ PD_ODR &= ~VSPI_SCK; /* SCK 拉低,准备下一 bit */ } return rx; }参数说明:这段代码实现 SPI Mode 0,即 CPOL=0、CPHA=0,SCK 空闲为低、上升沿采样。__no_operation() 是空指令,IAR 环境直接写,SDCC 下写成 __asm("nop"),两个空指令提供数百纳秒的建立和保持时间。如果读回数据全 0 或全 1,第一优先把采样点移到下降沿,也就是把读 MISO 的语句挪到 SCK 拉低之后;仍不对再改 SCK 空闲电平,这是 VirtualSPI 排错的标准顺序。
3.3 24 位帧封装:读、写和占位时钟
AFE4300 的 SPI 操作按 24 bit 为一帧:第 1 字节是命令(读写标志加寄存器地址),后 2 字节是数据。CS 在整个帧期间保持低电平,帧结束后拉高。具体命令位的定义以数据手册为准,但「24 位帧、读写标志在最高位、数据高字节在前」是这类芯片最常见的约定。
void afe4300_write_reg(uint8_t reg, uint16_t value) { PD_ODR &= ~VSPI_CS; vspi_shift_byte(reg & 0x7F); /* 写命令,最高位为 0 */ vspi_shift_byte((uint8_t)(value >> 8)); /* 高字节在前 */ vspi_shift_byte((uint8_t)(value & 0xFF)); PD_ODR |= VSPI_CS; } uint16_t afe4300_read_reg(uint8_t reg) { uint16_t value = 0; PD_ODR &= ~VSPI_CS; vspi_shift_byte((reg & 0x7F) | 0x80); /* 读命令,最高位为 1 */ value = (uint16_t)vspi_shift_byte(0) << 8; value |= vspi_shift_byte(0); PD_ODR |= VSPI_CS; return value; }注意:读操作命令字节之后的两个 0x00 只是占位时钟,AFE4300 会在数据阶段把寄存器值驱动到 MISO,真正有用的是 vspi_shift_byte 的返回值。
如果读回值和写入值对不上,先怀疑命令字节里的地址位宽而不是数据位序;地址位错位会表现为「写能写、读不对」,或者读回的是相邻寄存器。帧结束后 CS 高电平保持一个短延时再开始下一帧,给器件留出内部退出时间。
4. AFE4300 寄存器配置与阻抗读取的标定流程
4.1 上电时序:先自检再配置
AFE4300 上电后不要立刻写寄存器。常见顺序是:电源稳定 → 延时 10 ms 量级 → 读取设备标识寄存器确认 SPI 链路已通 → 再进入配置流程。标识寄存器的值和手册不一致时,先回头查 VirtualSPI 的时序和硬件连接,不要继续往下配。
链路自检的实用做法是找一个配置后不影响模拟输出的寄存器,写入 0x5A5A 再读回,连续三次一致才认为链路可靠。注意避开激励使能、通道切换这类一写就动模拟状态的寄存器,否则自检过程会把测量链路带进不可控状态。
4.2 激励频率、电流幅度、增益与均值次数的选择
以下是体脂秤场景的典型配置项。具体位的定义以 AFE4300 数据手册为准,这里关注的是参数方向和量级。
| 配置项 | 典型值 | 对结果的影响 |
|---|---|---|
| 激励频率 | 50 kHz 单频;多频另加 5 kHz、250 kHz 附近档位 | 低频偏细胞外液,高频含细胞内液信息 |
| 激励电流幅度 | 数百 µA 量级 | 过小噪声大,过大致电极非线性 |
| 测量模式 | 四电极 | 决定 I+/I- 与 V+/V- 的接入方式 |
| PGA 增益 | 匹配 200 Ω~2 kΩ 人体段阻抗 | 增益高易饱和,低则分辨率不足 |
| ADC 均值次数 | 16~32 次 | 次数多噪声低,但单次测量时间变长 |
逻辑说明:50 kHz 是单频体脂秤的行业默认频点,综合考虑了细胞内外的电流分布和回归公式的稳定性。多频机型增加低频估计细胞外液、高频看总水量,再用几个频点的阻抗差推算相位角变化趋势。均值次数是噪声和响应速度的折中,体脂秤一次完整测量要求在 1~2 秒内结束,16 次均值在这个约束下性价比最高。
4.3 一次完整测量的代码骨架
配置完成后,一次测量的流程是:选通道 → 启动转换 → 轮询数据就绪位 → 读 I/Q。I/Q 寄存器是解调器的正交输出,读取后按补码解释为有符号数。
#define REG_BCM_MODE 0x10 /* 示例寄存器地址,以厂家头文件为准 */ #define REG_BCM_START 0x11 #define REG_BCM_STAT 0x12 #define REG_BCM_I 0x13 #define REG_BCM_Q 0x14 typedef struct { int16_t i; int16_t q; } bcm_vector_t; bcm_vector_t bcm_read_once(uint8_t freq_cfg) { bcm_vector_t v; uint16_t guard = 0; afe4300_write_reg(REG_BCM_MODE, freq_cfg); /* 切频点 */ afe4300_write_reg(REG_BCM_START, 0x01); /* 启动转换 */ while (!(afe4300_read_reg(REG_BCM_STAT) & 0x01)) { if (++guard > 1000) break; /* 超时保护 */ } v.i = (int16_t)afe4300_read_reg(REG_BCM_I); v.q = (int16_t)afe4300_read_reg(REG_BCM_Q); return v; }参数说明:切频点必须在启动转换之前完成,切换后最好留一小段稳定时间,否则解调器输出的 I/Q 会混入上一频点的残余。轮询循环必须加超时,均值次数配得很大或激励未真正使能时,状态位可能长时间不置位,没有超时保护的代码在产线调试时会死等。读回 I/Q 后还要检查符号扩展,寄存器位宽小于 16 位时,高位的符号位要按实际位宽手工扩展。
4.4 用标准电阻做两点标定
标定的目标是把 I/Q 码值映射成欧姆。操作步骤:
- 准备 100 Ω 和 1 kΩ(或 200 Ω 与 500 Ω)精度 0.1% 的电阻,四线接入:I+ 与 V+ 在电阻同一端就近分开走线,I- 与 V- 在另一端同样处理。
- 在每个工作频率点分别记录两枚电阻对应的幅值码值,连同理论阻抗存成一组数据。
- 用线性拟合 R = a × mag + b 求出系数,两个点直接解,三到五个点可以看拟合残差,判断信号链是否线性。
float mag1 = measure_mag(100.0f); /* 100 Ω 对应的幅值码值 */ float mag2 = measure_mag(1000.0f); /* 1 kΩ 对应的幅值码值 */ float a = (1000.0f - 100.0f) / (mag2 - mag1); float b = 100.0f - a * mag1;参数说明:a 是比例系数,b 是零点偏移。b 的来源主要是接收通道的直流失调和激励泄漏,如果 b 随时间漂移,优先查解调器后的低通滤波是否彻底,以及 V+/V- 采样线是否被 I+/I- 激励线串扰。纯电阻标定覆盖不了相位,校虚部要换 RC 并联网络,例如 470 Ω 并 100 nF,在 50 kHz 下的理论阻抗可以解析计算,用来核对读回的相位角。
5. ALLtestOK 的最后一公里:VirtualSPI 时序验证、重复性测试与电极细节
5.1 示波器三看
VirtualSPI 的时序验证要靠抓波形,不要靠肉眼数延时。第一看 CS 是否干净包住 24 个 SCK,CS 提前拉高或 SCK 多一个毛刺都会导致错帧;第二看 SCK 空闲电平是否稳定,MISO 线上有没有振铃;第三看读操作时 MISO 是否只在数据阶段输出,三态期间浮空会让读回的低字节随机跳。抓读操作时用 CS 下降沿做示波器触发,能稳定看到完整的命令和数据两段。
5.2 标准电阻重复性测试
同一枚电阻连续采 30 次,算标准差和变异系数 CV。体脂秤对阻抗重复性的接受线一般在 1% 以内,做到 0.5% 才比较放心。CV 超标的排查顺序:先加大均值次数,再查激励幅度档位,最后怀疑电极接触和线缆屏蔽。注意重复性和准确性是两个指标,重复性看同一电阻多次读数,准确性看标定后的欧姆值和万用表误差,两者要分开测。
/* 重复性:同一通道、同一电阻连续 30 次 */ float mean = 0, var = 0; for (int i = 0; i < 30; i++) { float z = measure_impedance(0); mean += z; } mean /= 30; for (int i = 0; i < 30; i++) { float d = measure_impedance(0) - mean; var += d * d; } float cv = sqrtf(var / 29) / mean; /* 样本标准差 / 均值 */参数说明:这里用 29 做分母,取的是样本标准差的无偏估计。CV 超目标值时先区分随机噪声还是趋势性漂移:随机噪声靠均值次数压,趋势性漂移大概率是温度或电极接触变化,加滤波只会掩盖问题。
5.3 人体测量节点的三个细节
人体不是标准电阻,电极接触状态直接改变读数。第一,电极表面湿度要统一,干湿两种状态下接触阻抗可能差一倍,同一台秤要规定一致的测量条件,否则回归系数全部失真。第二,四根引线尽量等长、同屏蔽,V+/V- 是高阻抗采样线,最容易吸收工频干扰,屏蔽层单端接地,不要两端都接。第三,站位压力影响低频分量,重心不稳时虚部漂移明显,量产固件应该在确认体重稳定后再启动阻抗测量,而不是一上秤就采。把这三项固定后,用同一人同一时段连测五次,读数波动压进 0.5 Ω,标定系数就可以放心写进量产固件了。
本文还有配套的精品资源,点击获取