云计算百科
云计算领域专业知识百科平台

STM32+HX711称重模块+OLED完整称重项目讲解

摘要

本篇博文讲解基于STM32F103C8T6、HX711称重采集模块、电阻应变式称重传感器以及OLED显示屏实现的电子秤项目。从硬件接线、传感器原理、HX711芯片工作逻辑、芯片串行读取时序,到STM32程序去皮、标定算法、实物演示、故障现象全部讲解,适合单片机课程实验、电子秤入门学习。

整体工作逻辑:称重传感器把重量转换成微小电压变化,HX711把微弱电压转换成数字计数,STM32经过去皮、砝码标定,将原始计数换算成实际重量,最后OLED屏幕显示重量数据。

视频讲解:STM32+HX711称重模块+OLED完整称重项目讲解

一、项目演示目标

本项目实现一套10kg量程简易电子秤。 完整信号流转:物体施加重量 → 称重传感器弹性体形变,应变片电阻改变 → 惠斯通电桥输出毫伏级差分电压 → HX711内部放大+24位ADC模数转换输出原始计数 → STM32读取计数,完成去皮零点补偿、砝码标定换算为克单位 → OLED屏幕输出重量数值,异常情况输出故障提示。

⚠️注意:本项目不使用STM32片内ADC,模数转换全部由HX711芯片完成。

二、硬件接线说明

主控芯片:STM32F103C8T6最小系统板 外设:HX711模块、10kg电阻应变式称重传感器、0.96寸I2C OLED显示屏

HX711与STM32接线

模块引脚STM32引脚功能说明
VCC 5V HX711模块供电
SCK PB3 STM32输出时钟,对应芯片PD_SCK
DT PB4 HX711数据输出DOUT,数据就绪信号
GND GND 必须和主控共地

补充说明:PB3、PB4引脚上电默认复用JTAG调试功能,代码初始化函数HX711_Init()内部会关闭JTAG,保留SWD下载功能,再把引脚配置为普通GPIO口。更换HX711模块时,需要确认DT引脚输出电平是否和STM32兼容,不能只看模块供电是5V。

OLED与STM32接线(软件I2C)

模块引脚STM32引脚功能说明
VCC 5V OLED供电
SCL PB8 I2C时钟线
SDA PB9 I2C数据线
GND GND 共地

称重传感器接HX711模块

传感器接到HX711模块:E+、E‑是惠斯通电桥激励电源端;A+、A‑为A通道差分信号输入端。

传感器的导线颜色没有统一标准,接线严格按照模块丝印标识,不要依靠导线颜色判断。

三、电阻应变式称重传感器工作原理

我们使用的是电阻应变式传感器。当物体压在传感器上,金属弹性体发生微小弹性形变,粘贴在弹性体上的应变片同步伸长或者压缩,应变片电阻发生极其微小变化。

四个应变片组成惠斯通电桥,把电阻变化转化为差分电压输出。

  • 电桥激励端口:E+、E‑
  • 差分信号输出端口:A+、A‑
  • 输出差分电压:V差 = V(A+) – V(A‑)

输出电压属于毫伏级别微弱信号,STM32单片机无法直接采集,因此必须依靠HX711做信号放大和AD转换。

小知识点:加载重物之后,原始计数变大还是变小,取决于A+、A‑接线以及传感器受力方向。代码中标定系数支持负值,适配正负计数情况。 ⚠️10kg只是传感器物理额定量程,不是固定换算参数。即使同样是10kg传感器,零点、每千克对应的计数值都不一样,每一套硬件都需要重新标定。

四、HX711芯片工作原理

