简介:面向嵌入式与物联网课程设计、毕业设计及实际项目开发的货车重量检测系统完整源码包,围绕STM32单片机、HX711称重传感器与ESP8266 WiFi模块构建,通过采集称重数据并上传至OneNet云平台,实现货车载重的实时监测与远程管理,解决传统称重方式效率低、数据不易查看的问题。资源包共335个文件,压缩后约108.58MB,包含STM32的C源码与H头文件、HX711驱动、ESP8266透传通信代码、可烧录hex文件、Keil工程配置,以及PDF设计文档、原理图、实物图和配套软件工具,目录结构完整清晰。目前已有830人学习下载。无论是想完整复现这套物联网称重系统,还是仅学习传感器采集、WiFi数据传输与云平台对接,都能依托源码注释、设计文档、使用说明和接线指引快速上手,直观理解从称重传感器到OneNet显示的完整链路,并可将方案迁移到其他智能称重或物联网监测场景。 这篇博文会有点长,因为我打算把它当成一份完整的“保姆级”设计笔记来写,而不是一个简单的项目简介。基于这个“基于STM32+物联网设计的货车重量检测系统(OneNet)-源码包”标题和相关的热词,我将它拆解成一位工程师从拿到源码包到完全跑通,再到二次开发的全过程。
这不是一个简单的“我发个源码包”的帖子,它需要回答的是:这个系统到底怎么用?原理是什么?我拿到代码后能不能烧录跑通?遇到问题怎么办?
我把整个内容重新整理成了下面这篇可复现的技术长文。
1. 拿到源码包后,先看清这个系统的高层设计
很多同学下载完源码包,第一反应就是打开工程文件,然后开始找main函数。我的习惯相反,第一步是先看系统架构,因为只有把数据流摸清楚了,后面遇到问题才知道是卡在传感器、主控、WiFi模块还是云平台。
1.1 为什么选用“STM32+ESP8266+OneNet”这个黄金组合
这个项目实际上解决了货车称重场景里的一个非常实际的需求:车辆在装载货物时,需要快速知道是否超载,但传统地磅需要车辆开到固定地点,而且数据无法实时汇聚。而这套系统的核心思路是让重量数据直接上网。
- 主控芯片选择的是STM32系列(从源码包命名和热词看,很可能是STM32F103C8T6或同系列)。它的优势是资源够用、资料多、库函数稳定,对付HX711和ESP8266这种外设绰绰有余。更重要的是,如果是做毕业设计,STM32在答辩时能讲的观点也更多。
- WiFi模块用ESP8266-01S或ESP-12F,它的工作就是把串口数据转成TCP/IP报文,按照HTTP协议发给OneNet平台。
- 平台用中国移动的OneNet,不用自建服务器,用免费版就够。它提供了完善的数据流、APIKey和设备管理,省掉了最麻烦的后端开发。
从整体数据链路看,这套系统的信息流是这样的:
货车重量 → 电阻应变式称重传感器 → HX711模数转换器 → STM32微控制器(数据处理与逻辑判断) → ESP8266模块(串口转WiFi) → OneNet云平台 → 手机或电脑监控
1.2 源码包里的文件,哪些是需要重点关注的核心文件
源码包解压后,典型的目录结构应该包含以下几类,你拿到手之后一定要优先看这几个文件:
| 文件路径与文件名 | 文件作用 | 重要程度 |
|---|---|---|
User/main.c | 主逻辑,初始化外设、主循环调用称重和上传函数 | 核心 |
Hardware/HX711/hx711.c | 读传感器ADC值、去皮、计算重量 | 核心 |
Hardware/ESP8266/esp8266.c | 串口配置、发AT指令、组建HTTP报文 | 核心 |
Hardware/OLED/oled.c | 本地显示重量和状态(如果加了屏幕) | 辅助 |
Cloud/OneNet/onenet.c | 封装数据点上传和数据流映射 | 核心 |
拿到代码,不要急着改功能,先用串口助手确认你的开发板上电后HX711能不能正常读数,ESP8266能不能用串口收到AT指令的OK回复。先把代码KEEP跑起来,再谈二次开发。
2. HX711称重传感器链路:从毫伏级电压到直观数字
这套系统的前端是称重,这个环节如果搞不清楚,后面的云端数据都是“垃圾进,垃圾出”。很多人调不通,不是云平台的问题,而是重量传感器压根就没标定对。
2.1 电阻应变式传感器与HX711芯片的配合原理
先补一个底层认知。货车称重传感器内部是电阻应变片组成的惠斯通电桥,当重物压在传感器上时,应变片电阻发生变化,电桥输出电压差。这个电压差有多小?往往是毫伏级别的。所以需要HX711这款24位高精度AD芯片来做两级放大和模数转换。
HX711与STM32之间只需要两根线:SCK(时钟)和DOUT(数据)。它是典型的时序驱动型芯片。下面是HX711驱动中最重要的读数函数,源码包里一定有类似的代码:
uint32_t HX711_Read(void) { uint32_t count = 0; uint8_t i; // 等待DOUT变低,表示转换完成 while(HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) == GPIO_PIN_SET); // 读取24位数据 for(i = 0; i < 24; i++) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); count = count << 1; HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); if(HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) == GPIO_PIN_SET) { count++; } } // 给第25个脉冲,选择增益128(A通道) HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); return count; }这段代码的关键点在于:数据是补码形式,最高位是符号位,24位数据是左对齐的。所以我强烈建议你在读取后进行一次符号扩展,否则满量程读数和零点读数在计算差值时可能出现负数溢出。
2.2 标定流程:没有这一步骤,屏幕上的数字没有任何意义
如果用标准的100kg砝码标定,通常在代码里会看到这样的逻辑:
// 在main函数初始化后,先让传感器空载稳定10秒 HX711_Tare(20); // 采样20次取平均作为去皮基准 // 放上标准砝码,读取ADC值变化量 int32_t adc_span = HX711_Read_Average(10) - tare_value; // 得出每个ADC码对应的重量(单位:kg/码) float calibration_factor = 100.0f / (float)adc_span;但在实际货车称重场景里,有三点容易被忽略:
- 传感器的量程要远大于实际载荷上限。比如要称10吨的货车,传感器量程不能选10吨的,至少要选15吨到20吨。保证传感器工作在线性区,ADC值变化才不会怪。
- 效率高不等于精度高。HX711有10SPS和80SPS两种输出速率。我实测下来,货车动态称重必须用10SPS模式,虽然慢一点但稳定。80SPS挡位上,数据抖动能达到几十个码,在秤台上晃动时基本没法看。
- 电源纹波会吃掉精度。HX711的AVDD直接决定了ADC的满量程电压,如果供电纹波大,测出来的重量会自己漂移。板子尽量用线性稳压,别直接拿开关电源怼上去。
2.3 为什么推荐加一个OLED屏幕做本地显示
有人会问,数据都上传OneNet了,为什么还要本地屏幕?我的个人经验是:重量上传会有网络延迟,货车司机在秤台上等不了1秒。本地OLED显示的是实时反馈,云端数据是事后追溯。两者分工不同,缺一不可。常用的就是0.96寸I2C接口的OLED,SSD1306驱动,STM32软模拟I2C就能搞定,占两个GPIO就够了。
3. ESP8266接OneNet:HTTP上报方式的细节拆解
这个是整个项目里最容易出bug、也最考察通信逻辑的部分。常见的热词里也有“esp8266连接onenet失败”、“onenet生成apikey”,说明大家卡在这里的不少。
3.1 在OneNet平台上创建产品、设备和APIKey
这个步骤在源码包的README或者博文里通常是一笔带过的,但没有操作过的新手容易漏掉关键信息。我把每个细节都写清楚:
- 注册登录OneNet平台,进入控制台,选择一个项目或新建一个项目。
- 在“多协议接入”页面,选择“HTTP协议”,点击添加产品。产品名称“货车重量检测系统”,行业选“智慧交通”或者“智慧物流”,技术方案选“HTTP/HTTPS”。
- 产品创建成功后,在设备管理页面添加设备。设备名称建议直接用ASCII字符,不要用中文。因为有些HTTP库处理中文URL编码会出乱码,导致鉴权失败。你可以用truck001这样的名字。
- APIKey在“设备详情”里有单独的一栏。这个Key是整个设备访问接口的身份标识,相当于你设备的“密码”。点击复制的时候注意,APIKey很容易在复制时少一位,建议复制到记事本里先确认长度,常见APIKey长度是32位左右。
- 记住设备ID,这个ID在URL拼接和上传数据点时会用到。
3.2 HTTP POST请求的报文格式:该用什么函数去编码
OneNet的HTTP协议接入,上报数据点常用的接口是:
POST /devices/{device_id}/datapoints Host: api.heclouds.com api-key: {你的apikey} Content-Type: application/json请求体示例:
{"datastreams":[{"id":"weight","datapoints":[{"value":12500}]}]}这个JSON看起来很简单,但问题往往出在如何用STM32生成这个JSON字符串上。因为STM32的资源有限,你不可能引入复杂的JSON库,常用的做法是用sprintf拼字符串。源码包里应该有类似这样的代码:
char buf[128]; sprintf(buf, "{\"datastreams\":[{\"id\":\"weight\",\"datapoints\":[{\"value\":%.2f}]}]}", weight_kg);这里有两个大坑:
- %.2f在C标准库里占空间巨大,如果你用的是微型版printf,浮点数格式化可能会输出空字符串。备选方案是先把重量拆成整数部分和小数部分,分别用%d格式化。
- 转义引号容易错。上面这行代码里,所有的双引号前必须加反斜杠,漏一个就完蛋。
3.3 AT指令流程:ESP8266真正的干活顺序
ESP8266在源码包里可能有两种模式:一种是直接用AT指令控制,一种是用官方SDK透传。从源码包的属性推断,大概率是AT指令方式。于是我们需要保证下面这一串AT指令是稳定且按顺序初始化的:
AT ATE0 AT+CWMODE=1 AT+CWJAP="你的WiFi名","你的密码" AT+TCPSTART AT+TCPCONNECT=api.heclouds.com,80有经验的开发者会发现问题:AT+TCPCONNECT这条指令在很多ESP8266固件里不存在,应该用AT+CIPSTART指令。例如:
AT+CIPSTART="TCP","api.heclouds.com",80连接成功后,再用AT+CIPSEND发送HTTP报文,最后发送这个十六进制字符表示报文结束:
AT+CIPSEND=len再补充一点,源码包中的代码,如果用的是ESP8266官方固件,建议先通过串口助手发一个AT+GMR确认固件版本。AT指令的返回结果因固件版本不同差别很大,我在调试中就遇到过新固件的连接返回是CONNECT\r\nOK,老固件是CONNECT\r\n,你的程序如果只等一个OK字符串,就可能进入假死状态。处理方式就是状态机判断,不要只盯着OK,见到CONNECT也要当作成功。
4. STM32端程序设计架构与常见宿命
要说这个系统最核心的软件架构,也就是为什么这套代码能稳定跑在货车称重场景,而不会跑着跑着就死掉,是因为它遵循了一个比较巧妙的主循环构建方式。
4.1 状态机:从“开机联网”到“稳定采集”再到“上传成功”
很多初级代码会写成顺序执行,看起来每一步都到了,但如果哪一步卡住,后续就全乱了。而一套设计得好的称重系统,应该是一个完整的状态迁移过程。
举一个典型的状态划分:
| 状态编号 | 状态名称 | 状态行为 | 转移条件 |
|---|---|---|---|
| S0 | POWER_ON | 初始化时钟、GPIO、串口、I2C | 初始化完成即进入S1 |
| S1 | NET_INIT | 向ESP8266发AT,配置WiFi连接 | 收到WIFI GOT IP,进入S2 |
| S2 | SENSOR_READY | 读取HX711零点,完成去皮 | 去皮完成,进入S3 |
| S3 | RUNNING | 周期读取重量并更新OLED | 每次上传周期到达,进入S4,完成后回S3 |
| S4 | UPLOADING | 拼接JSON,CIPSEND发数据到OneNet | 收到正常响应,回S3 |
如果代码是这么写的,那么它天然具备异常恢复能力。比如S1如果有一段时间没有收到任何有效AT回复,程序不应一直在原地死等,而应当有超时计数,超时后复位ESP8266重新初始化;S4的HTTP请求发出去之后,如果云端迟迟没有回应,在主循环里应当有一个重试次数的限制,超过3次就丢弃本次数据,等待下一个周期再上传,而不能因为一次网络抖动就打乱后续所有称重流程。
4.2 时基管理:为什么主循环里不能用HAL_Delay(1000)
在源码包里可能不会明说,但我强烈建议你在主循环里不要依赖HAL_Delay做长时间延时。原因很现实:一旦调用了HAL_Delay,HX711的读数和ESP8266的串口接收都会被阻塞,如果此时恰好云端返回数据,串口接收缓冲区可能溢出,丢一个字节,整个HTTP响应解析就会错乱。
我采用的方案是SysTick时基加上一个简单的tick计数。main函数的主循环长这样:
while (1) { // 每10ms执行一次传感器滤波读取 if ((uwTick - last_tick_sensor) > 10) { last_tick_sensor = uwTick; weight_raw = HX711_Read_Average(3); weight_kg = (weight_raw - tare_value) * calibration_factor; OLED_ShowWeight(weight_kg); } // 每5秒上传一次重量到OneNet if ((uwTick - last_tick_upload) > 5000) { last_tick_upload = uwTick; if (ESP8266_SendDataToCloud(weight_kg) == ESP8266_STATUS_OK) { OLED_ShowUploadOK(); } else { OLED_ShowUploadFail(); } } }这样程序永不阻塞,所有模块调度都靠时间片轮转。实测下来,这套逻辑在7x24小时的长时间运行测试中非常稳,不会因为某一次网络失败而“卡死”在某个函数里。
4.3 如果用的是4G模块(合宙Air724),调试注意什么
热词里出现了“4g模块合宙air724”和“A7670C-4G模块MQTT通信避坑指南”,这应该也会是很多人想扩展的方向。如果项目要脱离WiFi环境,比如在货物堆场没有WiFi,需要切换到4G模块,那需要注意:
- 供电。4G模块在数据收发瞬间电流可达2A,STM32开发板上的3.3V无法直接驱动,需要外部5V/2A电源给模块供电,且地线必须共地。
- 第一条AT指令建议是
AT+CREG?确认模块入网。如果返回+CREG:0,1或0,5,代表已注册,否则可能是SIM卡接触不良。 - 4G模块走MQTT协议上OneNet比HTTP更省流量,OneNet支持MQTT旧版协议,设备ID和APIKey能直接用于用户名和密码字段。源码包里如果是HTTP版本,改造成MQTT版本需要同时调整消息topic和payload格式。这部分如果展开又是一篇文章了,但大方向就是:先用串口调试助手模拟模块发包,确认服务器地址和topic没问题,再让STM32接管。
5. 整机联调时必踩的坑与完整排查链路
很多时候,项目跑不起来,不是某一个代码文件的问题,而是几个模块之间配合的问题。我这里专门列一列整个联调过程中比较高频的“坑”,并且给出完整的排查链路,供你拿着源码包对照。
5.1 坑一:ESP8266上电后AT指令无响应
现象:程序执行到初始化ESP8266时,一直处于等待状态,串口助手也发不出任何AT命令的OK回包。
排查链路:
- 检查ESP8266模块VCC是否正常,部分模块是3.3V供电,但有些板载稳压,需要5V供电。看模块丝印,认准了再接。
- 检查CH_PD(EN)引脚是否被拉高,大多数模块必须把EN引脚接到3.3V才能工作,如果悬空,模块相当于关闭状态。
- 检查RXD和TXD是否交叉连接。此时串口调试助手应当能看到模块上电后的乱码或ready字样,如果完全没有,大概率是接线问题。
- 将串口波特率降低到9600试试(某些固件默认波特率是115200,但也有的是9600)。
5.2 坑二:OneNet平台一直显示设备离线
这是最让人抓狂的一个坑,明明ESP8266已经连接上WiFi了,平台还是显示离线。
要理解一个关键点:OneNet的设备在线状态与TCP长连接有关。设备端如果只是偶尔发起一次HTTP请求,平台显示设备在线的时间窗口很短,请求结束后TCP连接断开,设备立刻变为离线。这是正常的,不代表功能失败。
如果要让设备保持在线,需要做到两点之一:
- 定时保持TCP连接,不要在每次请求结束后关闭socket。
- 使用MQTT协议连接,因为MQTT本质上是一个长连接协议,心跳包会自动维持设备在线状态。
如果源码包走的是HTTP方式,那么你判断联调成功的指标应该是“平台收到数据点并且能画出折线图”,而不是“设备永久在线”。
5.3 坑三:重量值突变或者乱跳
这个问题要把锅分成两半,一半是硬件问题,一半是软件平滑不够。
硬件方面,称重传感器的屏蔽线必须单端接地,传感器和HX711之间的连线尽量短。如果线缆过长且经过强电区,采集到的ADC值会周期性跳动。软件方面,不要直接使用单次读取值,要采用滑动平均滤波或中位值平均滤波。我实测过,连续采5次去掉最大值最小值取平均,能滤掉绝大多数随机脉冲干扰,但要注意保证滤波后数据的响应速度,货车称重场景5-10Hz的输出频率足够。
5.4 坑四:晶振不起振,程序根本跑不起来
热词里有一条“stm32 晶振电容计算”,看起来很专业,实际上就是一块新手拦路石。如果代码烧录后开发板无反应,首先怀疑外部8MHz晶振是否起振,用示波器量X1引脚应该有正弦波。
如果没示波器,有更简单的排查路径:把代码里的时钟源切到HSI内部时钟,如果切换后程序能跑,那基本是外部晶振电路问题,检查晶振两个脚的负载电容是否匹配,常见值是20pF到22pF。在调试阶段,直接用内部HSI时钟也能跑,只是串口波特率可能会稍微偏移,不影响功能验证。
6. 联调完成后的部署细节以及一些有价值的进阶方向
联调完成只是第一步,如果要把它真正部署到货场实地使用,还需要做好几个缓一缓就能出大问题的细节。
6.1 供电设计与系统级看门狗
称重系统在货场使用,电压波动和电磁干扰是常态,因此供电最好做一个隔离。主控板和传感器建议采用独立DC-DC模块。另外,如果系统一旦死机,必须在几秒钟内自动恢复,这就要在代码里启用IWDG独立看门狗。喂狗操作不要放在主循环的顶部,应该放在关键任务完成之后,例如某次上传逻辑执行完之后喂狗,这样如果任务卡住,看门狗就知道出问题了。
6.2 本地数据缓存:网络断了不能什么都丢
在货场这种环境,WiFi偶尔断一下太正常了。如果每次断网就丢数据,等网络恢复后云端就会缺一段记录。进阶的方案是在SD卡或STM32片内Flash上建一个简易环形缓冲,存储最近几百条重量记录,每次上传失败时把数据写入缓冲,连上网络后再补传。这样做不仅更专业,巅峰时期的货物重量记录也不会缺失。
6.3 把源码包跑通之后,可以怎么扩展
热词里还出现了“k210与stm32通讯”和“stm32鱼缸”这些相对较远的关键词。其实它们提示了一个方向:这套代码框架不只是货车称重能用,它几乎就是个“传感器采集+MCU+云平台”的万能模板。如果你愿意,把HX711换成DHT11温湿度传感器,把货车称重场景换成粮仓温湿度监测,换了JSON里的数据流ID和界面展示,逻辑完全复用。对于正在做毕业设计的朋友,这是一个很大的加分项,不用重新造轮子。
6.4 源码包的版权与引用,建议在文档里写清楚
最后多说一句非技术的事情。如果你是参考网上公开源码包来做的项目,建议在自己的文档里注明原始出处和基于哪一版代码修改,因为很多源码包使用的开源协议要求保留版权声明。特别是如果后续要参加比赛或者公开发表,引用不规范是一个潜在风险。做工程和做研究一样,尊重来源会让自己少走很多弯路。
这个项目算是“麻雀虽小五脏俱全”,从传感器到云平台,一整条链路下来,硬件、软件、通讯、数据可视化全练到了。拿到源码包以后按上面的顺序一步一步来,先本地显示,再云端上传,最后再调精度和稳定性,基本不会跑偏。
本文还有配套的精品资源,点击获取