Arduino+HX711称重传感器实战:从零打造高精度厨房电子秤(附校准技巧)

不知道你有没有过这样的经历:照着菜谱准备食材,上面写着“面粉150克”,你手忙脚乱地翻出家里那个用了好几年、屏幕都有些模糊的厨房秤,放上碗,按下归零,小心翼翼地倒面粉,结果数字跳来跳去,最后停在152克——算了,差不多就行。但如果你是个对烘焙精度有要求的爱好者,或者正在严格控制饮食需要精确计量,这种“差不多”可能会毁掉一整盘马卡龙,或是让每日的营养摄入计算失去意义。

市面上的厨房电子秤,精度稍好一些的价格就不菲,而且功能单一。有没有可能自己动手做一个?成本更低、精度更高,还能根据自己的需求定制功能,比如一键切换克与盎司、记录每次的称重数据,甚至接入智能家居系统?答案是肯定的,而且门槛远比你想象的低。

今天,我们就用最常见的Arduino开发板,搭配一枚小小的HX711模块和一个称重传感器,从零开始,打造一台专为厨房环境优化的高精度电子秤。这不仅仅是简单的模块拼接,我们会深入探讨如何针对厨房常见的温湿度变化、台面轻微震动等干扰进行优化,分享真正可靠的砝码校准技巧,并教你用亚克力板制作一个既美观又防潮的称重托盘。你会发现,创客的乐趣不仅在于结果,更在于这个亲手搭建、调试并最终让一个想法“活”起来的过程。

1. 核心器件解析:为什么是HX711与称重传感器?

在开始动手之前,我们有必要先搞清楚手中的“武器”。市面上测量重量的方案很多,为什么DIY电子秤领域,HX711和电阻应变式称重传感器的组合几乎成了标准答案?这背后是精度、成本与易用性的完美平衡。

称重传感器,本质上是一个电阻应变片构成的电桥。你可以把它想象成一根微型的“弹簧”。当它受到压力时,内部的金属丝会被拉伸或压缩,导致电阻值发生极其微小的变化。一个典型的称重传感器通常有四个应变片,连接成惠斯通电桥。在没有负载时,电桥平衡,输出为零;当放置重物时,桥臂电阻变化,会输出一个与重量成正比的毫伏级差分电压信号。

注意:厨房常用的传感器量程一般在1kg至5kg之间。量程并非越大越好,选择一个接近你常用重量范围的传感器,能获得更好的相对精度。例如,主要称量调料和面粉,1kg量程比5kg更合适。

这个信号有多微弱呢?可能只有0-几毫伏。Arduino开发板自带的ADC(模拟数字转换器)引脚,虽然能读取模拟电压,但其分辨率和对于微小信号的捕捉能力远远不够。直接连接,读数会充满噪声且无法反映真实重量。这时,就需要一个专业的“信号放大器”和“高精度ADC”——这就是HX711模块的舞台。

HX711是一款专为电子秤设计的24位A/D转换器芯片。它的核心价值在于:

  • 高精度24位ADC:提供高达2^24(约1677万)个数字输出代码,能分辨极其微小的重量变化。
  • 集成可编程增益放大器(PGA):提供32、64、128倍增益可选,能直接将传感器输出的微弱信号放大到适合处理的水平。
  • 内置稳压电路:可以直接为传感器提供稳定的激励电压,简化外部电路。
  • 简单的二线串行接口:只需连接单片机的两个数字IO口进行通信,极大简化了接线。

两者结合,HX711负责将传感器“细微的叹息”放大并清晰地“翻译”成数字语言,交给Arduino处理。下表对比了DIY方案与普通市售厨房秤的核心差异:

特性维度DIY Arduino + HX711方案普通市售厨房秤
核心精度潜力依赖HX711的24位ADC,理论精度高,可达0.1g级别取决于商用方案,中端产品多在0.1g-1g
成本低(传感器+HX711+Arduino Nano,总成本可控制在50元内)中低端产品价格相近,高精度产品昂贵
灵活性极高,可自由编程,添加单位切换、数据记录、蓝牙/WiFi上传等功能固化功能,无法扩展
可维护性模块化设计,哪个部分坏了换哪个,传感器可单独更换一体化设计,损坏通常需整体更换
学习价值涉及电路、传感器、编程、校准等全方位实践无

