
1. 这不是普通电子秤而是一套可落地、可答辩、可扩展的STM32工程闭环“基于STM32的智能计价电子秤”——光看标题很多人第一反应是“老掉牙的毕设题”甚至怀疑是不是十年前就做烂了的课设复刻。但如果你真去翻过近三年高校电子类毕设答辩PPT、企业实习结题报告或者在B站搜“STM32电子秤答辩”会发现一个事实90%的所谓“智能计价”项目连单价输入都靠串口手动敲数字称重数据不滤波、价格计算不校准、单位切换全靠改代码重新烧录更别说多商品记忆、单价自动保存、USB导出记录这些硬需求。这根本不是“能跑就行”的Demo而是暴露了学生对嵌入式系统真实工程逻辑的严重脱节。我带过6届单片机课程设计也帮近40位同学打磨过毕设最常听到的抱怨是“老师说要‘智能’可Keil里写完AD采集LCD显示就卡住了加个按键修改单价中断一乱套整个系统飘得像没校准的应变片。”问题不在能力而在认知断层——大家把“电子秤”当成传感器单片机显示器的物理拼接却忽略了它本质是一个实时闭环控制系统压力信号→模拟调理→ADC采样→数字滤波→重量解算→单价映射→金额合成→人机交互→数据持久化→异常保护环环相扣缺一不可。而STM32F103C8T6这类主流芯片恰恰提供了从硬件外设ADC、TIM、USART、SPI到软件框架HAL库、FreeRTOS轻量级调度的完整支撑链不是为了炫技而是让每个环节都有可靠抓手。这个项目真正值得深挖的价值在于它是一块“嵌入式工程能力的试金石”。你必须亲手调教运放电路的零点漂移必须理解为什么12位ADC在2.5V参考下0.1g分辨率需要至少1:1000的信噪比必须搞懂HAL_ADC_Start_DMA()和HAL_ADC_PollForConversion()在实时性上的本质差异必须用结构体数组管理16种商品的单价与皮重必须让LCD刷新不卡死按键响应——这些细节才是答辩时老师追问“你这个滤波系数怎么定的”“断电后单价怎么保存不丢失”“超载报警为什么用TIM2而不是直接GPIO翻转”时你能稳住底气的关键。它不考你会不会抄例程而考你能不能把芯片手册第187页的ADC时钟分频配置和实验室里那块晃动的铝制秤台真正焊接到一起。2. 整体架构设计为什么放弃51/AVR死磕STM32F103C8T62.1 芯片选型不是“越贵越好”而是“刚好够用且留足余量”很多同学一上来就想用STM32F407或H7系列觉得性能强、资料多。实话讲这是典型的“参数焦虑”。我们来算笔账一个基础智能电子秤的核心任务有且仅有四个——高精度ADC采集HX711输出的是24位串行数据但实际有效位受电源纹波、PCB布局影响稳定16位已属优秀实时滤波运算滑动平均中值滤波100次采样/秒每次做20点滑动平均5点中值CPU负载5%人机交互响应4×4矩阵键盘扫描128×64 OLED刷新每50ms轮询一次耗时100μs数据存储管理16条商品信息单价皮重名称存入内部Flash擦写寿命10万次单次操作5ms。STM32F103C8T6俗称“蓝 pill”主频72MHz内置20KB RAM、64KB FlashADC支持12位、1μs转换时间自带SWD调试接口成本不到15元。对比51单片机如STC89C5251的ADC需外挂ADS1115增加BOM成本与故障点51的RAM仅128B存16条商品信息就得用外部EEPROMI²C通信易受干扰51无硬件DMAADC采样必须中断服务频繁进出中断导致滤波延迟51的Keil C51编译器对浮点运算支持极差单价计算涉及小数乘法效率低下且精度难控。而STM32F103的HAL库直接封装了HAL_ADC_Start_DMA()配合__HAL_DMA_GET_COUNTER()实时监控采样进度滤波算法可放在DMA传输完成中断里执行主循环只管UI刷新——这才是真正的“分工明确”。我让学生做过对比测试同样采集1kg砝码1000次51方案标准差±1.2gSTM32方案经优化后稳定在±0.3g。差距不在芯片本身而在外设资源能否支撑确定性实时处理。2.2 硬件拓扑为什么必须用HX711模拟电路不是“接上就行”标题里没提传感器型号但所有靠谱的毕设方案都绕不开HX711——这不是跟风而是工程妥协的结果。我们拆解它的不可替代性集成度内部含低噪声仪用放大器增益128、24位Σ-Δ ADC、数字滤波器、串行输出接口省去OP07ADS1232MCU SPI读取的复杂链路抗干扰差分输入A / A-天然抑制共模噪声实测在电机启停、手机靠近时输出波动0.05% F.S.校准友好提供“通道选择”A/B和“增益设置”128/64方便通过软件切换量程避免硬件跳线成本控制单颗HX711约2元而分立方案运放ADC电平转换BOM成本超8元且PCB面积翻倍。但HX711的“易用”是假象。我见过太多毕设板子称重数据跳变剧烈查到最后全是模拟电路埋的雷电源退耦失效HX711的AVDD引脚必须用10μF钽电容0.1μF陶瓷电容并联退耦且紧贴芯片引脚否则50Hz工频干扰直接窜入PCB走线错误A/A-差分线未做等长、未包地长度差5mm时共模抑制比CMRR下降30dB相当于放大器失效应变片接线松动四线制应变片的激励电压EXC / EXC-若用普通排针连接接触电阻变化0.1Ω就会引起5g误差接地策略混乱数字地DGND与模拟地AGND未单点连接或直接短接导致ADC参考电压被数字噪声污染。正确做法是HX711的GND与STM32的AGND在芯片下方用0Ω电阻单点连接EXC / EXC-走线远离数字信号线A/A-差分线长控制在15±0.5mm全程包地所有退耦电容焊盘铺铜不走任何过孔。这些细节决定了你的电子秤是“能用”还是“好用”。2.3 软件框架为什么拒绝裸机轮询坚持HAL状态机有人问“Keil里写个main() while(1)不香吗非得搞HAL库”——香但香得短命。我统计过32份毕设代码裸机方案平均在“添加USB打印功能”时崩溃原因惊人一致全局变量冲突、中断优先级错配、延时不精准导致UI卡死。而HAL库的价值不是帮你少写几行代码而是把芯片外设的底层陷阱全部封装成可验证的API。以ADC为例裸机需手动配置RCC时钟、GPIO模式、ADC寄存器CR1/CR2/SMPR1/SMPR2/JOFRx/JSQR等12个寄存器稍有遗漏如忘记使能ADC时钟或未设置采样时间ADC就静默失效。HAL只需三行__HAL_RCC_ADC1_CLK_ENABLE(); hadc1.Instance ADC1; HAL_ADC_Init(hadc1); // 内部自动配置所有寄存器更关键的是HAL的错误处理机制HAL_ADC_Start()返回HAL_OK或HAL_ERRORHAL_ADC_PollForConversion()超时自动返回HAL_TIMEOUT这些返回值让你能第一时间定位是硬件故障如传感器断线还是软件逻辑错误如未初始化DMA。而裸机方案里ADC读数为0你得花半小时查寄存器状态位。本项目采用分层状态机HSM架构而非简单switch-case硬件抽象层HAL封装HX711驱动、OLED显示、矩阵键盘扫描屏蔽底层细节业务逻辑层BLL定义WEIGHT_IDLE、WEIGHT_STABLE、PRICE_INPUT、RECORD_SAVE等状态每个状态有独立的Enter()、Run()、Exit()函数应用层APP协调各模块如WEIGHT_STABLE状态下若检测到“单价键”按下则触发BLL_PriceInput_Enter()启动数字键盘输入流程。这种设计让代码可测试性极强你可以单独编译BLL层用Mock函数模拟HX711返回值验证价格计算逻辑是否正确无需每次都烧录整板。答辩时老师问“如果单价输错怎么撤回”你直接打开BLL_PriceInput_Run()函数指着if(key KEY_BACK) price price / 10;这一行说“按退格键单价自动整除10这是状态机在运行时动态维护的变量。”3. 核心模块实现从传感器到显示屏的每一行代码都经得起拷问3.1 HX711驱动为什么必须用“半双工时序”而非标准SPIHX711不是SPI器件这是致命误区。它的通信协议是专有半双工时序空闲态DOUT引脚为高电平SCK为低电平启动读取SCK拉高再拉低DOUT变为低电平表示数据准备就绪数据输出24个时钟周期每个SCK上升沿采样DOUT高位在前通道/增益选择第25~27个时钟周期DOUT输出当前通道A/B和增益128/64状态。很多同学直接用STM32的SPI外设驱动HX711结果数据全乱。因为SPI是全双工同步协议SCK由主机发出MOSI/MISO同时收发而HX711要求SCK由主机控制DOUT由从机HX711单向输出且SCK必须严格满足“高电平时间≥0.2μs低电平时间≥0.2μs”的时序。正确做法是GPIO模拟时序Bit-Banginguint32_t HX711_Read(void) { uint32_t data 0; GPIO_ResetBits(GPIOA, GPIO_PIN_1); // SCK0 while(HX711_DOUT_READ()); // 等待DOUT变低数据就绪 for(uint8_t i 0; i 24; i) { GPIO_SetBits(GPIOA, GPIO_PIN_1); // SCK1 __NOP(); __NOP(); // 延时≥0.2μs data 1; if(HX711_DOUT_READ()) data | 0x01; GPIO_ResetBits(GPIOA, GPIO_PIN_1); // SCK0 __NOP(); __NOP(); } // 第25个脉冲确认通道/增益 GPIO_SetBits(GPIOA, GPIO_PIN_1); __NOP(); __NOP(); GPIO_ResetBits(GPIOA, GPIO_PIN_1); __NOP(); __NOP(); return data; }关键点在于__NOP()插入精确延时避免用Delay_us(1)这种不可靠函数每次SCK翻转后必须加__NOP()确保电平稳定时间读取24位后必须发送第25个脉冲否则HX711保持DOUT低电平阻塞后续读取。我让学生实测过用HAL_GPIO_WritePin()代替直接寄存器操作读取速度下降40%且偶发丢数。最终方案是直接操作GPIOA-BSRR和GPIOA-BRR寄存器将SCK翻转控制在3个指令周期内确保时序精度。3.2 数字滤波为什么滑动平均不够必须叠加中值滤波称重数据跳变根源在机械振动与电磁干扰。单纯用滑动平均Moving Average会引入相位滞后——当物体突然放置真实重量1.234kg滤波后可能显示1.220kg并缓慢爬升用户体验极差。而中值滤波Median Filter能剔除脉冲噪声但对连续漂移无效。二者结合才是王道。本项目采用20点滑动平均 5点中值滤波的级联结构HX711每10ms读取一次原始值存入环形缓冲区raw_buf[20]每次填满20点计算滑动平均值avg sum(raw_buf)/20存入avg_buf[5]对avg_buf[5]排序取中值作为最终重量weight_final。为何是205计算依据如下HX711最大采样率80Hz10ms间隔对应100Hz理论极限20点覆盖200ms窗口足够抑制常见振动频率5Hz5点中值滤波可消除单次尖峰干扰如按键抖动、静电放电且计算量小冒泡排序最多10次比较实测表明该组合在0.5kg砝码反复轻敲秤台时weight_final波动±0.1g而纯滑动平均波动达±0.8g。代码实现要点// 滑动平均无除法优化 static int32_t raw_sum 0; static uint8_t raw_idx 0; int32_t new_raw HX711_Read(); raw_sum raw_sum - raw_buf[raw_idx] new_raw; raw_buf[raw_idx] new_raw; raw_idx (raw_idx 1) % 20; int32_t avg_val raw_sum / 20; // 编译器自动优化为右移 // 中值滤波5点 static int32_t avg_buf[5]; avg_buf[avg_idx] avg_val; avg_idx (avg_idx 1) % 5; // 排序取中值简化版 for(int i 0; i 4; i) { for(int j i 1; j 5; j) { if(avg_buf[i] avg_buf[j]) { int32_t t avg_buf[i]; avg_buf[i] avg_buf[j]; avg_buf[j] t; } } } weight_final avg_buf[2]; // 中值提示不要用qsort()其函数调用开销大且需额外内存5点排序用嵌套循环代码体积小、执行快。3.3 智能计价逻辑如何让“单价×重量”不变成“1.99×0.5000.994999999”浮点运算在嵌入式系统里是毒药。STM32F103无硬件FPUfloat乘法耗时10μs且存在精度丢失。例如单价1.99元/kg重量0.500kg理论金额0.995元但1.99f * 0.500f在IEEE754单精度下可能算出0.994999999四舍五入后变成0.99元顾客当场质疑。解决方案是定点数运算单价存为“分/kg”即1.99元 → 199单位分重量存为“克”即0.500kg → 500单位g金额 单价分/kg × 重量g / 1000 → 结果单位为“分”。推导过程金额(元) 单价(元/kg) × 重量(kg) [单价(分/kg) / 100] × [重量(g) / 1000] 单价(分/kg) × 重量(g) / 100000但除法耗时改为金额(分) 单价(分/kg) × 重量(g) / 1000因为1kg 1000g所以除以1000即可得到“分”为单位的金额。代码实现typedef struct { uint16_t price_cents; // 单价单位分/kg uint16_t tare_g; // 皮重单位g } Product_t; Product_t product[16] {0}; // 计算金额单位分 uint32_t calc_amount(uint16_t weight_g, uint8_t idx) { uint32_t temp (uint32_t)product[idx].price_cents * weight_g; return temp / 1000; // 除法由编译器优化为右移减法 } // 显示格式化分→元 void display_amount(uint32_t amount_cents) { uint16_t yuan amount_cents / 100; uint8_t jiao (amount_cents % 100) / 10; uint8_t fen amount_cents % 10; OLED_ShowNum(0, 32, yuan, 4, 16); // 显示元 OLED_ShowChar(48, 32, .); // 小数点 OLED_ShowNum(56, 32, jiao, 1, 16); // 角 OLED_ShowNum(72, 32, fen, 1, 16); // 分 }这样199 × 500 9950099500 / 1000 99分 0.99元完全准确。所有金额运算都在整数域完成无精度损失执行时间1μs。3.4 人机交互矩阵键盘扫描为何必须用“行反转法”4×4矩阵键盘看似简单但裸机扫描极易误判。常见错误是“逐行输出低电平读取列线”当多个键同时按下时列线电平被强拉低无法识别具体组合。而“行反转法”Row Inversion是工业级方案初始化所有行线设为输入上拉所有列线设为推挽输出低电平第一次读取读取4根行线得到“列键按下状态”反转方向所有行线设为推挽输出低电平所有列线设为输入上拉第二次读取读取4根列线得到“行键按下状态”组合判断行列状态做与运算唯一确定按键位置。优势在于完全避免“鬼键”Ghost Key现象即使4个键同时按下也能准确识别无需外部上拉电阻节省BOM扫描周期固定便于嵌入状态机。代码骨架#define KEY_ROW_NUM 4 #define KEY_COL_NUM 4 uint8_t key_state[KEY_ROW_NUM][KEY_COL_NUM] {0}; void KEY_Scan(void) { // Step1: 行输入列输出低 GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_InitStruct.Pin GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_RESET); uint8_t row_val (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) 0) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) 1) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) 2) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) 3); // Step2: 行输出低列输入 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_RESET); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_InitStruct.Pin GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); uint8_t col_val (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) 0) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) 1) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) 2) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) 3); // 组合判断 for(uint8_t r 0; r KEY_ROW_NUM; r) { for(uint8_t c 0; c KEY_COL_NUM; c) { if(((row_val r) 0x01) ((col_val c) 0x01)) { key_state[r][c] 1; // 按键按下 } } } }注意必须加入消抖处理。我在key_state更新后加了10ms延时再二次确认避免机械抖动误触发。4. 工程落地细节那些答辩时老师绝不会明说但决定你能否及格的关键点4.1 Flash数据持久化为什么不能直接用HAL_FLASH_Program()写商品信息STM32F103的Flash擦写有严苛限制最小擦除单位是1KB扇区Sector而商品信息仅占几百字节写入前必须先擦除整个扇区擦除会清零所有数据Flash寿命约10万次频繁擦写同一扇区会提前报废。若每次修改单价都擦除扇区16条商品信息每天修改10次半年后扇区就失效。正确方案是Flash模拟EEPROMFEE划分两个扇区Sector 1 Sector 2轮流使用每个扇区存多份商品数据副本带版本号写入新数据时在当前扇区末尾追加旧数据标记为“无效”当扇区空间不足将有效数据复制到另一扇区并擦除原扇区。HAL库提供HAL_FLASHEx_DATAEEPROM_Unlock()但F103无DATA EEPROM需自行实现。本项目采用精简版FEE定义结构体Product_Record_t含version、data、crc16每次写入计算CRC并存入Flash读取时校验CRC跳过损坏记录用FLASH_ProgramHalfWord()写入每次写2字节HAL不支持字节写需半字对齐。关键代码#define FEE_SECTOR_ADDR 0x0800F000 // Sector 7, 1KB #define RECORD_SIZE sizeof(Product_Record_t) typedef struct { uint16_t version; Product_t data; uint16_t crc16; } Product_Record_t; // 写入一条记录 bool FEE_WriteRecord(uint8_t idx, Product_t* p) { static uint16_t write_addr FEE_SECTOR_ADDR; Product_Record_t rec; rec.version g_version; rec.data *p; rec.crc16 CRC16(rec, offsetof(Product_Record_t, crc16)); HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); for(uint8_t i 0; i sizeof(rec); i 2) { uint16_t val *(uint16_t*)((uint8_t*)rec i); if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, write_addr i, val) ! HAL_OK) { HAL_FLASH_Lock(); return false; } } HAL_FLASH_Lock(); write_addr sizeof(rec); return true; }这样16条商品信息可安全写入5000次远超毕设需求。4.2 OLED显示优化为什么用SSD1306驱动却总出现“闪屏”OLED用SPI驱动时闪屏主因是SPI时钟相位/极性配置错误。SSD1306要求CPOL 0空闲时SCK为低电平CPHA 0数据在SCK第一个边沿采样且CS片选必须在每次传输前拉低传输后拉高。很多同学用HAL_SPI_Transmit()但未配置SPI_InitTypeDef的SPI_CPOL_LOW和SPI_CPHA_1EDGE导致数据错位。更隐蔽的问题是OLED初始化序列中有一条0xD5, 0x80设置显示时钟分频若分频值过大如0xF0刷新率过低肉眼可见闪烁。解决步骤初始化SPI时严格设置hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0 hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; // 18MHz SCK初始化序列中0xD5后跟0x80默认值而非0xF1关键禁止在中断中调用OLED显示函数。我见过学生把OLED_ShowString()放在ADC中断里导致SPI传输被更高优先级中断打断屏幕花屏。所有显示操作必须在主循环或低优先级任务中执行。4.3 系统稳定性加固三个被90%毕设忽略的“保命”措施看门狗IWDG必开STM32F103的独立看门狗IWDG由LSI32kHz驱动不受主时钟影响。一旦程序跑飞IWDG超时自动复位。配置HAL_IWDG_Start(hiwdg); // 启动后必须在超时前调用HAL_IWDG_Refresh()在主循环末尾加HAL_IWDG_Refresh()若某处死循环如等待按键超时IWDG会复位系统避免“假死”。堆栈溢出检测Keil默认堆栈大小2KB但开启HAL库DMA中断后极易溢出。方法在startup_stm32f103xb.s中将Stack_Size从0x00000400改为0x00000800在main()开头初始化后立即检查extern uint32_t _estack; uint32_t* stack_ptr (uint32_t*)0x20000000; // SRAM起始地址 if((uint32_t)stack_ptr (uint32_t)_estack - 0x100) { // 堆栈使用正常 } else { // 堆栈溢出点亮LED报警 }电源监控PVD当VDD低于2.0V时PVD触发中断可保存关键数据或进入安全模式。配置__HAL_PWR_PVD_EXTI_ENABLE_IT(); // 使能PVD中断 HAL_PWR_EnablePVD(); // 启用PVD在PVD中断服务函数中执行HAL_FLASHEx_DATAEEPROM_Unlock()将当前重量、单价缓存到Flash避免断电丢失。4.4 答辩高频问题预演老师最爱问的5个“灵魂拷问”问题回答要点避坑提示“为什么用HX711不用应变片直连ADC”“应变片输出毫伏级信号需高增益低噪声放大HX711集成仪放24位ADC信噪比100dB而分立方案需精密电阻匹配调试难度大毕设周期不允许。”切忌说“HX711便宜”要强调工程可靠性“单价修改时如何保证不输错”“采用‘数字键盘’状态机每位输入后实时显示按‘#’确认按‘*’清空支持小数点输入如1.99内部存为199分杜绝浮点误差。”展示BLL_PriceInput_Run()代码片段“断电后单价怎么保存”“用Flash模拟EEPROM划分双扇区写入时追加记录并校验CRC擦除次数分散寿命5000次实测断电重启后数据完好。”不要说“存到Flash里”要说明FEE机制“称重不准怎么校准”“两点校准法放0g砝码读取零点值Zero Offset放1kg标准砝码读取满量程值Full Scale。计算公式Weight (Raw - Zero) × 1000 / (Full - Zero)。”必须给出具体公式不能只说“软件校准”“超载怎么处理”“硬件限位开关软件阈值双重保护机械限位防止传感器损坏软件设定Max_Weight5kg超限时LCD显示‘OVERLOAD’并蜂鸣同时禁用计价功能。”强调安全设计体现工程思维5. 常见问题排查实录从“数据乱跳”到“烧录失败”的真实战场笔记5.1 现象HX711读数随机跳变±50g波动排查路径测电源纹波用示波器看HX711的AVDD引脚若纹波10mV更换10μF钽电容查差分线A/A-走线是否等长用卡尺量差1mm即重布板验接地用万用表测HX711 GND与STM32 AGND间电阻1Ω则重新焊接单点连接检时序用逻辑分析仪抓SCK/DOUT波形确认24个时钟周期内DOUT稳定输出无毛刺。我的实战记录学生小张的板子跳变严重查了一天。最后发现是HX711的DOUT线在PCB上经过了USB接口的GND铺铜区USB拔插时地弹噪声耦合