HX711是专门针对称重传感器设计的24位ADC芯片。

  • 称重传感器输出的差分电压送入芯片内部可编程增益放大器,本项目固定使用A通道,增益128倍;
  • 放大之后的模拟信号送入24位ADC,输出24位补码格式原始计数;
  • 通过DT、SCK两根信号线串行把计数传输给STM32单片机。
  • 重点提醒:24位只是输出数字位数,不等于实际称重精度就可以达到2的24次方分之一。实际精度会受机械安装、电源噪声、温度、标定误差、振动干扰影响。

    HX711转换输出速率由硬件RATE引脚决定,可以为10次/秒或者80次/秒,本项目没有软件控制RATE引脚,采样速率由购买的HX711模块硬件电路决定。

    五、STM32读取HX711时序逻辑

    两根信号线:

    • DT(DOUT):数据就绪引脚,高电平代表AD转换未完成;DT拉低,代表数据就绪,可以读取。
    • SCK(PD_SCK):单片机输出时钟信号。

    单次读取完整流程:

  • SCK保持低电平,循环等待DT引脚变为低电平;程序设置最大等待时间200ms,防止断线卡死。
  • STM32输出24个时钟脉冲,逐位读取24bit数据,高位优先。
  • 额外发送第25个时钟脉冲,用来配置下一次采集:选择A通道、128倍增益。
  • 将读到的24位补码转换为32位整型int32_t原始计数值;超时就返回超时错误码。
  • 补充:第25/26/27脉冲分别对应不同通道增益:A通道128倍、B通道32倍、A通道64倍,本项目固定使用25脉冲。 SCK引脚高电平维持时间不能超过60μs,否则HX711会进入掉电休眠,驱动代码读取期间临时关闭中断,读完再恢复中断。

    六、去皮零点、标定换算算法

    读到的原始计数不能直接作为重量,必须获取零点和标定系数两个关键参数。

  • 零点R0:秤台空载的时候,多次采样取平均得到空载计数,也就是去皮。项目中HX711_Tare(8)代表采集8次取平均作为零点。
  • 标定系数K:先完成去皮,放上已知重量砝码。调用HX711_Calibrate(砝码克数,8),读取8次采样做平均,计算每千克对应的计数值。
  • 计算公式:

    K(计数/千克) = (砝码原始计数 – R0) × 1000 / M(砝码克数)
    实际物体质量(克) = (当前原始计数 – R0) ×1000 / K

    举个例子方便理解: 空载零点R0=120000;放1000g砝码读到计数920000; K=(920000‑120000)*1000 /1000 =800000 计数每千克; 当采集原始计数等于520000,重量=(520000‑120000)*1000 / 800000 =500g。

    程序上电之后等待2s自动执行8次采样去皮,上电时刻秤盘上面必须清空,不能放物体。 当前代码只有去皮、标定的时候做8次平均;主循环显示读数没有滤波,如果想要抑制数字跳动,可以自己增加滑动平均滤波,但会牺牲刷新速度。

    七、源代码

    下面包含HX711、main代码

    7.1 工程文件说明

    文件函数/宏功能
    HARDWARE/HX711/HX711.C HX711_Init() 初始化PB3 PB4,关闭JTAG保留SWD调试
    HX711.C HX711_ReadRaw() 读取HX711原始24位计数,带超时保护
    HX711.C HX711_Tare() 空载多次采样,记录零点R0
    HX711.C HX711_Calibrate() 砝码标定,计算每千克计数值K
    HX711.C HX711_ReadWeightG() 原始计数换算得到重量(单位g)
    HARDWARE/HX711/HX711.H HX711_DEFAULT_COUNTS_PER_KG 默认标定系数宏定义
    USER/main.c Demo_ShowWeight() 重量数据、报错信息绘制输出到OLED屏幕

    main.c代码

    /* STM32F103C8T6 + HX711 10 kg load cell teaching demonstration.
    * HX711: PD_SCK = PB3, DOUT = PB4. OLED: SCL = PB8, SDA = PB9.
    * Keep the platform empty during startup tare. The displayed grams are
    * estimates until HX711_DEFAULT_COUNTS_PER_KG is calibrated for this cell. */
    #include "delay.h"
    #include "sys.h"
    #include "oled.h"
    #include "HX711.H"

    static void Demo_ShowText(const char *message, const char *detail)
    {
    OLED_Clear();
    OLED_ShowString(0u, 0u, (u8 *)"HX711 10KG SCALE", 12u);
    OLED_ShowString(0u, 13u, (u8 *)"PB3 SCK PB4 DT", 12u);
    OLED_ShowString(0u, 28u, (u8 *)message, 12u);
    OLED_ShowString(0u, 52u, (u8 *)detail, 12u);
    OLED_Refresh();
    }

    static void Demo_ShowWeight(int32_t weight_g)
    {
    uint32_t magnitude;

    if ((weight_g > (int32_t)HX711_CAPACITY_G) ||
    (weight_g < -(int32_t)HX711_CAPACITY_G)) {
    Demo_ShowText("OVER 10KG RANGE", "REMOVE LOAD");
    return;
    }

    magnitude = (weight_g < 0) ? (uint32_t)(-weight_g) : (uint32_t)weight_g;
    OLED_Clear();
    OLED_ShowString(0u, 0u, (u8 *)"HX711 10KG SCALE", 12u);
    OLED_ShowString(0u, 13u, (u8 *)"WEIGHT (EST):", 12u);
    OLED_ShowChar(0u, 29u, (weight_g < 0) ? '-' : '+', 16u);
    OLED_ShowNum(8u, 29u, magnitude, 5u, 16u);
    OLED_ShowChar(48u, 29u, 'g', 16u);
    OLED_ShowString(0u, 52u, (u8 *)"CALIBRATE FOR GRAMS", 12u);
    OLED_Refresh();
    }

    int main(void)
    {
    int32_t weight_g;
    HX711_Status status;

    delay_init();
    NVIC_Configuration();
    OLED_Init();
    OLED_ColorTurn(0u);
    OLED_DisplayTurn(0u);
    HX711_Init();

    Demo_ShowText("REMOVE ALL LOAD", "TARING IN 2 SEC");
    delay_ms(1000u);
    delay_ms(1000u);
    while (HX711_Tare(8u) != HX711_OK) {
    Demo_ShowText("HX711 NOT READY", "CHECK PB3 / PB4");
    delay_ms(500u);
    }

    while (1) {
    status = HX711_ReadWeightG(&weight_g);
    if (status == HX711_OK) {
    Demo_ShowWeight(weight_g);
    } else {
    Demo_ShowText("READ TIMEOUT", "CHECK HX711 WIRING");
    }
    delay_ms(100u);
    }
    }

    HX711.C代码

    #include "HX711.H"

    #include "delay.h"
    #include "stm32f10x.h"
    #include "stm32f10x_gpio.h"
    #include "stm32f10x_rcc.h"

    /* HX711 使用 GPIO 模拟时序,不占用硬件 SPI 外设。 */
    #define HX711_PORT GPIOB
    #define HX711_SCK_PIN GPIO_Pin_3
    #define HX711_DOUT_PIN GPIO_Pin_4

    static int32_t hx711_offset; /* 空载零点,单位为原始计数 */
    static int32_t hx711_counts_per_kg = HX711_DEFAULT_COUNTS_PER_KG;
    static uint8_t hx711_tared; /* 置 1 后才允许读取重量或进行砝码标定 */

    /* 配置时钟输出和数据输入;PB3/PB4 复用为 GPIO 后仍可使用 SWD。 */
    void HX711_Init(void)
    {
    GPIO_InitTypeDef gpio;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    /* SCK 持续高电平超过 60 us 会使 HX711 进入掉电状态,因此先置低。 */
    GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN);
    gpio.GPIO_Pin = HX711_SCK_PIN;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(HX711_PORT, &gpio);

    gpio.GPIO_Pin = HX711_DOUT_PIN;
    gpio.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(HX711_PORT, &gpio);

    hx711_offset = 0;
    hx711_tared = 0u;
    hx711_counts_per_kg = HX711_DEFAULT_COUNTS_PER_KG;
    }

    /* 读取一次原始数据:等待就绪、读取 24 位,再发送增益选择脉冲。 */
    HX711_Status HX711_ReadRaw(int32_t *raw, uint16_t timeout_ms)
    {
    uint32_t bits = 0u;
    uint32_t primask;
    uint8_t i;

    if (raw == 0) {
    return HX711_BAD_ARGUMENT;
    }

    /* 转换完成后 DT 变低。逐毫秒等待,断线时按设定时间返回超时。 */
    while (GPIO_ReadInputDataBit(HX711_PORT, HX711_DOUT_PIN) != Bit_RESET) {
    if (timeout_ms == 0u) {
    return HX711_TIMEOUT;
    }
    delay_ms(1u);
    –timeout_ms;
    }

    /* 时钟高电平期间若被中断打断,可能超过 60 us 并触发掉电。
    * 短暂关闭中断完成传输,结束后恢复调用前的中断状态。 */
    primask = __get_PRIMASK();
    __disable_irq();
    for (i = 0u; i < 24u; ++i) {
    GPIO_SetBits(HX711_PORT, HX711_SCK_PIN);
    delay_us(1u);
    bits = (bits << 1) |
    (GPIO_ReadInputDataBit(HX711_PORT, HX711_DOUT_PIN) == Bit_SET ? 1u : 0u);
    GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN);
    delay_us(1u);
    }
    /* 第 25 个脉冲选择下一次转换使用 A 通道、128 倍增益。 */
    GPIO_SetBits(HX711_PORT, HX711_SCK_PIN);
    delay_us(1u);
    GPIO_ResetBits(HX711_PORT, HX711_SCK_PIN);
    if (primask == 0u) {
    __enable_irq();
    }

    /* 将 24 位补码符号扩展为 int32_t,保留负值。 */
    *raw = (int32_t)bits;
    if ((bits & 0x800000u) != 0u) {
    *raw -= 0x1000000L;
    }
    return HX711_OK;
    }

    /* 多次采样求平均;使用 64 位累加器防止原始计数求和溢出。 */
    static HX711_Status HX711_ReadAverage(uint8_t samples, int32_t *average)
    {
    int64_t sum = 0;
    int32_t raw;
    uint8_t i;
    HX711_Status status;

    if ((samples == 0u) || (average == 0)) {
    return HX711_BAD_ARGUMENT;
    }
    for (i = 0u; i < samples; ++i) {
    status = HX711_ReadRaw(&raw, HX711_READ_TIMEOUT_MS);
    if (status != HX711_OK) {
    return status;
    }
    sum += raw;
    }
    *average = (int32_t)(sum / samples);
    return HX711_OK;
    }

    /* 空载采样并记录零点;失败时保留之前的零点。 */
    HX711_Status HX711_Tare(uint8_t samples)
    {
    int32_t offset;
    HX711_Status status = HX711_ReadAverage(samples, &offset);

    if (status == HX711_OK) {
    hx711_offset = offset;
    hx711_tared = 1u;
    }
    return status;
    }

    /* 已知质量标定:系数 = (加载计数 – 空载计数) × 1000 / 砝码克数。 */
    HX711_Status HX711_Calibrate(uint32_t known_mass_g, uint8_t samples)
    {
    int32_t loaded_raw;
    int64_t counts_per_kg;
    HX711_Status status;

    if ((known_mass_g == 0u) || (known_mass_g > HX711_CAPACITY_G)) {
    return HX711_BAD_ARGUMENT;
    }
    if (hx711_tared == 0u) {
    return HX711_NOT_TARED;
    }
    status = HX711_ReadAverage(samples, &loaded_raw);
    if (status != HX711_OK) {
    return status;
    }
    counts_per_kg = ((int64_t)loaded_raw – hx711_offset) * 1000 / known_mass_g;
    if ((counts_per_kg == 0) || (counts_per_kg > 0x7FFFFFFFL) ||
    (counts_per_kg < (-0x7FFFFFFFL – 1L))) {
    return HX711_BAD_CALIBRATION;
    }
    hx711_counts_per_kg = (int32_t)counts_per_kg;
    return HX711_OK;
    }

    /* 手动载入标定系数,可用于恢复预先测得的设备系数。 */
    HX711_Status HX711_SetCountsPerKg(int32_t counts_per_kg)
    {
    if (counts_per_kg == 0) {
    return HX711_BAD_CALIBRATION;
    }
    hx711_counts_per_kg = counts_per_kg;
    return HX711_OK;
    }

    /* 返回当前的计数/千克系数,便于记录标定结果。 */
    int32_t HX711_GetCountsPerKg(void)
    {
    return hx711_counts_per_kg;
    }

    /* 读取重量:先减去空载零点,再按当前系数换算为克。 */
    HX711_Status HX711_ReadWeightG(int32_t *weight_g)
    {
    int32_t raw;
    int64_t grams;
    HX711_Status status;

    if (weight_g == 0) {
    return HX711_BAD_ARGUMENT;
    }
    if (hx711_tared == 0u) {
    return HX711_NOT_TARED;
    }
    status = HX711_ReadRaw(&raw, HX711_READ_TIMEOUT_MS);
    if (status != HX711_OK) {
    return status;
    }
    grams = (((int64_t)raw – hx711_offset) * 1000) / hx711_counts_per_kg;
    if ((grams > 0x7FFFFFFFL) || (grams < (-0x7FFFFFFFL – 1L))) {
    return HX711_BAD_CALIBRATION;
    }
    *weight_g = (int32_t)grams;
    return HX711_OK;
    }

    HX711.H代码

    #ifndef HX711_H
    #define HX711_H

    #include <stdint.h>

    #ifdef __cplusplus
    extern "C" {
    #endif

    /* 接线:PB3 接 HX711 的 SCK,PB4 接 DT(DOUT)。
    * 驱动固定读取 A 通道、128 倍增益;每次读数共发送 25 个时钟脉冲。
    * PB3、PB4 原为 JTAG 引脚,初始化时关闭 JTAG,保留 SWD 调试功能。
    * 使用前先调用 delay_init(),再调用 HX711_Init()。
    * 10 kg 仅表示传感器量程,不能由量程推算标定系数。
    * 默认系数 755000 来自参考代码的估算,准确称重必须先空载去皮,
    * 再使用已知质量的砝码标定,并保存得到的系数。 */
    #define HX711_CAPACITY_G 10000u
    #define HX711_DEFAULT_COUNTS_PER_KG 755000L
    #define HX711_READ_TIMEOUT_MS 200u

    /* 各接口的返回状态;通信和标定失败时不会更新调用者的重量数据。 */
    typedef enum {
    HX711_OK = 0, /* 操作成功 */
    HX711_TIMEOUT, /* 等待 DT 变低超时 */
    HX711_BAD_ARGUMENT, /* 空指针、采样次数为零或砝码质量无效 */
    HX711_NOT_TARED, /* 尚未完成空载去皮 */
    HX711_BAD_CALIBRATION /* 系数为零或计算结果超出有效范围 */
    } HX711_Status;

    /* 初始化 PB3/PB4,关闭 JTAG 并保留 SWD;再次调用会清除去皮值和运行时标定值。 */
    void HX711_Init(void);

    /* 等待一次转换完成并读取 24 位有符号原始值。
    * raw:输出原始计数;timeout_ms:等待时间上限,单位毫秒。 */
    HX711_Status HX711_ReadRaw(int32_t *raw, uint16_t timeout_ms);

    /* 空载去皮:对 samples 次原始读数取平均,记录为零点;samples 不可为零。 */
    HX711_Status HX711_Tare(uint8_t samples);

    /* 已知质量标定:必须先去皮,再放上 known_mass_g 克砝码并取 samples 次平均。
    * 系数单位为“计数/千克”,断电后不会自动保存。 */
    HX711_Status HX711_Calibrate(uint32_t known_mass_g, uint8_t samples);

    /* 直接设置或读取标定系数;负系数可用于加重后原始计数下降的接线方向。 */
    HX711_Status HX711_SetCountsPerKg(int32_t counts_per_kg);
    int32_t HX711_GetCountsPerKg(void);

    /* 读取一次重量,单位为克;必须先去皮,结果会使用当前标定系数换算。 */
    HX711_Status HX711_ReadWeightG(int32_t *weight_g);

    #ifdef __cplusplus
    }
    #endif

    #endif

    7.2 OLED屏幕显示内容说明

    正常称重显示效果:

    HX711 10KG SCALE
    WEIGHT (EST):
    +00500g
    CALIBRATE FOR GRAMS

    • EST标识:代表使用默认标定系数,必须实物标定后读数才准确。
    • 重量超过10000g,屏幕提示:OVER 10KG RANGE,⚠️软件超限提示不能当做机械过载保护。
    • 初始化读取超时,屏幕打印:HX711 NOT READY
    • 运行过程读取超时:READ TIMEOUT

    八、实物演示章节

    本章节适合实验实操,按照步骤操作,可以复现整个电子秤功能,同时复现故障现象。

    📌实验准备物品

  • STM32F103C8T6最小系统板、下载器
  • HX711称重模块、10kg应变式称重传感器
  • 0.96寸I2C OLED显示屏
  • 杜邦线若干、已知重量砝码(推荐1kg砝码)
  • 秤盘、固定支架(固定称重传感器)
  • 📌正常标定演示步骤

  • 按照上面表格完成全部接线,所有模块GND共地,仔细核对PB3 PB4 PB8 PB9引脚不要接错。
  • 秤盘上面清空,不放置任何重物,上电,单片机自动执行上电去皮操作。如果需要重新去皮,把秤清空,按下复位按键。
  • 将1000g砝码平稳放置在秤盘上;代码中调用HX711_Calibrate(1000,8),执行标定。

    注意:原版main.c不会自动执行标定,需要自己添加代码触发标定动作。

  • 标定完成调用HX711_GetCountsPerKg()读取实际硬件得到的K值,把这个数值替换到头文件HX711_DEFAULT_COUNTS_PER_KG,重新编译烧录,系数掉电保存。也可以运行时调用Set函数设置,但是掉电丢失。
  • 拿其他已知重量物体放到秤盘,观察OLED读数,对比实际重量,校验准确度。读数偏差大优先检查接线、传感器固定、零点是否清空。
  • 📌现象复现演示(故障模拟)

  • 断线故障演示:断电,拔掉HX711的DT信号线,重新上电,OLED屏幕输出HX711 NOT READY,说明代码超时保护生效。实验结束恢复接线,恢复接线前务必断电,防止烧IO。
  • 空载数字轻微跳动:空载的时候看到数值小幅正负跳变,属于常见现象。原因来自机械振动、桌面抖动、电源噪声、零点温漂。解决思路:传感器牢固固定、增加软件滑动平均滤波、使用纹波更小电源。
  • 没有标定直接运行:直接烧录程序不放砝码标定,屏幕虽然会输出克数,但是数值是错的,只能够观察重量变化趋势,不能读取真实质量。
  • 实验小结:HX711的24位AD只是硬件能力,一套好用电子秤离不开良好机械结构、电源、正确标定。

    九、常见问题总结

  • Q:为什么硬件是10kg传感器,代码读数不准? A:10kg只是最大量程,每只传感器输出灵敏度不一样,必须实物砝码标定,不能直接使用默认系数。
  • Q:DT、SCK接好,但是一直报HX711 NOT READY? A:①没有共地;②PB3 PB4引脚没有关闭JTAG;③模块供电异常;④传感器E+E‑接线断开。
  • Q:读数不停跳动? A:检查传感器固定,不要悬空晃动;电源增加滤波电容;代码增加多次采样平均滤波。
  • Q:上电忘记拿开秤盘重物? A:零点R0会错误,去皮失败,所有重量全部读数错误,上电秤盘必须空载。
  • 十、最后总结

    称重传感器依靠惠斯通电桥,将重量转化为微弱差分电压;HX711完成信号放大和24位AD转换输出原始计数;STM32单片机通过去皮记录零点,砝码标定获取换算系数,最终把原始数字转化为真实重量,通过OLED展示。整个项目重点不在于AD位数,零点校准和实物标定才是电子秤可以准确测量的关键。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » STM32+HX711称重模块+OLED完整称重项目讲解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!