所以,选择这套方案,你得到的不仅仅是一个秤,更是一个可无限扩展的智能硬件平台。接下来,我们就进入实战环节。

2. 硬件搭建:从散件到原型的每一步

准备好以下材料,我们就可以开始组装了。清单力求精简,都是容易购得的模块:

  • 主控:Arduino Uno 或 Nano × 1(推荐Nano,体积更小巧)
  • 核心模块:HX711称重模块 × 1
  • 传感器:电阻应变式称重传感器(推荐1kg或5kg,根据需求选择)× 1
  • 显示:0.96寸OLED显示屏(I2C接口)或 LCD1602(I2C接口)× 1(OLED更省空间且清晰)
  • 结构件:亚克力板(用于制作托盘和支架)、M3螺丝螺母套件、杜邦线(公对公、公对母)若干
  • 校准工具:标准砝码(如100g、200g、500g。若无,可用已知重量的未开封包装食品替代,如500ml矿泉水约500g)

2.1 电路连接:一张图看懂的接线逻辑

接线是项目的基础,遵循“电源-信号-地”的清晰路径。下图清晰地展示了所有模块间的连接关系,建议在焊接或插接前先对照检查一遍。

[接线关系示意图 - 文字描述版]

称重传感器 (4线)      ->      HX711模块
红色 (E+ 或 VCC)      ->      E+
黑色 (E- 或 GND)      ->      E-
绿色/白色 (A+ 或 S+)  ->      A+
白色/绿色 (A- 或 S-)  ->      A-

HX711模块             ->      Arduino Nano
VCC                  ->      5V
GND                  ->      GND
DT (数据)            ->      D2 (可自定义)
SCK (时钟)           ->      D3 (可自定义)

OLED显示屏 (I2C)      ->      Arduino Nano
VCC                  ->      5V
GND                  ->      GND
SDA                  ->      A4 (或 Nano 的 D4)
SCL                  ->      A5 (或 Nano 的 D5)

提示:HX711的DT和SCK引脚可以连接到Arduino任意空闲的数字引脚,只需在后续代码中相应修改即可。使用I2C显示屏时,务必确保SDA和SCL正确连接。

2.2 机械结构制作:亚克力托盘的防潮与稳定设计

厨房环境多水、多油,一个裸露的传感器和电路板显然不行。我们需要为它打造一个“家”。

设计思路:采用双层亚克力板结构。底层固定Arduino和HX711模块;上层作为称重托盘,通过四根立柱与底层连接,传感器则安装在底层中心,其受力点与托盘中心刚性连接。这样,托盘上的重量会直接、垂直地传递给传感器,避免侧向力影响精度。

制作步骤:

  1. 设计图纸:使用激光切割软件(如LaserCAD)或在线工具,设计两块大小相同的亚克力板(如10x10cm),以及四根高度约2-3cm的亚克力立柱。在底板中心开孔,用于固定传感器;在四角开孔,用于安装立柱和固定主板。
  2. 切割与组装:
    • 将传感器用螺丝固定在底板中心。
    • 将四根立柱用螺丝固定在底板四角。
    • 将Arduino和HX711用铜柱或螺丝固定在底板上。
    • 将顶板(托盘)通过螺丝与立柱上端固定。关键一步:在传感器受力点与托盘之间,需要用一个小的金属垫片或专用的传力件进行刚性连接,确保力能有效传递。
  3. 防潮处理:在所有电路板表面喷涂三防漆(绝缘漆),这是保护电路免受厨房水汽侵蚀最有效的方法。传感器本身通常有密封,但接线处可以用热熔胶或硅胶做轻微密封。

这个自制的结构不仅解决了放置问题,其刚性连接方式也比许多市售DIY套件的软性连接(如用塑料柱支撑)精度更高、重复性更好。

3. 软件驱动与基础读数:让Arduino“感知”重量

硬件就绪后,我们需要让Arduino学会与HX711对话,并计算出重量值。这里会涉及库的安装和基础代码的编写。

3.1 安装HX711库

