CZL-601微型称重传感器从入门到精通

CZL-601 微型称重传感器:从入门到精通


目录


一、初识 CZL-601

1.1 什么是称重传感器?

称重传感器(Load Cell)是一种将力/重量 转换为电信号 的换能器。CZL-601 是一款广泛应用的电阻应变式微型称重传感器,因体积小、精度高、成本低,被大量用于:

  • 🛒 电子秤、厨房秤、快递称
  • 🏭 工业自动化称重分拣
  • 🤖 机器人末端力控
  • 📦 智能货架、无人零售
  • 🔬 实验力学测量

1.2 外观与结构

CZL-601 外形为长方体铝合金构件,两端各有两个通孔(用于固定安装),中间贴有应变片并覆盖保护胶。常见的量程有 1kg、2kg、3kg、5kg、10kg、20kg、40kg 等。

复制代码
        ┌──────────────────────┐
        │  ○  固定端  ○        │  ← 螺丝固定孔
        │                      │
        │   [应变片区域]        │  ← 中间开槽处,贴有应变片
        │                      │
        │  ○  受力端  ○        │  ← 螺丝固定孔
        └──────────────────────┘
            四根引出线(红/黑/白/绿 或 红/黑/绿/白)

二、工作原理深入解析

2.1 电阻应变效应

CZL-601 基于**惠斯通电桥(Wheatstone Bridge)**原理工作。传感器内部集成了 4 个应变片(应变电阻),组成全桥电路:

复制代码
         Vcc (激励电压 +)
          │
      ┌───┴───┐
      │       │
     R1      R2         R1·R3 = R2·R4 (平衡时)
      │       │
      ├───┬───┤
      │   │   │
     R4  Sig+ Sig-      Sig+ / Sig- 为差分信号输出
      │   │   │
      └───┴───┘
          │
         GND (激励电压 -)
  • R1、R2、R3、R4:四个应变片电阻,通常为 1000Ω(1kΩ)
  • Vcc:激励电压,通常为 5V ~ 10V DC
  • Sig+ / Sig-:差分输出信号,单位为 mV(毫伏)

2.2 受力与输出关系

当传感器受到外力时:

  1. 受力端向下弯曲 → 上表面应变片受拉伸(电阻增大),下表面应变片受压缩(电阻减小)

  2. 电桥失去平衡 → 差分输出端产生微小电压

  3. 输出电压与施加的力成正比

    复制代码
     输出电压 (mV) = 灵敏度 (mV/V) × 激励电压 (V) × 载荷 / 额定量程

实例计算:CZL-601 灵敏度为 1.0 mV/V,激励电压 5V,量程 5kg,施加 2.5kg:

复制代码
    满量程输出 = 1.0 mV/V × 5V = 5 mV
    实际输出   = 5 mV × (2.5kg / 5kg) = 2.5 mV

可以看到输出信号非常微弱(毫伏级),因此必须使用高精度 ADC 进行放大和采集。


三、技术参数与选型指南

3.1 CZL-601 典型技术参数

参数项 典型值 说明
额定量程 1kg / 2kg / 3kg / 5kg / 10kg / 20kg / 40kg 按需选择
灵敏度 1.0 ± 0.15 mV/V 即每伏激励产生 1mV 输出
综合误差 ≤ 0.05% F.S F.S = 满量程
零点输出 ± 0.1 mV/V 空载时的初始输出
输入阻抗 1000 ± 50 Ω 红-黑 线间电阻
输出阻抗 1000 ± 50 Ω 绿-白 线间电阻
绝缘电阻 ≥ 2000 MΩ (50VDC)
激励电压(推荐) 5 ~ 10 V DC 最大不超过 15V
工作温度范围 -20℃ ~ +65℃
安全过载 120% F.S 短期可承受 150%

3.2 如何选择合适量程?

应用场景 推荐量程 备注
厨房秤、咖啡秤 1kg ~ 3kg 追求高分辨率
快递称、行李秤 10kg ~ 20kg 兼顾量程与精度
人体秤 40kg × 4 多点支撑
工业分拣 5kg ~ 20kg 考虑动态冲击

选型原则 :实际最大载荷应在额定量程的 60% ~ 80% 之间,预留安全余量。

3.3 常见线序定义

不同厂家线序有差异,务必以实际 datasheet 为准。常见两种:

线色 定义-A(常见) 定义-B
🔴 红色 激励+ (E+) 激励+ (E+)
⚫ 黑色 激励- (E-) 激励- (E-)
🟢 绿色 信号+ (S+) 信号- (S-)
⚪ 白色 信号- (S-) 信号+ (S+)

⚠️ 判別方法:用万用表测量电阻。

  • 红-黑 之间 ≈ 1000Ω(输入阻抗)
  • 绿-白 之间 ≈ 1000Ω(输出阻抗)
  • 其他组合 ≈ 750Ω 左右

四、硬件接线与信号调理

4.1 为什么不直接接单片机 ADC?

CZL-601 满量程输出仅 5mV 左右 (5V 激励下),而常见单片机 ADC(如 STM32 的 12 位 ADC,参考电压 3.3V)分辨率约为 0.8mV/LSB------这意味着整个量程只对应约 6~7 个 LSB,几乎无法使用。

