目录
一、HX711称重模块简介
1.1 HX711简介
HX711是一种高精度、低噪声的模拟前端芯片,常用于称重传感器和负载细节测量应用中。它具有内置的放大器和ADC(模数转换器),可以将称重传感器的模拟信号转换为数字信号。其实物见下图1所示。

HX711模块通常与称重传感器(如压阻式称重传感器)配合使用,以便实现重量测量功能。在使用HX711时,通常需要将其与一个称重传感器连接,以便将传感器的模拟输出信号转换为数字信号。然后,这个数字信号可以被微控制器或其他数字系统处理和解读。
1.2 称重传感器简介
称重传感器,也可称为负载单元,称重单元。称重传感器测量重量(或者更准确地说是方向力)通常是通过弹簧元件和应变片的组合,转换为电气输出。应变片称重传感器由固定应变片的固体金属体(或"弹簧元件")组成。应用负载时,称重传感器的主体轻微变形并偏转。见下图2示。

如上图2所示,单点式称重传感器是低量程高精度的称重传感器。外形如四方体形状,测量结果准确可靠,广泛运用于电子秤,厨房秤,珠宝秤等领域,是工业和农业自动化系统中不可缺少的核心部件。
1.3 称重模块
称重模块实物图示意见下图3所示。

二、工作原理简介
HX711的串口通讯线由管脚PD_SCK(SCK)和DOUT(DT)组成,共同用来输出数据、选择输入通道和增益。下图4为数据手册贴出的工作时序图。

1. 当DOUT(数据输出引脚)为高电平时,表明A/D转换器还未准备好输出数据,此时串口时钟输入信号PD_SCK应为低电平;
2. 当DOUT从高电平变低电平后,PD_SCK应输入25至27个不等的时钟脉冲(其中前24各时钟脉冲输出的是传感器测得的ADC数据,第25-27个时钟脉冲与数据无关,仅用于选择下一次传感器的输出增益和输出通道)。
需要注意的是:这里说明的DOUT和PD_SCK是针对于传感器的数据手册来说明的,即在数据手册中,传感器的DOUT引脚是作为输出,那么我们在使用MCU控制器驱动该传感器时,需要将该引脚作为输入引脚进行配置;同理,PD_SCK引脚是需要MCU向传感器输出时钟脉冲的,因此MCU控制器需要将该引脚配置为输出模式向HX711输入时钟脉冲。
3. 在第一个时钟脉冲的上升沿会读取出输出的24位数据的最高位(即最高有效位,MSB),然后在接下来的时钟脉冲中,逐位输出这24位数据,从最高位到最低位。这个过程会一直持续,直到24位数据全部输出完成。因此传感器的数据输出是从高位到低位的,驱动编写时需注意。
4. 数据手册中还有一个可能会被忽略的细节,见下图5所示。

由上图5可知,我们需要给定的时PD_SCK正脉冲的电平(高电平)最大时间不能超过50us,而负脉冲的电平(低电平)的时间不能小于0.2us,而由于我们一般是选择将脉冲的占空比设置为50%,因此我们可以给定高低电平的延时时间相等,后续的驱动程序中给定的是1us。
5. PD_SCK 的输入时钟脉冲数不应少于 25 或多于 27,否则会造成串口通讯错误。 当 A/D 转换器的输入通道或增益改变时,A/D 转换器需要 4 个数据输出周期才能稳定。DOUT 在 4 个数据输出周期后才会从高电平变低电平,输出有效数据。见下图6所示。

