1. 项目背景与核心需求
在工业自动化、实验室仪器和商业称重领域,高精度电子秤一直是关键设备。传统电子秤受限于8位或12位ADC的分辨率,往往难以满足毫克级测量需求。而采用STM32F103内置的12位ADC配合外部24位Σ-Δ型ADC的方案,既能控制成本又能实现0.01g级精度,这正是本项目的设计初衷。
我去年为一家制药厂开发原料配比系统时,就深刻体会到:当称量范围在5kg内时,±0.05g的误差可能导致整批药品不合格。市面常见电子秤在温度波动时出现的漂移问题,也促使我在本设计中特别关注软件滤波算法的鲁棒性。
2. 硬件架构设计解析
2.1 核心器件选型
主控选用STM32F103C8T6,因其具备:
- 72MHz主频足够处理滤波算法
- 内置12位ADC可作辅助测量
- 丰富的外设接口(SPI/I2C)
关键器件HX711(24位ADC)的优势在于:
- 内置128倍可编程增益放大器(PGA)
- 差分输入有效抑制共模干扰
- 典型非线性误差仅±0.01%
- 单电源5V供电简化设计
注意:HX711的基准电压需选用LM385-2.5这类低温漂稳压管,实测普通LDO会导致满量程漂移达3%
2.2 称重传感器接口设计
采用350Ω全桥式应变片时,需注意:
- 激励电压建议5V,此时满量程输出约15mV
- 信号线必须使用双绞线并远离电源走线
- 在ADC输入端添加EMI滤波器(如100Ω+100nF)
典型连接方式:
c复制HX711_DOUT -> PC0 // 数据输入
HX711_PD_SCK -> PC1 // 时钟输出
传感器EXC+ -> 5V
传感器EXC- -> GND
3. 软件滤波算法实现
3.1 基础数据采集流程
HX711的典型读取时序:
c复制uint32_t HX711_Read() {
uint32_t count = 0;
PD_SCK_LOW();
while(DOUT_READ()); // 等待准备就绪
for(uint8_t i=0; i<24; i++) {
PD_SCK_HIGH();
delay_us(1);
count <<= 1;
if(DOUT_READ()) count++;
PD_SCK_LOW();
delay_us(1);
}
// 选择增益和通道
PD_SCK_HIGH();
delay_us(1);
PD_SCK_LOW();
return count ^ 0x800000; // 补码转原码
}
3.2 复合滤波算法设计
采用三级滤波架构:
-
滑动平均滤波:窗口宽度建议8-16点
c复制#define FILTER_N 12 int32_t moving_avg(int32_t new_val) { static int32_t buf[FILTER_N]; static uint8_t idx = 0; buf[idx++] = new_val; if(idx >= FILTER_N) idx = 0; int64_t sum = 0; for(uint8_t i=0; i<FILTER_N; i++) { sum += buf[i]; } return sum / FILTER_N; } -
中值滤波:消除突发干扰
c复制int32_t median_filter(int32_t new_val) { static int32_t window[5]; static uint8_t cnt = 0; window[cnt++ % 5] = new_val; // 排序取中值(省略排序代码) return sorted[2]; } -
卡尔曼滤波:动态跟踪信号变化
c复制typedef struct { float q; // 过程噪声协方差 float r; // 观测噪声协方差 float x; // 估计值 float p; // 估计误差协方差 float k; // 卡尔曼增益 } Kalman; float kalman_update(Kalman* k, float measurement) { k->p += k->q; k->k = k->p / (k->p + k->r); k->x += k->k * (measurement - k->x); k->p *= (1 - k->k); return k->x; }
实测数据:在1kg负载下,原始数据波动±5LSB,经三级滤波后稳定在±1LSB内
4. 校准与温度补偿
4.1 四点校准法实现
- 空载时采集AD值作为零点(TARE)
- 加载标准砝码记录三个点(如500g、1000g、2000g)
- 最小二乘法拟合线性方程:
c复制void calc_coeff(float x[], float y[], int n) { float sum_x=0, sum_y=0, sum_xy=0, sum_xx=0; for(int i=0; i<n; i++) { sum_x += x[i]; sum_y += y[i]; sum_xy += x[i]*y[i]; sum_xx += x[i]*x[i]; } float a = (n*sum_xy - sum_x*sum_y) / (n*sum_xx - sum_x*sum_x); float b = (sum_y - a*sum_x)/n; // 保存a(斜率)和b(截距)到EEPROM }
4.2 温度漂移补偿
方案对比:
| 方案 | 精度提升 | 成本增加 | 实现复杂度 |
|---|---|---|---|
| 单纯软件补偿 | 30% | 0 | 低 |
| 增加DS18B20传感器 | 60% | $0.5 | 中 |
| 恒温槽 | 90% | $50+ | 高 |
推荐采用DS18B20+多项式补偿:
c复制float temp_compensate(float raw, float temp) {
// 二次多项式补偿系数需实验测定
return raw * (1.0 + 0.0005*(temp-25) + 0.000002*(temp-25)*(temp-25));
}
5. 关键问题排查实录
5.1 读数不稳定问题
现象:末位数字持续跳动超过3个字
- 检查项:
- 电源纹波(应<10mVpp)
- 传感器固定螺栓是否拧紧
- 周围有无强电磁干扰源
- 采样速率是否过高(建议10-80Hz)
案例:某客户现场因变频器导致AD值跳变,通过以下措施解决:
- 在传感器输出端并联100uF钽电容
- 将SPI时钟从1MHz降至500kHz
- 在程序中增加数字陷波器滤除50Hz工频干扰
5.2 非线性误差超标
调试步骤:
- 用标准砝码从10%量程到100%分10点测试
- 记录实测AD值与理想直线偏差
- 典型修正方法:
c复制float nonlinear_correct(float raw) { // 分段线性补偿 if(raw < 1000000) return raw * 0.998; else if(raw < 2000000) return raw * 1.002 - 4000; else return raw * 1.005 - 10000; }
6. 低功耗优化技巧
6.1 硬件级省电设计
- 采用MOSFET控制传感器供电(休眠时完全断电)
- 将HX711采样率从80Hz降至10Hz可降低50%功耗
- 使用STOP模式+按键唤醒(电流可降至20uA)
6.2 软件优化策略
c复制void enter_sleep() {
HAL_ADC_Stop();
HAL_SPI_DeInit();
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后需重新初始化时钟
SystemClock_Config();
}
实测数据对比:
| 模式 | 工作电流 | 唤醒时间 |
|---|---|---|
| 持续采样 | 15mA | - |
| 间歇采样 | 3.5mA | 50ms |
| 深度休眠 | 0.02mA | 200ms |
7. 量产测试方案
7.1 自动化校准系统
开发基于Python的测试工装:
python复制import serial
import numpy as np
def auto_calibrate(port):
ser = serial.Serial(port, 9600)
weights = [0, 500, 1000, 2000] # 标准砝码(g)
ad_values = []
for w in weights:
input(f"放置{w}g砝码后按回车...")
ser.write(b'MEAS\n')
ad = int(ser.readline())
ad_values.append(ad)
coeff = np.polyfit(ad_values, weights, 1)
ser.write(f"SET {coeff[0]} {coeff[1]}\n".encode())
ser.close()
7.2 关键参数测试项
- 重复性测试:同一载荷连续10次测量极差
- 偏载测试:四角放置1/3量程砝码的示值差
- 蠕变测试:满载4小时后读数变化量
- 温度循环:-10℃~40℃环境下精度验证
8. 进阶改进方向
8.1 无线传输方案
- 蓝牙HC-05模块实现手机APP连接
- ESP8266支持Wi-Fi数据上传
- 注意RF干扰对ADC的影响(需增加屏蔽罩)
8.2 基于FreeRTOS的多任务设计
c复制void vMeasurementTask(void *pvParameters) {
while(1) {
xQueueSend(adc_queue, &hx711_read(), portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void vFilterTask(void *pvParameters) {
while(1) {
int32_t raw;
xQueueReceive(adc_queue, &raw, portMAX_DELAY);
float filtered = kalman_update(&kfilter, raw);
xQueueOverwrite(display_queue, &filtered);
}
}
8.3 外壳结构设计要点
- 采用铝合金底座抑制振动干扰
- 触摸按键代替机械按键防尘
- 水平泡确保安装姿态正确
- 防过载设计(机械限位+软件保护)