解决方案 :使用专用 ADC 芯片,最流行的选择是 HX711

4.2 完整信号链

复制代码
┌─────────┐    ┌──────────┐    ┌───────────┐    ┌──────────┐
│ CZL-601 │───▶│  HX711   │───▶│   MCU     │───▶│  OLED /  │
│ 传感器  │    │ 24位ADC  │    │ Arduino/  │    │  串口    │
│         │    │ + PGA    │    │  STM32    │    │  显示    │
└─────────┘    └──────────┘    └───────────┘    └──────────┘
     差分mV        数字量            重量值            输出

五、HX711 模数转换模块详解

5.1 HX711 简介

HX711 是一款专为高精度电子秤设计的 24 位 ADC 芯片,内置可编程增益放大器(PGA,增益 32/64/128),与 CZL-601 配合堪称"黄金搭档"。

核心特性

  • 24 位 Σ-Δ ADC,有效分辨率约 16~20 位(取决于速率和噪声)
  • 两路差分输入(通道 A:增益 128/64,通道 B:增益 32)
  • 内置稳压电源,可直接给传感器供电
  • 数据输出速率:10SPS 或 80SPS
  • 两线通信(PD_SCK + DOUT),类似简化版 SPI

5.2 HX711 模块接线

复制代码
HX711 模块       CZL-601 传感器        MCU (Arduino)
─────────────────────────────────────────────────
VCC  ─────────                        ────  5V / 3.3V
GND  ─────────                        ────  GND
E+   ─────────  红色线 (激励+)
E-   ─────────  黑色线 (激励-)
A+   ─────────  绿色线 (信号+)
A-   ─────────  白色线 (信号-)
DT   ────────────────────────────────  ────  数字引脚 (数据)
SCK  ────────────────────────────────  ────  数字引脚 (时钟)

📌 提示:部分 HX711 模块将 B+/B- 通道省略,仅保留 A 通道,不影响使用。

5.3 HX711 通信时序

复制代码
PD_SCK:  ─┐     ┌─┐   ┌─┐   ┌─┐   ...   ┌─┐   ┌───────
          └─────┘ └───┘ └───┘ └───┘       └─┘ └───┘
               ↑   ↑   ↑   ↑   ↑           ↑   ↑
             第25  1   2   3  ...         24  第25个脉冲:
             个脉                          ↑   拉高DOUT
             冲: DOUT=0         DOUT=1    回到高电平
              数   (数据有效)     (结束)
              据
              就
              绪

DOUT:   ──────┘                         ┌────────────────
              └─────────────────────────┘
                      24位数据 + 1位增益选择

通信流程:

  1. 等待 DOUT 变低(表示转换完成)
  2. 发送 25~27 个脉冲 到 PD_SCK
  3. 每个脉冲的上升沿读取 DOUT 上的 1 位数据(MSB 优先)
  4. 第 25 个脉冲将 DOUT 拉回高电平(进入下一次转换)
  5. 第 25 个脉冲的 DOUT 值表示增益设置(0=128, 1=64)

数据格式

  • 24 位有符号二进制补码
  • 范围:-8388608 ~ 8388607(即 -2^23 ~ 2^23-1)
  • 正数表示正向受力

六、Arduino 驱动开发实战

6.1 硬件清单

器件 数量 说明
Arduino UNO / Nano 1 或兼容板
CZL-601 传感器 1 以 5kg 量程为例
HX711 模块 1
OLED 128x64 (I2C) 1 可选,用于显示
杜邦线 若干

6.2 安装库

在 Arduino IDE 的库管理器中搜索 "HX711" ,安装 HX711 Arduino Library by Bogdan Necula(或 Rob Tillaart 的版本)。

6.3 基础驱动代码

cpp 复制代码
/**
 * CZL-601 称重传感器基础示例
 * 传感器: CZL-601 (5kg)
 * ADC:    HX711
 * MCU:    Arduino UNO
 */

#include "HX711.h"

// HX711 引脚定义
const int DOUT_PIN = 3;   // HX711 DT 接 D3
const int SCK_PIN  = 2;   // HX711 SCK 接 D2

// 标定参数(需要自行标定获得,见第八章)
const float SCALE_FACTOR = 430.0;   // 比例系数,实际需标定
const long  ZERO_OFFSET  = -83000;  // 零点偏移(去皮值)

HX711 scale;

void setup() {
  Serial.begin(115200);
  Serial.println("CZL-601 称重传感器 - 基础示例");
  Serial.println("=================================");

  // 初始化 HX711
  scale.begin(DOUT_PIN, SCK_PIN);

  // 设置量程比例(先写一个临时值,后面标定再修改)
  scale.set_scale(SCALE_FACTOR);

  // 去皮(假设空载)
  scale.tare();
  Serial.println("传感器初始化完成,已去皮");

  // 打印原始读数,帮助判断接线是否正确
  Serial.print("原始读数: ");
  Serial.println(scale.read_average(10));
}

