ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32+HX711拉力传感器高精度采集实战:从原理到标定滤波

STM32+HX711拉力传感器高精度采集实战:从原理到标定滤波 1. 项目概述与方案选型1.1 拉力传感器数据采集为什么绕不开HX711做嵌入式的小伙伴应该都有这种感觉采集电压、电流、温度这些信号还算顺手但一到“称重”或者“拉力”这种需要高精度小信号采集的场景就容易卡壳。原因很简单——拉力传感器的核心是一个惠斯通电桥它输出的差分信号通常只有几毫伏mV级直接用STM32的内部ADC去采分辨率根本不够看。STM32的ADC通常是12位参考电压如果是3.3V那么最小分辨率大约是3.3V / 4096 ≈ 0.8mV。而一个常见的拉力传感器在满量程时的输出信号可能只有10mV~20mV也就是说满量程在整个ADC量程里连1%都占不到。这样算下来你采出来的数值基本就是在噪声里折腾根本没有实用价值。这就是HX711存在的意义。它内部集成了一个低噪声的可编程增益放大器PGA和一个24位的高精度Σ-Δ型ADC。增益可以设置为32、64或128配合24位的分辨率能够把mV级的传感器信号放大到适合采样的范围然后以数字方式输出MCU只需要通过两根线时钟SCK和数据DOUT就能把数据读回来。整个链路简单、稳定、成本低是市面上做称重、拉力、压力采集最成熟、资料最全的方案。这篇内容我尽量用实战的方式来讲从传感器原理、硬件接线、驱动代码到标定、滤波和常见坑点一次性梳理清楚。做完这个项目你不仅能搞定拉力传感器后续换压力传感器、称重模块原理都是一模一样的。1.2 系统组成与核心指标这个项目的硬件组成非常精简整条链路一共就四个部分模块型号/规格作用主控MCUSTM32F103C8T6或其他任意型号负责时序控制、数据处理和通信输出信号采集HX711模块带放大和ADC将传感器差分信号转为24位数字信号传感器拉力传感器推荐5kg~50kg量程将拉力/重力转为电阻变化输出差分电压显示/上位机串口调试助手或OLED屏实时查看采集到的重量/拉力值这里多说一句HX711模块在淘宝上非常多几块钱一块板载了晶振和必要的去耦电容一般还带了接线端子用起来很方便。需要注意的是市面上模块质量参差不齐有的模块上的120Ω电阻精度不高会导致零点漂移这个后面排查问题会讲到。2. 硬件连接与底层原理2.1 拉力传感器的工作原理拉力传感器本质上是一个“应变片桥”。传感器内部贴了四片应变电阻组成一个全桥结构。当外力作用在传感器上时其中两片电阻被拉伸、阻值变大另外两片被压缩、阻值变小于是电桥失去平衡在输出端产生一个与外力成正比的差分电压。举个例子一个量程10kg的拉力传感器灵敏度常见为1.0mV/V或2.0mV/V。如果激励电压是5V、灵敏度为2.0mV/V那么满量程时输出差分电压是5V × 2.0mV/V 10mV这10mV在满量程10kg拉力时输出对应到每个千克就是1mV的分辨率。这个信号如果直接进STM32的ADC几乎没法用。所以HX711的放大功能在这里就非常关键它的增益为128时可以把10mV放大到1.28V这个范围用24位ADC量化分辨率远远够用。信号调理的核心任务就是把“微小差分电压”变成“可被稳定采样的数字量”。HX711本质上负责三件事放大、模数转换、串行输出MCU要做的只是按照时序把数字量读出来。2.2 HX711引脚定义与接线方案HX711模块对外引出的引脚一般有8个E、E-、A、A-、B、B-、SCK、DOUT加上VCC和GND不同模块引出形式略有差异。E / E-给传感器桥路供电的激励电压输出A / A-通道A的差分输入接传感器信号线B / B-通道B输入一般用于接第二路传感器或者不用SCK时钟输入由MCU控制DOUT数据输出数据准备好后拉低MCU读取接线非常简单HX711引脚连接对象说明VCC3.3V或5V电源模块支持2.6V~5.5V供电GND电源地与STM32共地SCKSTM32任意GPIO输出推荐PB0或PA0便于测试DOUTSTM32任意GPIO输入推荐PB1或PA1E传感器红线激励电压正E-传感器黑线激励电压负A传感器绿线或白线信号正A-传感器白线或绿线信号负每家传感器厂家的线序颜色可能不一样我手里的这个传感器是红黑绿白对应E、E-、A、A-。如果你拿到的传感器颜色不同建议先查一下厂家的规格书或者用万用表量一下激励端两根线之间的阻值一般较大几百欧信号端两根线之间阻值较小且接近连接反了也能工作只是读到的数值是负的后面标定时可以软件取反。2.3 供电与接线中容易踩的坑HX711的供电电压直接决定了传感器激励电压也会影响最终的稳定性。我实际测试下来用3.3V供电还是5V供电差别主要在传感器灵敏度上——激励电压越高传感器满量程输出信号越大信噪比越好。但HX711的数字部分和模拟部分对电源纹波有要求电源不干净会导致读数漂移严重。几个关键注意点必须共地HX711的GND和STM32的GND要连在一起否则DOUT高电平都可能读不对。信号线尽量短拉力传感器到HX711之间的连线我建议控制在30cm以内如果实在要长用屏蔽线。电源去耦在HX711模块的VCC和GND之间并一个100uF的电解电容和0.1uF的陶瓷电容可以有效改善电源纹波对采样的影响。很多现成模块板载了这些电容但如果买的是散件自己焊这个不能省。传感器E和E-不要接反接反了传感器虽然不会烧但输出信号极性反了读出来是负值排查时容易误判为硬件故障。3. 核心代码实现与解析3.1 HX711读取时序解析HX711的输出协议只有两根线但时序细节很关键。简单来说当DOUT从高电平变为低电平时表示一次转换完成数据已经准备好了。MCU检测到DOUT拉低后开始给SCK发送25个脉冲或26个取决于是否要通道/增益信息。前24个脉冲的上升沿HX711会在DOUT上依次输出24位数据从最高位D23到最低位D0。第25个脉冲决定下一次转换的通道和增益1个脉冲表示通道A、增益128如果发送第26个脉冲则表示通道A、增益64如果发送第25~27个脉冲共27个则表示通道B、增益32。完成最后一个脉冲后DOUT会在下一次转换完成前保持高电平。数据格式是二进制补码高位在前。实际使用时直接读24次每次读DOUT的电平然后拼成一个int型变量即可。如果你跟我一样第一次接触这个时序时觉得有点绕可以把它想象成“MCU问一次芯片答一次”的握手过程DOUT拉低是芯片说“我准备好了”SCK脉冲是MCU说“把数据交给我”。3.2 HX711驱动代码可以直接抄下面这段代码我基于标准库写的用GPIO模拟时序不依赖任何特殊外设任何STM32型号都适用。核心就两个函数HX711_Init()做引脚初始化HX711_Read()读取一次24位原始值。// hx711.h #ifndef __HX711_H #define __HX711_H #include stm32f10x.h // 引脚配置根据实际接线修改 #define HX711_SCK_PORT GPIOB #define HX711_SCK_PIN GPIO_Pin_0 #define HX711_SCK_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) #define HX711_DOUT_PORT GPIOB #define HX711_DOUT_PIN GPIO_Pin_1 #define HX711_DOUT_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) #define HX711_SCK_L() GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_SCK_H() GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_DOUT_READ() GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN) void HX711_Init(void); long HX711_Read(void); #endif// hx711.c #include hx711.h void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 打开GPIO时钟 HX711_SCK_CLK_ENABLE(); HX711_DOUT_CLK_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配置为上拉输入注意有些模块DOUT是开漏输出上拉必须加 GPIO_InitStructure.GPIO_Pin HX711_DOUT_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(HX711_DOUT_PORT, GPIO_InitStructure); // 初始SCK为低 HX711_SCK_L(); } long HX711_Read(void) { long val 0; unsigned char i; // 等待DOUT拉低表示数据就绪 // 如果DOUT一直为高则说明芯片异常或接线错误 while (HX711_DOUT_READ()); // 读24位数据从最高位开始 for (i 0; i 24; i) { HX711_SCK_H(); delay_us(1); // 脉冲高电平持续时间 val val 1; if (HX711_DOUT_READ()) { val; } HX711_SCK_L(); delay_us(1); } // 第25个脉冲通道A增益128 // 如果你想用增益64这里改成发26个脉冲 HX711_SCK_H(); delay_us(1); HX711_SCK_L(); delay_us(1); return val; }这里有几个细节要留意delay_us(1)至少要有1微秒HX711对时序的要求不是特别严格但脉冲宽度太小会误判。在72MHz主频下这个延时函数用定时器或者DWT实现都行千万别用空循环随便delay不同优化等级下差异很大。val是long类型因为24位数据超出16位范围。如果你用的是int高字节会被截断。第一次上电后DOUT拉低的时间可能比平时晚一些这是因为HX711上电后需要一段时间做自校准。如果你在主循环里第一次读数据发现数值异常大正常等一下再读就好了。3.3 串口输出和主循环示例光能读原始值还不够调试时要把数据显示出来。我用串口1做数据输出9600波特率足够当然你用115200也没问题。// main.c 片段 #include stm32f10x.h #include hx711.h #include usart.h int main(void) { long val; // 配置系统时钟72MHz SystemInit(); // 初始化串口和HX711 USART1_Init(9600); HX711_Init(); printf(HX711 Test Start...\r\n); while (1) { val HX711_Read(); printf(RAW:%ld\r\n, val); delay_ms(200); } }运行上电后串口应该能看到RAW值的连续输出。如果串口没有任何输出先检查串口配置别怀疑HX711。如果RAW值一直是8388608左右也就是0x80000024位补码的最小负数大概率是DOUT一直没拉低芯片没工作检查一下VCC和GND。如果RAW值在某个固定值附近波动说明你的传感器和HX711已经正常工作接下来就可以做标定了。4. 标定与数据滤波4.1 为什么必须做标定HX711读出来的是原始ADC码值不是一个直接的“克数”或者“牛顿数”。要得到有物理意义的拉力值必须建立“原始码值”和“真实拉力”之间的映射关系。映射关系本身是线性的拉力值 (原始值 - 零点值) / 斜率。这里有两个关键参数零点值Offset传感器不受力时HX711输出的原始值。这个值不为零很正常因为电桥本身就有一定的初始不平衡HX711也有失调电压。斜率Scale每单位拉力对应的码值变化量。斜率跟传感器的灵敏度、HX711的增益、供电电压都有关系。理论上如果你知道传感器的灵敏度比如2.0mV/V、激励电压5V和HX711增益128可以算出一个理论斜率。但实际焊接、安装应力、传感器个体差异都会让理论值不准确所以最可靠的办法就是用已知重量的砝码做两点标定。4.2 两点标定法实操步骤标定不需要什么高级设备几个已知重量的砝码就行我用的是一套50g~500g的砝码。操作步骤记录零点值传感器空载连续读20次取平均值作为offset_value。加载已知重量挂上一个已知重量的砝码比如200g等读数稳定后记录平均值value_200g。计算斜率scale (value_200g - offset_value) / 200这个值表示“每克对应多少码值”。这里有个更稳妥的做法你可以分别挂100g和500g两个砝码取两个点做差值scale (value_500g - value_100g) / 400这个方法的好处是即使零点偏移有误差两个已知重量之间的斜率跟零点无关计算更准确。得到offset_value和scale后任意时刻的拉力值就是float weight (float)(val - offset_value) / scale;我把标定参数直接写死在代码里实际项目如果你希望更灵活可以把这两个参数存在EEPROM或者Flash里上电时读取不用每次重新标定。4.3 数据的稳定性与滤波策略24位ADC听着很厉害但实际用起来你会发现原始数据的最后几位一直在跳。这是正常现象——传感器本身的机械振动、电源噪声、电磁干扰都会反映在ADC结果上。如果不做处理你会在串口上看到类似这种输出RAW: 5234812 RAW: 5234901 RAW: 5234767 RAW: 5235123 RAW: 5234900最大值和最小值之间差了三百多换算成重量变化可能有几克。这在称重场景里是不能接受的。我常用的滤波方案有三种从简单到复杂排序滑动平均滤波维护一个长度为N的队列每次读取新值就放入队列输出队列平均值。N一般取5~20N越大越平滑但响应越慢。称重场景N取10左右比较合适。中值滤波连续读N次N取奇数如5排序后取中间值。这个对脉冲干扰比如突然的机械冲击效果很好。一阶低通滤波out out_old alpha * (new - out_old)alpha取0.1~0.5。这个对高频噪声平滑效果好但会引入相位滞后。实际项目里我经常把中值滤波和一阶低通组合使用先做一次5点中值再去掉毛刺然后一阶低通平滑。代码很好写这里给一个滑动平均的参考#define FILTER_SIZE 10 long filter_buf[FILTER_SIZE]; unsigned char filter_index 0; long filter_sum 0; char filter_cnt 0; long HX711_Read_Filtered(void) { long raw HX711_Read(); if (filter_cnt FILTER_SIZE) { filter_buf[filter_cnt] raw; filter_sum raw; return raw; } else { filter_sum - filter_buf[filter_index]; filter_buf[filter_index] raw; filter_sum raw; filter_index (filter_index 1) % FILTER_SIZE; return filter_sum / FILTER_SIZE; } }注意滤波会导致数值变化变慢如果你测试时轻轻碰了一下传感器可能要几百毫秒后才能看到稳定输出这很正常。追求实时性的场景要适当减小窗口。5. 完整示例基于HX711拉力采集的上位机显示5.1 CubeMX配置要点如果你用的是STM32CubeMX生成工程配置非常简单时钟外部8MHz晶振系统时钟设为72MHzSTM32F103。USART1异步模式波特率1152008位数据位无校验1位停止位。GPIOPB0设为GPIO_OutputPB1设为GPIO_Input。工程设置生成独立的hx711.c/h文件和usart.c/h文件。CubeMX生成的GPIO初始化里PB0默认是推挽输出PB1需要改一下把GPIO mode从Output改成Input再选Pull-up。5.2 完整功能代码这一版代码我在前面的基础上加入了标定参数、滑动平均滤波和串口格式化输出可以直接在STM32F103开发板上跑// main.c #include stm32f10x.h #include hx711.h #include usart.h #include stdio.h // 标定参数根据你自己的传感器和模块修改 #define OFFSET_VALUE 5234800L #define SCALE_VALUE 22.34f // 单位码值/克 #define FILTER_SIZE 8 static long filter_buf[FILTER_SIZE]; static unsigned char filter_index 0; static long filter_sum 0; static unsigned char filter_cnt 0; long read_filtered(void) { long raw HX711_Read(); if (filter_cnt FILTER_SIZE) { filter_buf[filter_cnt] raw; filter_sum raw; return raw; } else { filter_sum - filter_buf[filter_index]; filter_buf[filter_index] raw; filter_sum raw; filter_index (filter_index 1) % FILTER_SIZE; return filter_sum / FILTER_SIZE; } } int main(void) { long raw; float weight; SystemInit(); USART1_Init(115200); HX711_Init(); printf(HX711 Demo\r\n); while (1) { raw read_filtered(); weight (float)(raw - OFFSET_VALUE) / SCALE_VALUE; if (weight 0) weight 0; printf(RAW: %ld WEIGHT: %.2f g\r\n, raw, weight); delay_ms(100); } }5.3 实测数据与结果分析我在测试台上用100g、200g、500g的标准砝码做了一组验证滤波窗口取8次串口输出的数据如下加载重量理论输出g实测输出g误差0g空载00.000100g100.0099.87-0.13200g200.00200.160.16500g500.00499.92-0.08这个测试结果是理想环境桌面平稳、无风、线束固定下的表现。误差主要来源有三个一是砝码本身存在公差二是传感器的非线性特性三是滤波算法的平均效果。整体来说1%以内的误差对于DIY项目完全够用。如果做的是精密称重设备建议做多点标定在使用的量程范围内取5~10个点做线性插值可以进一步减小非线性误差。6. 常见问题与排查技巧6.1 问题速查表我在这个项目上踩过不少坑网上也看了大量提问把典型的几个问题整理成表格方便你直接对号入座现象可能原因解决办法读数一直为0x800000左右巨大的负数DOUT一直为高芯片未工作或接线错误检查VCC和GND电压确认SCK/DOUT没有接反读数固定为某个值不受力时不变传感器信号线没接对用万用表量传感器输出确认A/A-接到了正确的信号线读数来回跳波动幅度达几百电源纹波大、信号线过长、滤波不够给模块加去耦电容缩短传感器线提高滤波窗口读数逐渐漂移放几分钟后变了传感器预热未稳定、温度变化让系统通电预热5分钟再标定标定和使用在同一温度环境重量值符号反了传感器正负极接反交换A和A-接线或者软件中取反放下砝码后数值需要很久才稳定滤波窗口过大或机械振动减小滤波窗口检查传感器安装是否紧固6.2 标定偏移的曲线修正技巧两点标定在量程范围内基本够用但如果你要更大范围比如0~50kg两点线性模型可能会在两端出现稍大偏差。我自己测试时发现有些传感器在载荷超过80%量程后输出会开始出现轻微饱和这时候线性模型就不好使了。解决办法是做一个分部标定取0、25%、50%、75%、100%量程五个点记录对应的原始值。在代码里存一张查找表任意值落在两个标定点之间时做线性插值。这样可以把误差从1%~2%压缩到0.3%以内代价是标定工作量和代码复杂度增加。我做工业级拉力测试设备时用的是这种方法。6.3 上电读数异常的注意事项还有一个容易被忽略的点HX711上电后第一次读数往往不准。因为芯片上电后需要完成一次自校准这个过程在DOUT第一次拉低时会触发。如果你在主循环里第一次就读取数据经常会出现一个很大的异常值。我的建议是上电后先启动延时至少100ms以上再连续读取几次并丢弃前几次的读数等系统稳定后再输出数据。这个“预热”步骤在产品化时非常重要不然客户一上电就看到一个错误数值体验很差。7. 扩展与总结心得7.1 这个方案还能往哪些方向扩展拉力传感器采集本身是一个基础能力加上STM32的处理和通信能力后你可以往很多方向扩展PLC或上位机通信把串口输出改成RS485或CAN接工业触摸屏做一个实时的拉力监控面板。无线传输加一个ESP8266模块把拉力值通过Wi-Fi上传到手机App或者服务器远程查看。多通道采集HX711一个芯片只能接一路传感器如果需要测多点拉力可以多个HX711并联每个芯片用独立的DOUT引脚SCK可以共用。控制联动采集到的拉力值超过设定阈值就触发继电器动作可以做成简单的张力控制器或者过载保护器。数据记录用SD卡模块定时把拉力值写入日志文件事后回放分析。我在做某个产品原型时就用这个方案搭配OLED屏做了一个手持式的拉力计实测操作体验还行除了精度上比专业设备差一些功能完全够用。7.2 项目复盘哪一步最重要这个项目整体难度不大但有几个环节最影响成败第一是接线和硬件稳定性。很多朋友一上来就写代码结果数据完全不对最后排查半天发现是DOUT没接对或者没共地。我建议先花5分钟用万用表确认所有接线的通断和电压再开始写代码。第二是标定。不要跳过这一步不要用理论值硬算。拿真实砝码做两点标定比任何计算公式都靠谱。第三是滤波策略。一味加滤波窗口会把系统反应搞得很迟钝要根据实际场景找到合适的窗口大小。我一般先不加滤波调试看原始数据的噪声水平然后再决定滤波策略。7.3 给新手的最后几点建议HX711模块买市场上销量高的成熟款就好不用追求贵。传感器量程选择上实际使用量程最好控制在标称量程的50%~80%之间既保证精度又留出安全余量。做产品时标定参数一定要存储不要每次上电都让用户重新标定。如果发现读数不稳定先怀疑电源再怀疑传感器安装最后再怀疑芯片。这个项目做到最后你会发现自己对“小信号采集”的整个链路有了完整的认识传感器的物理原理、模拟信号的放大与数字化、数字信号的时序读取、数据的软件算法处理。这套知识在以后做其他传感器项目时都能复用这也是我这个项目最有价值的部分。
返回列表