打开Arduino IDE,依次点击 工具 -> 管理库...,在搜索框中输入“HX711”,找到由 Bogdan Necula 等人维护的 HX711 库进行安装。这个库成熟稳定,函数丰富。

3.2 编写初始测试代码

我们先写一个最简单的程序,读取HX711的原始ADC值,验证硬件连接是否正常。

#include "HX711.h"

// 定义HX711连接的引脚 (DT, SCK)
HX711 scale;
#define LOADCELL_DOUT_PIN  2
#define LOADCELL_SCK_PIN   3

void setup() {
  Serial.begin(115200);
  Serial.println("HX711 初始化和测试");

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  // 初始化库,默认使用通道A,增益128
}

void loop() {
  if (scale.is_ready()) {
    long rawReading = scale.read(); // 获取原始ADC值
    Serial.print("原始读数: ");
    Serial.println(rawReading);
  } else {
    Serial.println("HX711未就绪,请检查连接。");
  }
  delay(500);
}

上传代码后,打开串口监视器(波特率设为115200)。你应该能看到一串不断变化的数值。此时,用手轻轻按压或松开托盘,观察数值应有大幅度的增减变化。这说明传感器和HX711的通信是正常的。这个原始值本身没有单位,它只是一个与施加的力成正比的数字。

4. 校准的艺术:从原始值到精确克重的关键一跃

校准是电子秤制作的灵魂,也是最容易出错的环节。校准的目的,是找到原始ADC读数与实际重量之间的线性比例关系(斜率)和零点偏移(截距)。公式很简单:重量 = (原始读数 - 偏移量) / 比例系数。

4.1 单点校准法(快速上手)

对于厨房秤,如果量程不大且对绝对精度要求不是极端苛刻,单点校准是最高效的方法。你需要一个已知重量的标准砝码(如100g)。

操作流程:

  1. 确保秤盘空载,上传并运行以下校准代码。
  2. 根据串口提示,先进行“去皮”(Tare),此时偏移量被设定。
  3. 根据提示放上已知重量的砝码。
  4. 程序会自动计算比例系数,并显示当前测量值。
#include "HX711.h"

HX711 scale;
#define LOADCELL_DOUT_PIN  2
#define LOADCELL_SCK_PIN   3

// 已知砝码的重量(单位:克)
const float KNOWN_WEIGHT = 100.0; // 例如100g砝码
float scaleFactor = 1.0; // 比例系数,将通过校准得出

void setup() {
  Serial.begin(115200);
  Serial.println("=== HX711 单点校准程序 ===");

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(); // 初始化比例系数为1
  scale.tare();      // 去皮,将当前读数设为偏移量

  Serial.println("请移走秤盘上所有物品...");
  delay(3000);
  Serial.println("正在计算零点偏移...完成。");

  Serial.println("现在请将已知重量的砝码(" + String(KNOWN_WEIGHT) + "g)放在秤盘中心。");
  Serial.println("放置好后,请等待5秒...");
  delay(5000);

  long rawReadingWithWeight = scale.read_average(10); // 读取10次取平均
  scaleFactor = rawReadingWithWeight / KNOWN_WEIGHT; // 计算比例系数

  Serial.print("计算得到的比例系数 (scale factor) 为: ");
  Serial.println(scaleFactor, 6); // 打印6位小数
  scale.set_scale(scaleFactor);   // 设置比例系数

  Serial.println("校准完成!当前读数:");
}

void loop() {
  if (scale.is_ready()) {
    float currentWeight = scale.get_units(5); // 读取5次平均,并应用比例系数
    Serial.print("重量: ");
    Serial.print(currentWeight, 1); // 显示1位小数
    Serial.println(" g");
  }
  delay(1000);
}

4.2 两点校准法(追求高精度)

单点校准假设零点绝对准确且线性完美。但传感器可能存在微小的非线性或零点温漂。两点校准通过“空载”和“满量程附近一个点”来确定一条更准确的直线,能更好地补偿这些误差。

操作流程:

  1. 空载状态下,记录一个原始读数作为零点参考(或直接tare())。
  2. 放置一个接近量程50%-80%的已知重物(如对于1kg传感器,放500g砝码)。
  3. 程序通过两点的差值计算比例系数。