void loop() {
  // 方式一:使用 set_scale + get_units(推荐)
  float weight = scale.get_units(5);  // 取5次平均

  // 方式二:手动处理原始值
  // long raw = scale.read_average(10);
  // float weight = (raw - ZERO_OFFSET) / SCALE_FACTOR;

  Serial.print("当前重量: ");
  Serial.print(weight, 1);  // 保留1位小数
  Serial.println(" g");

  delay(200);
}

6.4 带 OLED 显示版本

cpp 复制代码
#include "HX711.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

const int DOUT_PIN = 3;
const int SCK_PIN  = 2;
const float SCALE_FACTOR = 430.0;

HX711 scale;

void setup() {
  Serial.begin(115200);

  // 初始化 OLED
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED 初始化失败!");
    while (1);
  }
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  // 初始化 HX711
  scale.begin(DOUT_PIN, SCK_PIN);
  scale.set_scale(SCALE_FACTOR);
  scale.tare();
}

void loop() {
  float weight = scale.get_units(5);

  // OLED 显示
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(10, 10);
  display.print(weight, 1);
  display.print(" g");

  display.setTextSize(1);
  display.setCursor(10, 45);
  display.print("CZL-601 电子秤");

  display.display();
  delay(200);
}

七、STM32 驱动开发实战

7.1 硬件连接

HX711 STM32 (以 F103C8T6 为例)
VCC 3.3V / 5V
GND GND
DT PB6
SCK PB7

7.2 驱动代码 (HAL 库)

hx711.h

c 复制代码
#ifndef __HX711_H__
#define __HX711_H__

#include "stm32f1xx_hal.h"

typedef struct {
    GPIO_TypeDef *sck_port;
    uint16_t      sck_pin;
    GPIO_TypeDef *dout_port;
    uint16_t      dout_pin;
    int32_t       offset;
    float         scale;
} HX711_t;

void HX711_Init(HX711_t *hx,
                GPIO_TypeDef *sck_port, uint16_t sck_pin,
                GPIO_TypeDef *dout_port, uint16_t dout_pin,
                uint8_t gain);

int32_t HX711_Read(HX711_t *hx);
int32_t HX711_ReadAverage(HX711_t *hx, uint8_t samples);
void    HX711_Tare(HX711_t *hx, uint8_t samples);
float   HX711_GetWeight(HX711_t *hx, uint8_t samples);
void    HX711_SetScale(HX711_t *hx, float scale);
void    HX711_PowerDown(HX711_t *hx);
void    HX711_PowerUp(HX711_t *hx);

#endif

hx711.c

c 复制代码
#include "hx711.h"

#define HX711_SCK_HIGH()  HAL_GPIO_WritePin(hx->sck_port, hx->sck_pin, GPIO_PIN_SET)
#define HX711_SCK_LOW()   HAL_GPIO_WritePin(hx->sck_port, hx->sck_pin, GPIO_PIN_RESET)
#define HX711_DOUT_READ() HAL_GPIO_ReadPin(hx->dout_port, hx->dout_pin)

static uint8_t _gain;

void HX711_Init(HX711_t *hx,
                GPIO_TypeDef *sck_port, uint16_t sck_pin,
                GPIO_TypeDef *dout_port, uint16_t dout_pin,
                uint8_t gain)
{
    hx->sck_port  = sck_port;
    hx->sck_pin   = sck_pin;
    hx->dout_port = dout_port;
    hx->dout_pin  = dout_pin;
    hx->offset    = 0;
    hx->scale     = 1.0f;
    _gain         = gain;

    HX711_SCK_LOW();
    HX711_PowerUp(hx);
}

int32_t HX711_Read(HX711_t *hx)
{
    /* 等待 DOUT 变低(数据就绪)*/
    uint32_t timeout = 100000;
    while (HX711_DOUT_READ() == GPIO_PIN_SET) {
        if (--timeout == 0) return 0;  // 超时返回
    }

    /* 读取 24 位数据 */
    int32_t data = 0;
    for (uint8_t i = 0; i < 24; i++) {
        HX711_SCK_HIGH();
        data = data << 1;
        if (HX711_DOUT_READ() == GPIO_PIN_SET) {
            data++;
        }
        HX711_SCK_LOW();
    }

    /* 第 25 个脉冲,设置增益用于下次转换 */
    HX711_SCK_HIGH();
    // 此处不读 DOUT,仅发出脉冲
    HX711_SCK_LOW();

    /* 处理 24 位补码到 32 位 */
    if (data & 0x800000) {  // 最高位为 1 → 负数
        data |= 0xFF000000; // 符号扩展到 32 位
    }

    return data;
}

int32_t HX711_ReadAverage(HX711_t *hx, uint8_t samples)
{
    int64_t sum = 0;
    for (uint8_t i = 0; i < samples; i++) {
        sum += HX711_Read(hx);
    }
    return (int32_t)(sum / samples);
}

void HX711_Tare(HX711_t *hx, uint8_t samples)
{
    hx->offset = HX711_ReadAverage(hx, samples);
}

float HX711_GetWeight(HX711_t *hx, uint8_t samples)
{
    int32_t raw = HX711_ReadAverage(hx, samples);
    return (raw - hx->offset) / hx->scale;
}

