在之前的系列文章中,介绍了如何使用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、在导入点表的时候就把每个点位的信息给梳理好
根据数据类型判断它占用几个字节,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到最终的结果里。
结语
有兴趣的小伙伴可以留言一起学习哈。 |