ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无人值守地磅称重系统:C#上位机从串口到状态机的完整实现

无人值守地磅称重系统:C#上位机从串口到状态机的完整实现 简介这套基于C#技术的无人值守地磅称重系统设计源码面向工业自动化与物流领域的开发者、系统集成人员旨在解决称重环节人工依赖度高、数据记录繁琐等问题实现从车辆识别、称重采集到数据存储的全流程无人化作业。资源包共包含243个文件压缩后约148.16MB核心有100个C#源文件实现业务逻辑与算法47个DLL动态库提供运行支持19个XAML界面文件构建交互前端另有13个PDF文档、10个报表文件及SQLite相关配置便于二次开发与部署调试。项目已在CSDN公开已有256人浏览学习适合作为地磅称重系统设计参考或毕业设计蓝本。借助完整的源码、配置文件、可执行程序与CHM帮助手册可快速搭建测试环境理解自动化称重系统的架构、数据库设计及设备联调思路并能灵活拓展到仓储、矿业、港口等应用场景。1. 无人值守地磅称重系统把一次过磅压缩到十几秒的C#上位机骨架一台满载的货车碾上地磅驾驶室里没人下来。红外对射确认前后轮都在磅面上串口从称重仪表读回稳定重量摄像头抓拍车牌和驾驶室系统自动落库、抬杆、亮绿灯整个过程不超过二十秒。这就是无人值守地磅称重系统的典型现场。做这套系统的核心语言往往是C#因为它天生擅长串口通信、IO控制、Windows工控机环境下的界面与数据库整合。这篇博文不聊PPT架构直接把系统拆开讲硬件怎么接、串口数据怎么解析、红外怎么判定上磅完整性、状态机怎么流转、数据库怎么设计才不出坏账最后落到五条现场踩坑记录。2. 硬件拓扑与选型理由工控机、仪表、红外对射怎么组成一条链2.1 设备清单与信号流向每一个IO口负责什么无人值守地磅的核心不是软件算法多高深而是设备之间信号链路的可靠性。我习惯先把硬件清单列全再写一行代码。下面这张表是现场最常见的配置照着采购基本不会翻车。设备接口/协议信号流向作用称重仪表RS232 / RS485ASCII 协议仪表 → 工控机输出毛重、净重、稳定标志红外对射3组开关量 → IO 采集卡红外 → 工控机判定车辆是否完全上磅道闸 / 红绿灯IO 控制卡 → 继电器工控机 → 道闸/红绿灯控制车辆放行与等待车牌 / 全景摄像头USB / RTSP摄像头 → 工控机抓拍留证LED 屏 / 语音播报RS232/485工控机 → 外部显示显示重量、提示司机上下磅信号流向值得反复确认。称重仪表是主数据源必须用独立串口不能和LED屏共用否则一条线上两个设备抢答数据帧互相污染。红外对射输出的是开关量信号需要DI输入卡别直接往串口上接。道闸和红绿灯一般是继电器驱动工控机通过IO卡的DO口输出高低电平来控制选型时看IO卡驱动是否提供C#调用接口没有的话后期要绕DLL非常痛苦。2.2 为什么是C#从串口到数据库一整条链路我见过不少团队用PLC加组态软件做无人值守粗看能用但一遇到需求变化就难受组态软件入库逻辑笨重、抓拍联动像做手术、扩展一个语音播报还要另买驱动。C#上位机的自由度大得多SerialPort类做仪表读取、厂商IO卡SDK做开关量控制、AForge或者OpenCVSharp做摄像头抓帧、SQLite做本地落账全部在一个进程里闭环。Windows工控机上跑C#也不需要额外部署运行时发布时带一套.NET运行时就能长期运行。工控机的选型就一条标准串口数量要够。常见嵌入式工控机只有两个串口仪表占一个、LED屏占一个就满了摄像头还得走USB红外和道闸走IO卡勉强够。如果还要接扫码枪建议直接上支持四串口的机型预留扩展永远比后期加转接卡稳。3. 核心数据采集与设备控制串口解析、判稳、红外与状态机3.1 称重仪表串口通信先把每一帧数据稳稳接住称重仪表的数据帧是整套系统最关键的输入。常见仪表协议有两种一种是主动连续输出每帧固定长度一种是指令应答式上位机发问询帧仪表回固定字节。无论哪种采集端的处理模式都一样SerialPort的DataReceived事件只负责收字节不负责解析把原始字节塞进队列再由独立解析线程按协议帧结构截帧、校验、解重量。这样做的原因是串口事件触发频繁且每次携带的字节数不确定直接在事件里做解析很容易丢帧。下面是我常用的串口初始化写法// 以串口COM3、9600波特率、8数据位、无校验、1停止位为例 // 不同仪表波特率可能不同耀华、托利多、赛摩大多支持9600和19200 SerialPort port new SerialPort(COM3, 9600, Parity.None, 8, StopBits.One); port.ReadBufferSize 4096; port.ReadTimeout 500; port.WriteTimeout 500; port.DataReceived OnDataReceived; try { port.Open(); } catch (Exception ex) { Logger.Error(串口打开失败, ex); }DataReceived事件里只做入队和通知private readonly Queuebyte _recvQueue new Queuebyte(); private readonly AutoResetEvent _parseSignal new AutoResetEvent(false); private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { try { int count port.BytesToRead; byte[] buffer new byte[count]; int readLen port.Read(buffer, 0, count); lock (_recvQueue) { for (int i 0; i readLen; i) { _recvQueue.Enqueue(buffer[i]); } } _parseSignal.Set(); // 通知解析线程有数据到达 } catch (Exception ex) { Logger.Error(串口接收异常, ex); } }解析线程循环里做截帧。以常见STXASCII重量ETX协议举例// 协议约定0x02开头0x03结尾中间是ASCII字符数据。 // 每帧长度可能固定为16字节或按结束符动态识别两种都支持。 private WeightPacket DecodeFrame(byte[] frame) { if (frame.Length 8) return null; if (frame[0] ! 0x02 || frame[frame.Length - 1] ! 0x03) return null; // 去掉帧头帧尾取中间ASCII部分 string body Encoding.ASCII.GetString(frame, 1, frame.Length - 2); // 以某型号仪表为例第0~6位为重量值字符串第7位为符号第8位为稳定标志 if (body.Length 9) return null; string weightStr body.Substring(0, 7).Trim(); double weight double.Parse(weightStr); bool isStable body[8] S || body[8] 1; // 各家稳定标志不同 return new WeightPacket { Weight weight, IsStable isStable }; }这段代码有三个关键点。第一队列缓冲解决了串口事件高频触发带来的丢字节问题第二超时截帧可以这样处理解析线程每次检查队列头部如果等待时间超过80ms仍凑不齐完整帧就丢弃脏数据重新对帧头避免一个错位字节污染后续所有帧第三重量字段的偏移量必须按自己仪表说明书调不同厂商的ASCII重量起始位置可能差一个符号位或小数点占位符现场调试时最好先把仪表手动放砝码用串口调试助手观察原始帧再写死解析位置。3.2 稳定重量判定不要只信仪表上的稳定标志位称重仪表一般会输出一个稳定标志位但无人值守场景下这套逻辑不够用。现场状态是车还没停稳、司机在驾驶室里踩离合、旁边一辆大车路过引起秤台振动仪表稳定标志位的刷新节奏可能落后于实际物理状态。更麻烦的是一些老仪表在重量变化极小但未真正稳定时会一直保持上个稳定状态软件读到的可能是假稳定。我常用的做法是软件二次判稳。维护一个最近N帧重量样本窗口连续几帧的最大值与最小值之差在容差范围内并持续一段时间才认为重量可靠// 判稳参数容差±10kg窗口3秒最近5帧有效 private bool IsWeightStable(double newWeight, double toleranceKg 10, int maxSamples 5) { _weightSamples.Add(newWeight); if (_weightSamples.Count maxSamples) { _weightSamples.RemoveAt(0); } if (_weightSamples.Count maxSamples) return false; double min _weightSamples.Min(); double max _weightSamples.Max(); return (max - min) toleranceKg; }同时加一个整体超时兜底。进入称重状态后如果20秒内始终判不稳语音模块直接提示司机“车辆未停稳请重新上磅”而不是让系统一直干等。参数设置上容差建议设到±10kg不要太小现场秤台频繁有重型车经过±2kg的容差会让系统频繁判稳失败窗口也不用太长3秒足够时间越长效率越低司机体验越差。仪表自带稳定标志可以读取后写入日志用来和软件判稳结果做交叉校验但不要作为触发流程的唯一条件。3.3 红外对射识别上磅完整性三组光墙的完整度判定无人值守系统里最隐蔽的作弊方式是半车上磅车头压着磅台边缘只有前轴或后轴在磅上重量读数明显偏小。解决这个问题靠摄像头不够靠人眼更不现实标准方案是红外对射。现场安装三组红外对射分别命名为入口、磅前、磅尾。磅前和磅尾两组水平对射光束位置略高于车轴用于判断车轴是否完全进入磅面入口一组在磅前延伸段用于判断车辆是否还在进入过程中。IO卡读取三组对射的遮挡状态后组合出一个车辆位置枚举public enum VehPosition { Empty, // 磅上无车 Partial, // 部分上磅禁止称重 Complete // 完全上磅允许称重 } private VehPosition GetVehPosition() { bool entryBlocked IoCard.ReadDigitalIn(0); // 入口对射被遮挡 true bool frontBlocked IoCard.ReadDigitalIn(1); // 磅前对射被遮挡 bool rearBlocked IoCard.ReadDigitalIn(2); // 磅尾对射被遮挡 if (!entryBlocked frontBlocked rearBlocked) return VehPosition.Complete; if (!entryBlocked !frontBlocked !rearBlocked) return VehPosition.Empty; return VehPosition.Partial; }重点是不要直接拿单次读取结果做状态跳转。红外对射在雨雾天气、灰尘附着、强光直射时会产生瞬时误动作我一般在状态机里加两拍确认间隔200ms连续读取两次结果一致才生效。这个设计能在不增加硬件成本的情况下过滤掉大部分误触发。3.4 过磅流程状态机从待机到抬杆再到复位整套无人值守逻辑用一个状态机最清晰。定义枚举和主循环调度public enum WorkState { Idle, // 待机等待车辆上磅 WaitingOnBoard, // 车辆已进入等待完全上磅 Weighing, // 称重中读取并判稳 Saving, // 数据保存与抓拍 RisingBar, // 抬杆放行 Reset // 车辆离磅系统复位 } private WorkState _state WorkState.Idle; private void StateLoop() { switch (_state) { case WorkState.Idle: if (GetVehPosition() VehPosition.Partial) _state WorkState.WaitingOnBoard; break; case WorkState.WaitingOnBoard: if (GetVehPosition() VehPosition.Complete) _state WorkState.Weighing; break; case WorkState.Weighing: double weight GetCurrentWeight(); if (IsWeightStable(weight)) { _currentWeight weight; _state WorkState.Saving; } break; case WorkState.Saving: SaveWeighRecord(_currentWeight); TakeSnapshot(); _state WorkState.RisingBar; break; case WorkState.RisingBar: IoCard.WriteDigitalOut(0, true); // 抬杆 Task.Delay(5000).Wait(); IoCard.WriteDigitalOut(0, false); // 落杆 _state WorkState.Reset; break; case WorkState.Reset: if (GetVehPosition() VehPosition.Empty) _state WorkState.Idle; break; } }这个状态机是简化骨架但已经能跑通完整流程。两个参数值得留意抬杆后延时5秒落杆适用于大多数货车通过时间如果现场有挂车或者多节车要延长到8秒以上车辆完全离磅的判断依赖于磅尾红外复位同时要加超时看门狗——进入RisingBar后60秒内车辆未离磅要触发报警并记录日志防止司机长时间占磅影响下一辆车。4. 数据库设计与数据完整性无人值守最怕坏账4.1 建表SQL与字段设计称重记录为什么要分成多张表无人值守系统的数据设计核心原则是一次过磅事件要能完整审计。重量、图片、设备日志、操作记录全部关联到同一个业务编号上缺一不可。下面是我在SQLite里用的基础表结构字段命名可以直接复用。-- 称重主表一次过磅一条记录 CREATE TABLE weigh_record ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, record_no TEXT NOT NULL, -- 业务单号界面显示用 plate_no TEXT, -- 车牌号识别后自动填充 gross_weight REAL, -- 毛重单位kg tare_weight REAL, -- 皮重单位kg net_weight REAL, -- 净重可能为负空车进重车出时出现 weigh_time DATETIME DEFAULT CURRENT_TIMESTAMP, station_no TEXT, -- 磅房编号多磅房部署时区分 verify_flag INTEGER DEFAULT 0, -- 人工复核标记0未复核1已复核 remark TEXT ); -- 图片记录表每个抓拍点一条记录 CREATE TABLE image_record ( image_id INTEGER PRIMARY KEY AUTOINCREMENT, record_id INTEGER NOT NULL REFERENCES weigh_record(record_id), image_type TEXT, -- plate / cab / tail / overview image_path TEXT, capture_time DATETIME ); -- 设备报警表红外误触、超时、串口掉线都写这里 CREATE TABLE alarm_log ( alarm_id INTEGER PRIMARY KEY AUTOINCREMENT, alarm_type TEXT, -- io_fault / timeout / serial_lost device_name TEXT, alarm_desc TEXT, alarm_time DATETIME DEFAULT CURRENT_TIMESTAMP );注意几个细节。净重字段允许为负因为部分物流场景是空车进场重车出场或者反过来净重计算公式为毛重减皮重结果可能是负数不要在模型层强行取绝对值。verify_flag必须保留无人值守称重后续一定要有人工抽检环节没有这个标记后期对账非常痛苦。image_record单独成表而不是在主表里放四个图片字段是为了以后增加抓拍点位时不用改主表结构扩展性更好。4.2 事务写入与掉线缓存一次称重记录不允许半条数据称重记录保存涉及主表、图片记录、操作日志三处写入。如果分开执行任何一步中途崩溃都会产生脏数据重量有了图片没匹配上或者主表没写成功但道闸已经抬了。我统一用事务包住这三步// 使用SQLite事务一次称重事件保证原子写入 using (var conn new SQLiteConnection(Data Sourceweigh.db)) { await conn.OpenAsync(); using (var tx await conn.BeginTransactionAsync()) { var cmd1 conn.CreateCommand(); cmd1.CommandText INSERT INTO weigh_record (record_no, plate_no, gross_weight, tare_weight, net_weight, station_no) VALUES (no, plate, gross, tare, net, station); SELECT last_insert_rowid();; long recordId (long)await cmd1.ExecuteScalarAsync(); var cmd2 conn.CreateCommand(); cmd2.CommandText INSERT INTO image_record(record_id, image_type, image_path) VALUES(rid, type, path);; cmd2.Parameters.AddWithValue(rid, recordId); await cmd2.ExecuteNonQueryAsync(); var cmd3 conn.CreateCommand(); cmd3.CommandText INSERT INTO op_log(record_id, op_type, op_time) VALUES(rid, op, time);; await cmd3.ExecuteNonQueryAsync(); await tx.CommitAsync(); } }单条写入后立刻Commit在无人值守场景下足够了不需要批量攒单。数据库服务如果不在本地而是部署在远端服务器本地工控机还要加一层内存队列缓冲写入失败时先落CSV文件线程每30秒重试一次服务器恢复后自动补传。CSV的字段顺序要和表结构一致补传时按行解析再走事务别偷懒用一句INSERT拼大批量SQL万一某行字段带逗号或者引号整段重放会失败。5. 无人值守系统避坑指南五条现场血泪记录5.1 USB转串口芯片掉线运行一周后仪表读数没了现象系统连续运行三到五天某台工控机的称重仪表数据突然收不到重启软件恢复再过几天又复现。原因工控机用的是USB转RS232线Windows USB端口进入选择性挂起状态。早期的USB转串口芯片还存在静电击穿问题车间地磅附近大功率电机启停容易把转换器打掉。解决换PCIe接口的串口扩展卡或者工控机原生的DB9串口。如果只能用USB转串口在Windows设备管理器里打开对应COM口的属性把电源管理选项卡里的“允许计算机关闭此设备以节约电源”勾掉。同时程序里加心跳检测仪表连续30秒无数据自动关闭串口重新打开并写一条serial_lost报警记录。5.2 红外对射被雨雾误触发空磅判断成有车现象雨天傍晚磅上明明没有车系统状态从Idle跳到了WaitingOnBoard流程卡死。原因红外对射在雨雾环境中光束被散射接收端以为被遮挡或者太阳落山时阳光直射接收端光强变化导致误判。解决硬件上把对射支架的角度调低避免阳光直射接收透镜程序上那两拍确认就起作用了把间隔从200ms延长到500ms雨雾误触发基本能过滤。再不行就在IO卡逻辑里加一个“连续遮挡时长超过2秒才算有效遮挡”的计时器瞬时抖动直接丢弃。5.3 同一辆车两次过磅重量差几十公斤半车上磅与判稳太早现象同一辆空车在系统里连续过磅两次一次显示10.2吨一次显示10.6吨对账时发现偏差。原因最常见是车没有完全上磅后轮轧在磅台边缘其次是判稳容差设得太小车辆还在微晃时软件就采了重量。解决三组红外对射判定位置信号必须先于称重Position不是Complete时禁止进入Weighing状态。判稳容差放宽到±10kg同时延长稳定持续时间到3秒。还有一招是加二次采样校验保存前再读一次重量与刚才判稳的值做差超过30kg就丢弃本次流程并提示重新称重。5.4 突然断电后流程状态错乱道闸抬着不放行现象现场跳闸工控机重启后系统状态界面显示正在抬杆放行但实际上道闸并未抬起红绿灯也停在红灯。原因状态机只存在内存里断电后重启恢复为Idle但IO卡输出的电平状态没有跟随复位道闸继电器保持在上电时的状态。解决程序启动时先执行一次全设备复位所有DO口输出0道闸落杆、红绿灯清零再进入Idle状态。状态机本身也做轻量持久化每切换一个状态就把当前状态和时间写到本地配置文件重启后读取上次状态做恢复判断。5.5 摄像头抓拍黑帧USB带宽不够导致偶发无图现象三路摄像头同时抓拍其中一路经常出现黑图或卡住其他两路正常。原因多路USB摄像头共用一条USB控制器带宽抓拍瞬间带宽被占满某一帧数据没传完就超时了。解决优先换RTSP网络摄像头走网口不走USB。如果只能USB把三路摄像头分散插到不同的USB控制器上不要都堆在一个HUB口。抓拍代码里对每次取帧加超时控制超时后重新初始化设备再抓不阻塞主流程。6. 进阶把每一次过磅变成可审计的证据链无人值守系统上线后真正拉开差距的不是界面好不好看而是出了问题后你能多快定位。我现在的习惯是给系统加重度审计每一个关键节点都写一条带毫秒时间戳的结构化日志图片命名带上车号和业务单号数据库里的每张表字段再配合设备日志就能拼出一条完整时间线。日志写入不要用框架直接一个静态方法就够了public static void Audit(string module, string action, string detail) { string line string.Format({0:yyyy-MM-dd HH:mm:ss.fff} | {1} | {2} | {3}, DateTime.Now, module, action, detail); File.AppendAllText(D:\logs\audit.log, line Environment.NewLine); }调用很随意红外状态切换时记一笔、串口读回重量记一笔、道闸抬起记一笔。图片命名我用这个规则车牌号_业务单号_时间戳_抓拍点.jpg 例如冀A12345_NO20250613001_20250613101523_plate.jpg好处是后期不用查数据库直接按车牌扫文件名就能找出某一辆车所有批次过磅照片。跨设备时间偏差也要处理无人值守现场工控机通常没有外网NTP校时不现实我在程序里每天凌晨用工控机本地时间重置仪表和摄像头时间并把偏差值写入日志这样数据库时间和摄像头抓拍时间最多差几百毫秒复盘时不会出现“数据库显示10点15分抓拍图却标着10点16分”的糊涂账。有一次项目验收后一个班次交接对账发现一张单子净重偏大数据库、图片、报警日志三条时间线一对发现当时红外状态先跳到Complete又跳回Partial正好卡在重量保存前200毫秒说明车辆其实在缓慢溜车。那次复盘全靠这条日志链从那以后我每次上线无人值守地磅都强制走一遍全链路日志和跨设备时间校验闭环抓拍、红外、仪表、数据库四个环节的时间戳对不上绝对不签字交付。希望这篇拆解帮你在自己的项目里少走这段弯路。本文还有配套的精品资源点击获取
返回列表