void HX711_SetScale(HX711_t *hx, float scale)
{
    hx->scale = scale;
}

void HX711_PowerDown(HX711_t *hx)
{
    HX711_SCK_LOW();
    HX711_SCK_HIGH();  // PD_SCK 拉高超过 60μs 进入掉电模式
    HAL_Delay(1);
}

void HX711_PowerUp(HX711_t *hx)
{
    HX711_SCK_LOW();  // PD_SCK 拉低唤醒
}

main.c 使用示例:

c 复制代码
#include "hx711.h"

HX711_t scale;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    HX711_Init(&scale, GPIOB, GPIO_PIN_7, GPIOB, GPIO_PIN_6, 128);

    // 去皮
    HAL_Delay(500);
    HX711_Tare(&scale, 10);

    // 设置比例系数(需标定)
    HX711_SetScale(&scale, 430.0f);

    char buf[64];
    while (1) {
        float weight = HX711_GetWeight(&scale, 10);
        sprintf(buf, "Weight: %.1f g\r\n", weight);
        HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100);
        HAL_Delay(200);
    }
}

八、标定方法与数据处理

标定是使用称重传感器最关键的一步。不同传感器、不同激励电压、不同 HX711 模块的 scale 值都不同,必须逐一标定。

8.1 两点标定法(推荐)

原理 :利用两点确定一条直线(y = kx + b),即 重量 = (原始值 - 零点) / 比例系数

标定步骤:

第一步:零点标定

复制代码
1. 确保传感器空载(不放置任何物品)
2. 读取 20 次原始值,取平均,记为 ZERO_VALUE
3. 代码中 set_offset(ZERO_VALUE) 或直接 tare()

第二步:已知重量标定

复制代码
1. 在传感器上放置一个已知重量的砝码(如 200g)
2. 读取 20 次原始值(已减去零点),取平均,记为 RAW_200G
3. 计算比例系数: SCALE = RAW_200G / 200.0
4. 代码中 set_scale(SCALE)
Arduino 标定程序
cpp 复制代码
/**
 * CZL-601 标定程序
 * 通过串口发送命令完成标定
 */
#include "HX711.h"

const int DOUT_PIN = 3;
const int SCK_PIN  = 2;

HX711 scale;

float scale_factor = 430.0;   // 默认值
long  zero_offset  = 0;

void setup() {
  Serial.begin(115200);
  scale.begin(DOUT_PIN, SCK_PIN);
  scale.set_scale(1.0f);  // 先设为 1,读原始值
  printHelp();
}

void loop() {
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();

    if (cmd == "raw") {
      // 打印原始值
      long raw = scale.read_average(10);
      Serial.print("原始值: ");
      Serial.println(raw);

    } else if (cmd == "tare") {
      // 去皮
      zero_offset = scale.read_average(20);
      scale.set_offset(zero_offset);
      Serial.print("零点已设置: ");
      Serial.println(zero_offset);

    } else if (cmd.startsWith("cal ")) {
      // 标定: cal <已知重量(克)>
      float known_weight = cmd.substring(4).toFloat();
      if (known_weight > 0) {
        long raw = scale.read_average(20) - zero_offset;
        scale_factor = raw / known_weight;
        scale.set_scale(scale_factor);
        Serial.println("=== 标定完成 ===");
        Serial.print("已知重量: ");
        Serial.print(known_weight);
        Serial.println(" g");
        Serial.print("原始差值: ");
        Serial.println(raw);
        Serial.print("比例系数 (SCALE_FACTOR): ");
        Serial.println(scale_factor, 2);
        Serial.println("请将此值写入代码中的 SCALE_FACTOR");
      }

    } else if (cmd == "test") {
      // 连续输出重量
      for (int i = 0; i < 20; i++) {
        float w = scale.get_units(5);
        Serial.print("重量: ");
        Serial.print(w, 1);
        Serial.println(" g");
        delay(200);
      }

    } else if (cmd == "help") {
      printHelp();

    } else {
      Serial.println("未知命令,输入 help 查看帮助");
    }
  }
}

void printHelp() {
  Serial.println("\n======== CZL-601 标定工具 ========");
  Serial.println("命令列表:");
  Serial.println("  raw      - 读取原始值(检查接线)");
  Serial.println("  tare     - 空载去皮(设置零点)");
  Serial.println("  cal 200  - 用 200g 砝码标定比例");
  Serial.println("  test     - 连续读取重量 20 次");
  Serial.println("  help     - 显示此帮助");
  Serial.println("==================================\n");
}

标定操作流程

复制代码
1. 输入 raw  → 确认原始值有变化(接线正确)
2. 空载状态下输入 tare → 设置零点
3. 放上 200g 砝码,输入 cal 200 → 获得 SCALE_FACTOR
4. 输入 test → 验证读数准确性
5. 将 SCALE_FACTOR 写入正式代码

8.2 多点标定(线性校正)

当传感器线性度不够理想时,可以采用分段线性插值

cpp 复制代码
/**
 * 多点标定 - 线性插值
 * calibration_points: {重量, 原始值} 对,按重量升序排列
 */