核心计算逻辑如下:

// 假设:
// raw_zero: 空载时的原始读数
// raw_weight: 放置已知重量KNOWN_WEIGHT时的原始读数
// 则比例系数 scaleFactor = (raw_weight - raw_zero) / KNOWN_WEIGHT;

在实际代码中,我们可以先tare()得到raw_zero(此时为0),然后读取砝码的raw_weight,计算scaleFactor。许多HX711库的get_units()函数内部已经封装了这个计算。

4.3 厨房环境下的校准避坑指南

  • 预热:电子元件通电后性能会逐渐稳定。校准前,让系统通电工作5-10分钟。
  • 环境稳定:避免在校准和称重时有风吹、台面震动。将秤放在水平、坚固的台面上。
  • 砝码放置:始终将砝码放在托盘中心,确保受力垂直。
  • 温漂补偿:如果发现早上和晚上的读数有微小差异(特别是金属传感器受温度影响),可以考虑在代码中存储一个温度补偿系数,但这属于进阶优化。
  • 验证:校准后,用多个不同重量的砝码(如50g, 200g)进行验证,检查线性度。

校准完成后,那个至关重要的scaleFactor(比例系数)应该被记录下来,并硬编码到你的最终应用程序中,避免每次上电都需要重新校准。

5. 功能增强:打造一个实用的厨房智能秤

基础称重实现了,但一个真正好用的厨房秤还需要一些贴心功能。让我们用代码为它赋能。

5.1 单位切换(克/盎司/毫升)

对于烘焙和跨国菜谱,单位切换是刚需。我们可以在程序中预设转换系数,并通过按钮切换。

#include <Wire.h>
#include <Adafruit_SSD1306.h> // OLED库
#include "HX711.h"

HX711 scale;
#define LOADCELL_DOUT_PIN  2
#define LOADCELL_SCK_PIN   3
#define BUTTON_PIN         4 // 用于切换单位的按钮

Adafruit_SSD1306 display(128, 64, &Wire, -1);

enum WeightUnit { GRAM, OUNCE, ML_WATER }; // 枚举定义单位
WeightUnit currentUnit = GRAM;
float currentWeight = 0.0;

// 转换系数
const float GRAMS_PER_OUNCE = 28.3495;
const float ML_PER_GRAM_WATER = 1.0; // 简化:1克水 ≈ 1毫升

void setup() { /* 初始化scale, display, 校准scaleFactor */ }

void loop() {
  // 检测按钮切换单位
  if (digitalRead(BUTTON_PIN) == LOW) {
    delay(50); // 消抖
    if (digitalRead(BUTTON_PIN) == LOW) {
      switch(currentUnit) {
        case GRAM: currentUnit = OUNCE; break;
        case OUNCE: currentUnit = ML_WATER; break;
        case ML_WATER: currentUnit = GRAM; break;
      }
      while(digitalRead(BUTTON_PIN) == LOW); // 等待松开
    }
  }

  // 读取重量
  currentWeight = scale.get_units(5);

  // 单位转换
  float displayValue = currentWeight;
  String unitStr = "g";
  switch(currentUnit) {
    case OUNCE:
      displayValue = currentWeight / GRAMS_PER_OUNCE;
      unitStr = "oz";
      break;
    case ML_WATER:
      displayValue = currentWeight * ML_PER_GRAM_WATER; // 假设称的是水
      unitStr = "ml";
      break;
  }

  // 在OLED上显示
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0,0);
  display.print(displayValue, 1); // 显示1位小数
  display.println(unitStr);
  display.display();

  delay(200);
}

5.2 去皮与累计功能

去皮功能在混合多种食材时非常有用。累计功能则可以计算连续添加物品的总重。

// 在全局变量中定义
float tareOffset = 0;
float cumulativeWeight = 0;
bool cumulativeMode = false;

