
STM32项目开源智能体重秤代码原理图仿真搞嵌入式这几年我从一开始只会点灯到后面做过各种IoT小项目其中最常被群里朋友问起的就是这个STM32智能体重秤项目。原因很简单它麻雀虽小五脏俱全一个项目里既有称重传感器模拟量采集又有HX711数字转换、软件滤波、单位换算、OLED显示、蓝牙透传还涉及低功耗设计和仿真验证几乎把嵌入式入门该碰的东西都碰了一遍。而这个项目的代码、原理图、仿真工程我年前刚好整理完并开源了出来今天这篇博客就一次性把设计思路、硬件选型、核心代码、调试踩坑全部梳理清楚给想复现的朋友一条能直接走通的路。如果你正在学STM32或者想做一个能拿得出手的课程设计、开源作品甚至想为后续做智能家居产品练手这个项目都很适合。我会把每一个关键决策背后的原因都讲透而不是只丢给你一堆文件。毕竟从能跑到跑得稳中间隔着的是对原理的理解和对细节的把控。1. 项目整体设计与硬件架构1.1 核心需求拆解先看这东西到底要做什么。智能体重秤表面上就是站上去读出重量显示出来。但拆开看需求其实有五个维度重量采集需要能感知微小形变并且把形变量转成数字信号给MCU读。常规方案是电阻应变片式压力传感器配合24位高精度ADC芯片。数据处理原始传感器数据不是重量需要标定、滤波、换算还要考虑温漂、噪声和人体晃动带来的波动。人机交互显示重量是基本还得有单位切换公斤/斤/磅、历史记录查看高端一点的做BMI计算。数据上报加蓝牙模块把重量数据发到手机App或微信小程序这是智能二字的落脚点。低功耗与便携不能插着充电线用吧电池供电还得省电。这些需求对应到方案选型上主控我选了STM32F103C8T6传感器用了常见的电阻应变片式称重传感器ADC转换芯片是HX711显示用0.96寸OLEDI2C接口通信用HC-08蓝牙模块电源用3.7V锂电池加升压电路。这套组合是当前开源社区里最经典、资料最全、价格最便宜的方案之一。整板物料成本控制在50元以内非常适合学生党复现。1.2 系统架构与信号采集链路整个系统的信号流是人体重量 → 应变片电阻变化 → 电桥电压差 → HX711放大24位ADC → STM32读取数字值 → 软件滤波与标定 → OLED显示 / 蓝牙上报。这里面最关键的链路是传感器到ADC这一段。称重传感器本质是一个惠斯通电桥内部有四个电阻应变片。受力时应变片阻值发生微小的相对变化电桥输出一个毫伏级别的差分电压。这个电压太小了MCU内置的12位ADC根本采不出有效精度所以必须外挂HX711这种专用芯片。HX711本身集成了低噪声可编程放大器增益可选32/64/128和24位Sigma-Delta型ADC能够把传感器输出的微弱差分信号放大并数字化。它和MCU的通信接口是两线制的SCK时钟和DOUT数据时序是模拟的任何MCU只要GPIO够用都能驱动。这里有个特别重要的点HX711的供电和传感器供电必须干净否则ADC的参考电压噪声会直接折算进测量结果造成重量读数乱跳。我在原理图里对AVDD和VCC做了独立滤波这是很多新手容易忽略的。2. 硬件选型与关键模块解析2.1 主控芯片选型为什么是STM32F103C8T6先说结论这个项目用F103C8T6属于大材小用但绝对稳妥。它主频72MHzFlash 64KBRAM 20KB对于体重秤这种运算量很小的场景其实是过剩的。但为什么选它第一生态成熟。STM32F103是ST的经典型号网上资料多到溢出库函数版本和HAL库版本都有大量现成案例遇到问题搜一下基本能解决。对于开源项目来说让别人能复现比性能极致更重要。第二外设够用且不复杂。这个项目需要的外设就几个GPIO模拟时序HX711、I2COLED、UART蓝牙、ADC监测电池电压、定时器轮询调度。这些F103全都有而且不需要用到高级外设降低了学习门槛。第三成本低。国产替代型号如GD32F103、MM32F103都能直接兼容价格在5元上下批量更便宜。如果你手里只有F407或者F103ZET6也可以跑这个工程只需要改一下引脚定义和启动文件核心逻辑完全不变。2.2 称重传感器与HX711采集方案称重传感器我选用的是最常见的铝制单点式传感器四根线红E、黑E-、绿S、白S-。量程有5kg、10kg、20kg可选做人体秤选50kg或100kg的量程比较合适但要注意量程越大相同重量下的输出灵敏度越低所以选型不能只看上限。这里解释一下HX711的工作机制方便大家理解后面代码里的时序操作HX711的DOUT引脚平时是高电平当一次转换数据准备好时DOUT会被拉低。此时MCU给SCK发送25个脉冲前24个脉冲从高位到低位依次移出24位转换结果二进制补码第25个脉冲决定下一次转换的增益脉冲是高电平时下一次转换增益为128MCU继续把数据移出。如果不想在第25个脉冲后读数据也可以在24个脉冲收完数据后接着给一个脉冲让HX711进入下一个通道/增益配置。通常我们只用通道A、增益128所以在读完后补一个高电平脉冲即可复位。HX711内部集成的低噪声放大器是整个方案能测准的核心。它的差分输入端可以直接接传感器电桥的输出不需要额外的仪表放大器。这里我要强调输入端的走线必须短且粗最好加一个RC低通滤波100Ω0.1uF进一步抑制射频干扰。2.3 显示与通信外设设计显示方面0.96寸OLED屏SSD1306驱动芯片是嵌入式项目的标配。I2C接口只占两根线SCL、SDA功耗也低对比度在户外也看得清适合人体秤这种需要清晰显示数值的场景。但OLED有一个痛点屏幕刷新时电流会突变如果电源滤波做得不好可能干扰HX711的采样导致重量跳动。我的做法是OLED的VCC从独立的LDO输出取电并且靠近屏幕座子加一个10uF0.1uF去耦电容HX711的供电则完全独立。两者在PCB上用电感分隔开。通信模块用的是HC-08蓝牙4.0模块很多开源项目也用HC-05但HC-08更省电。它的本质是一个串口透传模块MCU侧只管发串口数据手机端用一个配套App接收即可。代码层面甚至不需要任何蓝牙协议栈大大降低复杂度。我在原理图里给蓝牙模块留了一个跳线帽正常工作是把模块的TXD/RXD接到STM32的USART1烧录程序时需要把这个跳线拔掉防止蓝牙占用下载口。这是个很细节的设计但能避免无数次烧录失败的烦恼。3. 核心代码实现与测量算法3.1 HX711驱动代码实现驱动代码的核心就是严格按照时序去读取24位数据。使用标准库不是HAL实现如下// hx711.h #ifndef __HX711_H #define __HX711_H #include stm32f10x.h #define HX711_SCK_PORT GPIOA #define HX711_SCK_PIN GPIO_PIN_0 #define HX711_DOUT_PORT GPIOA #define HX711_DOUT_PIN GPIO_PIN_1 #define HX711_SCK_HIGH() GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_SCK_LOW() GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_DOUT_READ() GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN) void HX711_Init(void); uint32_t HX711_Read(void); int32_t HX711_ReadAverage(uint8_t times); #endifHX711的读取函数实现// hx711.c #include hx711.h void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK推挽输出 GPIO_InitStructure.GPIO_Pin HX711_SCK_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(HX711_SCK_PORT, GPIO_InitStructure); // DOUT上拉输入 GPIO_InitStructure.GPIO_Pin HX711_DOUT_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(HX711_DOUT_PORT, GPIO_InitStructure); } uint32_t HX711_Read(void) { uint32_t value 0; uint8_t i; // 等待DOUT拉低表示转换完成 while (HX711_DOUT_READ()); for (i 0; i 24; i) { HX711_SCK_HIGH(); value (value 1) | HX711_DOUT_READ(); HX711_SCK_LOW(); } // 第25个脉冲配置为下次使用通道A、增益128 HX711_SCK_HIGH(); HX711_SCK_LOW(); return value; }从代码里能看到两个关键细节第一while (HX711_DOUT_READ())会阻塞等待转换完成。如果程序里还有其他实时任务这里可能会卡住所以我在后面补了一个超时保护版本——最多等待500ms超时直接返回0避免系统死锁。第二读到的24位数据是二进制补码格式。为了后续运算方便我会把它转成有符号整数并借鉴官方例程里的HX711_ReadAverage做多次采样平均。3.2 重量换算与标定算法这个环节是整个项目最容易让人绕晕的地方。我们先明确几个概念原始读数HX711输出的、未经标定的ADC数值。比例系数scale每克重量对应的ADC读数增量。偏移量offset空秤时的原始读数。重量计算公式是weight (raw - offset) / scale。其中raw是滤波后的原始读数offset是空秤基准scale需要在已知重物下才能标定出来。我的标定流程是这样的开机后确保秤体上没有重物连续读取50次原始值取平均作为offset并保存到Flash中。放一个已知重量的砝码比如5kg等待读数稳定读取当前原始值raw_cal计算scale (raw_cal - offset) / 5000。把scale保存到Flash后续启动直接读取不需要每次重新标定。这里有一个非常实用的小技巧不要直接用单次读数计算scale而是先做多次采样取平均再结合已记录的offset计算。否则哪怕跳跃一个LSB的噪声也会在5000g量程上带来明显的误差。我还做了动态标定支持长按按键5秒进入标定模式此时OLED显示当前读取的原始值再称重已知重物通过按键调整scale微调步进。有了这个功能即便传感器一致性不太好也可以在现场快速校准。下面是我在实际项目里使用的换算与均值处理代码#define SAMPLE_TIMES 10 #define WEIGHT_SCALE_ADDR 0x0800F000 float weight_scale 438.5f; int32_t weight_offset 824300; uint32_t HX711_ReadAverage(uint8_t times) { uint32_t sum 0; uint8_t i; for (i 0; i times; i) { sum HX711_Read(); delay_us(100); } return sum / times; } float Get_Weight_Gram(void) { int32_t raw (int32_t)HX711_ReadAverage(SAMPLE_TIMES); float gram (raw - weight_offset) / weight_scale; if (gram 0) gram 0; return gram; }需要特别提醒HX711是24位分辨率3.3V供电时理论上能分辨的微电压非常小但实际因为噪声、纹波、机械振动等因素有效分辨率通常只有20位左右这并不影响人体秤0.1kg精度要求。会跳动的话不要先怀疑芯片更多是电源和机械安装的问题后面排查章节里会专门讲。3.3 软件滤波与低功耗设计称重场景下人体不可能完全静止读数会连续跳动。这时候需要软件滤波把随机噪声平滑掉。我最开始只用简单滑动平均取10次求均值实测下来效果一般因为人体晃动的低频信号和噪声混在一起了。后来换成了去极值均值滤波排序10个采样值去掉最大和最小的两个剩下8个求平均。效果立竿见影跳动从±0.3kg缩小到±0.05kg。如果对稳定性要求更高可以考虑加权递推平均或者一阶低通滤波比如current 0.8 * previous 0.2 * new_sample。但要注意低通系数太小会导致显示滞后站在秤上要等两秒数字才稳定体验很差。我的经验是先做去极值平均再叠加一阶低通系数取0.6到0.7比较均衡。低功耗方面我做了两级策略空闲状态没有站人且超时10秒MCU进入STOP模式关掉OLED和蓝牙模块电源HX711断电。此时整机功耗做到40uA以下。站人检测用一个GPIO外部中断检测秤体震动或重量突变——最简单的是利用HX711的DOUT变化频率突变或者独立增加一个薄膜压力开关做站人检测信号。我在量产版本里选了后一种方案更可靠。需要说明的是进入STOP模式后HX711和OLED的供电要通过一个MOS管控制代码里用GPIO_WriteBit来控制电源开关。这是低功耗产品的常规做法也能避免外设的漏电流吃掉主芯片省下来的功耗。4. 原理图设计与PCB布局要点4.1 原理图设计要点开源包里的原理图是用嘉立创EDA画的完全开源可编辑。设计时我重点做了几个方面的处理这里逐一说明。电源部分系统输入是3.7V锂电池经一个升压芯片比如常用的MT3608升到5V再经过LDO降到3.3V给MCU和外设供电。为什么不用3.7V直接供电因为HX711和OLED需要3.3V稳定电压而锂电电压会从4.2V降到3.0V直接供电会导致显示亮度波动、称重基准漂移。用LDO虽然有点浪费效率但对于低功耗间歇工作场景完全值得。这里有一个设计细节HX711的AVDD供电我单独从主3.3V串了一个10Ω电阻再滤波避免主负载突变影响ADC精度。同时它的参考电压是AVDD分压得到的所以AVDD的噪声就是参考电压的噪声整个采样链路对AVDD质量极其敏感。原理图上靠近HX711的电源引脚放了10uF钽电容0.1uF陶瓷电容这个组合强烈建议大家保留。主控部分STM32F103C8T6的最小系统包含8MHz晶振、复位电路、BOOT0下拉电阻、3.3V去耦电容。另外我在NRST引脚并联了一个100nF电容做抗干扰这个电容在某些ST官方参考设计里不一定会画但对EMC有实际好处。传感器接口用了一个4pin的接线端子或者JST-XH插座引脚顺序依次是E、S、S-、E-。不同厂家的传感器的线序会有差异所以接线端子旁边我用丝印标注了颜色避免接线错误烧芯片。传感器属于外部连接器一定要加ESD防护器件我并联了3个3.3V的TVS管到地。OLED和蓝牙接口分别采用I2C和UART连接都加了上下拉电阻和串联电阻防止信号反射和静电损伤。蓝牙模块的EN脚接了一个GPIO不需要时直接拉低断电这样可以把待机功耗降下来。4.2 PCB布局与抗干扰设计PCB布局我踩过的坑比较多主要教训集中在三个方面模拟地和数字地分开。虽然STM32内部的ADC和数字电路共地但PCB上如果模拟地数字地混在一起高频数字噪声会顺着地平面窜进HX711的模拟前端导致读数毛刺。我在PCB上把HX711及其外围电路放在一个独立小区域用0欧电阻单点连接AGND和DGND。传感器差分信号线尽量短、尽量等长。我用两路差分对从传感器座子直接走到HX711引脚中间不打过孔两侧用地线包裹。这个设计实测可以让读数更稳定尤其是在开关电源供电时能明显降低共模干扰。蓝牙天线区下面不要走线和铺铜。HC-08模块底部有天线净空区我在PCB上把这个区域标记出来禁止走线和铺地。如果铺了铜会减小天线等效高度导致蓝牙连接距离大打折扣。这是最早一版PCB上实际踩过的问题后来专门改过一版才恢复正常的传输距离。另外我建议把烧录调试口SWDSWDIO、SWCLK单独引出来使用标准的4pin排针。虽然整个项目很小但留着调试口在后续升级维护时能省很多力气。原理图和PCB文件包里都包含了所有设计文件直接打开就能查看。5. 仿真搭建与调试实录5.1 仿真环境的搭建与验证有人会问纯硬件项目怎么仿真这里的仿真分两层一层是电路仿真另一层是逻辑仿真。开源包里我放了电路仿真文件Proteus工程用于在没有实物的情况下验证HX711时序和LCD显示逻辑。用Proteus搭这个项目其实很受限制因为Proteus自带的HX711模型并不完全符合真实芯片时序如果仿真文件里的时序太严格反而会跑不起来。我的做法是在仿真中用ADC模块模拟HX711的输出——设定一个电压值微控制器通过比较器模拟DOUT和SCK时序虽然不如真实芯片精确但在验证主逻辑时已经够用。更值得推荐的是用STM32CubeMX真机调试的方式。用CubeMX配置好GPIO和UART代码生成后直接在开发板上跑配合串口打印来验证HX711的时序是否正确。我在开发板的调试阶段经常打印原始ADC值观察其变化趋势和噪声幅度从而判断硬件是否工作正常。如果在仿真环境中跑代码需要注意仿真器没有真实的晶振和电源特性时序和真实硬件有区别不能把仿真当作最终的验证手段还是要以实物为准。这也是我开源的工程里一直强调仿真只是用来学习和验证逻辑的原因。5.2 调试中遇到的典型问题实录我这次项目从开发到开源前后调试了两个多月把最典型的几个问题列出来给大家省时间。问题一HX711读回来的数据一直是0xFFFFFF。这个现象本质是DOUT一直为高MCU在等待数据就绪时卡死代码里那个while (HX711_DOUT_READ())就永不退出。常见原因有三个传感器线接反导致电桥输出反向饱和、AVDD没供电、HX711损坏。排查方法是先用万用表量传感器E和E-之间是否有稳定电压再量S和S-是否有毫伏级别电压差空秤时应该在0附近。如果都正常再用示波器看HX711的DOUT引脚是否会在上电后拉低几十毫秒然后恢复高电平如果有这个动作说明芯片在工作问题在MCU侧接线。问题二重量读数跳变空秤时在-0.3kg到0.4kg之间跳。这个几乎都是供电噪声或滤波不够导致的。我首先检查HX711的AVDD处是否有高频纹波用示波器看在OLED刷新瞬间是否有毛刺。后来把OLED和HX711供电分开后跳变幅度显著下降。再配合滤波算法后空秤跳动已经控制在±0.02kg以内。问题三蓝牙连接不上或者一直断开。原因是我的代码里把串口波特率默认设置成115200但HC-08模块出厂默认是9600。两个速率不一致数据当然乱码或无法连接。解决办法先使用串口助手和USB-TTL模块直接对HC-08发送AT指令把波特率改成115200或把MCU的UART波特率改成9600。我后来在代码里加了开机自动识别波特率的逻辑如果首帧校验失败自动尝试下一个波特率。问题四首次标定后第二次开机读数偏了很多。这是没有做Flash掉电保存导致的。我最初的代码只是用全局变量保存offset和scale断电后自然忘了。后来改用Flash模拟EEPROM存储在标定完成后立即将参数写入Flash同时还在启动时加了校验读取异常则强制重新标定。这里一定要留意Flash的擦写寿命虽然大于1万次但也不要每次开机都写只在标定完成时写一次就好。还有个小经验很多人以为HX711的DOUT是高电平代表数据未就绪、低电平代表数据就绪所以代码里会写等待DOUT拉低。但实际上如果传感器接反或者电桥失衡过大DOUT也是不会拉低的因为转换结果不会有效。遇到这类情况优先检测传感器接线而不是反复修改代码。5.3 去极值均值滤波与一阶低通组合的实测数据对比我做过一组对比测试在50kg量程传感器上加载10kg标准砝码分别用三种方式读取并观察显示波动滤波方式读数范围kg波动幅度kg显示稳定时间单次直接读取9.62 ~ 10.38±0.38几乎不稳定10次滑动平均9.86 ~ 10.14±0.14约1.5秒去极值平均 一阶低通9.94 ~ 10.06±0.06约2秒从这个结果可以看到去极值均值低通滤波的组合能显著降低波动只是稳定时间会稍微久一点。实际产品里我会根据用户对响应速度的要求动态调整低通系数在刚上秤的1秒内用快系数快速逼近之后切换到慢系数稳定显示。这个快慢切换用代码实现很简单就是判断两次读数差值是否超过一个阈值超过就用快速系数否则用慢速系数。6. 常见问题速查与避坑指南6.1 常见问题排查速查表整个过程我整理了一份问题排查表大家按图索骥能省不少时间现象可能原因解决措施HX711数据全为0传感器供电异常或E/E-接反用万用表测量供电与差分电压调整接线HX711数据全为0xFFFFFFDOUT未就绪或SCK时序错误检查SCK是否悬空代码中增加超时保护读数缓慢漂移温漂、传感器蠕变导致更换质量更好的传感器缩短采样到显示的路径读数跳动大电源噪声、机械振动、滤波太弱分离模拟地数字地、增加去极值滤波OLED显示乱码I2C地址错误、信号线电平不匹配确认模块地址0x78/0x7A检查上拉电阻蓝牙能连但收不到数据波特率不匹配或串口映射错误确认双方波特率一致使用硬件串口开机后无法烧录程序蓝牙模块占用串口拔掉蓝牙跳线后再烧录低功耗模式电流仍很大外设未彻底断电或GPIO未配置用MOS管控制外设电源悬空GPIO设成下拉输出6.2 硬件焊接与机械结构避坑心得电路板焊接是小批量调试最容易出问题的一环尤其是HX711这种脚间距很小的芯片。我建议使用0.5mm院长的烙铁配合助焊剂先给焊盘上锡再用拖焊法焊接HX711这类QFN/SSOP封装芯片焊完用放大镜检查有没有连锡。如果条件允许最好用热风焊台回流焊温度曲线控制在240℃左右。另外传感器的机械固定非常重要。有人把传感器直接螺丝锁在PCB上PCB又和外壳硬连接站上去后整个板子变形读数会严重非线性。我的做法是传感器用螺丝固定在四个支脚的金属支架上PCB则通过橡胶柱和外壳相连中间留出变形空间。这样人站上去的时候只有传感器的应变梁在变形PCB保持平整信号质量会好很多。还有一个容易被忽略的小细节上秤时用户习惯先站一端会导致传感器受到偏心载荷。如果传感器量程余量不足或者结构上左右刚度不一致就会造成读数偏差。所以我建议在结构设计时尽量让四个支撑点均匀受力并且软件标定时用标准砝码模拟人体站在中心位置来完成校准。6.3 项目目录与资源使用建议开源包里目录结构如下STM32_SmartScale/ ├── Code/ # Keil工程标准库版本 │ ├── User/ │ ├── HARDWARE/ │ │ ├── hx711.c/.h │ │ ├── oled.c/.h │ │ └── uart.c/.h │ └── SYSTEM/ ├── Hardware/ │ ├── Schematic.pdf # 原理图PDF版 │ ├── STM32_SmartScale.dsn # 原理图源文件嘉立创EDA │ └── PCB_3D.png # PCB渲染图 ├── Simulation/ │ └── Proteus/ ├── Docs/ │ ├── 标定流程说明.md │ └── BOM表.xlsx └── README.md如果你拿来当课程设计建议重点关注HX711驱动时序、标定算法、低功耗处理这三个点在答辩时能详细讲清楚设计原理和调试过程老师能给高分。如果拿来做产品原型建议在开源基础上增加多秤校准参数存储、去皮功能、历史趋势记录等这些在现在的智能体脂秤上都非常常见。最后再说一个我自己的体会这类型的项目最重要的不是它能跑出结果的那一刻而是后面你愿意回头去打磨滤波算法、低功耗、可靠性的过程。很多人在看到OLED显示出稳定数字后就把项目丢在一边但如果你把时间花在读数在某个温度环境下是否稳定、电池用到多少电压还能正常工作这类细节上你会发现这个入门级项目里藏着大量值得深入学习的内容。这也是我把它开源出来的原因——希望有人能在基础功能之上把它推向更接近产品的水平。