struct CalPoint {
    float weight;   // 已知重量 (g)
    long  raw;      // 对应原始值
};

CalPoint calTable[] = {
    {0,     -83200},   // 0g
    {50,    -61500},   // 50g
    {100,   -39800},   // 100g
    {200,    3600},    // 200g
    {500,    133200},  // 500g
    {1000,   349200},  // 1000g
};
const int NUM_POINTS = sizeof(calTable) / sizeof(calTable[0]);

float interpolateWeight(long rawValue) {
    // 外推:低于最小点
    if (rawValue <= calTable[0].raw) {
        return calTable[0].weight;
    }
    // 外推:高于最大点
    if (rawValue >= calTable[NUM_POINTS - 1].raw) {
        float slope = (calTable[NUM_POINTS - 1].weight - calTable[NUM_POINTS - 2].weight)
                    / (calTable[NUM_POINTS - 1].raw - calTable[NUM_POINTS - 2].raw);
        return calTable[NUM_POINTS - 1].weight
             + slope * (rawValue - calTable[NUM_POINTS - 1].raw);
    }
    // 内插:在两点之间
    for (int i = 0; i < NUM_POINTS - 1; i++) {
        if (rawValue >= calTable[i].raw && rawValue <= calTable[i + 1].raw) {
            float slope = (calTable[i + 1].weight - calTable[i].weight)
                        / (calTable[i + 1].raw - calTable[i].raw);
            return calTable[i].weight + slope * (rawValue - calTable[i].raw);
        }
    }
    return 0;
}

8.3 温度补偿

CZL-601 的灵敏度会随温度变化(温漂约 0.05% F.S / ℃)。在高精度场合,可添加温度补偿:

cpp 复制代码
/**
 * 简化温度补偿
 * tempCoeff: 温度系数 (ppm/℃),由实验测定
 * refTemp:   标定时的参考温度
 */

float compensateTemperature(float weight, float currentTemp,
                            float refTemp, float tempCoeff)
{
    float deltaT = currentTemp - refTemp;
    return weight * (1.0 + tempCoeff * deltaT / 1e6);
}

💡 更精确的补偿需要实际测量不同温度下传感器输出的变化曲线。


九、进阶:数字滤波算法

原始数据含有噪声,需要数字滤波才能获得稳定读数。以下是几种常用方法:

9.1 滑动平均滤波(基础)

cpp 复制代码
#define FILTER_WINDOW 10

float window[FILTER_WINDOW];
int   winIndex = 0;
int   winCount = 0;

float movingAverage(float newValue) {
    window[winIndex] = newValue;
    winIndex = (winIndex + 1) % FILTER_WINDOW;
    if (winCount < FILTER_WINDOW) winCount++;

    float sum = 0;
    for (int i = 0; i < winCount; i++) {
        sum += window[i];
    }
    return sum / winCount;
}

优点:平滑效果好。缺点:响应慢,占用 RAM。

9.2 一阶低通滤波(推荐,兼顾响应和平滑)

cpp 复制代码
/**
 * 一阶低通(指数加权移动平均)
 * Y[n] = α·X[n] + (1-α)·Y[n-1]
 * α 越小 → 越平滑但响应越慢
 * α 越大 → 响应越快但越不平滑
 */
class LowPassFilter {
private:
    float _alpha;
    float _output;
    bool  _initialized;

public:
    LowPassFilter(float alpha = 0.1) : _alpha(alpha), _output(0), _initialized(false) {}

    float update(float input) {
        if (!_initialized) {
            _output = input;
            _initialized = true;
        } else {
            _output = _alpha * input + (1.0f - _alpha) * _output;
        }
        return _output;
    }

    void reset() { _initialized = false; }
    void setAlpha(float alpha) { _alpha = alpha; }
};

9.3 中值滤波(抗脉冲干扰)

cpp 复制代码
/**
 * 中值滤波 - 对突发脉冲干扰(如振动冲击)效果极佳
 */
float medianFilter(float *buffer, int size) {
    // 排序(简单冒泡,size 通常 ≤ 9)
    float sorted[size];
    memcpy(sorted, buffer, size * sizeof(float));

    for (int i = 0; i < size - 1; i++) {
        for (int j = i + 1; j < size; j++) {
            if (sorted[i] > sorted[j]) {
                float tmp = sorted[i];
                sorted[i] = sorted[j];
                sorted[j] = tmp;
            }
        }
    }
    return sorted[size / 2];  // 返回中值
}

9.4 组合滤波(生产中推荐)

实际产品中常将多种滤波串联使用:

cpp 复制代码
/**
 * 生产级滤波方案:中值 → 均值 → 低通
 */
class AdvancedFilter {
private:
    static const int MEDIAN_SIZE = 5;
    static const int MEAN_COUNT   = 8;
    float _medianBuf[MEDIAN_SIZE];
    LowPassFilter _lpf;

public:
    AdvancedFilter(float alpha = 0.08) : _lpf(alpha) {}

    float filter(long (*readRaw)(), HX711_t *hx) {
        // 第一级:采集中值滤波样本
        for (int i = 0; i < MEDIAN_SIZE; i++) {
            _medianBuf[i] = (float)readRaw(hx);
        }
        float medianVal = medianFilter(_medianBuf, MEDIAN_SIZE);

        // 第二级:低通滤波
        return _lpf.update(medianVal);
    }
};

