一、项目背景今年初接了个天津西青区汽车安全带总成厂的活他们原来的产线是8个工位各自用S7-1200单独控制没有统一的上位机系统存在三个致命问题工位之间完全靠人工传递信号经常出现漏工序、错工序不良率高达3.2%没有数据追溯系统一旦出现质量问题根本查不到是哪个工位、哪个时间生产的只能整批召回设备状态无法实时监控故障发生后要逐个工位排查平均停机时间超过40分钟客户预算6万要求用一台S7-1516F-3 PN/DP替换原来的8台S7-1200实现8个工位的统一控制和协同用C#写上位机实时显示所有工位的状态和生产数据实现全流程数据追溯每个产品绑定唯一二维码可查询所有工位的工艺参数支持异常报警和历史数据查询生成日/周/月生产报表预留MES对接接口以后可以直接对接工厂的MES系统二、整体架构设计我设计了一个四层架构这个架构在多个汽车零部件项目中验证过非常稳定而且扩展性极强。应用层数据层控制层设备层S7协议工位1上料扫码工位2织带裁剪工位3锁扣压装工位4卷簧装配工位5壳体组装工位6拉力测试工位7激光打标工位8下料分拣扫码枪×8拧紧枪×3压力传感器×2拉力传感器×1西门子S7-1516F-3 PN/DPSQL Server 2022 ExpressC#上位机服务WPF本地运维看板Vue3 Web远程看板企业微信报警2.1 为什么选S7-1500性能强劲单CPU可以轻松处理8个工位的逻辑控制和100多个IO点支持Profinet总线所有设备都可以通过网线连接布线简单集成安全功能满足汽车行业的安全要求支持结构化数据块方便和C#上位机进行数据交换2.2 为什么选C#上位机开发速度快WPF做界面美观、响应快和SQL Server集成完美数据存储和查询效率高S7.NET库成熟稳定对接西门子PLC非常方便可以同时支持本地WPF和远程Web访问满足不同用户的需求三、核心通信实现S7.NET对接S7-1500我用S7.NET库实现C#和S7-1500的通信这是.NET平台最成熟的西门子通信库开源免费支持所有S7系列PLC。3.1 DB块设计规范这是整个系统的基础DB块设计得好通信和数据处理会非常简单。我把所有数据分成三个DB块DB1工位状态数据存储8个工位的实时状态、传感器数据和执行器状态DB2生产数据存储当前产品的序列号、各工位的工艺参数和测试结果DB3控制指令存储上位机下发给PLC的控制指令和参数DB块数据结构示例// 对应PLC中的UDT_StationStatuspublicstructStationStatus{publicboolIsIdle;// 待机publicboolIsRunning;// 运行publicboolIsFault;// 故障publicboolIsFinish;// 完成publicintFaultCode;// 故障代码publiclongCycleTime;// 循环时间publiclongTotalCount;// 累计产量}// 对应PLC中的UDT_ProductDatapublicstructProductData{[MarshalAs(UnmanagedType.ByValTStr,SizeConst20)]publicstringSerialNo;// 产品序列号publicfloatTorque1;// 工位3扭矩1publicfloatTorque2;// 工位3扭矩2publicfloatPressDepth;// 工位3压装深度publicfloatTensileForce;// 工位6拉力publicboolIsQualified;// 是否合格publiclongProduceTime;// 生产时间戳}3.2 批量数据读写优化很多人用S7.NET都是单个变量读写这样效率很低8个工位的数据要读8次。我用批量读写的方式一次读取整个DB块然后在内存中解析效率提升了10倍以上。usingS7.Net;publicclassS71500Client{privatePlc_plc;privatereadonlystring_ip;privatereadonlyint_rack0;privatereadonlyint_slot1;publicS71500Client(stringip){_ipip;}publicboolConnect(){try{_plcnewPlc(CpuType.S71500,_ip,_rack,_slot);_plc.Open();return_plc.IsConnected;}catch(Exceptionex){Console.WriteLine($PLC连接失败:{ex.Message});returnfalse;}}// 批量读取整个DB块publicTReadDbBlockT(intdbNumber)whereT:struct{intsizeMarshal.SizeOfT();byte[]buffernewbyte[size];_plc.ReadBytes(DataType.DataBlock,dbNumber,0,size,buffer);IntPtrptrMarshal.AllocHGlobal(size);Marshal.Copy(buffer,0,ptr,size);TresultMarshal.PtrToStructureT(ptr);Marshal.FreeHGlobal(ptr);returnresult;}// 批量写入整个DB块publicvoidWriteDbBlockT(intdbNumber,Tdata)whereT:struct{intsizeMarshal.SizeOfT();byte[]buffernewbyte[size];IntPtrptrMarshal.AllocHGlobal(size);Marshal.StructureToPtr(data,ptr,true);Marshal.Copy(ptr,buffer,0,size);Marshal.FreeHGlobal(ptr);_plc.WriteBytes(DataType.DataBlock,dbNumber,0,buffer);}}3.3 自动重连机制工业现场网络不稳定是常有的事必须实现自动重连机制确保网络恢复后系统能自动恢复正常。privateasyncTaskAutoReconnectAsync(CancellationTokencancellationToken){while(!cancellationToken.IsCancellationRequested){if(_plcnull||!_plc.IsConnected){Console.WriteLine(PLC连接断开尝试重连...);if(Connect()){Console.WriteLine(PLC重连成功);}else{Console.WriteLine(PLC重连失败5秒后重试);}}awaitTask.Delay(5000,cancellationToken);}}四、8工位协同控制核心逻辑这是整个项目最复杂的部分也是最容易出问题的地方。我用主状态机工位状态机的双层架构配合请求-应答握手机制实现了8个工位的无缝协同。4.1 双层状态机设计工位状态机系统启动工件到位工序完成放行工件故障故障复位工位待机等待工件执行工序工序完成工位故障主状态机启动暂停继续故障故障复位系统待机系统运行系统暂停系统故障主状态机控制整个产线的运行状态工位状态机控制每个工位的具体流程。所有工位的状态都实时同步到上位机上位机可以随时查看每个工位的运行情况。4.2 握手信号同步机制工位之间的同步用请求-应答的握手机制实现确保不会出现漏工序和错工序。PLC侧握手逻辑// 工位1完成请求工位2接收 IF Station1.IsFinish AND NOT Station2.IsBusy THEN Handshake.Station1ToStation2_Req : TRUE; END_IF // 工位2应答接收工件 IF Handshake.Station1ToStation2_Req AND Station2.IsIdle THEN Handshake.Station1ToStation2_Ack : TRUE; Station2.Start : TRUE; END_IF // 应答完成清除请求 IF Handshake.Station1ToStation2_Req AND Handshake.Station1ToStation2_Ack THEN Handshake.Station1ToStation2_Req : FALSE; Station1.IsFinish : FALSE; END_IF // 工位2完成清除应答 IF Station2.IsFinish THEN Handshake.Station1ToStation2_Ack : FALSE; END_IF这种机制的好处是非常可靠任何一个环节出问题整个流程都会停止不会出现混乱。4.3 异常处理与回滚任何一个工位发生故障系统会自动停止上游所有工位下游工位继续完成当前工件的加工避免产生更多的废品。上位机会弹出报警窗口显示故障代码和处理建议。五、全流程数据追溯系统这是客户最看重的功能也是提升产品质量的关键。每个产品从进入产线开始就会被分配一个唯一的二维码所有工位的工艺参数和测试结果都会和这个二维码绑定实现全流程追溯。5.1 二维码扫码绑定工位1的扫码枪扫描产品上的二维码PLC将二维码发送给上位机上位机生成一个唯一的产品ID然后将产品ID和二维码绑定存入数据库。publicasyncTaskstringBindProductAsync(stringqrCode){stringproductIdGuid.NewGuid().ToString(N);varproductnewProduct{ProductIdproductId,QrCodeqrCode,StartTimeDateTime.Now,StatusProducing};await_dbContext.Products.AddAsync(product);await_dbContext.SaveChangesAsync();returnproductId;}5.2 生产数据实时记录每个工位完成工序后PLC会将该工位的工艺参数和测试结果发送给上位机上位机将这些数据和产品ID绑定存入数据库。publicasyncTaskRecordProcessDataAsync(stringproductId,intstationId,objectprocessData){varprocessRecordnewProcessRecord{RecordIdGuid.NewGuid().ToString(N),ProductIdproductId,StationIdstationId,ProcessDataJsonSerializer.Serialize(processData),RecordTimeDateTime.Now};await_dbContext.ProcessRecords.AddAsync(processRecord);await_dbContext.SaveChangesAsync();}5.3 产品追溯查询用户可以通过产品序列号、二维码或者生产时间段查询产品的全生命周期数据包括每个工位的工艺参数、测试结果、操作人员和生产时间。六、项目成果这个项目花了4周时间完成总成本不到5万西门子S7-1516F-3 PN/DP18000其他电气元件12000开发费用20000上线后运行了2个月效果非常显著不良率从3.2%降到了0.5%平均故障停机时间从40分钟降到了8分钟生产效率提升了25%实现了100%的产品全流程追溯客户非常满意已经决定把另外3条产线也改成这个方案。七、实战踩坑总结DB块一定要用优化的块访问S7-1500默认是优化的块访问这样数据存储更紧凑通信效率更高。如果用非优化的块访问数据偏移量会不一样很容易出现数据解析错误。字符串长度一定要匹配PLC中的字符串是带长度前缀的C#中的字符串是不带的。在定义结构体的时候一定要用MarshalAs(UnmanagedType.ByValTStr, SizeConst 20)指定字符串长度并且和PLC中的字符串长度一致。不要在UI线程中读写PLC读写PLC是IO操作会阻塞UI线程导致界面卡顿。一定要用后台线程或者异步方法来读写PLC。数据一定要做本地缓存不要每次界面更新都去读PLC这样会增加PLC的通信负载。应该用一个后台线程定时读取PLC数据存入本地缓存界面从本地缓存中获取数据。所有的控制指令都要有反馈上位机下发控制指令后一定要等待PLC的反馈确认指令执行成功后再进行下一步操作。