简介:基于STM32的公路四路超重检测系统是一份可直接运行的嵌入式完整工程,面向STM32开发者、物联网专业学生以及交通监控相关技术人员,用于解决公路超重检测效率低、无法远程管理的问题。系统通过四路高精度称重传感器采集车辆重量数据,经数据处理单元分析后实时显示在OLED屏上,当四路数据总和超过设定阈值时自动触发声光报警,并借助物联网模块将超重信息上传至监控中心,实现远程实时管控。资源包共306个文件,以C源码(.c/.h)为核心,包含Keil工程配置(.uvprojx)、编译产物(.axf/.o/.crf)以及分散加载文件(.sct)等,压缩包大小约10.31MB,可直接用Keil打开编译和烧录。目前已有333人学习下载,认可度较好。工程内附带完整的标准外设库驱动(定时器、ADC、USART等)和机智云协议代码,便于梳理称重采集、阈值判断、报警触发与联网上报的完整实现流程;模块化设计使二次开发更便捷,也适合在课程设计或毕业设计中参考借鉴。
1. 基于STM32的公路四路超重检测系统:为什么“四路”才能真正卡住超重
公路治超现场最怕的不是车多,而是称出来的数让人来回扯皮。一套基于STM32的公路四路超重检测系统,恰好能缓解这种尴尬:四个称重通道各自接独立称重传感器,整车低速通过时把左右轮胎的受力分别采回来,比传统单路或双路平均更能抵抗车身倾斜、轮胎压秤条边缘这类干扰。这个以 STM32F103C8T6 为主控的工程包,把四路 HX711 类 ADC 采集、零点标定、数字滤波、RS485/4G 联网上送都覆盖了,代码和接线图基本属于可照抄级别。适合要快速搭一台治超称重控制器原型,又不想从零折腾秤体桥路和通讯协议的硬件工程师、毕设学生或小规模项目负责人。
2. 系统框架与选型:从四路称重条到主控引脚的通道拓扑取舍
2.1 单路/双路在市治超现场的弱项
常规静态地磅往往是一台称重仪表接一组传感器,按轴组折算总重。动态治超不一样,车辆低速通过时车身会发生侧倾,两个前轮压秤条的位置并不完全对称。只用单路或双路取平均值,左边轮胎压到秤条边缘、右边正压在称重区中心,两路差出 15% 甚至更多都很常见。
四路方案各自独立采集、独立标定,最后在程序里做加权平均,能把这类偏载误差压到 ±2% 以内。这是四路通道存在的直接理由,也是这套 STM32 工程包把“四路”当作设计底线的原因。现场装秤时,四路秤条通常按左前、右前、左后、右后布置,每路覆盖一个轮胎的触地区域,车轴通过时四路数据同步刷新,由主控统一拼成一帧完整过车记录。
2.2 主控与四路 ADC 的引脚分配
主控选 STM32F103C8T6,核心原因是资源刚好够且开发环境成熟:72MHz 主频跑位码时序绰绰有余,GPIO 数量足够接四路 ADC,片内还带独立看门狗,治超现场环境差、程序偶发跑飞也能自动复位。更重要的是这个型号的替代品和下载器普及率高,后续维护不需要特殊烧录工具。
四路称重传感器对应四片独立的 HX711 模块,引脚分配如下:
| 路数 | 对应位置 | HX711 模块 | DOUT 引脚 | SCK 引脚 |
|---|---|---|---|---|
| 第 1 路 | 左前轮 | HX711_1 | PA0 | PB0 |
| 第 2 路 | 右前轮 | HX711_2 | PA1 | PB1 |
| 第 3 路 | 左后轮 | HX711_3 | PA2 | PB2 |
| 第 4 路 | 右后轮 | HX711_4 | PA3 | PB3 |
为什么不用两片 HX711 的 A/B 双通道凑四路?因为 HX711 的 B 通道增益固定为 32,A 通道是 128,两条通道增益不一致会给标定带来额外换算。四路独立模块各用 A 通道 128 倍增益,标定公式完全对称,程序里只需套同一套算法,排查问题也直观。代价是多占几个 GPIO,但 F103C8T6 完全承担得起。
2.3 传感器量程和分辨率估算
秤体选型决定了后续标定有没有余量,参考配置如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 单个称重条量程 | 10t ~ 15t | 按单轴最大载荷留 20% 以上余量 |
| 传感器灵敏度 | 2.0 mV/V 附近 | 过低的灵敏度会压缩 ADC 动态范围 |
| 桥路激励电压 | 5V 稳压 | 四路共用同一路稳压源 |
| ADC 有效位数 | 按 19 位估算 | HX711 类芯片标注 24 位但有效位要打折扣 |
一辆满轴重 30t 的挂车,单侧轮胎压在秤条上的载荷可能接近 15t,如果量程选得太紧,超重车光压上去就超出线性段,标定曲线在高端严重弯曲。反过来说,量程太大、灵敏度太低,小吨位皮卡的轮载荷在 ADC 里只占很小一段,分辨率被浪费。治超场景建议按最恶劣轴荷的 1.2 倍选型,再用 2.0mV/V 左右的灵敏度量做满量程对应,四路读出来的原始码范围能保持接近一致。
3. 电路与接线实战:HX711 四路差分采样的电源、电平与布线细节
3.1 模拟供电 AVDD:纹波与零点漂移的关联
HX711 模块上通常自带 10uF 和 0.1uF 两级去耦电容,很多人直接把这个模块挂到系统的 5V 开关电源上就开测,结果零点每秒都在跳。示波器看那路 5V,纹波常常有 30mV 以上,这些噪声会直接进入模拟前端,反映到输出码上就是无规律的几十 kg 来回摆动。
常见做法是给四路 AVDD 统一加一级 LC 滤波,电感用小体积磁珠,再并联一只 100uF 铝电解和 0.1uF 陶瓷电容。四路模块的 AVDD 从滤波后节点取电,数字电源 VDD 则单独从 3.3V 取,模拟地和数字地在主控板单点汇合。这样做的意义不是玄学,而是把数字总线和 MCU 的开关噪声挡在电桥激励之外。
提示:不要为了省事把 AVDD 和 VDD 直接短接,HX711 虽然看起来两路都能工作,但长时间跑数据稳定性会有差别。
3.2 信号线电平匹配与 HX711 读取时序
HX711 模块在 5V 供电时,DOUT 高电平接近 5V,而 STM32F103 的 GPIO 一般设计为 3.3V 耐压,直接对接虽说不至于立刻烧毁,但长期运行不是稳妥方案。我一般会在每根 DOUT 线上串联 1kΩ 电阻,SCK 方向由 3.3V 输出驱动 HX711,模块内部 VDD 接了 5V 时输入阈值约 2.5V,F103 的 3.3V 推挽输出能可靠翻过这个阈值。
读取时序以查询 DOUT 下降沿为主,不依赖外部中断:
uint32_t hx711_read(HX711_TypeDef *chip) { uint32_t data = 0; // DOUT 变低表示一次转换完成,查询等待 while (HAL_GPIO_ReadPin(chip->dout_port, chip->dout_pin) != GPIO_PIN_RESET) ; for (uint8_t i = 0; i < 24; i++) { HAL_GPIO_WritePin(chip->sck_port, chip->sck_pin, GPIO_PIN_SET); delay_us(2); // 在 SCK 高电平期间读取 DOUT,数据此时稳定 data = (data << 1) | HAL_GPIO_ReadPin(chip->dout_port, chip->dout_pin); HAL_GPIO_WritePin(chip->sck_port, chip->sck_pin, GPIO_PIN_RESET); delay_us(2); } // 第 25 个脉冲把下一次转换切换回 A 通道、128 倍增益 HAL_GPIO_WritePin(chip->sck_port, chip->sck_pin, GPIO_PIN_SET); delay_us(2); HAL_GPIO_WritePin(chip->sck_port, chip->sck_pin, GPIO_PIN_RESET); return data ^ 0x800000; // 补码转成有符号数 }逻辑说明:首先等待 DOUT 拉低表示数据已就绪,然后连续发送 24 个 SCK 脉冲,每个高电平期间移入一位数据,结束时补一个脉冲维持 A 通道 128 倍增益。最终结果通过异或 0x800000 把 24 位补码还原成有符号数,便于后续参与负压计算。参数上,两个 delay_us(2) 足以满足 HX711 对时钟脉冲宽度的要求,四路依次调用时不会互相干扰。
3.3 屏蔽与接地:避免伪数据来源
现场布线最容易被忽略的是传感线走线。四路称重条到 HX711 的信号线必须走双绞线,屏蔽层单端接地,也就是只在主控板一侧接地,另一侧悬空,避免两端接地形成地环路。
PCB 布局上,四路 HX711 尽量远离继电器、4G 模组天线座和电机驱动走线。RS485 的 A/B 线不要和称重信号线平行走长距离,否则数传瞬间的共模噪声会串进模拟通道。我见过一台现场设备因为把称重线绑在电源线上,车一过读数就跟心电图一样抖,重新分开走线后问题直接消失。这种伪数据问题在实验室里几乎不可能触发,只有到现场用真实车流压过去才能暴露。
4. STM32 称重固件实现:从原始读出到去毛刺滤波的代码与参数整定
4.1 四路原始值读取:查询 DOUT 代替中断嵌套
四路 HX711 如果都用外部中断通知 MCU 读取,中断服务函数会频繁进进出出,反而容易影响其它定时任务。查询方式更简单:主循环里轮询每路 DOUT,读到低电平就执行一次 4.2 里说的读取函数。F103 主频 72MHz,四路依次等待转换完成,总耗时也就几百微秒到几毫秒,完全够用。
四路共用一个数据结构管理会更清晰:
typedef struct { GPIO_TypeDef *dout_port; uint16_t dout_pin; GPIO_TypeDef *sck_port; uint16_t sck_pin; int32_t raw_value; int32_t filtered_value; } HX711_Channel; HX711_Channel g_channels[4];实际部署时,初始化函数里先把四路的 SCK 配置为推挽输出,DOUT 配置为浮空输入,再分别执行一次读取,让每片 HX711 的增益设置恢复一致。这样上电后四路的原始码都在同一量级,后面标定时斜率差异会小很多。
4.2 移动平均加中值剔除:参数怎么整
直接使用单次 ADC 读数作为重量值,在治超现场是灾难级别的误差。车轴压过秤条时,轮胎花纹、路面颠簸和桥体振动都会叠加到输出上,单次读数可能偏离真值几个百分点。经典做法是“中值剔除 + 移动平均”两级滤波:先把窗口内明显异常的大脉冲丢掉,再对剩余样本做平均。
#define FILTER_LEN 16 static int32_t filter_buf[4][FILTER_LEN]; static uint8_t filter_idx[4]; static int32_t compare_int32(const void *a, const void *b) { return (*(int32_t *)a) - (*(int32_t *)b); } int32_t weight_filter(int32_t raw, uint8_t ch) { int32_t tmp[FILTER_LEN]; filter_buf[ch][filter_idx[ch]++] = raw; if (filter_idx[ch] >= FILTER_LEN) filter_idx[ch] = 0; memcpy(tmp, filter_buf[ch], sizeof(tmp)); qsort(tmp, FILTER_LEN, sizeof(int32_t), compare_int32); // 去掉最小两个和最大两个,防止单点冲击残留 int64_t sum = 0; for (uint8_t i = 2; i < FILTER_LEN - 2; i++) sum += tmp[i]; return (int32_t)(sum / (FILTER_LEN - 4)); }逻辑说明:每路一个环形缓冲区,新样本写入后做一次排序,掐头去尾再平均。这样即使某次读取瞬间混入一个尖峰,大概率被剔除。参数方面,FILTER_LEN 取 16 时窗口时延约 0.2 秒,对 5~10km/h 的过车速度比较合适;如果现场车速经常超过 20km/h,建议把窗口缩到 8,否则峰值被削平,读出来反而偏低。
4.3 轮轴触发与峰值窗口:动态过车的一个关键分支
治超现场不是每辆车都停下来静止几秒,很多是低速缓慢通过。针对这种情况,工程包里通常会加一组轮轴触发开关,或根据四路数据突变自动检测。触发信号一旦有效,固件进入“动态峰值窗口”,在窗口内不断比较滤波后的重量值,记录最大值,窗口结束时把最大值作为本次过车结果。
#define WINDOW_LIMIT 20 uint8_t axle_trigger = 0; uint8_t in_window = 0; uint16_t window_cnt = 0; int32_t max_weight = 0; void process_axle(void) { // 轮轴开关动作,进入动态采集窗口 if (axle_trigger && !in_window) { in_window = 1; window_cnt = 0; max_weight = 0; } if (in_window) { window_cnt++; int32_t cur = g_channels[0].filtered_value + g_channels[1].filtered_value + g_channels[2].filtered_value + g_channels[3].filtered_value; if (cur > max_weight) max_weight = cur; if (window_cnt >= WINDOW_LIMIT) { in_window = 0; report_weight(max_weight); // 上送或本地存储 } } }逻辑说明:触发到来后强制进入窗口,窗口内四路滤波值相加得到轴组总重,持续比较出最大值作为最终过车重量。WINDOW_LIMIT 按采样周期换算成时间窗口,常见的采样率 50Hz,20 个周期约 0.4 秒,覆盖一个车轴缓慢通过秤条的过程。若车速偏高,要相应缩小窗口,否则窗口会捕到下一根轴的起点,把相邻两根轴的重量混在一起。
5. 标定、联网与常见排查:重量标定流程、RS485 帧协议及四个必踩的坑
5.1 两步标定法:空载零点与加载斜率
标定是最容易被现场施工人员跳过、但决定系统能不能用的环节。上电后不要让设备立刻标定,先热机 30 分钟,让秤体和电路温度稳定下来,不然早上调试好的零点,中午一晒就跑远。
第一步采集空载零点:四路各取 200 次滤波值求平均,存为各路的 zero_bias。第二步加载已知重量:把一辆已知整车重量、轴荷明确的卡车缓慢开到四路秤条上停稳,记录四路各自的 ADC 均值,和零点一起构成两个标定点。单路线性公式可以这样落地:
uint32_t cal_zero[4]; uint32_t cal_adc[4]; float cal_weight_ref; // 已知轴荷 float k_slope[4]; void calibrate(void) { for (uint8_t i = 0; i < 4; i++) { // 单位:kg k_slope[i] = (cal_weight_ref - 0.0f) / (float)(cal_adc[i] - cal_zero[i]); } } int32_t get_weight(uint8_t ch, int32_t raw) { float w = (float)(raw - cal_zero[ch]) * k_slope[ch]; return (int32_t)(w + 0.5f); }逻辑说明:零点对应 0kg,加载点对应已知轴荷,两者构成一条直线。每路独立斜率 k_slope,天然消掉了四路传感器灵敏度不一致的问题。注意参考点在中间位置取,不要只用最大量程做两端,否则小吨位段误差偏大。
5.2 RS485 Modbus-RTU 帧:CRC 计算与 DTU 联网
治超数据需要实时上传到监控平台,常见做法是在主控上加一块 RS485 转接板,通过 Modbus-RTU 裸透传接到 4G DTU,再由 DTU 把数据推到服务器。这种方式的优点是主控程序不需要直接处理复杂 TCP 协议,只要把帧结构组装好,剩下的交给 DTU。
Modbus-RTU 检测用 CRC16,代码可以独立成一个模块:
uint16_t modbus_crc16(uint8_t *data, uint8_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *data++; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }组装上送帧时,建议用固定帧格式减少解析歧义:
| 字节偏移 | 内容 | 说明 |
|---|---|---|
| 0 | 0xA5 | 帧头 |
| 1 | 0x5A | 帧头校验 |
| 2 | 0x04 | 数据长度 |
| 3~6 | 四路重量值 | 每路 2 字节,高字节在前 |
| 7 | 状态字 | 0 正常,1 标定异常 |
| 8~9 | CRC16 | 低字节在前 |
RS485 方向切换是这种方案的常见坑:发送前必须先把 DE/RE 方向脚拉成发送态,且保持至少 3~5 个字节的传输时间再发数据,否则数据头会被吃掉。工程包里默认配置 9600 8N1,优先匹配大多数 DTU 的出厂设置,不建议盲目上 115200。
5.3 现场排查记录:四个绕不开的坑
现象一:零点开机后几十公斤范围缓慢漂移。原因是电桥随温度变化的热漂移,加上电路板发热传导到传感器。解决:上电后至少热机 30 分钟再标定,并把主控板与秤体之间的传热路径隔开,必要时在传感器桥路供电端并联稳压二极管。
现象二:空载时四路原始值差异巨大,其中一路还出现明显负跳。原因多为该路传感器信号线 S+/S- 接反,或者模块供电不足。解决:用万用表确认每路电桥激励电压一致,再对调差分输入正负,程序里加一轮“接线自检”,上电时检查四路零点是否都在合理范围内。
现象三:车辆过秤时读数在真实重量上下跳 3%~5%,滤波看不出来。原因是采样率不足或滤波窗口太长,把真实峰值给削掉了。解决:提高主循环采样频率到 80Hz,同时把移动平均窗口从 16 缩到 8,峰值比较改为取窗口内最大值而不是平均值。
现象四:RS485 首帧总是乱码,或服务器收不全。原因是方向脚切换时序没做好,数据发出时半双工总线还没稳定。解决:发送前先延迟 1ms,然后把 DE 置高,再发完整帧,发完最后一位后延迟 1ms 再回切接收态,并在服务器侧按帧间隔做粘包拆分。
提示:现场排查时先把四路 HX711 的原始值通过串口打印出来,很多故障在原始码一对比就露馅了,不用急着动服务器和协议。
6. 最后:用一辆已知重量的车把“四路”跑出真数据
装完系统、标定完成,别急着交付,我会拿着工程包装好的验证脚本做三组测试,每组都能把隐藏问题翻出来。
第一组是静态踩秤:让一辆已知整车重量的车缓慢开到四路秤条上完全停稳,记录 10 组过滤后重量,四路总和与真实重量的偏差应该在 ±2% 以内。如果偏差偏大,先检查是否有个别路零点漂移,别急着改标定斜率。
第二组是动态通过:让同一辆车分别以 5km/h、10km/h、20km/h 三次通过秤条,记录动态峰值重量和静态重量对比。速度升高以后读数通常会略低,那是动态冲击被滤波吃掉的结果,偏差在 3% 以内可以接受,超了就缩滤波窗口。
第三组是压边测试:故意让车辆一侧轮胎压到秤条边缘通过,四路加权值应该比单路读数稳很多。这一项能直接验证四路方案的价值,也是我每次交付前必做的一步。
工程包里附带了一个简单 Python 脚本,钩在串口后面就能自动统计:
import serial import numpy as np ser = serial.Serial("COM5", 9600, timeout=1) raw = ser.read(128) values = np.frombuffer(raw, dtype=np.uint16) values = values.astype(np.float32) * 0.05 # 假设每码对应0.05kg print("平均重量: %.2f kg" % np.mean(values[values > 1000])) print("极差: %.2f kg" % (np.max(values) - np.min(values)))这段脚本只是做初步统计,真正批量验证时建议把触发信号一起读进来,按车轴切分数据块再逐段算峰值。
最让我长记性的一次,是某回在号称“已经调好”的现场,静态踩秤过了,动态通过也过了,结果压边测试直接把某一路读数打到天上。查到最后是那路秤条和安装基座之间松动,车一压上去传感器跟着扭,数据自然全乱。从那以后,我每次交付四路称重系统都强制走一遍这三组验证,尤其压边测试,不是折腾,而是把后期才爆的雷提前挖出来。希望帮到你。
本文还有配套的精品资源,点击获取