上图中,第25至27个时钟脉冲用来选择下一次A/D转换的输入通道和增益。关于整体HX711的介绍就到这里,感兴趣的读者可以参考HX711的数据手册,链接如下:HX711数据手册。
三、STM32驱动源码
3.1 STM32源码
其中hx711.c驱动源码如下:
/**********************************************************************
* 文件名称: hx711.c
* 作 者: JJ-KING
* 版 本 号: V1.0.0
* 创建日期: 2023-02-09
* 模块描述: 压力传感器配置
* 其他说明:
* 修改记录: <作者> <时间> <版本号> <描述>
**********************************************************************/
/*----------------------------------------------------------------------------
更新日志:
2023-02-09 V1.0.0: 修改引脚为宏定义声明
2024-01-04 V1.0.0: 重新定义获取重量的外部接口函数
----------------------------------------------------------------------------*/
/* 包含的头文件 --------------------------------------------------------------*/
#include "hx711.h"
#include "delay.h"
#define HX711_DOUT(x) GPIO_WriteBit(HX711_DOUT_GPIO_PORT, HX711_DOUT_GPIO_PIN, (BitAction)x)
#define HX711_SCK(x) GPIO_WriteBit(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN, (BitAction)x)
#define HX711_SCK_STATE GPIO_ReadOutputDataBit(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN)
#define HX711_DOUT_STATE GPIO_ReadInputDataBit(HX711_DOUT_GPIO_PORT, HX711_DOUT_GPIO_PIN)
static STRUCT_HX711_TYPEDEF hx711;
/* 传感器 gpio引脚配置 */
static void hx711_gpio_config(void)
{
RCC_APB2PeriphClockCmd(HX711_DOUT_GPIO_CLK | HX711_SCK_GPIO_CLK, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; /* IO口速度为2MHz */
GPIO_InitStructure.GPIO_Pin = HX711_SCK_GPIO_PIN; /* 端口配置 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */
GPIO_Init(HX711_SCK_GPIO_PORT, &GPIO_InitStructure);/* 根据设定参数初始化 */
GPIO_InitStructure.GPIO_Pin = HX711_DOUT_GPIO_PIN; /* 端口配置 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_Init(HX711_DOUT_GPIO_PORT, &GPIO_InitStructure);/* 根据设定参数初始化 */
HX711_SCK(0); HX711_DOUT(1);
}
/* 传感器结构体参数初始化 */
static void hx711_struct_config(void)
{
hx711.gain = GAIN_128; /* 当前增益 128 */
hx711.isTare = true; /* 默认第一次称重为皮重 */
hx711.k = 0.004757f; /* 传感器转换系数 */
hx711.b = 0.f; /* 传感器误差校正/补偿值 */
hx711.tare = 0.f; /* 保存皮重数据 */
hx711.actual = 0.f; /* 保存净重数据 */
}
/* 读取传感器返回的ADC数据值(数字量) 假定为一阶线性关系 y=kx+b */
static unsigned long hx711_readCount(void)
{
unsigned long count = 0; /* 需要读取25-27个0/1数据 */
unsigned char timeOut_cnt = 0; /* 检测超时计数值 */
HX711_SCK(0);
/* 添加超时检测 防止一直未识别到传感器导致程序卡死在这里 */
while(HX711_DOUT_STATE) { /* 等待DOUT从高电平到低电平跳变 */
timeOut_cnt ++;
delay_ms(1);
if(timeOut_cnt > 100) return 0;
}
/* 读取24位的ADC数字量数据 */
for(unsigned char i=0; i<24; i++) {
HX711_SCK(1);
delay_us(1);
HX711_SCK(0);
count |= (HX711_DOUT_STATE) << (24-i); /* 上升沿读取数据 */
delay_us(1);
}
/* 用于设置输出增益和输出通道 */
for(unsigned char j=0; j<hx711.gain; j++) {
HX711_SCK(1);
delay_us(1);
HX711_SCK(0);
delay_us(1);
}
return (count ^ 0x800000);
}
/* ----------------------------- 接口函数 ---------------------- */
/* 传感器 初始化 */
void hx711_init(void)
{
hx711_gpio_config();
hx711_struct_config();
}
/* 传感器断电 */
void hx711_power_off(void)
{
HX711_SCK(0);
delay_us(1);
HX711_SCK(1);
delay_us(100);
}
/* 传感器上电 */
void hx711_reset(void)
{
if(HX711_SCK_STATE == 1) {
HX711_SCK(0);
}
}
/* 设置传感器转换系数 */
void hx711_set_convert_ratio(float ratio)
{
hx711.k = ratio;
}
/* 设置传感器误差校正/补偿值 */
void hx711_set_offset_value(float offset)
{
hx711.b = offset;
}
/* 设置传感器增益 */
void hx711_set_gain(ENUM_HX711_GAIN_TYPEDEF gain)
{
hx711.gain = gain;
}
/* 获取传感器皮重数据 */
float hx711_get_tare_weight(void)
{
hx711.isTare = true; /* 获取皮重的时候 将该标志位置位 */
if(hx711.isTare == true) {
hx711.isTare = false;
hx711.tare = (float)hx711_readCount() * hx711.k + hx711.b; /* 此时获取到的重量为皮重 */
}
return (hx711.tare);
}
/* 获取传感器实际重量 */
float hx711_get_actual_weight(void)
{
float weight = 0.f;
if(hx711.isTare == false) { /* 当该标志位复位 代表此时应计算净重 */
weight = (float)hx711_readCount() * hx711.k + hx711.b;
hx711.actual = weight - hx711.tare;
}
return (hx711.actual);
}
hx711.h文件如下:
/**********************************************************************
* 文件名称: hx711.h
* 作 者: JJ-KING
* 版 本 号: V1.0.0
* 创建日期: 2023-02-09
* 模块描述: 压力传感器配置
* 其他说明:
* 修改记录: <作者> <时间> <版本号> <描述>
**********************************************************************/
/*----------------------------------------------------------------------------
更新日志:
2023-02-09 V1.0.0: 修改引脚为宏定义声明
2024-01-04 V1.0.0: 重新定义获取重量的外部接口函数
----------------------------------------------------------------------------*/
#ifndef __HX711_H
#define __HX711_H
#include "stm32f10x.h"
#include "stdbool.h"
/* 用于保存HX711的增益 */
typedef enum {
GAIN_128 = 1,
GAIN_32,
GAIN_64,
} ENUM_HX711_GAIN_TYPEDEF;
/* 结构体 用于实现HX711本身的属性 */
typedef struct {
ENUM_HX711_GAIN_TYPEDEF gain; /* 增益 */
bool isTare; /* 判断传感器数据是否是皮重 true-是 false-否 */
float k; /* 比例系数 */
float b; /* 补偿值 */
float tare; /* 皮重 */
float actual; /* 实重 */
} STRUCT_HX711_TYPEDEF;
/* HX711传感器引脚定义 */
#define HX711_DOUT_GPIO_CLK RCC_APB2Periph_GPIOC
#define HX711_DOUT_GPIO_PORT GPIOC
#define HX711_DOUT_GPIO_PIN GPIO_Pin_0
#define HX711_SCK_GPIO_CLK RCC_APB2Periph_GPIOC
#define HX711_SCK_GPIO_PORT GPIOC
#define HX711_SCK_GPIO_PIN GPIO_Pin_1
/* ---------------- 函数清单 --------------- */
void hx711_init(void); /* HX711初始化 */
void hx711_power_off(void); /* HX711断电 */
void hx711_reset(void); /* HX711断电后复位 */
void hx711_set_convert_ratio(float ratio); /* 设置HX711重量比例系数 */
void hx711_set_offset_value(float offset); /* 设置HX711重量补偿值 */
void hx711_set_gain(ENUM_HX711_GAIN_TYPEDEF gain); /* 设置HX711增益系数 */
float hx711_get_tare_weight(void); /* 获取皮重 */
float hx711_get_actual_weight(void); /* 获取实重 */
#endif
3.2 代码分析
上述代码中设置了结构体STRUCT_HX711_TYPEDEF用于配置传感器的自有属性:
/* 结构体 用于实现HX711本身的属性 */
typedef struct {
ENUM_HX711_GAIN_TYPEDEF gain; /* 增益 */
bool isTare; /* 判断传感器数据是否是皮重 true-是 false-否 */
float k; /* 比例系数 */
float b; /* 补偿值 */
float tare; /* 皮重 */
float actual; /* 实重 */
} STRUCT_HX711_TYPEDEF;
其中结构体成员变量k,用于配置传感器与实际重量之间的比例关系,即传感器系数。根据传感器数据手册的时序图得到的传感器数据,其实质上是一个24位的ADC数据值,与实际的物体重量其本质上并没有关联,我们可以认为的让他们之间存在一个一阶线性相关,即:
y = kx + b
因此我们可以得到如下的公式:
实际重量(w)= 比例系数(k)* 读取的传感器adc数据 + 补偿值(b)
那么如何计算这个参数k呢?
1. 首先我们可以在hx711.c中将结构体初始化中的成员变量(k)初始化为1,然后我们不在称重传感器上放置任何物体,那么我们通过hx711_get_tare_weight()函数得到的皮重数据其本质上为传感器本身的adc值。
2. 然后找到一个可以确定重量的物体,比如我们的手机(在官网上可以查到手机的参考重量),然后放置在称重传感器上,此时通过hx711_get_actual_weight()函数得到的实际重量数据为传感器本身的adc值(k值为1,即此时调用该函数得到的重量数据为ADC模数转换值,并不是具体重量)。
3. 我们可以通过如下公式将得到的两个传感器数据值相减,算出参数k的值。公式如下:
比例系数(k)=(手机实际重量(w1) - 电子秤皮重(w0)) / (手机传感器adc数值 - 皮重adc值)
4. 这样得到的比例系数(k)值就是适配与当前传感器模块的参数了,我们叫它传感器系数。
5. 补偿值(b)的确定可以由物体在测量过程中的实际表现酌情设置。
注意:测重的adc数据值可能会跳变的比较快,这是由于读取的传感器精度决定的,由于传感器是24位高精度,因此细小的变动都会导致传感器数据值发生改变。所以我们只需要取跳变中的任意一个时刻的数据值即可。
四、测试实例
1. 首先需要调用hx711_init()函数用于初始化引脚及结构体;
2. 然后需要先调用获取皮重函数hx711_get_tare_weight()获取一次物体的皮重
3. 接着就可以使用hx711_get_actual_weight函数获取物体的实时重量了