news 2026/10/15 15:41:02

hx711压力传感器51单片机代码实战:从跳数到稳定称重

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
hx711压力传感器51单片机代码实战:从跳数到稳定称重

简介:这份资源是面向51单片机初学者与电子制作爱好者的HX711压力传感器驱动代码包,用于搭建电子秤、配料称重等需要高精度重量检测的小型项目。包内共12个文件,约18KB,以Keil工程文件为主:包含uvproj工程配置、uvopt调试选项、c源码与h头文件、hex可烧录固件,以及编译过程产生的lst、m51、obj等中间文件,结构完整,可直接打开编译与下载验证。代码覆盖了IO口初始化、DATA与SCK时序读取、增益设置、滑动平均或低通滤波处理,以及串口或LCD显示输出等关键环节,并涉及电源接地、抗干扰布线与传感器校准等注意事项。目前已有4298人学习下载,适合想快速跑通HX711与51单片机通信、理解称重数据采集与滤波流程的读者参考借鉴。

1. 称重采样从“跳数”到“稳如老狗”:hx711压力传感器51单片机代码到底怎么写

很多人第一次把 hx711压力传感器51单片机代码烧进板子,串口打印出来的重量值能在 0 到 5000 之间乱蹦,第一反应是“传感器坏了”。其实 hx711 本身是一颗 24 位差分 ADC,专门给桥式传感器做前端,它输出的不是重量,而是一串和压力成正比的原始码值。51 单片机要做的,是把这串码值稳定地读出来、换算成克或千克,再决定是送数码管、LCD 还是上传。这套代码解决的核心问题就三个:时序读得准、零点跟得住、量程标得对。适合正在做电子秤、配料机、压力检测小项目的嵌入式新手,也适合被“跳数”折磨过的老手回头补基础。下面按硬件接线、时序驱动、滤波标定、避坑、进阶标定五块讲透,代码可以直接抄。

2. 先搞懂 hx711 的时序和 51 的接线:两根线为什么能读 24 位

hx711 和 51 之间只有两根信号线:SCK(时钟)和 DOUT(数据)。它不像 I2C 有地址,也不像 SPI 有模式,本质是一个“你来一个时钟脉冲,我吐一位数据”的移位接口。51 单片机没有硬件 SPI 也能做,因为时序简单到用普通 IO 口翻转就能模拟。这一章先把电气连接和时序讲清楚,不然后面代码全是玄学。

2.1 接线表与供电选择:3.3V 还是 5V

hx711 模块常见有两种供电:模块自带稳压的可以接 5V,裸芯片的必须按传感器激励电压来。51 单片机一般是 5V 系统,所以选带稳压的模块最省事。接线如下:

hx711 引脚51 单片机引脚说明
VCC5V模块带稳压时接 5V
GNDGND共地,必须接
DT / DOUTP1.0数据输出,51 输入
SCKP1.1时钟输出,51 输出

传感器四根线(E+、E-、A+、A-)接到 hx711 的对应端子,注意 A 通道增益是 128,适合称重传感器;B 通道增益 32,一般不用。接线长度尽量短,超过 20cm 又没屏蔽,读数会明显变差。

2.2 24 位数据是怎么移出来的

hx711 的读取规则是:DOUT 变低表示数据准备好,此时给 SCK 发 25 到 27 个脉冲。前 24 个脉冲上升沿,hx711 在 DOUT 上依次输出 24 位数据,高位在前;第 25 个脉冲用来选择下一次的通道和增益。如果只发 25 个脉冲,下一次还是 A 通道 128 增益;发 26 个是 B 通道 32 增益;发 27 个是 A 通道 64 增益。很多人代码里发 25 个脉冲却读 24 次,最后一位没读就拉高 SCK,结果数据整体错位,这就是“跳数”的常见来源。

注意:DOUT 在数据未准备好时是高电平,代码里必须等它变低再开始读,不能死等固定延时,否则采样率会随主频漂移。

3. 51 单片机驱动 hx711 的最小代码:从读原始值到串口打印

