3C工厂实战:C#上位机+信捷PLC搞定SMT贴片质量检测,附通信避坑指南 📅 2026/7/15 8:40:28 做3C产线自动化的兄弟应该都有体会SMT贴片检测这个工位是整个车间里最“娇气”的环节。速度要求高、精度要求严关键是设备还得7×24小时连轴转。去年我们给一家代工厂改造SMT检测线甲方指定用信捷XD5系列PLC做主控上位机用C#自研。项目交付后连续跑了三个月没出过通信故障今天把这套方案的核心设计和踩过的坑整理出来希望能帮到正在做类似项目的同行。一、为什么这套组合在SMT场景下能跑通先说结论不是信捷比西门子强而是SMT检测的业务模型恰好匹配了信捷的能力边界。SMT贴片检测的典型节拍是200-400ms/板PLC负责传送带启停、气缸顶升、光源触发等IO控制上位机负责相机采图、AI缺陷识别、结果判定和数据上传。两者之间的交互本质上是“握手式串行协议”——PLC发触发信号→上位机拍照推理→回传OK/NG→PLC执行分拣动作。整个过程单次通信数据量不超过20个寄存器对并发和带宽的要求极低。信捷XD5的优势恰恰在这个区间价格便宜同点数约为S7-1200的1/3、Modbus TCP响应时间在8-12ms之间完全够用、编程软件对国内工程师友好。它的短板并发连接数少、大报文处理能力弱在SMT检测场景中根本不会暴露。选型建议如果你的SMT线体CT150ms、单站IO点数60、不需要多上位机同时访问同一台PLC信捷XD5/XL5系列是性价比最优解。反之如果CT80ms或需要MES/SCADA/视觉三端并发读取请直接上西门子或汇川AM600。二、 系统架构四层分离各管各的事很多新手写上位机喜欢把所有逻辑塞在一个Form里结果代码越写越乱现场出了问题根本没法排查。我们采用的是四层分离架构┌─────────────────────────────────────────────────────┐ │ UI层 (WPF) │ │ 实时画面 | 检测统计 | 参数配置 | 报警日志 │ └──────────────────────┬──────────────────────────────┘ │ 事件订阅不直接调用下层 ┌──────────────────────▼──────────────────────────────┐ │ 业务编排层 (Orchestrator) │ │ 检测状态机 | 结果过滤 | IO联动时序 | 数据持久化 │ └──────┬───────────────┬────────────────┬─────────────┘ │ │ │ ┌──────▼──────┐ ┌──────▼─────────┐ ┌───▼──────────┐ │ 相机SDK │ │ PLC通信管理器 │ │ AI推理引擎 │ │ (海康MVS) │ │ (NModbus重连) │ │ (ONNX RT) │ └─────────────┘ └────────────────┘ └──────────────┘核心原则只有一条UI层只做展示和交互绝不包含任何业务逻辑和硬件操作。所有硬件交互都封装在独立的服务类中通过ChannelT或事件与编排层通信。这样即使将来换相机品牌、换PLC型号、换AI框架UI代码一行都不用改。三、 PLC通信层把信捷的“脾气”摸透这是整个项目中最花时间的部分。信捷PLC的Modbus TCP实现和标准规范有些微妙差异照搬网上的通用库必然翻车。以下是我们验证过的可靠方案。3.1 地址映射新手第一个坑信捷编程软件里的地址和Modbus协议地址不是简单的一一对应必须查手册确认。以XD5为例PLC资源Modbus功能码协议地址(0-based)备注D寄存器03/06/160 D0最常用掉电保持区另算M继电器01/05/150 M0位操作X输入020 X0只读Y输出01/050 Y0可读可写FD寄存器03/06需查型号手册有偏移量别猜⚠️血泪教训FD闪存寄存器的Modbus地址在不同固件版本间有差异。我们曾因为固件升级后地址偏移变了导致配方写入位置错位烧了一批料。务必在项目启动时用Modbus Poll逐地址验证一遍并记录到项目文档中。3.2 通信管理器串行化心跳指数退避重连信捷PLC对并发请求极其敏感两个Read同时发出就可能有一个超时。解决方案是全局请求队列强制串行化publicclassXjPlcClient:IAsyncDisposable{privatereadonlyChannelFuncIModbusMaster,Task_queue;privateIModbusMaster?_master;privateTcpClient?_tcp;privateConnectionState_stateConnectionState.Disconnected;privateint_retryCount;privateconstintMaxBatchRegs64;// 信捷安全上限publicXjPlcClient(stringip,intport502){_queueChannel.CreateBoundedFuncIModbusMaster,Task(newBoundedChannelOptions(50){FullModeBoundedChannelFullMode.Wait});_Task.Run(ProcessQueue);_Task.Run(HealthCheckLoop);}// 所有读写统一入口天然串行publicasyncTaskTExecuteAsyncT(FuncIModbusMaster,TaskTop,CancellationTokenctdefault){vartcsnewTaskCompletionSourceT();await_queue.Writer.WriteAsync(asyncmaster{try{tcs.SetResult(awaitop(master));}catch(Exceptionex){tcs.SetException(ex);}},ct);returnawaittcs.Task;}privateasyncTaskProcessQueue(){varreader_queue.Reader;while(awaitreader.WaitToReadAsync()){while(reader.TryRead(outvaraction)){if(_state!ConnectionState.Connected){// 等待重连完成最多等10秒for(inti0;i100_state!ConnectionState.Connected;i)awaitTask.Delay(100);}if(_stateConnectionState.Connected)awaitaction(_master!);}}}// 每3秒主动探针不等业务请求失败才发现断线privateasyncTaskHealthCheckLoop(){while(true){awaitTask.Delay(3000);if(_state!ConnectionState.Connected)continue;try{usingvarctsnewCancellationTokenSource(2000);awaitExecuteAsync(mm.ReadHoldingRegistersAsync(1,0,1),cts.Token);_retryCount0;}catch{awaitReconnectAsync();}}}privateasyncTaskReconnectAsync(){_stateConnectionState.Reconnecting;Cleanup();while(_stateConnectionState.Reconnecting){intdelayMath.Min(500*(1_retryCount),30000);awaitTask.Delay(delay);try{_tcpnewTcpClient{NoDelaytrue,SendTimeout3000,ReceiveTimeout3000};usingvarctsnewCancellationTokenSource(5000);await_tcp.ConnectAsync(192.168.1.10,502,cts.Token);_masternewModbusFactory().CreateMaster(_tcp);_master.Transport.ReadTimeoutTimeSpan.FromSeconds(3);// 验证读await_master.ReadHoldingRegistersAsync(1,0,1);_retryCount0;_stateConnectionState.Connected;Log.Information(PLC重连成功);return;}catch(Exceptionex){_retryCount;Log.Warning(ex,重连失败 #{Count},_retryCount);Cleanup();}}}privatevoidCleanup(){(_masterasIDisposable)?.Dispose();_masternull;_tcp?.Close();_tcp?.Dispose();_tcpnull;}publicasyncValueTaskDisposeAsync()Cleanup();}几个关键点说明NoDelaytrueModbus是小包协议Nagle算法会无意义地攒包增加延迟指数退避500ms→1s→2s→…→30s封顶避免网络故障时对PLC发起重连风暴MaxBatchRegs64信捷单次读取超过64个寄存器时偶发返回不完整数据。批量读取必须分包包间加2ms间隔HealthCheck独立于业务队列心跳探针走ExecuteAsync排队既保证了串行化又能真实反映通信链路状态3.3 SMT握手时序设计PLC和上位机的交互协议定义如下D寄存器分配D100: 触发信号 (PLC→上位机) 1新板到位请求检测 D101: 应答信号 (上位机→PLC) 1已接收触发开始检测 D102: 检测结果 (上位机→PLC) 1OK, 2NG, 0未完成 D103: 结果确认 (PLC→上位机) 1已读取结果可清除 D104-D119: 缺陷详情 (上位机→PLC) 缺陷类型位置编码状态机流转[空闲] ──D1001──► [已触发] ──写入D1011──► [检测中] ▲ │ │ 推理完成 │ │ ▼ │ [结果就绪] │ 写入D102OK/NG │ │ │ D1031 │ └──────── 清除D101/D102 ◄──────────────────┘防呆设计上位机写入D102之前必须先确认D1011即PLC确实在等待结果。如果D101被意外清零说明PLC侧已超时复位此时不应再写入旧结果否则会导致下一块板的判定错乱。这个检查只需要一次Read耗时10ms但能避免90%的时序错乱问题。四、 AI推理集成ONNX Runtime零拷贝接入SMT检测用的是YOLOv5s导出的ONNX模型640×640输入。推理引擎封装为独立服务与PLC通信完全解耦publicclassSmtDetector:IDisposable{privatereadonlyInferenceSession_session;privatereadonlyDenseTensorfloat_inputTensor;// 预分配零GCpublicSmtDetector(stringmodelPath){varoptsnewSessionOptions();opts.AppendExecutionProvider_DirectML(0);opts.AppendExecutionProvider_CPU();opts.GraphOptimizationLevelGraphOptimizationLevel.ORT_ENABLE_ALL;_sessionnewInferenceSession(modelPath,opts);_inputTensornewDenseTensorfloat(new[]{1,3,640,640});// 预热_inputTensor.Buffer.Span.Fill(0f);usingvar__session.Run(new[]{NamedOnnxValue.CreateFromTensor(images,_inputTensor)});GC.Collect();}publicDetectionResultDetect(ImageRgb24image){// Span直写TensorHWC→CHW 归一化一步完成varspan_inputTensor.Buffer.Span;intplane640*640;image.ProcessPixelRows(acc{for(inty0;yacc.Height;y){varrowacc.GetRowSpan(y);for(intx0;xrow.Length;x){intidxy*640x;span[idx]row[x].R/255f;span[planeidx]row[x].G/255f;span[2*planeidx]row[x].B/255f;}}});usingvaroutputs_session.Run(new[]{NamedOnnxValue.CreateFromTensor(images,_inputTensor)});returnPostProcess(outputs.First().AsTensorfloat());}}实测性能i7-12700 DirectML预处理0.9ms 推理8.2ms 后处理1.2ms 端到端10.3ms。对于200ms节拍的SMT线体绰绰有余甚至预留了一半以上的余量应对突发情况。五、 现场调试与长期运维经验5.1 通信质量看板在上位机主界面嵌入一个小型通信质量面板实时显示最近1分钟请求成功率95%变黄90%变红平均响应延迟200ms告警当前连接状态和重连次数队列积压深度这个面板的价值远超你的想象。现场操作员看到黄灯就知道该检查网线了不用等到整线停机才报修。5.2 日志分级策略不是所有信息都值得记到磁盘上。我们的分级标准级别内容保留周期Error通信失败、推理异常、IO超时永久Warning重试成功、延迟超标、队列积压30天Info检测结果、握手完成、配置变更7天Debug原始寄存器值、帧耗时明细仅调试模式开启SMT线体每天产生上万条检测记录Info级日志按天滚动压缩Error级单独存档。永远不要让日志文件无限增长这是工控机磁盘爆满的头号原因。5.3 信捷专属避坑清单现象根因解法批量读100个寄存器偶发返回全0超出PLC单帧处理能力分包≤64个包间Delay(2ms)重连后第一次Read超时旧TCP连接未完全释放Cleanup后Delay(100ms)再Connect写入D寄存器值不对FD区地址偏移随固件变化用Modbus Poll逐地址验证多Timer同时读PLC间歇超时并发请求冲突全局队列串行化运行数小时后通信变慢中间Mat/Tensor未释放对象池using严格管理六、 部署与交付最终发布采用单文件自包含模式dotnet publish-cRelease-rwin-x64 --self-containedtrue\-p:PublishSingleFiletrue\-p:IncludeNativeLibrariesForSelfExtracttrue\-p:EnableCompressionInSingleFiletrue输出一个exe models文件夹 appsettings.json总计约90MB。拷到工控机双击即用不需要安装.NET Runtime不需要Python环境不需要任何前置依赖。甲方IT部门验收时看到干净的发布目录当场签字。配置文件支持热重载现场调整检测阈值、通信超时、ROI区域等参数不需要重启程序。配合IOptionsMonitor修改JSON后秒级生效。总结回到最初的问题C#信捷能不能做SMT检测答案是不仅能做而且在合适的场景下是最优解。关键不在于用了什么品牌而在于你是否真正理解了业务需求和硬件特性的匹配关系。SMT检测的串行握手模型、低并发、小数据量特征恰好避开了信捷的所有短板发挥了它的成本优势。再加上应用层的可靠性保障串行队列、心跳探针、指数退避、分包策略就能把一个“便宜货”变成“靠谱的生产力工具”。工程没有银弹只有权衡。希望这篇文章能帮你在下一个项目中做出更理性的技术选型。参考资料信捷XD5系列Modbus TCP通信手册 V3.5NModbus4 GitHub仓库及Issue区ONNX Runtime C# Performance Tuning GuideImageSharp ProcessPixelRows高性能API文档