[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (08) 實作 RtuModbusRequest
續上篇,Modbus TCP Protocol 暫時告一段落,緊接著往 Modbus RTU Protocol 邁進
要特別注意的是,Modbus RUT 必須要檢查 CRC,CRC 的元件在這裡我是引用 Tako.CRC.dll
https://tako.codeplex.com/downloads/get/663904
下圖為 RTU 框架,要特別注意 CRC 的擺放位置,是先擺 Low Byte 再擺 Hi Byte
依 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