[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (08) 實作 RtuModbusRequest

[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (08) 實作 RtuModbusRequest

續上篇,Modbus TCP Protocol 暫時告一段落,緊接著往 Modbus RTU Protocol 邁進

image

 

要特別注意的是,Modbus RUT 必須要檢查 CRC,CRC 的元件在這裡我是引用 Tako.CRC.dll

https://tako.codeplex.com/downloads/get/663904

 

下圖為 RTU 框架,要特別注意 CRC 的擺放位置,是先擺 Low Byte 再擺 Hi Byte

image

 

依 Modbus RTU 實作 Request Protocol

{
    private byte[] createReadMessage(byte Unit, EnumModbusFunctionCode FunctionCode, ushort StartAddress, ushort Quantity)
    {
        using (MemoryStream memory = new MemoryStream())
        {
            memory.WriteByte((byte)Unit);
            memory.WriteByte((byte)FunctionCode);
            memory.WriteByte((byte)(StartAddress >> 8));
            memory.WriteByte((byte)(StartAddress));
            memory.WriteByte((byte)(Quantity >> 8));
            memory.WriteByte((byte)(Quantity));

            var crcArray = ModbusUtility.CalculateCRC(memory.ToArray());
            memory.Write(crcArray, 0, crcArray.Length);
            return memory.ToArray();
        }
    }

    public override byte[] ReadCoils(byte Unit, ushort StartAddress, ushort Quantity)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 2000);
        var requestArray = this.createReadMessage(Unit, EnumModbusFunctionCode.ReadCoils, StartAddress, Quantity);
        return requestArray;
    }

    public override byte[] ReadDiscreteInputs(byte Unit, ushort StartAddress, ushort Quantity)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 2000);
        var requestArray = this.createReadMessage(Unit, EnumModbusFunctionCode.ReadDiscreteInputs, StartAddress, Quantity);
        return requestArray;
    }

    public override byte[] ReadHoldingRegisters(byte Unit, ushort StartAddress, ushort Quantity)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 175);
        var requestArray = this.createReadMessage(Unit, EnumModbusFunctionCode.ReadHoldingRegisters, StartAddress, Quantity);
        return requestArray;
    }

    public override byte[] ReadInputRegisters(byte Unit, ushort StartAddress, ushort Quantity)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 175);
        var requestArray = this.createReadMessage(Unit, EnumModbusFunctionCode.ReadInputRegisters, StartAddress, Quantity);
        return requestArray;
    }

    public override byte[] WriteSingleCoil(byte Unit, ushort OutputAddress, bool OutputValue)
    {
        ushort outputValue = 0x0000;
        if (OutputValue)
        {
            outputValue = 0xFF00;
        }
        using (MemoryStream memory = new MemoryStream())
        {
            memory.WriteByte((byte)Unit);
            memory.WriteByte((byte)EnumModbusFunctionCode.WriteSingleCoil);
            memory.WriteByte((byte)(OutputAddress >> 8));
            memory.WriteByte((byte)(OutputAddress));
            memory.WriteByte((byte)(outputValue >> 8));
            memory.WriteByte((byte)(outputValue));

            var crcArray = ModbusUtility.CalculateCRC(memory.ToArray());
            memory.Write(crcArray, 0, crcArray.Length);
            return memory.ToArray();
        }
    }

    public override byte[] WriteSingleRegister(byte Unit, ushort OutputAddress, short OutputValue)
    {
        using (MemoryStream memory = new MemoryStream())
        {
            memory.WriteByte((byte)Unit);
            memory.WriteByte((byte)EnumModbusFunctionCode.WriteSingleRegister);
            memory.WriteByte((byte)(OutputAddress >> 8));
            memory.WriteByte((byte)(OutputAddress));
            memory.WriteByte((byte)(OutputValue >> 8));
            memory.WriteByte((byte)(OutputValue));

            var crcArray = ModbusUtility.CalculateCRC(memory.ToArray());
            memory.Write(crcArray, 0, crcArray.Length);
            return memory.ToArray();
        }
    }

    public override byte[] WriteMultipleCoils(byte Unit, ushort StartAddress, ushort Quantity, byte[] OutputValues)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 0x07B0);

        byte counter = base.GetByteCount(Quantity);

        using (MemoryStream memory = new MemoryStream())
        {
            memory.WriteByte((byte)Unit);
            memory.WriteByte((byte)EnumModbusFunctionCode.WriteMultipleCoils);
            memory.WriteByte((byte)(StartAddress >> 8));
            memory.WriteByte((byte)(StartAddress));
            memory.WriteByte((byte)(Quantity >> 8));
            memory.WriteByte((byte)(Quantity));
            memory.WriteByte((byte)(counter));
            memory.Write(OutputValues, 0, OutputValues.Length);

            var crcArray = ModbusUtility.CalculateCRC(memory.ToArray());
            memory.Write(crcArray, 0, crcArray.Length);
            return memory.ToArray();
        }
    }

    public override byte[] WriteMultipleRegisters(byte Unit, ushort StartAddress, ushort Quantity, short[] OutputValues)
    {
        this.QuantityValidate(StartAddress, Quantity, 1, 0x007B);

        byte[] outputArray = base.GetByteCount(OutputValues);
        byte counter = (byte)outputArray.Length;

        if (Quantity * 2 != outputArray.Length)
        {
            ModbusException.GetModbusException(0x02);
        }

        using (MemoryStream memory = new MemoryStream())
        {
            memory.WriteByte((byte)Unit);
            memory.WriteByte((byte)EnumModbusFunctionCode.WriteMultipleRegisters);
            memory.WriteByte((byte)(StartAddress >> 8));
            memory.WriteByte((byte)(StartAddress));
            memory.WriteByte((byte)(Quantity >> 8));
            memory.WriteByte((byte)(Quantity));
            memory.WriteByte((byte)(counter));
            memory.Write(outputArray, 0, outputArray.Length);

            var crcArray = ModbusUtility.CalculateCRC(memory.ToArray());
            memory.Write(crcArray, 0, crcArray.Length);
            return memory.ToArray();
        }
    }
}

 

 


 

 