9.5 去皮与稳定检测

cpp 复制代码
/**
 * 智能去皮:检测稳定后再去皮
 */
bool waitForStable(HX711_t *hx, int stableTimeMs, float threshold) {
    int stableCount = 0;
    float lastWeight = HX711_GetWeight(hx, 5);

    while (stableCount < stableTimeMs / 100) {
        delay(100);
        float currentWeight = HX711_GetWeight(hx, 5);

        if (fabs(currentWeight - lastWeight) < threshold) {
            stableCount++;
        } else {
            stableCount = 0;
        }
        lastWeight = currentWeight;
    }
    return true;
}

十、项目实战:智能电子秤

10.1 功能需求

  • ✅ 重量实时显示(OLED)
  • 去皮功能(按键触发)
  • 超重报警(蜂鸣器 + LED)
  • 蓝牙数据传输(HC-05,手机 APP 接收)
  • 自动休眠(30s 无变化降低采样率)

10.2 硬件清单

器件 数量 用途
Arduino Nano 1 主控
CZL-601 (5kg) 1 称重
HX711 模块 1 ADC
OLED 0.96" I2C 1 显示
HC-05 蓝牙模块 1 无线传输
轻触开关 2 去皮 / 单位切换
有源蜂鸣器 1 超重报警
LED (红色) 1 超重指示灯
锂电池 + 充电模块 1 供电

10.3 系统框图

复制代码
              ┌──────────┐
    ┌─────────┤  电池    ├─────────┐
    │         └──────────┘         │
    ▼                              ▼
┌───────┐  ┌──────┐  ┌────────┐  ┌──────┐
│CZL-601│─▶│HX711 │─▶│Arduino │─▶│OLED  │
└───────┘  └──────┘  │ Nano   │  └──────┘
                     │        │─▶┌──────┐
              ┌──────┤        │  │HC-05 │
              │      │        │  └──────┘
         ┌────┴──┐   │        │─▶┌──────┐
         │按键×2 │   └────────┘  │蜂鸣器│
         └───────┘               │+ LED │
                                 └──────┘

10.4 完整代码

cpp 复制代码
/**
 * 智能电子秤 - 完整项目
 * 功能:称重、去皮、超重报警、蓝牙透传、自动休眠
 * 传感器: CZL-601 + HX711
 * 显示: OLED 128x64 I2C
 * 通信: HC-05 蓝牙 (9600bps)
 */

#include "HX711.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SoftwareSerial.h>

// ==================== 引脚定义 ====================
#define PIN_HX711_DOUT    3
#define PIN_HX711_SCK     2
#define PIN_BTN_TARE      4    // 去皮按键
#define PIN_BTN_UNIT      5    // 单位切换按键 (g ↔ oz)
#define PIN_BUZZER        6    // 蜂鸣器
#define PIN_LED_ALARM     7    // 报警 LED
#define PIN_BT_RX         8    // HC-05 TX → D8
#define PIN_BT_TX         9    // HC-05 RX → D9

// ==================== 常量 ====================
#define SCALE_FACTOR      430.0f  // 标定系数
#define MAX_WEIGHT        5000.0f // 最大量程 (g)
#define ALARM_THRESHOLD   4800.0f // 报警阈值 (g)
#define SLEEP_TIMEOUT     30000   // 休眠超时 (ms)
#define OLED_ADDR         0x3C

// ==================== 对象 ====================
HX711 scale;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
SoftwareSerial bluetooth(PIN_BT_RX, PIN_BT_TX);

// ==================== 全局变量 ====================
bool    unitGram     = true;      // true=g, false=oz
float   lastStableWeight = 0;
unsigned long lastActivityTime = 0;
bool    sleeping     = false;

// 低通滤波器
class SimpleLPF {
    float _a, _out;
    bool _init;
public:
    SimpleLPF(float a = 0.1) : _a(a), _out(0), _init(false) {}
    float update(float in) {
        if (!_init) { _out = in; _init = true; }
        else _out = _a * in + (1 - _a) * _out;
        return _out;
    }
};
SimpleLPF lpf(0.1);

// ==================== 初始化 ====================
void setup() {
    Serial.begin(115200);
    bluetooth.begin(9600);

    // GPIO 初始化
    pinMode(PIN_BTN_TARE, INPUT_PULLUP);
    pinMode(PIN_BTN_UNIT, INPUT_PULLUP);
    pinMode(PIN_BUZZER, OUTPUT);
    pinMode(PIN_LED_ALARM, OUTPUT);
    digitalWrite(PIN_BUZZER, LOW);
    digitalWrite(PIN_LED_ALARM, LOW);

    // OLED 初始化
    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
        Serial.println("OLED init failed!");
        while (1);
    }

    // 开机动画
    showBootScreen();

    // HX711 初始化
    scale.begin(PIN_HX711_DOUT, PIN_HX711_SCK);
    scale.set_scale(SCALE_FACTOR);
    delay(500);
    scale.tare(20);

    lastActivityTime = millis();
}

