Modbus通信

7
我正在使用C#通过modbus rs485 rs232与两个相位计通信,这些计量器记录了电压等信息。
我需要通过总线发送数据以接收读数。我连接了一条普通的电线并将发送和接收短接在一起。
数据已被接收并触发了此事件:
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    byte[] buff = new byte[sp.BytesToRead];

    //Read the Serial Buffer
    sp.Read(buff, 0, buff.Length);
    string data= sp.ReadExisting();

    foreach (byte b in buff)
    {
        AddBuffer(b); //Add byte to buffer
    }
}

然后将这个缓冲区发送到另一个函数,该函数如下:
private void AddBuffer(byte b)
{
    buffer.Add(b);

    byte[] msg =  buffer.ToArray();

    //Make sure that the message integrity is correct
    if (this.CheckDataIntegrity(msg))
    {
        if (DataReceived != null)
        {
            ModbusEventArgs args = new ModbusEventArgs();
            GetValue(msg, args);
            DataReceived(this, args);
        }
        buffer.RemoveRange(0, buffer.Count);

    }
}

我认为问题在于数据完整性检查:
public bool CheckDataIntegrity(byte[] data)
{
    if (data.Length < 6)
        return false;
    //Perform a basic CRC check:
    byte[] CRC = new byte[2];
    GetCRC(data, ref CRC);
    if (CRC[0] == data[data.Length - 2] && CRC[1] == data[data.Length - 1])
        return true;
    else
        return false;
}

有一个CRC检查,奇怪的是它从来没有成为真。 CRC计算:

private void GetCRC(byte[] message, ref byte[] CRC)
{

    ushort CRCFull = 0xFFFF;
    byte CRCHigh = 0xFF, CRCLow = 0xFF;
    char CRCLSB;

    for (int i = 0; i < (message.Length) - 2; i++)
    {
        CRCFull = (ushort)(CRCFull ^ message[i]);

        for (int j = 0; j < 8; j++)
        {
            CRCLSB = (char)(CRCFull & 0x0001);
            CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);

            if (CRCLSB == 1)
                CRCFull = (ushort)(CRCFull ^ 0xA001);
        }
    }
    CRC[1] = CRCHigh = (byte)((CRCFull >> 8) & 0xFF);
    CRC[0] = CRCLow = (byte)(CRCFull & 0xFF);
}

有没有在线资源提供典型通信会话的转录,包括CRC?那么你至少可以将你的算法应用于这些示例消息,并查看是否得出相同的CRC。 - Andyz Smith
1
“buffer”变量是什么?它是一个List<byte>吗?你确定你的“msg”变量比6大吗?为什么不直接使用串口缓冲区而不是通过字节循环分解,重构成列表,然后将其转换回全局字节数组?你还在读取缓冲区内容后立即调用串口上的ReadExisting,为什么? - B L
或者更好的是,是否有一种终止字符可以用来表示有效数据传输的结束,而不是无限累积字节并检查长度? - B L
好的,谢谢你的时间和帮助! - Combinu
故障排除。如果您使用一些示例消息,它们返回的CRC [0]和CRC [1]的值是否正确?如果您确定算法正在正确计算CRC,请逐步执行代码。它在哪里未能批准CRC。您有一些可能容易出错的地方。 - Andyz Smith
显示剩余5条评论
2个回答

2
问题在于使用了ReadExisting()方法。这种方式不应该被使用,因为缓冲区中充满了来自串口的无用数据。@glace在评论中指出了这个问题!

1
你首先需要通过一些现有的MODBUS主应用程序(如MODPOLL)与你的计量器建立通信。然后,一旦你的通信正常并从设备获得有效的回复,才开始测试你的代码。这样可以确保问题只存在于你的代码中,而不是其他方面。
例如,要同时连接两个从设备,必须使用RS485而不是RS232,这需要不同的布线和PC端的RS485到RS232转换器。
在RS232中将RX和TX连接起来进行模拟并不是一个好主意,因为每个MODBUS主机发送的消息(除广播消息外)都需要有一个不同于消息回显的回复。此外,每个MODBUS主机发送的消息中嵌入了MODBUS客户端地址,只有单个客户端应该对其进行回复(MODBUS是单主多从协议)。
至于CRC计算,这可能有助于MODBUS RTU协议(ASCII协议不同):
function mb_CalcCRC16(ptr: pointer to byte; ByteCount: byte): word;
var
  crc: word;
  b, i, n: byte;
begin
  crc := $FFFF;
  for i := 0 to ByteCount do
    if i = 0 then           // device id is 1st byte in message, and it is not in the buffer
      b := mb_GetMessageID; // so we have to calculate it and put it as 1st crc byte
    else
      b := ptr^;
      Inc(ptr);
    endif;
    crc := crc xor word(b);
    for n := 1 to 8 do
      if (crc and 1) = 1 then
        crc := (crc shr 1) xor $A001;
      else
        crc := crc shr 1;
      endif;
    endfor;
  endfor;
  Return(crc);
end;

function mb_CalcCRC: word; // Calculate CRC for message in mb_pdu
begin // this message can be one that is just received, or in a reply we have just composed
  Return(mb_CalcCRC16(@mb_pdu[1], mb_GetEndOfData));
end;

这是一个嵌入式AVR设备实现了MODBUS RTU从站协议的引用。


谢谢你的帮助,我已经解决了这个问题...看起来是我从串口读取数据时出现了错误!...但仍然非常感谢你的帮助!! - Combinu
@MysticJay,你能否把你的解决方案作为答案添加进去(你可以回答自己的问题)并接受它呢?我觉得如果未来读者遇到类似的问题,这会对他们有所帮助。(我参考了xkcd - default
我认为这不是C#。 - mrid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接