// 在loop中检测两个不同的按钮
void loop() {
  // ... 单位切换代码 ...

  float rawWeight = scale.get_units(5);

  // 按钮A:去皮
  if (digitalRead(BUTTON_A_PIN) == LOW) {
    tareOffset = rawWeight;
    scale.set_offset(scale.get_offset() + tareOffset); // 库函数方式更佳
  }

  // 按钮B:累计模式开关/清零
  if (digitalRead(BUTTON_B_PIN) == LOW) {
    delay(50);
    if (digitalRead(BUTTON_B_PIN) == LOW) {
      if (!cumulativeMode) {
        cumulativeMode = true;
        cumulativeWeight = 0;
      } else {
        // 长按清零累计值
        cumulativeWeight = 0;
      }
      while(digitalRead(BUTTON_B_PIN) == LOW);
    }
  }

  float netWeight = rawWeight - tareOffset;
  if (netWeight < 0.1) netWeight = 0; // 忽略微小噪声

  if (cumulativeMode && netWeight > 0.5) { // 检测到稳定新物品
    cumulativeWeight += netWeight;
    // 这里可以添加逻辑,在物品稳定后(如重量2秒不变)才累加
  }

  // 显示净重或累计重...
}

5.3 数据记录与简单统计(进阶)

通过添加一个实时时钟模块(如DS3231)和SD卡模块,你可以让电子秤记录每次称重的时间、物品和重量,生成简单的日志文件,用于分析饮食或烘焙实验。

6. 优化与调试:解决那些烦人的小问题

即使按照步骤操作,你可能还是会遇到一些典型问题。这里提供一份快速排查指南。

  • 问题:读数不稳定,数值跳动。

    • 检查电源:确保Arduino和HX711供电稳定。尝试使用外部5V电源适配器为Arduino供电,而非USB口。
    • 检查接线:DT和SCK信号线是否过长?尽量缩短至15cm内。传感器线是否可靠连接?
    • 软件滤波:在代码中增加软件滤波。scale.get_units(10)中的参数10就是读取次数,增大它(如20)可以平滑数据,但会降低响应速度。也可以使用移动平均滤波。
      // 简单的移动平均滤波示例
      #define FILTER_SIZE 5
      float weightBuffer[FILTER_SIZE];
      int bufferIndex = 0;
      float filteredWeight = 0;
      
      float raw = scale.get_units(3);
      weightBuffer[bufferIndex] = raw;
      bufferIndex = (bufferIndex + 1) % FILTER_SIZE;
      
      filteredWeight = 0;
      for (int i=0; i<FILTER_SIZE; i++) {
        filteredWeight += weightBuffer[i];
      }
      filteredWeight /= FILTER_SIZE;
      
  • 问题:放上重物后读数归零或为负。

    • 传感器方向:确认传感器受力方向正确。通常传感器上有一个箭头标记,指示受力面。
    • 接线错误:检查传感器四根线(红黑绿白)与HX711模块的E+, E-, A+, A-是否一一对应。不同厂家颜色编码可能不同,以说明书为准。
  • 问题:校准后,换一个重物测试误差很大。

    • 线性度:你的传感器可能在量程内线性不佳。尝试用两点校准,并使用量程中段的砝码进行校准。
    • 机械结构:检查托盘与传感器的连接是否牢固,是否有翘曲或侧向力。确保结构刚性。
  • 问题:每次断电重启后需要重新校准。

    • 保存系数:将最终校准得到的scaleFactor和offset(通过scale.get_offset()获得)保存到Arduino的EEPROM中。上电时从EEPROM读取并设置。
      #include <EEPROM.h>
      struct ScaleCalibration {
        float scaleFactor;
        long offset;
      } calData;
      
      void saveCalibrationToEEPROM() {
        EEPROM.put(0, calData);
      }
      
      void loadCalibrationFromEEPROM() {
        EEPROM.get(0, calData);
        scale.set_scale(calData.scaleFactor);
        scale.set_offset(calData.offset);
      }
      

完成以上所有步骤后,你得到的将不再是一个简单的测量工具。它是一台完全按照你的厨房习惯定制的高精度秤,你了解它的每一个细节,可以随时修复或升级它。从一堆散件到一个稳定工作的设备,这种成就感是购买任何成品都无法替代的。更重要的是,在这个过程中积累的关于传感器、信号处理、校准和结构设计的经验,会让你在面对下一个创意项目时更加游刃有余。

更多推荐