// ==================== 主循环 ====================
void loop() {
    // 读取重量
    float rawWeight = scale.get_units(5);
    float weight = lpf.update(rawWeight);

    // 单位转换
    float displayWeight = unitGram ? weight : weight * 0.035274f;
    const char *unit = unitGram ? "g" : "oz";

    // 超重检测
    bool overload = (weight > ALARM_THRESHOLD);

    // 按键处理
    handleButtons(weight);

    // 显示
    updateDisplay(displayWeight, unit, overload);

    // 蓝牙发送
    sendBluetoothData(displayWeight, unit);

    // 休眠管理
    manageSleep(weight);

    delay(100);
}

// ==================== 功能函数 ====================
void showBootScreen() {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(20, 10);
    display.println("Smart");
    display.setCursor(20, 35);
    display.println("Scale");
    display.display();
    delay(1500);
}

void handleButtons(float weight) {
    static unsigned long lastTarePress = 0;
    static unsigned long lastUnitPress = 0;

    // 去皮键(消抖 + 长按识别)
    if (digitalRead(PIN_BTN_TARE) == LOW) {
        if (millis() - lastTarePress > 300) {
            scale.tare(20);
            lpf = SimpleLPF(0.1);  // 重置滤波
            tone(PIN_BUZZER, 1000, 50);  // 声音反馈
            lastActivityTime = millis();
        }
        lastTarePress = millis();
    }

    // 单位切换键
    if (digitalRead(PIN_BTN_UNIT) == LOW) {
        if (millis() - lastUnitPress > 500) {
            unitGram = !unitGram;
            tone(PIN_BUZZER, 2000, 50);
            lastActivityTime = millis();
        }
        lastUnitPress = millis();
    }
}

void updateDisplay(float weight, const char *unit, bool overload) {
    display.clearDisplay();

    if (overload) {
        // 超重警告画面
        display.setTextSize(2);
        display.setCursor(15, 5);
        display.println("OVERLOAD");
        display.setTextSize(1);
        display.setCursor(10, 40);
        display.print("Max: ");
        display.print(MAX_WEIGHT, 0);
        display.println(" g");
    } else if (sleeping) {
        display.setTextSize(1);
        display.setCursor(30, 28);
        display.println("Sleeping...");
    } else {
        // 正常称重画面
        display.setTextSize(3);
        display.setCursor(10, 5);
        display.print(weight, 1);

        display.setTextSize(2);
        display.setCursor(80, 10);
        display.print(unit);

        // 底部状态栏
        display.setTextSize(1);
        display.setCursor(0, 56);
        display.print(unitGram ? "[g] TARE  [oz] UNIT" : "[oz] TARE [g] UNIT");
    }

    display.display();
}

void sendBluetoothData(float weight, const char *unit) {
    static unsigned long lastSend = 0;
    if (millis() - lastSend > 500) {  // 每 500ms 发送一次
        bluetooth.print("W:");
        bluetooth.print(weight, 1);
        bluetooth.print(unit);
        bluetooth.print("\n");
        lastSend = millis();
    }
}

void manageSleep(float weight) {
    if (abs(weight - lastStableWeight) > 2.0f) {
        lastActivityTime = millis();
        lastStableWeight = weight;
        sleeping = false;
    }

    if (millis() - lastActivityTime > SLEEP_TIMEOUT) {
        sleeping = true;
    } else {
        sleeping = false;
    }
}

// ==================== 超重报警(在主循环中调用)====================
void alarmCheck(bool overload) {
    static unsigned long lastBeep = 0;
    static bool beepState = false;

    if (overload) {
        if (millis() - lastBeep > 300) {
            beepState = !beepState;
            digitalWrite(PIN_BUZZER, beepState);
            digitalWrite(PIN_LED_ALARM, beepState);
            lastBeep = millis();
        }
    } else {
        digitalWrite(PIN_BUZZER, LOW);
        digitalWrite(PIN_LED_ALARM, LOW);
        beepState = false;
    }
}

⚠️ 注意:此代码需将 alarmCheck(overload) 加入 loop() 中调用(已省略)。

10.5 手机端数据接收

HC-05 蓝牙模块设置为从机模式(默认),手机使用串口蓝牙助手 APP(如 Serial Bluetooth Terminal)连接后即可接收:

复制代码
W:235.7g
W:235.8g
W:235.6g
...

进阶玩法:用 MIT App Inventor 编写自定义 APP,实现数据记录、图表绘制。


十一、常见问题与排查指南

Q1:读数一直为 0 或不变?

检查项 方法
接线是否正确 对照第五章线序表,用万用表确认四线对应关系
HX711 供电 万用表测 VCC-GND 应为 3.3V~5V
DT/SCK 引脚 代码中引脚号是否正确,是否与 HX711 模块一致
读原始值 scale.read_average(10) 应输出一个较大的整数(如 -80000 ~ 80000)
传感器受力 按压传感器,原始值是否变化?(变化 = 接线正确)

Q2:读数跳动大、不稳定?