这一章给出一份能在 STC89C52 这类经典 51 上跑通的代码,包含 GPIO 定义、读函数、主循环和串口打印。代码不依赖任何库,纯寄存器操作,换到别的 51 核只要改引脚定义和延时。

3.1 引脚定义与微秒级延时

51 的 IO 口翻转速度受晶振影响,11.0592MHz 晶振下一个机器周期约 1.085us。hx711 的 SCK 高电平最短 0.2us,低电平最短 0.2us,所以不需要额外延时也能满足,但为了稳定,建议在 SCK 拉高后加一个空操作。

#include <reg52.h> #include <stdio.h> sbit HX711_DT = P1^0; // 数据输入 sbit HX711_SCK = P1^1; // 时钟输出 // 短延时,约 1us,用于 SCK 高低电平保持 void hx711_delay(void) { unsigned char i = 2; while (i--); } // 读一次 24 位原始值,返回 long 型有符号数 long hx711_read(void) { long value = 0; unsigned char i; HX711_SCK = 0; // 等待 DOUT 变低,表示数据准备好 while (HX711_DT == 1); // 读 24 位,高位在前 for (i = 0; i < 24; i++) { HX711_SCK = 1; hx711_delay(); value = value << 1; // 左移一位 HX711_SCK = 0; hx711_delay(); if (HX711_DT == 1) { value++; } } // 第 25 个脉冲,选择下一次 A 通道 128 增益 HX711_SCK = 1; hx711_delay(); HX711_SCK = 0; hx711_delay(); // 24 位有符号数补码转 long if (value & 0x800000) { value |= 0xFF000000; } return value; }

逻辑说明:while (HX711_DT == 1)是等待数据就绪,不能省。循环里先拉高 SCK 再读 DOUT,是因为 hx711 在上升沿输出数据,读的时机要在 SCK 拉高之后、拉低之前。第 25 个脉冲单独发,决定下一次通道和增益。最后补码扩展是因为 24 位有符号数在 C 语言里要转成 32 位 long,否则负数会变成很大的正数。

参数说明:hx711_delay里的i = 2是经验值,晶振不同可调整;如果读出来全是 0 或全是 0xFFFFFF,先查接线,再查这个延时是不是太短。

3.2 主循环与串口打印原始值

先把原始值打印出来,不要急着换算重量。原始值稳定了,后面才有意义。

