资讯动态

STM32体脂秤完整开发:HX711称重与AD5933阻抗测量详解

发布时间:2026/10/17 19:26:51 来源:尧图企业网站定制
简介这套基于STM32F103C8T6的体脂秤设计方案完整覆盖体重测量、生物阻抗分析、体脂率与肌肉量计算、数据显示和WiFi上传等环节面向具备一定嵌入式开发基础的研发人员尤其适合学习传感器融合与健康数据采集处理。压缩包共1个docx文档体积约25KB内容包含硬件架构设计、HX711驱动与线性校准、AD5933阻抗测量代码、主程序框架、滑动平均滤波优化以及蓝牙连接、语音播报、多用户管理等扩展思路。文档采用四电极系统获取人体阻抗通过回归方程估算体脂率与肌肉量并给出可直接编译运行的C源码和校准参数配置文档读者可据此快速搭建原型结合实际硬件调试验证数据准确性。资源已有129人学习下载对希望深入了解体脂秤工作原理并开展二次开发的技术人员有较高参考价值。1. 入门理由STM32 体脂秤为什么值得自己调一遍把体重、生物阻抗、显示、WiFi 上传四路传感器融进一颗 STM32F103C8T6听起来是嵌入式开发的常规组合真正跑起来才知道校准和滤波的坑全在细节里。这份体脂秤方案是我拆过的原型项目里资料最完整的一份HX711 负责称重AD5933 负责生物电阻抗分析OLED 显示结果ESP-01S 把数据送到云端C/C 代码从驱动到主循环都齐了。对想学传感器融合的嵌入式开发者来说它最大的价值不是抄代码而是能用一条真实测量链路把校准、滤波、I2C 通信和数据上传的完整节奏跑通。做产品原型、课程设计或毕设选型都值得先把它调通再谈二次开发。2. 硬件架构与引脚分配把四路传感器装进同一块 STM32F103C8T6体脂秤的核心难点不在主控而在传感器搭配。称重需要 24 位 ADC 分辨微小应变体脂需要 1kHz 到 100kHz 频率范围内的阻抗响应显示和上传又要尽量少占引脚。这套方案用 HX711 做称重、AD5933 做阻抗、0.96 寸 OLED 做显示、ESP-01S 做上传正好覆盖了测量链路的四个环节。2.1 传感器选型与测量链路原理HX711 是专为称重设计的 24 位 ADC内部自带 PGA可编程增益放大器量程 0 到 150kg精度标称 ±10g。它用两根线DOUT/SCK输出数据不占 I2C 总线这一点比很多 16 位 ADC 方便。只要称重传感器四个应变片组成的桥式电路输出差分毫伏信号HX711 就能在内部完成放大和模数转换主控只负责时序读取。AD5933 是生物阻抗分析芯片内部集成频率发生器和一个 12 位 ADC能在 1kHz 到 100kHz 范围内做频率扫描输出阻抗的实部和虚部。体脂测量的原理是人体去脂组织含水量高、导电性好脂肪组织含水量低、阻抗大所以在某个频率下测到的阻抗模值可以反推体脂率。这套方案用四电极系统——手和脚分别接触电极电流从外侧两个电极注入内侧两个电极测量电位差能显著减小接触电阻干扰。OLED 选 0.96 寸 I2C 版本12864 分辨率SCL/SDA 两根线就能驱动。ESP-01S 是 ESP8266 系列的最小模组跑 AT 指令固件通过 UART 和主控通信成本低适合做原型验证。整个链路的信号流向是体重传感器 → HX711 → STM32 → OLED 显示人体电极 → AD5933 → STM32 → ESP-01S → 云平台。两个测量通道互不干扰数据在主程序里汇合计算。2.2 引脚映射与供电注意引脚分配可以直接参考方案里的宏定义实际打板时按这个表接线就行。PA0/PA1 给 HX711I2C1 给 AD5933I2C2 给 OLEDPC4 接触摸电极USART1 接 ESP-01S。注意 PA0 和 PA1 是 5V 容忍引脚但 HX711 的数字输出是 3.3V 电平直接连没问题如果换 5V 供电的 HX711 模块要确认模块上是否带电平转换。功能模块引脚STM32F103C8T6 引脚说明HX711 DOUTPA0数据输出低电平表示数据准备好HX711 SCKPA1时钟输入每个上升沿读一位AD5933 SCL/SDAI2C1PB6/PB77 位地址 0x0DOLED SCL/SDAI2C2PB10/PB110.96 寸 12864触摸电极PC4输入悬浮人体接触检测ESP-01S TX/RXUSART1PA9/PA10115200 波特率 AT 指令供电是这个方案的第一个玄学点。AD5933 和 OLED 都是 3.3V 器件直接共用 STM32 的 3.3V 没问题。ESP-01S 发射瞬间峰值电流能到 300mA 以上如果直接从主控的 3.3V LDO 取电板子会在 WiFi 连上瞬间掉压重启。我一般单独给 ESP-01S 配一个 500mA 的 LDO或者用 AMS1117-3.3 并一个大电容输入从 5V 电源取。三个模块的 GND 必须共地否则 I2C 波形会乱。3. HX711 称重驱动补码读取、通道 A 增益 128 与线性标定流程称重模块是整个方案里最容易被低估的部分。代码逻辑看着简单实际上时序、补码转换和校准系数三个环节任何一个出错读出来的重量都是玄学。这一章把 HX711 的驱动拆开讲直接给可编译的整理版本。3.1 HX711 时序读取与补码转换HX711 的协议比 I2C 简单得多DOUT 拉低表示一次转换完成主控给 SCK 脉冲每个上升沿从 DOUT 读一位共 24 位数据然后是第 25 个脉冲用于切换通道和增益。代码里整理过的读取函数如下。uint32_t HX711_Read(void) { uint32_t data 0; GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN); // 等待DOUT拉低表示数据已准备完毕 while (GPIO_ReadInputDataBit(HX711_PORT, HX711_DOUT_PIN)); // 读取24位数据MSB先出 for (uint8_t i 0; i 24; i) { GPIO_SetBits(HX711_PORT, HX711_SCK_PIN); data data 1; if (GPIO_ReadInputDataBit(HX711_PORT, HX711_DOUT_PIN)) { data; } GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN); } // 第25个脉冲选中通道A增益128 GPIO_SetBits(HX711_PORT, HX711_SCK_PIN); GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN); return data ^ 0x800000; // 补码转无符号数据位宽24位 }这段代码里最关键的是return data ^ 0x800000。HX711 输出的是二进制补码最高位是符号位但 C 语言里用 uint32_t 读进来后高 8 位是空的。把 0x800000 异或进去本质是把符号位取反得到带偏移的无符号表示方便后续做减法计算。等价写法是(int32_t)(data 8) 8先把 24 位数据移到 int32 的高 24 位再算术右移恢复符号。两种写法都能用我习惯用异或少一次移位运算。读取之前那个while等待循环建议加超时保护否则 HX711 没接好时程序会死等。常见做法是改成计数等待比如循环 10000 次还没等到 DOUT 拉低就返回上一次的值避免主循环卡死。另外第 25 个脉冲后的通道切换在连续读取时才会体现如果你只读一次这个脉冲也要给否则下次读取可能落在通道 B。3.2 标定流程从 raw 值到校准系数驱动能读到原始值只是开始calibration_factor才是决定重量准不准的变量。方案里给的 -7050.0 是某个具体称重传感器的标定结果换上你的传感器必须重新标定否则 1kg 的砝码可能显示成 1.6kg。标定流程我一般按下面四步走。static int32_t tare_offset 0; // 空载偏移上电后先去皮 void HX711_Tare(void) { // 空载时取8次平均值作为零点偏移 int32_t sum 0; for (uint8_t i 0; i 8; i) { sum (int32_t)(HX711_Read() ^ 0x800000); HAL_Delay(50); } tare_offset sum / 8; } float GetCalibratedWeight(void) { static float calibration_factor -7050.0f; // 需要按实际标定修改 int32_t raw_data (int32_t)(HX711_Read() ^ 0x800000); return (raw_data - tare_offset) / calibration_factor; // 单位kg }标定步骤是这样的第一步上电后空载运行 HX711_Tare()记录 tare_offset第二步放一个已知重量的标准砝码比如 5kg读取 raw 值并记录第三步用 (raw - tare_offset) / 已知重量 算出基准系数第四步检查符号——如果放上砝码后 raw 变大系数就是正的如果 raw 变小系数是负的。方案里的 -7050.0 为负说明那块板子的接线或传感器受力方向让 raw 随重量增加而减小很多人在这一步把正负号搞反结果越重显示越轻。标定完成后建议做一次回称验证放 1kg、5kg、10kg 三个砝码记录显示值。如果误差超过 ±20g多半是称重传感器本身线性度问题HX711 标定只能修正比例关系和零点修正不了非线性失真。滑动平均滤波建议放在标定之后的读取链路里否则砝码读数来回跳你会以为是标定没做对。4. AD5933 阻抗测量与体脂率回归模型从 I2C 实部虚部到计算边界AD5933 是这套方案里最容易让人卡壳的芯片。它不像 HX711 那样靠时序就能读数据而是要先配置频率发生器的起始频率、频率增量和扫描点数再启动扫频最后读取实部和虚部寄存器。寄存器配置错了读回来的阻抗模值完全没意义。4.1 AD5933 初始化与频率扫描芯片的 I2C 7 位地址是 0x0D写地址 0x1A读地址 0x1B。初始化先做软件复位然后配置三个 3 字节寄存器组起始频率寄存器0x82-0x84、频率增量寄存器0x85-0x87、扫描点数寄存器0x88-0x89。频率值按公式f_code f_hz / (MCLK/4)计算MCLK 通常取 16MHz 内部时钟。下面给一个 50kHz 单点测量的最小配置这也是方案里说的校准频率。#define AD5933_ADDR_W 0x1A #define AD5933_ADDR_R 0x1B #define AD5933_CTRL_REG 0x80 #define AD5933_REAL_REG 0x94 void AD5933_Init(void) { uint8_t reg 0xB0; // 内部时钟复位 I2C_WriteByte(AD5933_ADDR_W, AD5933_CTRL_REG, reg); HAL_Delay(100); // 设置起始频率 50kHzMCLK16MHz uint32_t f_start 50000; uint32_t code (f_start * 4) / 16000000.0 * (1 27); uint8_t freq_cmd[4] {0x82, (code 16) 0xFF, (code 8) 0xFF, code 0xFF}; I2C_WriteBytes(AD5933_ADDR_W, freq_cmd, 4); // 频率增量设为 0单点扫描点数设为 1 uint8_t inc_cmd[4] {0x85, 0x00, 0x00, 0x00}; I2C_WriteBytes(AD5933_ADDR_W, inc_cmd, 4); uint8_t num_cmd[3] {0x88, 0x00, 0x01}; I2C_WriteBytes(AD5933_ADDR_W, num_cmd, 3); } float MeasureImpedance(void) { uint8_t ctrl 0x11; // 启动频率扫描 I2C_WriteByte(AD5933_ADDR_W, AD5933_CTRL_REG, ctrl); HAL_Delay(10); uint8_t rxData[4]; // 从0x94连续读4字节实部高、实部低、虚部高、虚部低 I2C_ReadBytes(AD5933_ADDR_R, AD5933_REAL_REG, rxData, 4); int16_t real (int16_t)((rxData[0] 8) | rxData[1]); int16_t imag (int16_t)((rxData[2] 8) | rxData[3]); return sqrtf((float)real * real (float)imag * imag); // 阻抗模值 }读出来的是原始码值要转成真正的欧姆数还需要一个已知电阻做增益校准。常见做法是在电极位置接一个精确的 1000Ω 电阻启动扫描测量得到模值 M_cal然后所有测量结果按R_actual M_measured / M_cal * 1000换算。方案里提到的「50kHz 频率下校准」就是这个意思。如果跳过这一步实部虚部只能看相对变化算不出绝对阻抗。还要注意 AD5933 的输出激励幅度。默认设置是 1.98V 峰峰值驱动人体电极时电流偏大我一般会改成 0.5V 或 0.2V 档避免刺痛感和安全问题。寄存器 0x81 的 D4-D5 位控制范围改完要重新校准增益。4.2 体脂率与肌肉量计算模型有了体重和阻抗体脂率计算用的是基于人群统计的回归方程不是物理推导。方案里给出的公式是最常见的变体之一输入是 BMI、年龄和性别。typedef struct { uint8_t isFemale; // 1女0男 uint8_t age; // 年龄 10-99 float height; // 身高单位米 } UserProfile; float CalculateBodyFat(float weight, float impedance, UserProfile user) { float BMI weight / (user.height * user.height); if (user.isFemale) { // 女性回归系数 return (1.20f * BMI) (0.23f * user.age) - 5.4f; } else { // 男性回归系数 return (1.20f * BMI) (0.23f * user.age) - 16.2f; } } float CalculateMuscle(float weight, float bodyFat) { // 肌肉量按去脂体重估算单位kg return weight * (1.0f - bodyFat / 100.0f); }注意第二个参数impedance在代码里没进公式这是对的——这种简化模型只用 BMI 和年龄做回归。但实际产品里阻抗数据是有价值的常见做法是把阻抗模值换算成身体总水量再做体脂修正。方案里留这个参数是为了让你扩展公式时不用改函数签名二次开发时可以在公式里加上impedance / weight这一项做修正。这里的性别、年龄、身高参数必须由用户输入代码里用 GetUserInput() 占位你可以通过矩阵键盘、蓝牙或手机 APP 下发。身高单位是米很多人在测试时直接填 175算出来 BMI 是 175结果体脂率成了负数。改成 1.75 就正常了。年龄大于 70 或小于 10 的人群回归公式误差会明显增大这点要在设计说明里写清楚别拿它当医疗设备用。5. 主程序整合与排查从模块拼装到四个高频翻车点模块驱动单独调好后主程序把测量、计算、显示、上传串起来。这个环节最容易出现的问题不是单个模块坏了而是模块之间的时序、缓冲区复用和电源干扰互相影响。先看整理后的主循环。5.1 主循环框架与数据链路int main(void) { SystemInit(); HX711_Init(); AD5933_Init(); OLED_Init(); USART1_Init(115200); // ESP-01S AT指令固件默认115200 HX711_Tare(); // 上电强制去皮这一步不能省 while (1) { float weight GetCalibratedWeight(); // 对体重做滑动平均防止人体站上瞬间数据抖动 weight WeightFilter(weight); float impedance MeasureImpedance(); UserProfile user GetUserInput(); float bodyFat CalculateBodyFat(weight, impedance, user); float muscle CalculateMuscle(weight, bodyFat); OLED_ShowData(weight, bodyFat, muscle); WiFi_SendToCloud(weight, bodyFat, muscle); HAL_Delay(1000); } }主循环顺序很重要先去皮再测重先测重再测阻抗最后统一计算。如果先做阻抗测量再做体重人站在秤上后肌肉微微受力体重读数会偏大。1 秒的 Delay 是给测量和上传留出余量实际产品可以改成触摸唤醒后连续测量 5 次取中位数避免用户站上去瞬间数据没稳定。OLED 显示和 WiFi 上传两者都要用 sprintf 格式化字符串缓冲区要分开。方案里 OLED 用一个 char[20]WiFi 用一个 char[128]千万别复用同一个缓冲区否则 OLED 刷完数据显示就被 WiFi 的 JSON 字符串覆盖了。OLED 12864 每行最多显示 16 个英文字符Weight:65.2kg这类格式刚好放得下多了会溢出到下一行开头。5.2 高频排查点现象、原因与解决第一个坑是 HX711 读回来一直是 0xFFFFFF 或者 0x800000重量无论怎么压都不变。原因是 DOUT 和 SCK 接反了或者 SCK 悬空导致时序错乱。解决方法是先核对引脚映射表然后单独写一段测试代码用手按压秤体看 raw 值是否变化变化就说明接线没问题。第二个坑是标定完成后空载显示不为 0一直偏一个固定值。原因是 tare_offset 没有初始化就直接参加计算上电后第一次读数用了上一次的脏数据。解决方法是把去皮函数放在主程序最前面而且要等 HX711 完成第一次转换后再去读——上电后 HX711 需要约 400ms 稳定立刻读回来的是随机值。第三个坑是 AD5933 读回全是 0xFFI2C 通信根本没建立。原因基本是两个地址写错或者 SCL/SDA 漏上拉。AD5933 是 7 位地址 0x0D不是 0xD0很多人把 8 位写地址当成 7 位用读写全错。排查时先用 I2C 扫描器读 0x92 温度寄存器能读回合理值说明地址和时序都对再去配频率。第四个坑是 ESP-01S 发送数据乱码或发不出去。翻车点通常在ATCIPSEND的长度参数上。void WiFi_SendToCloud(float weight, float fat, float muscle) { char json[128]; // 先拼好JSON再计算长度 int len snprintf(json, sizeof(json), {\weight\:%.1f,\fat\:%.1f,\muscle\:%.1f}, weight, fat, muscle); char cmd[32]; snprintf(cmd, sizeof(cmd), ATCIPSEND%d\r\n, len); USART_SendString(USART1, cmd); HAL_Delay(50); USART_SendString(USART1, json); }原方案里用sprintf(cmd, ATCIPSEND%d, strlen(cmd))是个经典错误——先格式化字符串再用同一个缓冲区的 strlen 当长度此时 cmd 里没有数据体长度计算完全错误。正确做法是先拼 JSON 并记录长度再发 AT 指令。另外 ESP-01S 默认波特率要看固件版本老固件 9600新固件 115200串口助手乱码时先试这两个常用值。6. 进阶技巧多用户档案、HC-05 蓝牙切换与低功耗唤醒主流程跑通之后再往前走一步就是把方案从「能跑」变成「能用」。体脂秤最现实的场景是一家多口人轮流测每次重新输入性别年龄身高肯定不现实。用 STM32 内部 Flash 或者外挂 AT24C02 存用户档案是成本最低的进阶做法。typedef struct { uint8_t id; uint8_t isFemale; uint8_t age; float height; float calibration_factor; // 每个用户独立的体重校准系数 } UserRecord; // 按id读取用户档案不存在则返回默认值 UserRecord LoadUserRecord(uint8_t id);我一般会预留一个calibration_factor字段给每个用户。原因是同一台秤换人站上去传感器受力点和鞋袜情况不一样零点会有细微偏移。虽然 HX711 标定了一次全局系数但多用户场景各自微调校准系数显示误差能压到 50g 以内。Flash 擦写次数有限每次测量完不要立刻写回只在用户主动修改参数时更新。HC-05 替换 ESP-01S 也很顺手。差别只在初始化指令和发送方式HC-05 蓝牙透传模式下不用 CIPSEND直接向串口写 JSON 字符串手机端串口助手里能直接看到。常见的指令序列是ATROLE1主模式、ATCMODE0绑定地址、ATBINDXX绑定手机地址配对成功后进入透传。如果只想把数据扔给手机看这一步比 WiFi 配置省心得多唯一的代价是传输距离从几十米降到十米内。低功耗方面体脂秤不是一直要供电的。方案里的主循环是 1 秒轮询实际产品应该在测量完成后进 STOP 模式用 PC4 触摸电极做唤醒源。测量完 10 秒无操作就关 OLED、停止 AD5933 扫描、ESP-01S 进入睡眠或直接断电把整机电流从几十 mA 压到 μA 级。唤醒后再重新初始化外设注意 HX711 和 AD5933 都有上电稳定时间唤醒后先等 100ms 再做第一次测量。最后说一个我自己的教训。有段时间我把一台样机给几个同事轮着测数据显示忽高忽低折腾了两天才发现是 calibration_factor 被人改成了正值——放上砝码 raw 值本来就该朝一个方向变正负号一乱所有数据全翻车。从那以后我每次拿到新板子都强制走一遍「空载去皮 → 标准砝码标定 → 回称验证」的完整流程还在代码里加了个编译期校验如果校准系数大于 0 直接报错。这套流程和数据链路检查清单也值得你复现这份方案时用上希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