ModbusUtility.CalculateCRC 方法取得CRC

{
    private CRCManager m_CrcManager = new CRCManager();
    private AbsCRCProvider m_CrcProvider;
    public byte[] CalculateCRC(byte[] DataArray)
    {
        if (m_CrcProvider == null)
        {
            m_CrcProvider = m_CrcManager.CreateCRCProvider(EnumCRCProvider.CRC16Modbus);
        }
        var crcArray = m_CrcProvider.GetCRC(DataArray).CrcArray;
        Array.Reverse(crcArray);
        return crcArray;
    }
}

建立單元測試,驗證資料組合的正確性

[TestMethod()]
public void ReadCoilsTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 0;
    ushort Quantity = 10;
    byte[] expected = _modbusUtility.HexStringToBytes("01 01 00 00 00 0A BC 0D");
    byte[] actual;
    actual = target.ReadCoils(Unit, StartAddress, Quantity);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void ReadDiscreteInputsTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;

    ushort StartAddress = 0;
    ushort Quantity = 10;
    byte[] expected = _modbusUtility.HexStringToBytes("01 02 00 00 00 0A F8 0D");
    byte[] actual;
    actual = target.ReadDiscreteInputs(Unit, StartAddress, Quantity);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void ReadHoldingRegistersTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 0;
    ushort Quantity = 10;
    byte[] expected = _modbusUtility.HexStringToBytes("01 03 00 00 00 0A C5 CD");
    byte[] actual;
    actual = target.ReadHoldingRegisters(Unit, StartAddress, Quantity);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void ReadInputRegistersTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 0;
    ushort Quantity = 10;

    byte[] expected = _modbusUtility.HexStringToBytes("01 04 00 00 00 0A 70 0D");
    byte[] actual;
    actual = target.ReadInputRegisters(Unit, StartAddress, Quantity);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteSingleCoilTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 2;
    bool OutputValue = true;

    byte[] expected = _modbusUtility.HexStringToBytes("01 05 00 02 FF 00 2D FA");
    byte[] actual;
    actual = target.WriteSingleCoil(Unit, StartAddress, OutputValue);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteSingleRegisterTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort OutputAddress = 2;
    short OutputValue = 2344;
    byte[] expected = _modbusUtility.HexStringToBytes("01 06 00 02 09 28 2E 44");

    byte[] actual;
    actual = target.WriteSingleRegister(Unit, OutputAddress, OutputValue);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteMultipleCoilsTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 0;
    ushort Quantity = 24;
    byte[] OutputValues = new byte[] { 147, 82, 5 };
    byte[] expected = _modbusUtility.HexStringToBytes("01 0F 00 00 00 18 03 93 52 05 4D 3A");

    byte[] actual;
    actual = target.WriteMultipleCoils(Unit, StartAddress, Quantity, OutputValues);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

[TestMethod()]
public void WriteMultipleRegistersTest()
{
    RtuModbusRequest target = new RtuModbusRequest();
    byte Unit = 1;
    ushort StartAddress = 0;
    ushort Quantity = 10;
    short[] OutputValues = new short[] { 1234, 0, 0, -13, 12, -86, 223, 0, 0, -8 };
    byte[] expected = _modbusUtility.HexStringToBytes("01 10 00 00 00 0A 14 04 D2 00 00 00 00 FF F3 00 0C FF AA 00 DF 00 00 00 00 FF F8 58 66");
    byte[] actual;
    actual = target.WriteMultipleRegisters(Unit, StartAddress, Quantity, OutputValues);
    Assert.IsTrue(expected.SequenceEqual(actual));
}
 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo