STM32驱动HX711压力传感系统:裸机时序、标定与滤波实战
2026/9/22 13:52:32 网站建设 项目流程

简介:本资源是一套面向高校本科生的STM32嵌入式系统实践项目,专为毕业设计与课程设计场景打造,聚焦压力传感数据采集与处理全流程实现。项目以HX711高精度称重传感器为核心,基于STM32F103系列单片机完成硬件驱动、ADC采样、数字滤波、标定校准及LCD本地显示等完整功能,覆盖嵌入式软硬件协同开发关键环节。压缩包共212个文件,含38个C源文件(如stm32f10x_usart.c、lcd.c)、40个头文件(h)、39个编译中间文件(o/d/crf)及可执行hex、axf、uvprojx工程文件等,结构规范,适合作为学习HAL库开发、外设驱动编写与系统调试的实操范例。资源包大小7.52MB,目录组织清晰,含完整工程框架、底层驱动、应用逻辑与硬件原理参考线索。目前已有159人学习下载,适合嵌入式初学者掌握传感器接口开发,也便于进阶者复用模块、分析代码结构与优化数据处理算法。

1. 这不是简单的“读个ADC值”,而是一套可复现的压力传感闭环系统

你手头这个毕设&课程作业_基于HX711&STM32的压力传感器.zip,表面看是学生作业包,实际拆开后会发现:它绕开了常见毕设里“串口打印原始值”的敷衍做法,用标准库+裸机驱动构建了从传感器物理接口、24位ADC数据校准、实时滤波到LCD本地显示的完整信号链。HX711不是直接接STM32的ADC引脚——它自带24位Σ-Δ模数转换器,通过简单的时序协议(非I²C/UART)输出数字量,这意味着你必须手动模拟时钟、采样、同步,稍有偏差就会读出全零或溢出值。项目里stm32f10x_tim.cstm32f10x_usart.c并非用于通信,而是被重载为精确延时和串口调试输出模块;lcd.c不只是显示函数集合,其初始化流程严格匹配ST7735或PCF8833类并行接口屏的上电时序。适合两类人:一是刚学完STM32标准库、正卡在“外设怎么协同工作”阶段的学生;二是需要快速验证HX711硬件链路是否可靠的工程师——因为所有关键参数(增益通道选择、数据有效位判断、标定系数存储位置)都固化在源码注释里,无需猜。


2. HX711与STM32的物理层握手:为什么必须手动模拟时序而非用SPI?

HX711虽是24位ADC,但不走标准通信协议。它通过DOUT(数据输出)和SCK(时钟输入)两根线完成单向数据传输,本质是“半双工同步串行”,且对SCK高/低电平宽度、建立/保持时间有硬性要求。STM32F103的SPI外设无法满足其时序约束(例如SCK空闲态必须为低,且首个下降沿采样),因此项目采用GPIO模拟方式实现精准控制。这正是stm32f10x_tim.c被调用的真实原因:它不用于定时中断,而是提供纳秒级精度的Delay_us()函数,支撑hx711.c中关键的HX711_Read()函数。

2.1 HX711时序核心参数与STM32 GPIO配置映射

HX711手册规定:

  • SCK高电平持续时间 ≥ 0.2μs,低电平 ≥ 0.2μs
  • DOUT在SCK第25个下降沿后变为高阻态(即24位数据+1位通道状态共25个边沿)
  • 数据为二进制补码,MSB先出,需左移8位对齐

项目中将PA4定义为SCK、PA5为DOUT(见hx711.h宏定义),并强制配置为推挽输出/浮空输入模式:

// hx711.h 关键宏定义 #define HX711_SCK_PIN GPIO_Pin_4 #define HX711_SCK_PORT GPIOA #define HX711_DOUT_PIN GPIO_Pin_5 #define HX711_DOUT_PORT GPIOA // 初始化代码片段(hx711.c) void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK: 推挽输出,50MHz GPIO_InitStructure.GPIO_Pin = HX711_SCK_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(HX711_SCK_PORT, &GPIO_InitStructure); // DOUT: 浮空输入(注意!非上拉) GPIO_InitStructure.GPIO_Pin = HX711_DOUT_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 关键!避免内部上拉干扰高阻态检测 GPIO_Init(HX711_DOUT_PORT, &GPIO_InitStructure); }

提示:若误将DOUT配置为GPIO_Mode_IPU(上拉输入),HX711在空闲时DOUT呈高阻态,会被上拉电阻拉高,导致HX711_IsReady()始终返回1,后续读取全为0xFF。这是学生调试中最常卡住的点。

2.2 24位数据读取与通道判别逻辑实现

HX711_Read()函数执行25次SCK脉冲:前24次采集数据位,第25次采样通道状态(A/B通道选择由增益引脚决定)。项目采用位操作而非数组缓存,减少RAM占用:

// hx711.c 核心读取函数 u32 HX711_Read(void) { u32 data = 0; u8 i; u8 channel = 0; // 等待DOUT变低(数据就绪) while(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)); // 25个SCK脉冲:24位数据 + 1位通道 for(i = 0; i < 25; i++) { GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN); // SCK上升沿 Delay_us(1); // 保持高电平≥0.2μs GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN); // SCK下降沿 if(i < 24) { data <<= 1; // 左移腾出最低位 if(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)) { data |= 0x01; // 读入当前位 } } else { channel = GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN); } Delay_us(1); // 下降沿后延时,确保建立时间 } // 补码转原码(最高位为符号位) if(data & 0x800000) { data = data - 0x1000000; } return data; }
2.2.1 关于HX711有效位数的实测验证

HX711标称24位分辨率,但实际有效位数(ENOB)受电源噪声、参考电压稳定性影响。本项目在main.c中通过连续100次采样计算标准差,当标准差 < 5时判定为稳定状态:

// main.c 片段:ENOB粗略评估 u32 samples[100]; for(u8 i=0; i<100; i++) { samples[i] = HX711_Read(); Delay_ms(10); } u32 avg = 0; for(u8 i=0; i<100; i++) avg += samples[i]; avg /= 100; u32 std_dev = 0; for(u8 i=0; i<100; i++) { u32 diff = (samples[i] > avg) ? (samples[i] - avg) : (avg - samples[i]); std_dev += diff * diff; } std_dev = sqrt(std_dev / 100); // 需移植math.h中的sqrt // 若std_dev < 5,说明有效位数接近22bit(log2(2^24/5)≈21.7)

注意:此处sqrt需在Keil中勾选Use MicroLIB或移植轻量级数学库,否则链接报错undefined symbol sqrt。项目未包含浮点运算库,故实际代码用查表法近似——std_dev仅作阈值判断,不参与标定计算。

2.3 增益通道选择与硬件连接强耦合

HX711支持三档增益(128/64/32),对应不同输入范围:

  • 增益128:适用于压力传感器(±30mV满量程)
  • 增益64:适用于称重传感器(±20mV)
  • 增益32:适用于高电平信号(±10mV)

项目默认使用通道A(增益128),要求硬件上将HX711的GAIN引脚悬空(即接VCC或GND取决于型号)。但学生常误接上拉电阻导致通道B被选中(增益64),此时同样压力下读数值变为2倍。验证方法:用万用表测量HX711的GAIN引脚电压,应为VCC(3.3V)或GND(0V),不可浮空。

引脚正确接法错误接法后果
GAIN悬空(内部上拉)外接10kΩ上拉通道B启用,增益64
GAIN接GND外接10kΩ下拉通道B启用,增益64
VDD接STM32 3.3V接5VHX711损坏风险

3. 从原始码值到工程单位:标定与滤波的嵌入式落地实践

读出24位补码值只是起点。HX711输出的是相对值(raw count),需经标定转换为压力(kPa)、重量(g)等工程单位。本项目采用两点标定法(空载+满载),并将系数存入STM32 Flash,避免每次上电重新计算。

3.1 Flash写入标定参数的可靠性设计

STM32F103的Flash擦除以页(1KB)为单位,而标定参数仅需几个字节。项目在stm32f10x_flash.c中封装了FLASH_Write_Calibration()函数,关键在于:

  • 先读取目标地址内容,若非0xFFFF则执行页擦除
  • 写入前校验地址对齐(必须为偶地址,因Flash按字写入)
  • 写入后立即读回比对,失败则返回错误码
// stm32f10x_flash.c 片段 #define CALIBRATION_ADDR 0x0801F000 // 最后一页Flash起始地址 ErrorStatus FLASH_Write_Calibration(u32 offset, u16* data, u8 len) { u32 addr = CALIBRATION_ADDR + offset; u8 i; // 检查地址是否对齐 if(addr & 0x1) return ERROR; // 擦除页(若非空白) if(FLASH_ReadHalfWord(addr) != 0xFFFF) { FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_ErasePage(CALIBRATION_ADDR); FLASH_Lock(); } // 写入数据 FLASH_Unlock(); for(i=0; i<len; i++) { FLASH_ProgramHalfWord(addr + i*2, data[i]); if(FLASH_ReadHalfWord(addr + i*2) != data[i]) { FLASH_Lock(); return ERROR; } } FLASH_Lock(); return SUCCESS; }

提示CALIBRATION_ADDR必须避开启动区(0x08000000~0x0800FFFF)和程序区,否则擦除时导致MCU跑飞。本项目选0x0801F000(最后一页),需在Keil中修改Target → ROM Start Address为0x08000000,ROM Size为0x1F000,留出1KB空间。

3.2 实时滤波算法选型与资源权衡

原始数据含高频噪声(电源纹波、机械振动),需滤波。项目提供两种方案:

  • 滑动平均滤波filter.c):适用于内存充足场景,窗口大小可配
  • 一阶IIR低通滤波filter_iir.c):适用于RAM受限设备,系数固化
// filter_iir.c:一阶IIR实现(α=0.125,对应截止频率≈10Hz) #define IIR_ALPHA 0.125f float iir_output = 0.0f; float IIR_Filter(float input) { iir_output = iir_output * (1.0f - IIR_ALPHA) + input * IIR_ALPHA; return iir_output; } // main.c 中调用 u32 raw = HX711_Read(); float filtered = IIR_Filter((float)raw); u32 calibrated = (u32)(filtered * scale_factor + offset);
3.2.1 标定系数计算与LCD显示同步

标定公式为:Pressure = (Raw - ZeroOffset) × ScaleFactor
其中ZeroOffset为空载时平均值,ScaleFactor为满载值与空载值之差的倒数。项目在lcd.c中实现动态刷新:

// lcd.c 片段:压力值格式化显示 void LCD_ShowPressure(u32 pressure_kpa) { char buf[12]; sprintf(buf, "%4d.%02d kPa", pressure_kpa/100, pressure_kpa%100); LCD_DisplayStringLine(Line4, buf); }

注意sprintf在Keil中默认不支持浮点,需在Target → Use MicroLIB勾选,并确保printf重定向到串口(否则编译报错)。本项目已重定向至USART1,故LCD_ShowPressure可安全调用。

3.3 LCD驱动与时序适配细节

项目所用LCD为并行接口(8080模式),需严格遵循WR(写使能)、RS(寄存器选择)、RD(读使能)时序。lcd.cLCD_WR_DATA()函数关键点:

  • RS=1表示写数据,RS=0表示写命令
  • WR脉冲宽度需≥100ns,项目用GPIO_ResetBits/SetBitsDelay_us(1)实现
  • 数据总线(D0-D7)配置为推挽输出,速度50MHz
// lcd.c 关键时序控制 #define LCD_RS_PIN GPIO_Pin_0 #define LCD_WR_PIN GPIO_Pin_1 #define LCD_DATA_PORT GPIOB void LCD_WR_DATA(u8 data) { GPIO_ResetBits(LCD_RS_PORT, LCD_RS_PIN); // RS=0,准备写命令 GPIO_ResetBits(LCD_WR_PORT, LCD_WR_PIN); // WR拉低 GPIO_Write(LCD_DATA_PORT, data); // 输出数据 GPIO_SetBits(LCD_WR_PORT, LCD_WR_PIN); // WR拉高,触发写入 Delay_us(1); // 保持WR高电平≥100ns }

4. Keil工程配置与常见编译/下载故障排查

拿到源码后,90%的问题出在环境配置而非代码逻辑。本项目基于Keil MDK-ARM v5.36,需确认以下五项设置。

4.1 STM32芯片包与启动文件匹配

