[西门子] C#读取西门子S7系列PLC(番外)

[复制链接]
查看96669 | 回复0 | 2024-8-20 10:27:50 | 显示全部楼层 |阅读模式
在之前的系列文章中,介绍了如何使用Sharp7这个库,连接西门子plc,并将读取上的数据转换成modbus协议。

C#读取西门子S7系列PLC

这么做的原因是,plc和我们的服务器不通,但是上位机和服务器通,把这个小软件放在上位机上,通过软件把plc数据采集上来,然后让服务器通过上位机的modbusTcp协议采集。

当然还有别的形式:


  • 通过上位机的opc da 形式,配置opc dcom,远程采集,但是这种方式不稳定

  • 在上位机上用一个 opc转modbus的小软件,同样的进行数据转发

  • 上位机自带opcua的话,通过opcua连接,如FameView ,易控,力控

  • 上位机自带modbus的话,通过modbus连接,如FameView

  • 在上位机上装一个kepServer,为所欲为

opc转modbus , kepServer的使用,前面的文章都有介绍,不再赘述。

KEPServer与OPCUA

OPCDA转ModbusTCP (二)

今天还是接着讲这个s7转modbus小软件,补充一下代码细节。

代码思路



1、在导入点表的时候就把每个点位的信息给梳理好


  • 这个点位属于哪个区域(I 、Q、V、M、DB)



  • 根据数据类型判断它占用几个字节,word/short2个字节,float4个字节,double8个字节

  • 根据地址判断它在对应区域的起始位置

  • 如果是开关量,再记录一下它的位置索引(offset)




如上图这个点,DB10.DBW212,对应的各种参数如框所示。

2、按照分区统计每个区域的点表,DB块也按块分类



3、通过对应的函数去读取该区域的数据

借助上面统计出来的起始地址和字节长度,调用sharp7的方法



4、sharp7内部对 byte[] 对象进行了方法扩展,可以直接对读出来的字节数据结果进行按index和类型取数据





bug修复

主要思路就是上面列的,在项目上跑了一年,也没出过什么大问题。

只是其中读取的时候有个隐患,每次读取的字节不能超过 PduSize,不然会报错,"0x00900000:CPU: Address out of range"

西门子PLC的PDU大小是和CPU息息相关的,一般会有240、480、960三个档次,知道PDU之后,那么一次性读取的字节长度,就是在PDU的基础上减去18,这个18是指包头包尾会有18个字节,这样我们就知道了一般的PLC,一次性能读取222个字节(240-18=222),但是对于S7-1516这样的PLC,我们一次性是可以读取942个字节的(960-18=942),这个一次性能读取的字节越长,越能提高上位机的通信效率。

也就是说,我们在读数据之前,需要获取一下当前协商的Pdu大小,然后减去18,得到的结果就是单次读取的最大字节数,如果我们算出来的字节数 比这个大,就得拆分成段,分开去读。
public Result<byte[]> ReadMultiVar(S7Tag s7tag)
{
    //s7tag.DBNumber;  DB号,V区是1 其他非DB区会自动忽略
    //s7tag.Elements;  要读取的长度
    //s7tag.Start;     读取起始地址
    //s7tag.WordLen;   枚举类型 S7WordLength
    //s7tag.Area;      I区PE Q区PA M区MK V区DB1

    var result = new Result<byte[]>();

    byte[] buffer = new byte[s7tag.Elements];//长度超过PDU会报错

    int chunkSize = PduSize - 18; //去掉帧头帧尾
    int totalBytesRead = 0;

    while (totalBytesRead < s7tag.Elements)
    {
        int remainingBytes = s7tag.Elements - totalBytesRead;
        int currentChunkSize = Math.Min(remainingBytes, chunkSize);

        S7MultiVar reader = new S7MultiVar(client);
        byte[] chunkBuffer = new byte[currentChunkSize];

        // Adjust the start address based on the totalBytesRead
        S7Tag chunkTag = new S7Tag()
        {
            Area = s7tag.Area,
            DBNumber = s7tag.DBNumber,
            Start = s7tag.Start + totalBytesRead,
            Elements = currentChunkSize,
            WordLen = s7tag.WordLen
        };

        reader.Add(chunkTag, ref chunkBuffer);

        int resCode = reader.Read();
        if (resCode != 0)
        {
            result.IsSucceed = false;
            result.ErrCode = resCode;
            result.Err = $"ReadMultiVar chunk读取出错: 0x{resCode.ToString("X8")}:{client.ErrorText(resCode)}";
            //break;
        }

        // Copy the chunkBuffer into the result buffer
        Array.Copy(chunkBuffer, 0, buffer, totalBytesRead, currentChunkSize);

        totalBytesRead += currentChunkSize;
    }

    result.Value = buffer;

    return result.EndTime();
}
这种代码 在很多大佬的socket 通讯中很常见,就是拆分字节数组分批次通讯,start 和 size对应变更,最后Array.Copy到最终的结果里。

结语

有兴趣的小伙伴可以留言一起学习哈。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册哦

x
您需要登录后才可以回帖 登录 | 注册哦

本版积分规则