diff --git a/Document/FAQ.doc b/Document/FAQ.doc index 72e92d7..4f31625 100644 Binary files a/Document/FAQ.doc and b/Document/FAQ.doc differ diff --git a/Document/ModbusRTU使用说明.doc b/Document/ModbusRTU使用说明.doc new file mode 100644 index 0000000..def1b15 Binary files /dev/null and b/Document/ModbusRTU使用说明.doc differ diff --git a/SCADA/Program/ModbusDriver/ModbusRTUDriver.cs b/SCADA/Program/ModbusDriver/ModbusRTUDriver.cs index 836583c..733e873 100644 --- a/SCADA/Program/ModbusDriver/ModbusRTUDriver.cs +++ b/SCADA/Program/ModbusDriver/ModbusRTUDriver.cs @@ -13,8 +13,91 @@ namespace ModbusDriver //ModbusRTUReader : IPLCDriver IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable public sealed class ModbusRTUReader : IPLCDriver { + #region :IDriver + //从站地址 + short _id; + public short ID + { + get + { + return _id; + } + } + + string _name; + public string Name + { + get + { + return _name; + } + } + + string _port; + public string ServerName + { + get { return _port; } + set { _port = value; } + } + public bool IsClosed + { + get + { + return _serialPort.IsOpen == false; + } + } + + private int _timeOut; + public int TimeOut + { + get { return _timeOut; } + set { _timeOut = value; } + } + + List _grps = new List(); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + public bool Connect() + { + try + { + _serialPort.Open(); + return true; + } + catch (IOException error) + { + if (OnClose != null) + { + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + } + return false; + } + } + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) + { + ModbusRtuGroup grp = new ModbusRtuGroup(id, name, updateRate, active, this); + _grps.Add(grp); + return grp; + } + + public bool RemoveGroup(IGroup grp) + { + grp.IsActive = false; + return _grps.Remove(grp); + } + public event ShutdownRequestEventHandler OnClose; + #endregion //自定义构造函数3 - public ModbusRTUReader(IDataServer server, short id, string name, string remote = null, int timeOut = 10000, string port = "COM1", string baudRate = "9600") + public ModbusRTUReader(IDataServer server, short id, string name, string port = "COM1", int timeOut = 10000, string spare1 = "0", string baudRate = "9600") { _id = id; _name = name; @@ -28,10 +111,10 @@ namespace ModbusDriver _serialPort.DataBits = 8; _serialPort.Parity = Parity.Even; _serialPort.StopBits = StopBits.One; + if (!string.IsNullOrEmpty(spare1)) byte.TryParse(spare1, out _slaveId); } private SerialPort _serialPort; - /* Sbyte:代表有符号的8位整数,数值范围从-128 ~ 127    Byte:代表无符号的8位整数,数值范围从0~255 @@ -45,38 +128,32 @@ namespace ModbusDriver private byte[] CreateReadHeader(int startAddress, ushort length, byte function) { byte[] data = new byte[8]; - data[0] = (byte)_id; // Slave id high byte 从站地址 + data[0] = _slaveId; // Slave id high byte 从站地址 data[1] = function; // Message size byte[] _adr = BitConverter.GetBytes((short)startAddress);//以字节数组的形式返回指定的 16 位无符号整数值。 - data[2] = _adr[0]; // Start address high byte 起始地址的高八位 - data[3] = _adr[1]; // Start address low byte 起始地址的低八位 + //apply on small endian, TODO:support big endian + data[2] = _adr[1]; // Start address 起始地址的高八位 + data[3] = _adr[0]; // Start address起始地址的低八位 byte[] _length = BitConverter.GetBytes((short)length); - data[4] = _length[0]; // Number of data to read high byte 寄存器数量的高八位 - data[5] = _length[1]; // Number of data to read low byte 寄存器数量的低八位 + //apply on small endian, TODO:support big endian + data[4] = _length[1]; // Number of data to read 寄存器数量的高八位 + data[5] = _length[0]; // Number of data to read 寄存器数量的低八位 byte[] arr = Utility.CalculateCrc(data, 6); - data[6] = arr[0]; //CRC校验的低八位 - data[7] = arr[1]; //CRC校验的高八位 + data[6] = arr[0]; //CRC校验的低八位 + data[7] = arr[1];//CRC校验的高八位 return data; } + #region 写单个线圈或单个离散输出 功能码:0x05 public byte[] WriteSingleCoils(int startAddress, bool OnOff) { byte[] data = new byte[8]; - data[0] = (byte)_id; // Slave id high byte + data[0] = _slaveId; // Slave id high byte data[1] = Modbus.fctWriteSingleCoil; // Function code byte[] _adr = BitConverter.GetBytes((short)startAddress); data[2] = _adr[0]; // Start address - data[3] = _adr[1]; // Start address - if (OnOff) - { - data[4] = 0xFF; - data[5] = 0x00; - } - else - { - data[4] = 0x00; - data[5] = 0x00; - } + data[3] = _adr[1]; // Start address + if (OnOff) data[4] = 0xFF; byte[] arr = Utility.CalculateCrc(data, 6); data[6] = arr[0]; data[7] = arr[1]; @@ -89,7 +166,7 @@ namespace ModbusDriver { int len = values.Length; byte[] data = new byte[len + 9]; - data[0] = (byte)_id; // Slave id high byte 从站地址高八位 + data[0] = _slaveId; // Slave id high byte 从站地址高八位 data[1] = Modbus.fctWriteMultipleCoils; // Function code 功能码 byte[] _adr = BitConverter.GetBytes((short)startAddress); data[2] = _adr[0]; // Start address 开始地址高八位 @@ -97,8 +174,6 @@ namespace ModbusDriver byte[] _length = BitConverter.GetBytes((short)numBits); data[4] = _length[0]; // Number of data to read 寄存器数量高八位 data[5] = _length[1]; // Number of data to read 寄存器数量低八位 - - data[6] = (byte)len; //字节数量 Array.Copy(values, 0, data, 7, len); //在data中加入变更数据 byte[] arr = Utility.CalculateCrc(data, len + 7); @@ -112,7 +187,7 @@ namespace ModbusDriver public byte[] WriteSingleRegister(int startAddress, byte[] values) { byte[] data = new byte[8]; - data[0] = (byte)_id; // Slave id high byte 从站地址高八位 + data[0] = _slaveId; // Slave id high byte 从站地址高八位 data[1] = Modbus.fctWriteSingleRegister; // Function code 功能码 byte[] _adr = BitConverter.GetBytes((short)startAddress); data[2] = _adr[0]; // Start address 开始地址高八位 @@ -132,7 +207,7 @@ namespace ModbusDriver int len = values.Length; if (len % 2 > 0) len++; byte[] data = new byte[len + 9]; - data[0] = (byte)_id; // Slave id high byte 从站地址 + data[0] = _slaveId; // Slave id high byte 从站地址 data[1] = Modbus.fctWriteMultipleRegister; // Function code 功能码 byte[] _adr = BitConverter.GetBytes((short)startAddress); data[2] = _adr[0]; // Start address 开始地址高八位 @@ -148,25 +223,28 @@ namespace ModbusDriver return data; } #endregion - - - + #region :IPLCDriver public int PDU { // get { return 0x100; } //0x100十进制值为256 - - /* 更新人:yjz 更新日期:20171125 更新原因: 在串行通信中RS232 / RS485 modbus协议规定如下: ADU=地址域+功能码+数据+差错校验 其中 ADU 256字节,地址域1字节,功能码1字节,数据为252字节 , 差错检验2字节, PDU=功能码+数据 - 所以PDU应为: 253字节 - */ + 所以PDU应为: 253字节 */ get { return 0xFD; } //0xFD 十进制为253 + } - + byte _slaveId;//设备ID 单元号 字节号 + /// + /// 设备ID 单元号 字节号 + /// + public byte SlaveId + { + get { return _slaveId; } + set { _slaveId = value; } } public DeviceAddress GetDeviceAddress(string address)//PLC地址一般为5位 如40001,也有可能为40001.1 首位代表地址类型 @@ -176,27 +254,32 @@ namespace ModbusDriver return dv; switch (address[0]) { - case '0': + case '0'://功能码:01 读线圈或离散量输出状态 00001 - 09999:数字量输出( 线圈) { - dv.Area = Modbus.fctReadCoil;//功能码:01 读线圈或离散量输出状态 00001 - 09999:数字量输出( 线圈) + dv.Area = Modbus.fctReadCoil; int st; int.TryParse(address, out st); - dv.Bit = (byte)(st % 16); - st /= 16; + //delete : if st<16, execute st /= 16, st will equal 0, in func ReadBytes() address.Start * 16 has no effect + //dv.Bit = (byte)(st % 16); + //st /= 16; dv.Start = st; + //add : use PLC address(base 1) + dv.Start--; } break; - case '1': + case '1'://功能码:02 读离散量输入 10001 - 19999:数字量输入(触点) { - dv.Area = Modbus.fctReadDiscreteInputs;//功能码:02 读离散量输入 10001 - 19999:数字量输入(触点) + dv.Area = Modbus.fctReadDiscreteInputs; int st; int.TryParse(address.Substring(1), out st); - dv.Bit = (byte)(st % 16); - st /= 16; + //delete : if st<16, execute st /= 16, st will equal 0, in func ReadBytes() address.Start * 16 has no effect + //dv.Bit = (byte)(st % 16); + //st /= 16; dv.Start = st; + //add : use PLC address(base 1) + dv.Start--; } - break; - + break; case '4': { int index = address.IndexOf('.'); @@ -235,119 +318,45 @@ namespace ModbusDriver } #endregion - #region :IDriver - //从站地址 - short _id; - public short ID - { - get - { - return _id; - } - } - string _name; - public string Name - { - get - { - return _name; - } - } - - string _port; - public string ServerName - { - get { return _port; } - set { _port = value; } - } - public bool IsClosed - { - get - { - return _serialPort.IsOpen == false; - } - } - - private int _timeOut; - public int TimeOut - { - get { return _timeOut; } - set { _timeOut = value; } - } - - List _grps = new List(); - public IEnumerable Groups - { - get { return _grps; } - } - - IDataServer _server; - public IDataServer Parent - { - get { return _server; } - } - public bool Connect() - { - try - { - _serialPort.Open(); - return true; - } - catch (IOException error) - { - if (OnClose != null) - { - OnClose(this, new ShutdownRequestEventArgs(error.Message)); - } - return false; - } - } - - public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) - { - ModbusRtuGroup grp = new ModbusRtuGroup(id, name, updateRate, active, this); - _grps.Add(grp); - return grp; - } - - public bool RemoveGroup(IGroup grp) - { - grp.IsActive = false; - return _grps.Remove(grp); - } - public event ShutdownRequestEventHandler OnClose; - #endregion - - #region : IDisposable - - public void Dispose() - { - foreach (IGroup grp in _grps) - { - grp.Dispose(); - } - _grps.Clear(); - _serialPort.Close(); - } - #endregion - #region :IReaderWriter public byte[] ReadBytes(DeviceAddress address, ushort size) { int area = address.Area; try { - byte[] header = area == Modbus.fctReadCoil ? CreateReadHeader(address.Start * 16, (ushort)(16 * size), (byte)area) : - CreateReadHeader(address.Start, size, (byte)area); + //no need to multiply by 16 + //byte[] header = area == Modbus.fctReadCoil ? CreateReadHeader(address.Start * 16, (ushort)(16 * size), (byte)area) : + // CreateReadHeader(address.Start, size, (byte)area); + byte[] header = CreateReadHeader(address.Start, size, (byte)area); _serialPort.Write(header, 0, header.Length); - byte[] frameBytes = new byte[size * 2 + 5]; - byte[] data = new byte[size * 2]; + + /* different function_code have different realy frameBytes + * function_code 1/2: one register variable use one bit + * function_code 3/4: one register variable use two bytes + */ + int modbus_rtu_replay_frameBytes_size = 0; + int modbus_rtu_replay_data_size = 0; + switch (area) + { + case 1: + case 2: + modbus_rtu_replay_frameBytes_size = size / 8 + 1 + 5; + break; + case 3: + case 4: + modbus_rtu_replay_frameBytes_size = size * 2 + 5; + break; + } + modbus_rtu_replay_data_size = modbus_rtu_replay_frameBytes_size - 5; + + byte[] frameBytes = new byte[modbus_rtu_replay_frameBytes_size]; + byte[] data = new byte[modbus_rtu_replay_data_size]; int numBytesRead = 0; - while (numBytesRead != size) - numBytesRead += _serialPort.Read(frameBytes, numBytesRead, size - numBytesRead); - if (frameBytes[0] == (byte)_id && Utility.CheckSumCRC(frameBytes)) + while (numBytesRead != modbus_rtu_replay_frameBytes_size) + numBytesRead += _serialPort.Read(frameBytes, numBytesRead, modbus_rtu_replay_frameBytes_size - numBytesRead); + if (frameBytes[0] == _slaveId && Utility.CheckSumCRC(frameBytes)) { - Array.Copy(frameBytes, 3, data, 0, size); + Array.Copy(frameBytes, 3, data, 0, modbus_rtu_replay_data_size); return data; } return null; @@ -407,7 +416,6 @@ namespace ModbusDriver return this.ReadValueEx(address); } - public int WriteBytes(DeviceAddress address, byte[] bit) { var data = WriteMultipleRegister(address.Start, bit); @@ -467,7 +475,7 @@ namespace ModbusDriver _serialPort.Write(data, 0, data.Length); _serialPort.ReadByte(); var chr = _serialPort.ReadByte(); - return chr == (byte)_id ? -1 : 0; + return chr == _slaveId ? -1 : 0; } public int WriteValue(DeviceAddress address, object value) @@ -477,10 +485,18 @@ namespace ModbusDriver #endregion + #region : IDisposable - - - + public void Dispose() + { + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + _serialPort.Close(); + } + #endregion } public sealed class ModbusRtuGroup : PLCGroup @@ -512,76 +528,121 @@ namespace ModbusDriver } else { - int len = rcvBytes.Length / 2; - fixed (byte* p1 = rcvBytes) + //function code = 01 or 02 + if ((area.Start.Area == 1) || (area.Start.Area == 2)) { - short* prcv = (short*)p1; - int index = area.StartIndex;//index指向_items中的Tag元数据 - int count = index + area.Count; - while (index < count) + fixed (byte* p1_coil = rcvBytes) { - DeviceAddress addr = _items[index].Address; - int iShort = addr.CacheIndex; - int iShort1 = iShort - k; - if (addr.VarType == DataType.BOOL) + byte* prcv_coil = p1_coil; + int index = area.StartIndex;//index指向_items中的Tag元数据 + int count = index + area.Count; + int len = rcvBytes.Length; + for (int rcvBytes_index = 0; rcvBytes_index < len; rcvBytes_index++) //for index whitch byte { - int tmp = prcv[iShort1] ^ cache[iShort]; - DeviceAddress next = addr; - if (tmp != 0) + for (int rcvBytes_bit_index = 0; rcvBytes_bit_index < 8; rcvBytes_bit_index++) //for index whitch bit { - while (addr.Start == next.Start) + short change_flag = 0; + if ((prcv_coil[rcvBytes_index] & (0x01 << rcvBytes_bit_index)) != 0) { - if ((tmp & (1 << next.Bit)) > 0) _changedList.Add(index); - if (++index < count) - next = _items[index].Address; - else - break; + change_flag = 1; } - } - else - { - while (addr.Start == next.Start && ++index < count) + int tmp = change_flag ^ cache[index]; + if (tmp != 0) { - next = _items[index].Address; + _changedList.Add(index); + cache[index] = change_flag; + } + index++; + if (index >= count) + { + break; } } } - else + } + } + else //function code = 03 or 04 + { + #region apply on small endian, if you are big endian, no need to do this + byte exchange_temp; + for (int index = 0; index < rcvBytes.Length; index = index + 2) + { + exchange_temp = rcvBytes[index + 1]; + rcvBytes[index + 1] = rcvBytes[index]; + rcvBytes[index] = exchange_temp; + } + #endregion + + int len = rcvBytes.Length / 2; + fixed (byte* p1 = rcvBytes) + { + short* prcv = (short*)p1; + int index = area.StartIndex;//index指向_items中的Tag元数据 + int count = index + area.Count; + while (index < count) { - if (addr.DataSize <= 2) + DeviceAddress addr = _items[index].Address; + int iShort = addr.CacheIndex; + int iShort1 = iShort - k; + if (addr.VarType == DataType.BOOL) { - if (prcv[iShort1] != cache[iShort]) _changedList.Add(index); + int tmp = prcv[iShort1] ^ cache[iShort]; + DeviceAddress next = addr; + if (tmp != 0) + { + while (addr.Start == next.Start) + { + if ((tmp & (1 << next.Bit)) > 0) _changedList.Add(index); + if (++index < count) + next = _items[index].Address; + else + break; + } + } + else + { + while (addr.Start == next.Start && ++index < count) + { + next = _items[index].Address; + } + } } else { - int size = addr.DataSize / 2; - for (int i = 0; i < size; i++) + if (addr.DataSize <= 2) + { + if (prcv[iShort1] != cache[iShort]) _changedList.Add(index); + } + else { - if (prcv[iShort1 + i] != cache[iShort + i]) + int size = addr.DataSize / 2; + for (int i = 0; i < size; i++) { - _changedList.Add(index); - break; + if (prcv[iShort1 + i] != cache[iShort + i]) + { + _changedList.Add(index); + break; + } } } + index++; } - index++; } + for (int j = 0; j < len; j++) + { + cache[j + k] = prcv[j]; + }//将PLC读取的数据写入到CacheReader中 } - for (int j = 0; j < len; j++) - { - cache[j + k] = prcv[j]; - }//将PLC读取的数据写入到CacheReader中 + k += len; } - k += len; } } return 1; } - } - public sealed class Modbus + public sealed class Modbus { public const byte fctReadCoil = 1; public const byte fctReadDiscreteInputs = 2; diff --git a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs index 08fc88e..004e9ee 100644 --- a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs +++ b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs @@ -12,8 +12,6 @@ namespace ModbusDriver [Description("Modbus TCP协议")] public sealed class ModbusTCPReader : IPLCDriver, IMultiReadWrite //IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable { - - #region public int PDU { @@ -27,7 +25,6 @@ namespace ModbusDriver 所以PDU应为: 249字节 */ get { return 249; } //0xF9 十进制为249 - } public DeviceAddress GetDeviceAddress(string address) @@ -164,14 +161,14 @@ namespace ModbusDriver get { return _server; } } - public ModbusTCPReader(IDataServer server, short id, string name, string ip, int timeOut = 500, string spare1 = null, string spare2 = null) + public ModbusTCPReader(IDataServer server, short id, string name, string ip, int timeOut = 500, string spare1 = "0", string spare2 = null) { _id = id; _name = name; _server = server; _ip = ip; _timeout = timeOut; - byte.TryParse(spare1, out _slaveId); + if (!string.IsNullOrEmpty(spare1)) byte.TryParse(spare1, out _slaveId); } public bool Connect() @@ -404,7 +401,7 @@ namespace ModbusDriver public byte[] ReadBytes(DeviceAddress address, ushort size) { int area = address.DBNumber; - return area < 2 ? WriteSyncData(CreateReadHeader(address.Area, address.Start * 16, (ushort)(16 * size), (byte)area)) + return area < 3 ? WriteSyncData(CreateReadHeader(address.Area, address.Start * 16, (ushort)(16 * size), (byte)area)) : WriteSyncData(CreateReadHeader(address.Area, address.Start, size, (byte)area)); } diff --git a/SCADA/dll/ModbusDriver.dll b/SCADA/dll/ModbusDriver.dll index c22e713..5acf584 100644 Binary files a/SCADA/dll/ModbusDriver.dll and b/SCADA/dll/ModbusDriver.dll differ