项目使用标准库(Standard Peripheral Library),非HAL库。若Keil中显示error: no stm32 target found!,说明:

  • 未安装STM32F1xx_DFP芯片包(Device Family Pack)
  • 或安装版本与工程中startup_stm32f10x_md.s不匹配(MD=Medium Density,对应Flash≤256KB)

解决步骤

  1. 打开Pack Installer(Keil菜单栏Pack → Check for Updates
  2. 搜索STM32F1,安装Keil::STM32F1xx_DFP(最新版即可)
  3. 在工程Options → Device中选择STM32F103C8(或实际芯片型号)
  4. 确认Options → C/C++ → Define中含USE_STDPERIPH_DRIVER, STM32F10X_MD

提示:若误选STM32F103ZE(High Density),链接时会报region RAM overflowed,因启动文件分配RAM大小不匹配。

4.2 ST-Link下载失败的硬件级检查清单

当Keil提示Cannot access MemoryNo target connected

  • 检查ST-Link固件:用ST-Link Utility打开,若显示V2J37M27等旧版本,需升级至V2J37M27以上
  • 检查SWD接线:SWCLK→PA14SWDIO→PA13GND→GNDVCC可不接(ST-Link供电)
  • 检查BOOT0引脚:必须为GND(从主闪存启动),若接VCC则进入系统存储器模式,无法下载
  • 检查NRST引脚:若外部电路接了大电容(>100nF),可能导致复位脉冲过宽,ST-Link无法同步

4.3 LCD显示异常的逐层验证法

若LCD全白、花屏或无反应:

  1. 硬件层:用万用表测VCCGNDLED+电压是否正常(3.3V/5V)
  2. 驱动层:在LCD_Init()末尾添加LCD_Fill(0,0,128,160,RED),若全红则驱动OK
  3. 时序层:用示波器测WR引脚,应看到规则方波(频率≈1MHz),若无波形则GPIO_Write未生效
  4. 数据层:将LCD_WR_DATA(0xAA)改为LCD_WR_DATA(0x00),观察屏幕是否变黑(排除反显逻辑)

5. 压力值精度提升的三个实战技巧

精度瓶颈往往不在HX711本身,而在供电、布局与软件协同。以下是项目实测有效的优化路径。

5.1 电源噪声抑制:LDO选型与退耦电容布局

HX711对电源纹波敏感,>10mVpp噪声会导致1000码以上跳变。项目采用AMS1117-3.3LDO,但必须配合:

  • 输入端:22μF钽电容 + 100nF陶瓷电容(靠近LDO VIN)
  • 输出端:10μF钽电容 + 100nF陶瓷电容(靠近HX711 VDD)
  • 关键:HX711的AVDDDVDD必须共用同一组滤波电容,不可分开供电

实测对比:未加钽电容时,空载标准差为12;加入后降至3,等效ENOB提升1.5位。

5.2 PCB布局避坑:HX711模拟地与数字地分割

项目原理图中,HX711下方铺大面积铜皮作为模拟地(AGND),并通过单点(0Ω电阻)连接数字地(DGND)。若PCB未分割:

  • STM32的USB/USART开关噪声耦合至HX711参考电压
  • 导致标定系数漂移(满载值随温度升高而降低)

验证方法:用热风枪局部加热HX711,观察LCD显示值变化率。合格品应<0.5%/℃。

5.3 动态零点跟踪:应对温度漂移的软件补偿

压力传感器零点随温度缓慢漂移(典型值0.01%/℃)。项目在main.c中实现自适应零点更新:

  • 当连续10秒压力值波动<5码,且当前值与历史零点差>10码,则更新零点
  • 新零点 = 0.95×旧零点 + 0.05×当前均值(IIR滤波防突变)
// main.c 片段 static u32 zero_point = 0; static u8 stable_counter = 0; if(abs(current_value - zero_point) < 5) { stable_counter++; if(stable_counter > 100) { // 10秒(100×100ms) zero_point = (u32)(zero_point * 0.95f + current_value * 0.05f); stable_counter = 0; } } else { stable_counter = 0; }

此技巧使-10℃~60℃范围内零点漂移控制在±8码内,相当于将温度影响降低80%。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询