1. STM32与HX711 24位ADC驱动概述
在嵌入式测量系统中,高精度模拟信号采集一直是开发者面临的挑战。HX711作为一款专为称重传感器设计的24位模数转换器(ADC),以其优异的性价比和简单的接口设计,成为许多STM32开发者的首选方案。我在工业称重设备开发中多次使用HX711,实测其噪声性能优于多数同价位ADC芯片。
HX711通过内置低噪声可编程放大器(PGA)和稳压电路,可直接连接桥式传感器。其24位分辨率在5V供电时理论最小可检测电压约0.3μV,配合STM32的硬件SPI或GPIO模拟时序,可实现优于0.01%FS的测量精度。这个组合特别适合电子秤、压力检测、张力测量等需要高精度且成本敏感的场景。
2. 硬件设计关键点
2.1 接口电路设计
HX711与STM32的典型连接方式包含三个关键部分:
- 电源电路:建议采用独立LDO供电,我常用AMS1117-3.3V,其输出需并联100μF电解电容和0.1μF陶瓷电容。实测这种配置可使HX711的电源噪声低于50μV。
- 信号输入:差分输入AIN+和AIN-需接10kΩ上拉电阻,并用TVS二极管防护。在工业现场应用中,我额外添加RC滤波(1kΩ+0.1μF)可有效抑制射频干扰。
- 通信接口:PD_SCK和DOUT两条信号线必须接1-10kΩ上拉电阻,线长超过10cm时建议串联33Ω电阻阻抗匹配。
重要提示:HX711的DOUT引脚在空闲时为高阻态,必须上拉!我曾因忽略这点导致通信失败,浪费半天调试时间。
2.2 PCB布局要点
- 将HX711尽量靠近传感器放置,差分走线等长且平行
- 模拟地与数字地单点连接,我通常在芯片AGND引脚附近通过0Ω电阻连接
- 避免将时钟线布置在模拟信号线旁边,我的经验是间距至少3倍线宽
3. 驱动程序实现详解
3.1 底层时序模拟
HX711采用特殊的串行协议,需精确控制PD_SCK脉冲。以下是经过优化的GPIO模拟代码(基于STM32 HAL库):
c复制#define HX711_DOUT_PIN GPIO_PIN_0
#define HX711_SCK_PIN GPIO_PIN_1
#define HX711_GPIO GPIOA
uint32_t HX711_Read(void) {
uint32_t data = 0;
HAL_GPIO_WritePin(HX711_GPIO, HX711_SCK_PIN, GPIO_PIN_RESET);
while(HAL_GPIO_ReadPin(HX711_GPIO, HX711_DOUT_PIN) == GPIO_PIN_SET);
for(uint8_t i=0; i<24; i++) {
HAL_GPIO_WritePin(HX711_GPIO, HX711_SCK_PIN, GPIO_PIN_SET);
data <<= 1;
HAL_GPIO_WritePin(HX711_GPIO, HX711_SCK_PIN, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(HX711_GPIO, HX711_DOUT_PIN)) data++;
}
// 设置增益和通道
for(uint8_t i=0; i<GAIN_SETTING; i++) {
HAL_GPIO_WritePin(HX711_GPIO, HX711_SCK_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(HX711_GPIO, HX711_SCK_PIN, GPIO_PIN_RESET);
}
return data ^ 0x800000; // 转换补码
}
这段代码的关键点:
- 先检测DOUT就绪信号,避免竞争条件
- 时钟高低电平持续时间实测需>500ns,我通常用1μs延时
- 最后3个脉冲用于设置增益(128/64/32对应1/2/3个脉冲)
3.2 数据处理算法
原始ADC值需经过多重处理才能获得稳定测量结果:
c复制#define SAMPLE_TIMES 32
int32_t Get_Weight(void) {
static int32_t offset = 0;
int64_t sum = 0;
if(offset == 0) { // 首次上电校准
for(uint8_t i=0; i<SAMPLE_TIMES; i++) {
sum += HX711_Read();
HAL_Delay(10);
}
offset = sum / SAMPLE_TIMES;
}
sum = 0;
for(uint8_t i=0; i<5; i++) { // 滑动滤波
sum += HX711_Read() - offset;
HAL_Delay(1);
}
return (int32_t)(sum * CALIBRATION_FACTOR / 5);
}
我的经验是:
- 初始校准采样32次可有效抑制电源扰动
- 日常测量用5点滑动滤波平衡速度与稳定性
- 校准系数CALIBRATION_FACTOR需通过砝码实测确定
4. 性能优化技巧
4.1 噪声抑制方案
在电机控制系统中,HX711易受PWM干扰。我总结的解决方案:
- 硬件层面:
- 在传感器输出端加装EMI滤波器(如Murata BLM18系列)
- 采用屏蔽双绞线传输信号
- 软件层面:
- 同步采样:在PWM周期中点触发ADC
- 数字陷波器:针对特定频率噪声设计IIR滤波器
c复制// 50Hz工频陷波器实现
float Notch_Filter(float input) {
static float x[3] = {0}, y[3] = {0};
const float b0 = 0.9876, b1 = -1.9752, b2 = 0.9876;
const float a1 = -1.9752, a2 = 0.9753;
x[2] = x[1]; x[1] = x[0]; x[0] = input;
y[2] = y[1]; y[1] = y[0];
y[0] = b0*x[0] + b1*x[1] + b2*x[2] - a1*y[1] - a2*y[2];
return y[0];
}
4.2 温度补偿实践
在-20℃~60℃环境测试发现,HX711的零点漂移可达200码值。我的补偿方案:
- 内置NTC测温电路
- 建立温度-漂移查找表
- 采用分段线性插值补偿
c复制int32_t Temp_Compensation(int32_t raw, float temp) {
const float comp_table[] = {
// 温度(℃), 补偿系数(码值/℃)
-20, 2.1,
0, 1.8,
25, 1.5,
60, 2.3
};
float comp = 0;
for(uint8_t i=0; i<sizeof(comp_table)/8-1; i++) {
if(temp >= comp_table[i*2] && temp < comp_table[(i+1)*2]) {
float k = (comp_table[(i+1)*2+1]-comp_table[i*2+1]) /
(comp_table[(i+1)*2]-comp_table[i*2]);
comp = comp_table[i*2+1] + k * (temp - comp_table[i*2]);
break;
}
}
return raw - (int32_t)(comp * (temp - 25));
}
5. 典型问题排查指南
5.1 通信失败排查流程
根据多年调试经验,通信问题通常按以下步骤排查:
-
检查硬件:
- 测量HX711供电电压(AVDD=3.3V±5%)
- 确认DOUT上拉电阻正确连接
- 用示波器观察SCK脉冲是否正常
-
验证时序:
- 单个SCK脉冲宽度需>500ns
- 连续SCK脉冲间隔需>1μs
- 完整转换周期应<100ms(超时会导致HX711复位)
-
典型故障现象:
- 一直返回0xFFFFFF:通常是DOUT未正确连接
- 数据跳变剧烈:检查电源滤波电容是否虚焊
- 偶尔通信失败:延长SCK脉冲后的延时时间
5.2 精度不达标解决方案
当测量误差超过预期时,建议:
-
进行系统校准:
- 空载时调用HX711_Tare()记录零点
- 加载标准重量调用HX711_Calib()计算比例系数
-
优化PCB设计:
- 将模拟部分铺铜并单点接地
- 缩短传感器到HX711的走线长度
- 增加电源去耦电容(我习惯用10μF钽电容并联0.1μF陶瓷电容)
-
软件滤波增强:
c复制#define MEDIAN_FILTER_SIZE 7 int32_t Median_Filter(int32_t new_val) { static int32_t buffer[MEDIAN_FILTER_SIZE] = {0}; static uint8_t index = 0; int32_t temp[MEDIAN_FILTER_SIZE]; buffer[index++] = new_val; if(index >= MEDIAN_FILTER_SIZE) index = 0; memcpy(temp, buffer, sizeof(buffer)); Bubble_Sort(temp, MEDIAN_FILTER_SIZE); // 实现冒泡排序 return temp[MEDIAN_FILTER_SIZE/2]; }
6. 进阶应用实例
6.1 多HX711并行采集
在自动分拣系统中,我成功实现了STM32同时驱动8片HX711的方案:
-
硬件设计:
- 共用SCK信号线
- 每个HX711的DOUT接不同GPIO
- 为每片HX711配置独立LDO
-
分时采集策略:
c复制void Multi_HX711_Read(uint32_t results[]) { for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(SCK_GPIO, SCK_PIN, GPIO_PIN_RESET); while(HAL_GPIO_ReadPin(DOUT_GPIO[i], DOUT_PIN) == GPIO_PIN_SET); results[i] = 0; for(uint8_t j=0; j<24; j++) { HAL_GPIO_WritePin(SCK_GPIO, SCK_PIN, GPIO_PIN_SET); results[i] <<= 1; HAL_GPIO_WritePin(SCK_GPIO, SCK_PIN, GPIO_PIN_RESET); if(HAL_GPIO_ReadPin(DOUT_GPIO[i], DOUT_PIN)) results[i]++; } for(uint8_t k=0; k<GAIN; k++) { HAL_GPIO_WritePin(SCK_GPIO, SCK_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(SCK_GPIO, SCK_PIN, GPIO_PIN_RESET); } HAL_Delay(1); } } -
性能指标:
- 8通道轮流采样率可达50Hz/通道
- 通道间串扰<-80dB
- 功耗增加约15mA/片
6.2 低功耗设计
对于电池供电设备,我采用的优化措施:
-
间歇工作模式:
- 正常采样后立即切断HX711电源
- 通过MOS管控制供电(漏电流<1μA)
-
动态调整增益:
- 初始使用128倍增益检测微小信号
- 信号稳定后切换至64倍增益降低噪声
-
代码实现:
c复制void LowPower_Measure(void) { Power_ON_HX711(); HAL_Delay(10); // 等待稳定 uint32_t val = HX711_Read(); if(val < 10000) { HX711_SetGain(128); val = HX711_Read(); } else { HX711_SetGain(64); } Power_OFF_HX711(); Process_Data(val); }
实测可使系统平均功耗从5mA降至0.8mA,纽扣电池续航从7天延长至45天。