原因 解决方案
电源纹波 HX711 VCC 并联 100μF 电解电容 + 0.1μF 瓷片电容
机械振动 确保传感器安装牢固,桌面稳定
电磁干扰 传感器引线远离电机、继电器等干扰源;使用屏蔽线
采样次数太少 增大 read_average() 的参数到 10~20
滤波不够 启用第九章的低通滤波,调小 α 值

Q3:线性度差(轻了准、重了不准)?

  • 原因:单点标定精度不足
  • 解决 :改用两点或多点标定(见第八章)

Q4:长时间使用后读数漂移?

  • 原因 1:传感器蠕变(长时间受力产生的塑性变形)
  • 解决:去掉负载让其恢复,或降低长期负载比例
  • 原因 2:温度漂移
  • 解决:加入温度补偿(见第八章)
  • 原因 3:HX711 参考电压漂移
  • 解决:定期重新去皮

Q5:HX711 读数为负数或溢出?

  • 传感器反向安装 → 检查受力方向(通常有箭头标注)
  • 线序错误(S+ 和 S- 接反)→ 对调绿色和白色线
  • 超过量程 → 减轻负载

Q6:如何判断传感器是否损坏?

用万用表测量:

测试项 正常值 异常判断
E+ 与 E-(红-黑) ≈ 1000Ω 断路或短路 → 损坏
S+ 与 S-(绿-白) ≈ 1000Ω 断路或短路 → 损坏
E+ 与 S+ / S- ≈ 750Ω 异常偏差 → 应变片损坏
任何线对外壳 ∞ (绝缘) < 2MΩ → 绝缘损坏

十二、总结与学习路线

学习路线图

复制代码
第一阶段:入门(1~2 天)
├─ 理解惠斯通电桥原理
├─ 看懂 CZL-601 技术手册
├─ 正确接线(传感器 → HX711 → Arduino)
├─ 运行基础示例代码,读取原始值
└─ 完成两点标定

第二阶段:进阶(3~5 天)
├─ 掌握 HX711 通信时序(可手动模拟)
├─ 编写 STM32 驱动
├─ 多点标定与线性校正
├─ 数字滤波算法实践
└─ OLED 显示 + 按键交互

第三阶段:精通(1~2 周)
├─ 温度补偿算法
├─ 动态称重(传送带/分拣场景)
├─ 低功耗设计(电池供电产品)
├─ 蓝牙/WiFi 物联网数据传输
├─ PC 上位机开发(Python/C# 数据采集)
└─ 量产标定工装设计

推荐资源

资源 链接/关键词
HX711 数据手册 "HX711 datasheet PDF"
HX711 Arduino 库 GitHub: bogde/HX711
CZL-601 规格书 向供应商索取或搜索中文资料
惠斯通电桥 模拟电子技术教材第三章

关键要点速查

复制代码
✅ 选型:量程 = 最大载荷 ÷ 0.6 ~ 0.8
✅ 线序:用万用表确认,不依赖颜色
✅ 供电:5V 稳压,加滤波电容
✅ 标定:两点法 or 多点法,必须逐一标定
✅ 滤波:一阶低通 + 中值 = 生产级方案
✅ 抗干扰:远离电机/继电器,加屏蔽
✅ 安全:结构上防止侧向力和过载

📝 作者寄语

CZL-601 是一款经典且可靠的微型称重传感器,掌握它不仅是入门称重传感领域的捷径,更是嵌入式系统设计中"模拟前端 + 数字处理"的绝佳实践项目。

从读懂一张电桥原理图,到写出一套完整的标定与滤波算法,再到交付一个稳定可靠的称重产品------这条路走完,你对传感器和嵌入式系统的理解将上升一个层次。

如果本文对你有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区交流讨论 🔧


本文发布于 CSDN,未经许可不得转载。文中代码均为示例,可直接用于学习和非商业用途。

相关推荐
llilian_161 小时前
基于PCI总线校时时钟同步卡的高精准授时解决方案
大数据·服务器·功能测试·单片机
破晓单片机2 小时前
093、STM32项目分享开源:蓝牙智能晾衣架系统
stm32·单片机·嵌入式硬件·开源
木由里予2 小时前
TEE(Trusted Execution Environment)技术详解
java·linux·开发语言·网络·嵌入式硬件·android-studio
zcmodeltech3 小时前
火电厂模型多设备时序控制系统设计:基于STM32与Modbus RTU的燃煤-燃机全流程联动方案
分布式·stm32·单片机·嵌入式硬件·物联网
芯岭技术12 小时前
Wi-Fi+BLE双模无线芯片 BK7238,低功耗、高性能,集成MCU和Flash
单片机·嵌入式硬件
云空13 小时前
《ARM Cortex‑M 系列完整梳理》
stm32·单片机·嵌入式硬件·mcu
单片机杂货铺13 小时前
基于单片机的智能花盆浇水系统控制器设
stm32·单片机·嵌入式硬件·毕业设计·51单片机·硬件工程
Dr.kangder14 小时前
嵌入式处理器仿真技术——Hypervisor 原理与实践
服务器·嵌入式硬件·架构·嵌入式
Hello_Damon_Nikola16 小时前
TM1637芯片从入门到精通
单片机·嵌入式硬件