Browse Source

update modbus RTU

pull/17/head
Gavin 9 years ago
parent
commit
1327bab12f
  1. BIN
      Document/FAQ.doc
  2. BIN
      Document/ModbusRTU使用说明.doc
  3. 447
      SCADA/Program/ModbusDriver/ModbusRTUDriver.cs
  4. 9
      SCADA/Program/ModbusDriver/ModbusTCPDriver.cs
  5. BIN
      SCADA/dll/ModbusDriver.dll

BIN
Document/FAQ.doc

Binary file not shown.

BIN
Document/ModbusRTU使用说明.doc

Binary file not shown.

447
SCADA/Program/ModbusDriver/ModbusRTUDriver.cs

@ -13,8 +13,91 @@ namespace ModbusDriver
//ModbusRTUReader : IPLCDriver IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable //ModbusRTUReader : IPLCDriver IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable
public sealed class ModbusRTUReader : IPLCDriver 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<IGroup> _grps = new List<IGroup>();
public IEnumerable<IGroup> 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 //自定义构造函数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; _id = id;
_name = name; _name = name;
@ -28,10 +111,10 @@ namespace ModbusDriver
_serialPort.DataBits = 8; _serialPort.DataBits = 8;
_serialPort.Parity = Parity.Even; _serialPort.Parity = Parity.Even;
_serialPort.StopBits = StopBits.One; _serialPort.StopBits = StopBits.One;
if (!string.IsNullOrEmpty(spare1)) byte.TryParse(spare1, out _slaveId);
} }
private SerialPort _serialPort; private SerialPort _serialPort;
/* /*
Sbyte:8-128 127 Sbyte:8-128 127
   Byte:80255    Byte:80255
@ -45,38 +128,32 @@ namespace ModbusDriver
private byte[] CreateReadHeader(int startAddress, ushort length, byte function) private byte[] CreateReadHeader(int startAddress, ushort length, byte function)
{ {
byte[] data = new byte[8]; 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 data[1] = function; // Message size
byte[] _adr = BitConverter.GetBytes((short)startAddress);//以字节数组的形式返回指定的 16 位无符号整数值。 byte[] _adr = BitConverter.GetBytes((short)startAddress);//以字节数组的形式返回指定的 16 位无符号整数值。
data[2] = _adr[0]; // Start address high byte 起始地址的高八位 //apply on small endian, TODO:support big endian
data[3] = _adr[1]; // Start address low byte 起始地址的低八位 data[2] = _adr[1]; // Start address 起始地址的高八位
data[3] = _adr[0]; // Start address起始地址的低八位
byte[] _length = BitConverter.GetBytes((short)length); byte[] _length = BitConverter.GetBytes((short)length);
data[4] = _length[0]; // Number of data to read high byte 寄存器数量的高八位 //apply on small endian, TODO:support big endian
data[5] = _length[1]; // Number of data to read low byte 寄存器数量的低八位 data[4] = _length[1]; // Number of data to read 寄存器数量的高八位
data[5] = _length[0]; // Number of data to read 寄存器数量的低八位
byte[] arr = Utility.CalculateCrc(data, 6); byte[] arr = Utility.CalculateCrc(data, 6);
data[6] = arr[0]; //CRC校验的低八位 data[6] = arr[0]; //CRC校验的低八位
data[7] = arr[1]; //CRC校验的高八位 data[7] = arr[1];//CRC校验的高八位
return data; return data;
} }
#region 写单个线圈或单个离散输出 功能码:0x05 #region 写单个线圈或单个离散输出 功能码:0x05
public byte[] WriteSingleCoils(int startAddress, bool OnOff) public byte[] WriteSingleCoils(int startAddress, bool OnOff)
{ {
byte[] data = new byte[8]; 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 data[1] = Modbus.fctWriteSingleCoil; // Function code
byte[] _adr = BitConverter.GetBytes((short)startAddress); byte[] _adr = BitConverter.GetBytes((short)startAddress);
data[2] = _adr[0]; // Start address data[2] = _adr[0]; // Start address
data[3] = _adr[1]; // Start address data[3] = _adr[1]; // Start address
if (OnOff) if (OnOff) data[4] = 0xFF;
{
data[4] = 0xFF;
data[5] = 0x00;
}
else
{
data[4] = 0x00;
data[5] = 0x00;
}
byte[] arr = Utility.CalculateCrc(data, 6); byte[] arr = Utility.CalculateCrc(data, 6);
data[6] = arr[0]; data[6] = arr[0];
data[7] = arr[1]; data[7] = arr[1];
@ -89,7 +166,7 @@ namespace ModbusDriver
{ {
int len = values.Length; int len = values.Length;
byte[] data = new byte[len + 9]; 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 功能码 data[1] = Modbus.fctWriteMultipleCoils; // Function code 功能码
byte[] _adr = BitConverter.GetBytes((short)startAddress); byte[] _adr = BitConverter.GetBytes((short)startAddress);
data[2] = _adr[0]; // Start address 开始地址高八位 data[2] = _adr[0]; // Start address 开始地址高八位
@ -97,8 +174,6 @@ namespace ModbusDriver
byte[] _length = BitConverter.GetBytes((short)numBits); byte[] _length = BitConverter.GetBytes((short)numBits);
data[4] = _length[0]; // Number of data to read 寄存器数量高八位 data[4] = _length[0]; // Number of data to read 寄存器数量高八位
data[5] = _length[1]; // Number of data to read 寄存器数量低八位 data[5] = _length[1]; // Number of data to read 寄存器数量低八位
data[6] = (byte)len; //字节数量 data[6] = (byte)len; //字节数量
Array.Copy(values, 0, data, 7, len); //在data中加入变更数据 Array.Copy(values, 0, data, 7, len); //在data中加入变更数据
byte[] arr = Utility.CalculateCrc(data, len + 7); byte[] arr = Utility.CalculateCrc(data, len + 7);
@ -112,7 +187,7 @@ namespace ModbusDriver
public byte[] WriteSingleRegister(int startAddress, byte[] values) public byte[] WriteSingleRegister(int startAddress, byte[] values)
{ {
byte[] data = new byte[8]; 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 功能码 data[1] = Modbus.fctWriteSingleRegister; // Function code 功能码
byte[] _adr = BitConverter.GetBytes((short)startAddress); byte[] _adr = BitConverter.GetBytes((short)startAddress);
data[2] = _adr[0]; // Start address 开始地址高八位 data[2] = _adr[0]; // Start address 开始地址高八位
@ -132,7 +207,7 @@ namespace ModbusDriver
int len = values.Length; int len = values.Length;
if (len % 2 > 0) len++; if (len % 2 > 0) len++;
byte[] data = new byte[len + 9]; 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 功能码 data[1] = Modbus.fctWriteMultipleRegister; // Function code 功能码
byte[] _adr = BitConverter.GetBytes((short)startAddress); byte[] _adr = BitConverter.GetBytes((short)startAddress);
data[2] = _adr[0]; // Start address 开始地址高八位 data[2] = _adr[0]; // Start address 开始地址高八位
@ -148,25 +223,28 @@ namespace ModbusDriver
return data; return data;
} }
#endregion #endregion
#region :IPLCDriver #region :IPLCDriver
public int PDU public int PDU
{ {
// get { return 0x100; } //0x100十进制值为256 // get { return 0x100; } //0x100十进制值为256
/* yjz /* yjz
20171125 20171125
RS232 / RS485 modbus协议规定如下 RS232 / RS485 modbus协议规定如下
ADU=+++ ADU 25611252 2 ADU=+++ ADU 25611252 2
PDU=+ PDU=+
PDU应为 253 PDU应为 253 */
*/
get { return 0xFD; } //0xFD 十进制为253 get { return 0xFD; } //0xFD 十进制为253
}
byte _slaveId;//设备ID 单元号 字节号
/// <summary>
/// 设备ID 单元号 字节号
/// </summary>
public byte SlaveId
{
get { return _slaveId; }
set { _slaveId = value; }
} }
public DeviceAddress GetDeviceAddress(string address)//PLC地址一般为5位 如40001,也有可能为40001.1 首位代表地址类型 public DeviceAddress GetDeviceAddress(string address)//PLC地址一般为5位 如40001,也有可能为40001.1 首位代表地址类型
@ -176,27 +254,32 @@ namespace ModbusDriver
return dv; return dv;
switch (address[0]) switch (address[0])
{ {
case '0': case '0'://功能码:01 读线圈或离散量输出状态 00001 - 09999:数字量输出( 线圈)
{ {
dv.Area = Modbus.fctReadCoil;//功能码:01 读线圈或离散量输出状态 00001 - 09999:数字量输出( 线圈) dv.Area = Modbus.fctReadCoil;
int st; int st;
int.TryParse(address, out st); int.TryParse(address, out st);
dv.Bit = (byte)(st % 16); //delete : if st<16, execute st /= 16, st will equal 0, in func ReadBytes() address.Start * 16 has no effect
st /= 16; //dv.Bit = (byte)(st % 16);
//st /= 16;
dv.Start = st; dv.Start = st;
//add : use PLC address(base 1)
dv.Start--;
} }
break; break;
case '1': case '1'://功能码:02 读离散量输入 10001 - 19999:数字量输入(触点)
{ {
dv.Area = Modbus.fctReadDiscreteInputs;//功能码:02 读离散量输入 10001 - 19999:数字量输入(触点) dv.Area = Modbus.fctReadDiscreteInputs;
int st; int st;
int.TryParse(address.Substring(1), out st); int.TryParse(address.Substring(1), out st);
dv.Bit = (byte)(st % 16); //delete : if st<16, execute st /= 16, st will equal 0, in func ReadBytes() address.Start * 16 has no effect
st /= 16; //dv.Bit = (byte)(st % 16);
//st /= 16;
dv.Start = st; dv.Start = st;
//add : use PLC address(base 1)
dv.Start--;
} }
break; break;
case '4': case '4':
{ {
int index = address.IndexOf('.'); int index = address.IndexOf('.');
@ -235,119 +318,45 @@ namespace ModbusDriver
} }
#endregion #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<IGroup> _grps = new List<IGroup>();
public IEnumerable<IGroup> 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 #region :IReaderWriter
public byte[] ReadBytes(DeviceAddress address, ushort size) public byte[] ReadBytes(DeviceAddress address, ushort size)
{ {
int area = address.Area; int area = address.Area;
try try
{ {
byte[] header = area == Modbus.fctReadCoil ? CreateReadHeader(address.Start * 16, (ushort)(16 * size), (byte)area) : //no need to multiply by 16
CreateReadHeader(address.Start, size, (byte)area); //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); _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; int numBytesRead = 0;
while (numBytesRead != size) while (numBytesRead != modbus_rtu_replay_frameBytes_size)
numBytesRead += _serialPort.Read(frameBytes, numBytesRead, size - numBytesRead); numBytesRead += _serialPort.Read(frameBytes, numBytesRead, modbus_rtu_replay_frameBytes_size - numBytesRead);
if (frameBytes[0] == (byte)_id && Utility.CheckSumCRC(frameBytes)) 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 data;
} }
return null; return null;
@ -407,7 +416,6 @@ namespace ModbusDriver
return this.ReadValueEx(address); return this.ReadValueEx(address);
} }
public int WriteBytes(DeviceAddress address, byte[] bit) public int WriteBytes(DeviceAddress address, byte[] bit)
{ {
var data = WriteMultipleRegister(address.Start, bit); var data = WriteMultipleRegister(address.Start, bit);
@ -467,7 +475,7 @@ namespace ModbusDriver
_serialPort.Write(data, 0, data.Length); _serialPort.Write(data, 0, data.Length);
_serialPort.ReadByte(); _serialPort.ReadByte();
var chr = _serialPort.ReadByte(); var chr = _serialPort.ReadByte();
return chr == (byte)_id ? -1 : 0; return chr == _slaveId ? -1 : 0;
} }
public int WriteValue(DeviceAddress address, object value) public int WriteValue(DeviceAddress address, object value)
@ -477,10 +485,18 @@ namespace ModbusDriver
#endregion #endregion
#region : IDisposable
public void Dispose()
{
foreach (IGroup grp in _grps)
{
grp.Dispose();
}
_grps.Clear();
_serialPort.Close();
}
#endregion
} }
public sealed class ModbusRtuGroup : PLCGroup public sealed class ModbusRtuGroup : PLCGroup
@ -512,76 +528,121 @@ namespace ModbusDriver
} }
else else
{ {
int len = rcvBytes.Length / 2; //function code = 01 or 02
fixed (byte* p1 = rcvBytes) if ((area.Start.Area == 1) || (area.Start.Area == 2))
{ {
short* prcv = (short*)p1; fixed (byte* p1_coil = rcvBytes)
int index = area.StartIndex;//index指向_items中的Tag元数据
int count = index + area.Count;
while (index < count)
{ {
DeviceAddress addr = _items[index].Address; byte* prcv_coil = p1_coil;
int iShort = addr.CacheIndex; int index = area.StartIndex;//index指向_items中的Tag元数据
int iShort1 = iShort - k; int count = index + area.Count;
if (addr.VarType == DataType.BOOL) int len = rcvBytes.Length;
for (int rcvBytes_index = 0; rcvBytes_index < len; rcvBytes_index++) //for index whitch byte
{ {
int tmp = prcv[iShort1] ^ cache[iShort]; for (int rcvBytes_bit_index = 0; rcvBytes_bit_index < 8; rcvBytes_bit_index++) //for index whitch bit
DeviceAddress next = addr;
if (tmp != 0)
{ {
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); change_flag = 1;
if (++index < count)
next = _items[index].Address;
else
break;
} }
} int tmp = change_flag ^ cache[index];
else if (tmp != 0)
{
while (addr.Start == next.Start && ++index < count)
{ {
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 else
{ {
int size = addr.DataSize / 2; if (addr.DataSize <= 2)
for (int i = 0; i < size; i++) {
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); if (prcv[iShort1 + i] != cache[iShort + i])
break; {
_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++) k += len;
{
cache[j + k] = prcv[j];
}//将PLC读取的数据写入到CacheReader中
} }
k += len;
} }
} }
return 1; return 1;
} }
} }
public sealed class Modbus public sealed class Modbus
{ {
public const byte fctReadCoil = 1; public const byte fctReadCoil = 1;
public const byte fctReadDiscreteInputs = 2; public const byte fctReadDiscreteInputs = 2;

9
SCADA/Program/ModbusDriver/ModbusTCPDriver.cs

@ -12,8 +12,6 @@ namespace ModbusDriver
[Description("Modbus TCP协议")] [Description("Modbus TCP协议")]
public sealed class ModbusTCPReader : IPLCDriver, IMultiReadWrite //IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable public sealed class ModbusTCPReader : IPLCDriver, IMultiReadWrite //IPLCDriver : IDriver, IReaderWriter IDriver : IDisposable
{ {
#region #region
public int PDU public int PDU
{ {
@ -27,7 +25,6 @@ namespace ModbusDriver
PDU应为 249 PDU应为 249
*/ */
get { return 249; } //0xF9 十进制为249 get { return 249; } //0xF9 十进制为249
} }
public DeviceAddress GetDeviceAddress(string address) public DeviceAddress GetDeviceAddress(string address)
@ -164,14 +161,14 @@ namespace ModbusDriver
get { return _server; } 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; _id = id;
_name = name; _name = name;
_server = server; _server = server;
_ip = ip; _ip = ip;
_timeout = timeOut; _timeout = timeOut;
byte.TryParse(spare1, out _slaveId); if (!string.IsNullOrEmpty(spare1)) byte.TryParse(spare1, out _slaveId);
} }
public bool Connect() public bool Connect()
@ -404,7 +401,7 @@ namespace ModbusDriver
public byte[] ReadBytes(DeviceAddress address, ushort size) public byte[] ReadBytes(DeviceAddress address, ushort size)
{ {
int area = address.DBNumber; 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)); : WriteSyncData(CreateReadHeader(address.Area, address.Start, size, (byte)area));
} }

BIN
SCADA/dll/ModbusDriver.dll

Binary file not shown.
Loading…
Cancel
Save