CZL-601 微型称重传感器:从入门到精通
目录
- [一、初识 CZL-601](#一、初识 CZL-601)
- 二、工作原理深入解析
- 三、技术参数与选型指南
- 四、硬件接线与信号调理
- [五、HX711 模数转换模块详解](#五、HX711 模数转换模块详解)
- [六、Arduino 驱动开发实战](#六、Arduino 驱动开发实战)
- [七、STM32 驱动开发实战](#七、STM32 驱动开发实战)
- 八、标定方法与数据处理
- 九、进阶:数字滤波算法
- 十、项目实战:智能电子秤
- 十一、常见问题与排查指南
- 十二、总结与学习路线
一、初识 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 受力与输出关系
当传感器受到外力时:
-
受力端向下弯曲 → 上表面应变片受拉伸(电阻增大),下表面应变片受压缩(电阻减小)
-
电桥失去平衡 → 差分输出端产生微小电压
-
输出电压与施加的力成正比
输出电压 (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位增益选择
通信流程:
- 等待 DOUT 变低(表示转换完成)
- 发送 25~27 个脉冲 到 PD_SCK
- 每个脉冲的上升沿读取 DOUT 上的 1 位数据(MSB 优先)
- 第 25 个脉冲将 DOUT 拉回高电平(进入下一次转换)
- 第 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,未经许可不得转载。文中代码均为示例,可直接用于学习和非商业用途。