void uart_init(void) { TMOD = 0x20; // 定时器1 工作方式2 TH1 = 0xFD; // 9600 波特率 @11.0592MHz TL1 = 0xFD; SCON = 0x50; // 串口方式1,允许接收 TR1 = 1; } void uart_send_char(unsigned char c) { SBUF = c; while (TI == 0); TI = 0; } void uart_send_string(char *s) { while (*s) { uart_send_char(*s++); } } void main(void) { long raw; char buf[16]; uart_init(); while (1) { raw = hx711_read(); sprintf(buf, "%ld\r\n", raw); uart_send_string(buf); // 简单延时,控制打印频率 { unsigned int d = 60000; while (d--); } } }

逻辑说明:串口初始化用定时器 1 方式 2 产生波特率,11.0592MHz 下 9600 波特率对应 TH1=0xFD。主循环里读一次原始值就打印,加一个空循环降低打印频率,方便观察。sprintf在 51 上比较耗资源,如果 Flash 紧张可以自己写整数转字符串。

参数说明:如果串口助手收到乱码,先确认晶振是不是 11.0592MHz,再确认波特率设置。如果原始值一直是 0x800000 或 0x7FFFFF,说明传感器没接好或激励电压不对。

3.3 把原始值变成重量:标定系数怎么算

原始值本身没有物理意义,需要两点标定。空载时读一个值叫零点offset,放一个已知砝码读一个值叫raw_ref,则:

// 标定参数,根据实际测量填写 long offset = 0; // 空载原始值 float scale = 1.0f; // 每克对应的原始值增量 float hx711_get_weight(long raw) { return (float)(raw - offset) / scale; }

逻辑说明:scale = (raw_ref - offset) / 砝码重量。比如空载读 8000,放 500g 砝码读 108000,则 scale = (108000-8000)/500 = 200,表示每克对应 200 个原始码。之后重量就是(raw - offset) / 200。

参数说明:offset 和 scale 要存在 Flash 或 EEPROM 里,否则每次上电都要重新标定。51 内部没有 EEPROM 的型号可以外挂 24C02,或者用 STC 的 IAP 功能。

4. 滤波、去皮和量程:让读数从“能用”到“好用”

原始值读通了,接下来是让它稳。hx711 本身输出速率可选 10Hz 或 80Hz,51 这边读得再快也没用,关键是软件滤波和去皮逻辑。这一章讲三种滤波的取舍、去皮实现和量程保护。

4.1 三种滤波方式对比:均值、中值、滑动

滤波方式适用场景优点缺点
算术平均静态称重简单,降噪明显响应慢,突变被抹平
中值滤波有脉冲干扰去毛刺强对连续噪声无效
滑动平均动态称重兼顾响应和平滑需要环形缓冲

我一般用“中值 + 滑动平均”组合:先取 5 个原始值排序取中间,再对中间值做 8 点滑动平均。这样既去掉偶发跳变,又不会太迟钝。

#define FILTER_N 8 long filter_buf[FILTER_N]; unsigned char filter_idx = 0; long hx711_filter(long new_val) { long sum = 0; unsigned char i; filter_buf[filter_idx] = new_val; filter_idx = (filter_idx + 1) % FILTER_N; for (i = 0; i < FILTER_N; i++) { sum += filter_buf[i]; } return sum / FILTER_N; }

逻辑说明:环形缓冲存最近 8 次采样,每次替换最旧的一个再求平均。初始化时要把 buffer 填满,否则前几次平均值偏小。

参数说明:FILTER_N 越大越稳但越慢,8 到 16 是常用范围。如果用于配料秤,建议 8;如果用于静态称重,可以到 16。

4.2 去皮和零点跟踪

去皮就是记录当前重量作为皮重,显示值减去皮重。零点跟踪是当重量长时间在小范围内波动时,自动把零点微调过去,防止温漂导致空载不为零。

long tare = 0; // 皮重原始值 long zero_track = 0; // 零点跟踪偏移 // 去皮:把当前滤波后的原始值记为皮重 void hx711_tare(long raw) { tare = raw; } // 零点跟踪:空载时缓慢修正 void hx711_zero_track(long raw) { static unsigned int stable_cnt = 0; long diff = raw - tare - zero_track; if (diff > -50 && diff < 50) // 波动在 ±50 码内认为空载 { stable_cnt++; if (stable_cnt > 100) { zero_track += diff / 100; // 缓慢逼近 stable_cnt = 0; } } else { stable_cnt = 0; } }

逻辑说明:tare是手动去皮,zero_track是自动微调。判断波动范围用原始码,不用重量,因为重量受 scale 影响。diff / 100是每次只修正 1%,避免把真实重量也跟没了。

参数说明:±50 和 100 次是经验值,传感器噪声大就放宽到 ±100,跟踪速度要求快就减小分母。

4.3 量程保护和超载判断

传感器有额定容量,超过 120% 可能永久损坏。代码里要设一个上限,超过就报警并停止采样。

#define MAX_RAW 2000000L // 对应最大量程的原始值 bit hx711_overload(long raw) { if (raw > MAX_RAW || raw < -MAX_RAW) { return 1; } return 0; }

逻辑说明:MAX_RAW 根据标定系数和最大重量算出来。比如 scale=200,最大 5kg,则 MAX_RAW = 5000*200 = 1000000,留 20% 余量取 1200000。

参数说明:超载后不要继续读,先报警,等重量降下来再恢复。否则 hx711 可能进入异常状态,需要重新上电。

5. 避坑记录:hx711 配 51 最容易翻车的 5 个地方

这一章全是血泪经验,每条按“现象 → 原因 → 解决”写。如果你正在调 hx711,建议先看这一章再动手。

5.1 现象:读数一直跳,幅度几百上千

原因:最常见的是 SCK 和 DOUT 接反,或者传感器四根线接错。其次是供电不稳,hx711 的 AVDD 和 DVDD 没有去耦电容。还有一种情况是 51 的 IO 口上电默认高电平,而 hx711 的 DOUT 是开漏输出,没有上拉电阻时读不到低电平。

解决:先量电压,VCC 和 GND 之间必须是稳定 5V;再查接线,DT 接 P1.0,SCK 接 P1.1;最后在 DOUT 上加 10k 上拉电阻到 VCC。传感器线序用万用表量,E+ 和 E- 之间电阻约 1k,A+ 和 A- 之间约 1k,两组之间不通。

5.2 现象:读出来全是 0 或全是 0xFFFFFF

原因:while (HX711_DT == 1)死循环,说明 DOUT 一直高,数据没准备好。可能是 hx711 没供电,或者 SCK 初始状态不对。还有一种可能是第 25 个脉冲没发,hx711 一直停在等待状态。

解决:用示波器看 SCK 有没有波形,没有就是代码没跑起来。确认 hx711 的 VCC 和 GND 接对。检查读函数最后有没有发第 25 个脉冲。

5.3 现象:重量线性度差,小重量准大重量偏

原因:传感器本身非线性,或者标定只做了一点。hx711 的 A 通道增益 128 是固定的,但传感器的输出灵敏度和量程要匹配。如果传感器是 1mV/V,激励 5V,满量程输出只有 5mV,hx711 的输入范围是 ±20mV,勉强够用但余量小。

解决:做多点标定,至少空载、半量程、满量程三点,用最小二乘拟合。如果传感器灵敏度太低,考虑换 2mV/V 的传感器,或者用仪表放大器先放大。

5.4 现象:上电后零点慢慢漂移

原因:hx711 内部有温漂,传感器也有温漂。51 单片机本身发热也会影响。另外,如果代码里没有零点跟踪,空载值会随温度变化。

解决:加零点跟踪,如 4.2 节。传感器尽量远离发热元件。如果要求高,加温度传感器做补偿,但 51 资源有限,一般零点跟踪就够了。

5.5 现象:串口打印正常,但接上 LCD 后读数变差

原因:LCD 刷新时 IO 口翻转频繁,干扰了 hx711 的时序。特别是 1602 的 E 引脚和 hx711 的 SCK 共用端口时,写 LCD 会误触发 hx711。

解决:hx711 的 SCK 和 LCD 控制线不要共用同一组 IO。如果必须共用,读 hx711 前先关 LCD 使能,读完再开。或者在读 hx711 时关中断,防止定时器中断打断时序。

6. 进阶:用 51 的定时器做 10Hz 精确采样和串口曲线

最后一章讲一个具体技巧:用定时器 0 产生 10Hz 中断,在中断里读 hx711,主循环只负责显示和通信。这样采样间隔精确,滤波效果更好,也方便把数据传到上位机画曲线。

6.1 定时器 0 初始化与中断服务

11.0592MHz 晶振,定时器 0 方式 1,每次 50ms 中断,20 次就是 1 秒,即 10Hz 采样。

#define SAMPLE_DIV 20 volatile unsigned char sample_cnt = 0; volatile long g_raw = 0; volatile bit g_new_data = 0; void timer0_init(void) { TMOD &= 0xF0; TMOD |= 0x01; // 定时器0 方式1 TH0 = 0x4C; // 50ms @11.0592MHz TL0 = 0x00; ET0 = 1; EA = 1; TR0 = 1; } void timer0_isr(void) interrupt 1 { TH0 = 0x4C; TL0 = 0x00; sample_cnt++; if (sample_cnt >= SAMPLE_DIV) { sample_cnt = 0; g_raw = hx711_read(); // 在中断里读 g_new_data = 1; } }

逻辑说明:定时器 0 每 50ms 中断一次,累计 20 次就是 1 秒。在中断里读 hx711 可以保证采样间隔均匀,不受主循环其他任务影响。g_new_data是标志位,主循环检测到新数据再处理。

参数说明:TH0 和 TL0 的计算公式是65536 - 50000,因为 11.0592MHz 下机器周期约 1.085us,50ms 需要 46080 个机器周期,取 0x4C00 是近似值。如果晶振不同,要重新算。

6.2 主循环处理与串口曲线格式

主循环只做三件事:等新数据、滤波、按固定格式打印。上位机用串口曲线工具可以直接看波形。

void main(void) { long filtered; char buf[24]; uart_init(); timer0_init(); while (1) { if (g_new_data) { g_new_data = 0; filtered = hx711_filter(g_raw); // 格式:$DATA,原始值,滤波值\r\n sprintf(buf, "$DATA,%ld,%ld\r\n", g_raw, filtered); uart_send_string(buf); } } }

逻辑说明:$DATA是自定义帧头,方便上位机解析。原始值和滤波值都打印,可以对比滤波效果。如果串口带宽不够,可以只打印滤波值。

参数说明:sprintf 在 51 上占用较多栈空间,如果程序跑飞,先检查栈是否溢出。可以改成手动拼接字符串。

6.3 一个我常用的标定习惯

每次换传感器或改结构,我一定先跑一遍“原始值观察”:空载打印 100 个值,看波动范围;放砝码再打印 100 个值,看稳定时间。波动小于 100 码、稳定时间小于 2 秒,才进入标定。标定时用两点法算 scale,再用第三点验证,误差超过 1% 就重来。这个习惯帮我省了很多“明明代码一样为什么不准”的后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:41:00

51单片机IO口驱动能力不足?上拉电阻、蜂鸣器与步进电机驱动全解析

从零开始学51单片机的人&#xff0c;多半会在“点灯”和“流水灯”之后卡在同一个地方&#xff1a;明明LED能亮、能闪、能花样呼吸&#xff0c;但一到驱动蜂鸣器或者步进电机&#xff0c;就发现IO口的输出“带不动”设备&#xff0c;要么声音小得可怜&#xff0c;要么电机纹丝不…

作者头像 李华
网站建设 2026/10/15 15:40:33

基于Django与机器学习的景区人流预测系统实践

1. 项目概述&#xff1a;景区人流预测系统的现实意义每到旅游旺季&#xff0c;黄山光明顶、故宫午门、西湖断桥这些知名景点总会出现"人从众"现象。作为在旅游信息化领域深耕多年的从业者&#xff0c;我见过太多景区管理者面对突发大客流时的手忙脚乱。去年帮某5A景区…

作者头像 李华
网站建设 2026/10/15 15:40:23

Java序列化与Serializable接口:为什么实体类必须实现它

刚转Java后端那会儿&#xff0c;我的实体类都是光秃秃的POJO&#xff0c;字段、getter/setter一应俱全&#xff0c;唯独没有实现Serializable接口。直到有一次&#xff0c;我往缓存里放用户对象&#xff0c;测试环境一切正常&#xff0c;可一上生产环境&#xff0c;接口直接抛了…

作者头像 李华
网站建设 2026/10/15 15:40:04

Python多线程批量下载B站收藏夹封面图实战

1. 项目缘起与整体设计思路收藏夹攒了几百条视频&#xff0c;想导出封面图做个本地画廊&#xff0c;手动一张张右键另存显然不现实。这个项目的核心目标很明确&#xff1a;给定一个收藏夹地址&#xff0c;自动把里面所有视频的封面图批量下载到本地&#xff0c;并且用多线程把速…

作者头像 李华
网站建设 2026/10/15 15:39:52

神经网络架构搜索(NAS)实战:从原理到工业落地

1. 这不是调参&#xff0c;是让模型自己“长出”结构&#xff1a;NAS到底在解决什么问题&#xff1f;“自动化机器学习&#xff08;三&#xff09;神经网络架构搜索综述(NAS)简述”——这个标题里藏着一个被很多人低估的转折点。它不是在讲怎么用AutoML工具自动选超参&#xff…

作者头像 李华