通信协议:三菱
2026-03-15 16:38:47一、三菱PLC协议
1. 三菱PLC设备
- PLC以及相关通信模块:FX、Q、R、L、A
- 通信模块:C24、E71
2. 三菱PLC存储区与访问规则

3. 通信协议
MC协议:
- 公开
- 支持Ascii以及二进制两种数据格式
- 协议体系:串口:1C、2C、3C、4C 以太网:4E、3E、1E
- Modbus、MX-Component
请求流程,WireShark监控通信过程,McProtocol库测试使用:仿真环境验证
- (1)建立TCP连接 Scoket Connect
- (2)执行相关操作 读、写
字节序:
小端 byte[] bytes {低 高 _ _ _ _ } 报文中的字节序小端模式
123 0x00,0x7B bytes[0]=0x7B bytes[1]=0x00
4. 功能操作分类
读
- 成批读出:字、位 :指定一个地址 D0 指定一个数量 返回相关数据 连续请求数据
- 字单位的随机读出 :不连续的地址请求 D0 D100 W10 每个地址读一个长度
- 多块成批读出 :不连续的地址 地址带长度 位按字处理?
写
- 成批写入:字、位
- 随机写入:字、位
- 多块成批写入
5. 成批读出报文

// 发送批量读取请求报文:0401
// 按字从 D05 地址开始请求 3个地址的数据
// D存储区,一个地址就是一个字(两个字节)
// 3*2=6个字节的数据响应返回
// 按位从 M10 地址开始请求5个地址的数据 10 -> 0A 00 00
// M存储区,按位进行地址分配(ON/OFF)
// 3个字节 M10 M11 M12 M13 M14
// 0x11 0x00 0x10
//
// *******按位从X123地址请求********
// 起始地址 123 00 01 23 -> 0x23 0x01 0x00
//
// 按字从 M10 地址开始 请求20个地址的数据 M10 M11 M12 ..... M29
// 一个字存16个状态 20-》需要2个字
// 0x00 0x00 0x00 0x00
// 第一个字节:1000 1000 M17 M16 M15 M14 M13 M12 M11 M10
// 第二个字节:0001 0000 M25 M24 M23 M22 M21 M20 M19 M18
// 0000 1010 M29 M28 M27 M26
// 0000 0000
byte[] bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
// 主指令
0x01,0x04,
// 子指令
0x00,0x00, // 表示按字处理
//0x01,0x00, // 表示按位处理
// 地址
//0x05,0x00,0x00,
//0x09,0x00,0x00,
0x15,0x00,0x00, // 15-16-17-18-19-1A-1B-1C-1D-1E
// 区域
//0xA8, // D
//0x90, // M
0x9D, // Y
// 数量
0x02,0x00
};
socket.Send(bytes);
6. 成批写入报文

// 发送批量写入请求报文:1401
// 按字写入D30开始的三个地址,分别写入123、124、125
// 在写入数量后跟上3个字,6个字节的数据 0x7b,0x00 0x7C,0x00 0x7D,0x00
// 按位写入M00开始写入10个状态:分别写入:01,00,01,01,01,00,00,01,00,01
// 在写入数量后跟上5个字节 0x10, 0x11,0x10,0x01,0x01
// 按字写入M00开始写入10个状态:分别写入:01,00,01,01,01,00,00,01,00,01
// 在写入数量后跟上2个字节 1001 1101 0000 0010
// 0x9D 0x02
byte[] bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x12,0x00,
0x10,0x00,
0x01,0x14,
0x00,0x00,
0x1E,0x00,0x00,
0xA8,
0x03,0x00,// 写入数量
0x7B,0x00,
0x7C,0x00,
0x7D,0x00
};
// 写入M5开始的三个地址数据,01、01、01
bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x0E,0x00,
0x10,0x00,
0x01,0x14,
0x01,0x00,
0x05,0x00,0x00,
0x90,
0x03,0x00,
0x11,0x10
};
//
bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x0E,0x00,
0x10,0x00,
0x01,0x14,
0x00,0x00,
0x30,0x00,0x00,
0x90,
0x01,0x00,// 1个字 16个位状态 Y30----Y3F
0x35,0x89
};
socket.Send(bytes);
7. 随机读出报文

// 发送随机读取请求报文:0403
// 1、没有位操作
// 2、主要涉及字与双字类型的请求
// 3、操作响应结果只有一个当前地址的数据,不涉及相关地址开始的读取长度问题,只有一个
byte[] bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x1C,0x00, // 长度需要调整
0x10,0x00,
0x03,0x04,
0x00,0x00,
0x03,// 后面跟着的前三个地址,按字进行数据返回
0x02,// 后面跟着的后两个地址,按双字进行数据返回
// D100 --响应数据返回2个字节 D100
0x64,0x00,0x00,
0xA8,
// M00 --响应数据返回2个字节, M0-M15
0x00,0x00,0x00,
0x90,
// X120 --响应数据返回2个字节 X120-X12F 或者 X120 - X127、X130-X137
0x20,0x01,0x00,
0x9C,
// D125 --响应数据返回4个字节 D125\D126
0x7D,0x00,0x00,
0xA8,
// M16 --响应数据返回4个字节 M10-M41
0x10,0x00,0x00,
0x90,
};
socket.Send(bytes);
8. 随机写入报文

// 发送随机写入请求报文:1402
// 按位将XA0地址设置为ON(0x01) XA1地址设置为OFF(0x00)
// 按字分两种情况:字-将D0、M100-M115开始写入相就的字数据
// 双字-将D1000-D1001 Y120-Y127/Y130-Y137/Y140-Y147/Y150-Y157
// Y120-Y12F/Y130-Y13F
byte[] bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x11,0x00,// 长度需要调整
0x10,0x00,
0x02,0x14,
0x01,0x00,//按位处理
0x02,// 处理的位点数
0xA0,0x00,0x00,
0x9C,
0x01,// 需要设置的状态结果 ON
0xA1,0x00,0x00,
0x9C,
0x01 // 需要设置的状态结果 ON
};
//socket.Send(bytes);
//return;
/////****************************
bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x24,0x00,// 长度需要调整
0x10,0x00,
0x02,0x14,
0x00,0x00,//按字处理
0x02,// 处理的字点数
0x02,// 处理的双字点数
0x00,0x00,0x00,
0xA8,
0x6F,0x00,// 向D0写入111
0x50,0x00,0x00,
0x90,
0x45,0x82, // 向M100开始的位地址写入16个状态。
// 这里需要将这一个字,拆分成16个位进行设置,
// 从第一个字节的最低位开始,进行第一个状态的提交
// 第一个字节:0100 0101 -- M107 M106 M105 M104 M103 M102 M101 M100
// 第二个字节:1000 0010 -- M115 M114 M113 M112 M111 M110 M109 M108
// 0x03,0xE8
0xE8,0x03,0x00,
0xA8,
0x7B,0x00,0x7C,0x00, // 向D1000-D1001地址写入2个字的数据
// 32位的数据类型 float 4.5:0x40 0x90 0x00 0x00
0x28,0x01,0x00,
0x9D,
0x01,0x00,0x00,0x80 // 向Y120开始的32个位地址写入状态数据
// 第一个字节:0000 0001 -- Y127 Y126 Y125 Y124 Y123 Y122 Y121 Y120
// 第二个字节:0000 0000
// 第三个字节:0000 0000
// 第四个字节:1000 0000
};
socket.Send(bytes);
9. 多块成批读出报文

// 一个报文里同时处理字、位
byte[] bytes = new byte[] {
// 头部信息
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x26,0x00,// 长度需要调整
0x10,0x00,
0x06,0x04,
0x00,0x00,// 子指令
/// 处理字的地址数量
0x02,
/// 处理位的地址数量
0x03,
// D0 - 4
0x00,0x00,0x00,
0xA8,
0x04,0x00, // 请求4个点 D0、D1、D2、D3
// D100 - 2
0x64,0x00,0x00,
0xA8,
0x02,0x00, // 请求2个点 D100-D101
// X10 - 2
0x10,0x00,0x00,
0x9C,
0x02,0x00, // 请求2个点 32个位 X10-X1F X20-X2F
// M16 - 2
0x10,0x00,0x00,
0x90,
0x02,0x00, // 请求2个点 32个位 M16-M48
// M96 - 2
0x50,0x00,0x00,
0x90,
0x02,0x00, // 请求2个点 32个位 M96-M128
};
socket.Send(bytes);
10. 多块成批写入报文


// 一个报文里同时处理字、位
byte[] bytes = new byte[] {
// 头部信息
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x40,0x00,// 长度需要调整
0x10,0x00,
0x06,0x14,
0x00,0x00,// 子指令
/// 处理字的地址数量
0x02,
/// 处理位的地址数量
0x03,
// D0 - 4
0x00,0x00,0x00,
0xA8,
0x04,0x00, // 请求4个点 D0、D1、D2、D3
0x01,0x00,0x02,0x00,0x03,0x00,0x04,0x00,// 一共写入8个字节的数据
// D100 - 2
0x64,0x00,0x00,
0xA8,
0x02,0x00, // 请求2个点 D100-D101
0x01,0x00,0x02,0x00, // 一共写入4个字节的数据
// X10 - 3
0x10,0x00,0x00,
0x9C,
0x03,0x00, // 请求2个点 32个位 X10-X1F X20-X2F
0x01,0x00,0x02,0x00,0x03,0x00,// 一共写入6个字节的数据,48个状态
// M10 - 2
0x10,0x00,0x00,
0x90,
0x02,0x00, // 请求2个点 32个位 M10-M41
0x01,0x00,0x02,0x00,// 一共写入4个字节的数据,32个状态
// M100 - 2
0x50,0x00,0x00,
0x90,
0x02,0x00, // 请求2个点 32个位 M100-M131
0x01,0x00,0x02,0x00,// 一共写入4个字节的数据,32个状态
};
socket.Send(bytes);
11. PLC启停报文

// 1001
static void Mc3E_Run()
{
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("192.168.1.128", 6000);
byte[] bytes = new byte[] {
// 头部
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x0A,0x00,// 长度需要调整
0x10,0x00,
0x01,0x10,
0x00,0x00,
0x03,0x00,// 是否强制执行:0x01不强制执行 0x03强制执行
0x00,// 清除模式 :00 不清空;01 清空锁存以外;02 清空全部
0x00// 默认值
};
socket.Send(bytes);
}
// 1002
static void Mc3E_Stop()
{
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("192.168.1.128", 6000);
byte[] bytes = new byte[] {
// 头部
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x08,0x00,// 长度需要调整
0x10,0x00,
0x02,0x10,
0x00,0x00,
0x01,0x00,// 是否强制执行:0x01不强制执行 0x03强制执行
};
socket.Send(bytes);
}
// 0101
static void Mc3E_PlcInfo()
{
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect("192.168.1.128", 6000);
byte[] bytes = new byte[] {
// 头部
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x06,0x00,// 长度需要调整
0x10,0x00,
0x01,0x01,
0x00,0x00
};
socket.Send(bytes);
// 响应:型号名称
}
附录
- 附录一,命令代码

- 附录二,结束代码-异常判断
结束代码为5BH/“5B”的情况下,结束代码之后包含异常代码(10H~21H)的数据和00H/“00”

二、三菱PLC通信库封装
1. 功能清单
- TCP连接
- 读取封装
- 地址解析
- 地址分组
- 写入封装
- 效率测试
- 自动重连(心跳)
- 异步读写(优化)
2. 定义枚举与数据字典
public enum Areas
{
X = 0x9C,
Y = 0x9D,
M = 0x90,
L = 0x92,
S = 0x98,
B = 0xA0,
F = 0x93,
TS = 0xC1,
TC = 0xC0,
TN = 0xC2,
CS = 0xC4,
CC = 0xC3,
CN = 0xC5,
D = 0xA8,
W = 0xB4,
R = 0xAF,
ZR = 0xB0
}
public enum RequestType
{
BIT = 0x01,
WORD = 0x00
}
public class DataParameter
{
public Areas Area { get; set; }
public string Address { get; set; } = null!;
public int Count { get; set; }
public List<byte>? Datas { get; set; }
}
internal class Status
{
public static Dictionary<string, string> Errors = new Dictionary<string, string>()
{
{"0055","将运行中写入设置为不允许时,通过对象设备向CPU模块发出了运行中数据写入请求" },
{"C050","在“通信数据代码设置”中,设置了ASCII代码通信时,接收了不能转换为二进制代码的ASCII代码" },
{"C051","写入或读取点数超出了允许范围" },
{"C052","写入或读取点数超出了允许范围" },
{"C053","写入或读取点数超出了允许范围" },
{"C054","写入或读取点数超出了允许范围" },
{"C056","写入及读取请求超出了最大地址" },
{"C058","ASCII- 二进制转换后的请求数据长与字符部分(文本的一部分)的数据数不相符" },
{"C059","指令、子指令的指定有误,或者在CPU模块中禁止使用的指令、子指令" },
{"C05B","CPU 模块不能对指定软元件进行写入及读取" },
{"C05C","请求内容中存在有错误。(以位为单位对字软元件进行了写入及读取等)" },
{"C05D","未进行监视登录" },
{"C05F","是不能对对象CPU模块执行的请求" },
{"C060","请求内容中存在有错误。(对位软元件进行的数据指定中存在有错误等)" },
{"C061","请求数据长与字符部分(文本的一部分)的数据数不相符" },
{"C06F","“通信数据代码设置”为二进制时,接收了ASCII的请求报文。此外,设置为ASCII时,接收了二进制的请求报文" },
{"C070","不能对对象站进行软元件存储器的扩展指定" },
{"C200","远程口令有错误" },
{"C201","通信所使用的端口处于远程口令的锁定状态。或者“通信数据代码设置”为ASCII代码时,由于处于远程口令的锁定状态,因此无法将子指令以后转换为二进制代码" },
{"C204","与发出了远程口令解锁处理请求的对象设备不相同" },
};
}
3. 封装基础方法
我们已经有好几个协议封装的经验了,这里我们提前把一些基础方法封装好
private byte[] SendAndReceive(byte[] bytes)
{
socket.Send(bytes);
byte[] resp = new byte[1024];
socket.Receive(resp);
return resp;
}
private byte[] GetAddress(string address, Areas area)
{
int addr = 0;
int.TryParse(address, out addr);
List<byte> addr_bytes = new List<byte>();
if (new Areas[] { Areas.X, Areas.Y }.Contains(area))
{
string addr_str = address.PadLeft(6, '0'); //填充0
try
{
byte[] _bytes = Convert.FromHexString(addr_str);
Array.Reverse(_bytes);
addr_bytes.AddRange(_bytes);
}
catch { throw new Exception("地址格式错误"); }
}
else
{
addr_bytes.Add((byte)(addr % 256));
addr_bytes.Add((byte)(addr / 256 % 256));
addr_bytes.Add((byte)(addr / 256 / 256 % 256));
}
return addr_bytes.ToArray();
}
private void CheckResponse(byte[] resp)
{
// 检查状态码
string state = resp[10].ToString("X2") + resp[9].ToString("X2");
if (state != "0000")
{
if (Status.Errors.ContainsKey(state))
throw new Exception(Status.Errors[state]);
else
throw new Exception("未知错误");
}
}
- 公共方法封装
public (Areas, string) GetAddress(string addr)
{
string prefix = addr.Substring(0, 2);
if (Enum.TryParse<Areas>(prefix, out Areas area))
{
string start = addr.Substring(2);
return (area, start);
}
prefix = addr.Substring(0, 1);
if (Enum.TryParse<Areas>(prefix, out area))
{
string start = addr.Substring(1);
return (area, start);
}
throw new Exception("地址有误,请确认");
}
// 数据转换
// 从字节到数据的转换
public List<T> GetDatas<T>(byte[] bytes)
{
List<T> datas = new List<T>();
if (typeof(T) == typeof(bool))
{
foreach (byte b in bytes)
{
dynamic d = (b == 0x01);
datas.Add(d);
}
}
else if (typeof(T) == typeof(string))
{
dynamic d = Encoding.UTF8.GetString(bytes);
datas.Add(d);
}
else
{
int size = Marshal.SizeOf<T>();
Type tBitConverter = typeof(BitConverter);
MethodInfo[] mis = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static);
if (mis.Count() <= 0) return datas;
MethodInfo mi = mis.FirstOrDefault(m => m.ReturnType == typeof(T) && m.GetParameters().Count() == 2)!;
for (int i = 0; i < bytes.Length; i += size)
{
byte[] data_bytes = bytes.ToList().GetRange(i, size).ToArray();
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data_bytes);
}
dynamic v = mi.Invoke(tBitConverter, new object[] { data_bytes, 0 })!;
datas.Add(v);
}
}
return datas;
}
// 从数据到字节的转换
public byte[] GetBytes<T>(params T[] values)
{
List<byte> bytes = new List<byte>();
if (typeof(T) == typeof(bool))
{
foreach (var v in values)
{
bytes.Add((byte)(bool.Parse(v.ToString()!) ? 0x01 : 0x00));
}
}
else if (typeof(T) == typeof(string))
{
byte[] str_bytes = Encoding.UTF8.GetBytes(values[0].ToString()!);
bytes.AddRange(str_bytes);
}
else
{
foreach (var v in values)
{
dynamic d = v;
byte[] v_bytes = BitConverter.GetBytes(d);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(v_bytes);
}
bytes.AddRange(v_bytes);
}
}
return bytes.ToArray();
}
4. 批量读写
public byte[] Read(Areas area, string addr, ushort count, RequestType type = RequestType.WORD)
{
byte[] addr_bytes = this.GetAddress(addr, area);
byte[] bytes = new byte[] {
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
// 主指令
0x01,0x04,
// 子指令
(byte)type,0x00,
// 地址
addr_bytes[0],
addr_bytes[1],
addr_bytes[2],
// 区域
(byte)area,
// 数量
(byte)(count%256),
(byte)(count/256%256)
};
byte[] resp = this.SendAndReceive(bytes);
// 检查状态码
this.CheckResponse(resp);
// 获取数据字节,进行返回
// 字 2字节 count*2个字节 位软元件 count*16位信息
// 位 1个2个状态 count/2
if (RequestType.WORD == type)
{
// 按字进行位软元件请求
byte[] values_bytes = resp.Skip(11).Take(count * 2).ToArray();
List<byte> data_values = new List<byte>();
// 关于这个列表可以将所有位软元件编号都放进来
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(area))
{
foreach (byte value in values_bytes)
{
for (int i = 0; i < 8; i++)
{
data_values.Add((byte)((value & (1 << i)) > 0 ? 0x01 : 0x00));
}
}
}
else// 按字进行字软元件请求
{
for (int i = 0; i < count * 2; i += 2)
{
byte[] temp = values_bytes.Skip(i).Take(2).ToArray();
Array.Reverse(temp);
data_values.AddRange(temp);
// 或者
//data_values.Add(values_bytes[i + 1]);
//data_values.Add(values_bytes[i]);
}
}
return data_values.ToArray();
}
else if (RequestType.BIT == type)
{
// 0x11 0x01
// 0x01 0x01 0x00 0x01
byte[] state_bytes = resp.Skip(11).Take(count / 2 + (count % 2)).ToArray();
byte[] state_value = new byte[count];
int index = 0;
for (int i = 0; i < count; i++)
{
if (i % 2 == 0)
state_value[i] = (byte)((state_bytes[index] & (1 << 4)) > 0 ? 0x01 : 0x00);
else
{
state_value[i] = (byte)((state_bytes[index] & 1) > 0 ? 0x01 : 0x00);
index++;
}
}
return state_value;
}
else
return null;
}
// X0 XA0 X1A M0 D0
public byte[] Read(string address, ushort count, RequestType type = RequestType.WORD)
{
(Areas area, string start) = this.GetAddress(address);
return this.Read(area, start, count, type);
}
// datas:{0x01,0x00,0x00,0x01,0x01}
// datas字节按大端处理
public void Write(byte[] datas, Areas area, string addr, RequestType type = RequestType.WORD)
{
int count = 0;
// 数据处理
List<byte> data_bytes = new List<byte>();
if (type == RequestType.WORD)
{
// 这里只是部分们软元件,其他自行添加
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(area))
{
count = (int)Math.Ceiling(datas.Length * 1.0 / 16);
int byte_count = (int)(count * 2);
byte[] bytes = new byte[byte_count];
int index = -1;
for (int i = 0; i < datas.Length; i++)
{
if (i % 8 == 0)
index++;
byte current = 0x00;
if (datas[i] == 0x01)
current = (byte)(current | (1 << (i % 8)));
bytes[index] |= current;
}
data_bytes = new List<byte>(bytes);
}
else
{
count = datas.Length / 2;
for (int i = 0; i < datas.Length; i += 2)
{
data_bytes.Add(datas[i + 1]);
data_bytes.Add(datas[i]);
}
}
}
else if (type == RequestType.BIT)
{
count = datas.Length;
byte[] temp = new byte[datas.Length / 2 + (datas.Length % 2)];
int index = 0;
for (int i = 0; i < datas.Length; i++)
{
byte d = 0x00;
if (i % 2 == 0 && datas[i] == 0x01)
{
d = (byte)(d | (1 << 4));
temp[index] |= d;
}
if (i % 2 != 0 && datas[i] == 0x01)
{
d = (byte)(d | 1);
temp[index] |= d;
index++;
}
}
data_bytes = new List<byte>(temp);
}
// 地址处理
byte[] addr_bytes = this.GetAddress(addr, area);
List<byte> req_bytes = new List<byte>{
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x12,0x00, // 长度
0x10,0x00,
0x01,0x14,
(byte)type,0x00,
addr_bytes[0],
addr_bytes[1],
addr_bytes[2],
(byte)area,
(byte)(count%256),
(byte)(count/256%256),// 写入数量
};
req_bytes.AddRange(data_bytes);
int len = 12 + data_bytes.Count;
req_bytes[7] = (byte)(len % 256);
req_bytes[8] = (byte)(len / 256 % 256);
byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
// 检查状态码
this.CheckResponse(resp_bytes);
}
public void Write(byte[] datas, string address, RequestType type = RequestType.WORD)
{
(Areas area, string start) = this.GetAddress(address);
this.Write(datas, area, start, type);
}
5. 随机读写
// 随机读写 按字、双字读取
public void RandomRead(List<DataParameter> w_address, List<DataParameter> dw_address)
{
List<byte> bytes = new List<byte>{
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
0x03,0x04,
0x00,0x00,
(byte)w_address.Count,
(byte)dw_address.Count,
};
foreach (var address in w_address)
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
}
foreach (var address in dw_address)
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
}
// 计算请求长度 字节数
int len = 8 + (w_address.Count + dw_address.Count) * 4;
bytes[7] = (byte)(len % 256);
bytes[8] = (byte)(len / 256 % 256);
byte[] resp = this.SendAndReceive(bytes.ToArray());
// 检查响应状态
this.CheckResponse(resp);
// 数据字节整理
int index = 11;
foreach (var address in w_address)
{
if (address.Datas == null)
address.Datas = new List<byte>();
byte[] values_bytes = resp.Skip(index).Take(2).ToArray();
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
foreach (byte value in values_bytes)
{
for (int i = 0; i < 8; i++)
{
address.Datas.Add((byte)((value & (1 << i)) > 0 ? 0x01 : 0x00));
}
}
}
else
{
Array.Reverse(values_bytes);
address.Datas.AddRange(values_bytes);
}
index += 2;
}
foreach (var address in dw_address)
{
if (address.Datas == null)
address.Datas = new List<byte>();
byte[] values_bytes = resp.Skip(index).Take(4).ToArray();
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
foreach (byte value in values_bytes)
{
for (int i = 0; i < 8; i++)
{
address.Datas.Add((byte)((value & (1 << i)) > 0 ? 0x01 : 0x00));
}
}
}
else
{
Array.Reverse(values_bytes);
address.Datas.AddRange(values_bytes);
}
index += 4;
}
}
// 按位进行写入
public void RandomWriteBit(List<DataParameter> address)
{
List<byte> bytes = new List<byte>{
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
0x02,0x14,
0x01,0x00,// 按位操作
(byte)address.Count,
};
foreach (var item in address)
{
byte[] addr_bytes = this.GetAddress(item.Address, item.Area);
bytes.AddRange(addr_bytes);
bytes.Add((byte)item.Area);
// 数据部分
if (item.Datas == null || item.Datas.Count == 0)
throw new Exception("需要写入的数据有异常,无法写入");
else
{
if (item.Datas[0] > 0)
bytes.Add(0x01);
else
bytes.Add(0x00);
}
}
int len = 7 + address.Count * 5;
bytes[7] = (byte)(len % 256);
bytes[8] = (byte)(len / 256 % 256);
byte[] resp = this.SendAndReceive(bytes.ToArray());
this.CheckResponse(resp);
}
// 按字、双字进行写入
public void RandomWrite(List<DataParameter> w_address, List<DataParameter> dw_address)
{
List<byte> bytes = new List<byte>{
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
0x02,0x14,
0x00,0x00,// 按字操作
(byte)w_address.Count,
(byte)dw_address.Count,
};
foreach (var address in w_address)
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
if (address.Datas == null || address.Datas.Count != 16)
throw new Exception("需要写入的数据有异常,无法写入");
byte[] data_bytes = new byte[2];
int index = 0;
// 将16个状态字节,转换成两个字节,一个字节8个位进行汇总
for (int i = 0; i < 16; i++)
{
if (i > 0 && i % 8 == 0) index++;
byte bit = (byte)(address.Datas[i] == 0x01 ? 0x01 : 0x00);
data_bytes[index] |= (byte)(bit << (i % 8));
}
bytes.AddRange(data_bytes);// 将
}
else
{
if (address.Datas == null || address.Datas.Count != 2)
throw new Exception("需要写入的数据有异常,无法写入");
bytes.AddRange(address.Datas);
}
}
foreach (var address in dw_address)
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
if (address.Datas == null || address.Datas.Count != 32)
throw new Exception("需要写入的数据有异常,无法写入");
byte[] data_bytes = new byte[4];
int index = 0;
// 将16个状态字节,转换成两个字节,一个字节8个位进行汇总
for (int i = 0; i < 32; i++)
{
if (i > 0 && i % 8 == 0) index++;
byte bit = (byte)(address.Datas[i] == 0x01 ? 0x01 : 0x00);
data_bytes[index] |= (byte)(bit << (i % 8));
}
bytes.AddRange(data_bytes);// 将
}
else
{
if (address.Datas == null || address.Datas.Count != 4)
throw new Exception("需要写入的数据有异常,无法写入");
bytes.AddRange(address.Datas);
}
}
int len = 8 + w_address.Count * 6 + dw_address.Count * 8;
bytes[7] = (byte)(len % 256);
bytes[8] = (byte)(len / 256 % 256);
byte[] resp = this.SendAndReceive(bytes.ToArray());
this.CheckResponse(resp);
}
6. 多块读写
public void MultiBlockRead(List<DataParameter> w_address, List<DataParameter> b_address)
{
List<byte> bytes = new List<byte>{
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
// 剩余字节长度
0x0C,0x00, // 注意小端字节序
0x10,0x00, // 响应时间 *250ms
0x06,0x04,
0x00,0x00,
(byte)w_address.Count,
(byte)b_address.Count,
};
foreach (var address in w_address)
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
bytes.Add((byte)(address.Count % 256));
bytes.Add((byte)(address.Count / 256 % 256));
}
foreach (var address in b_address)
{
if (!new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
throw new Exception("位地址有误");
}
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
bytes.Add((byte)(address.Count % 256));// 数量 表示多少字
bytes.Add((byte)(address.Count / 256 % 256));
}
int len = 8 + (w_address.Count + b_address.Count) * 6;
bytes[7] = (byte)(len % 256);
bytes[8] = (byte)(len / 256 % 256);
byte[] resp = this.SendAndReceive(bytes.ToArray());
// 检查响应状态
this.CheckResponse(resp);
// 解析数据
int index = 11;
foreach (var address in w_address)
{
if (address.Datas == null)
address.Datas = new List<byte>();
int byte_count = address.Count * 2;
byte[] values_bytes = resp.Skip(index).Take(byte_count).ToArray();
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
foreach (byte value in values_bytes)
{
for (int i = 0; i < 8; i++)
{
address.Datas.Add((byte)((value & (1 << i)) > 0 ? 0x01 : 0x00));
}
}
}
else
{
for (int i = 0; i < values_bytes.Length; i += 2)
{
address.Datas.Add(values_bytes[i + 1]);
address.Datas.Add(values_bytes[i]);
}
}
index += byte_count;
}
foreach (var address in b_address)
{
if (address.Datas == null)
address.Datas = new List<byte>();
int byte_count = address.Count * 2;
byte[] values_bytes = resp.Skip(index).Take(byte_count).ToArray();
foreach (byte value in values_bytes)
{
for (int i = 0; i < 8; i++)
{
address.Datas.Add((byte)((value & (1 << i)) > 0 ? 0x01 : 0x00));
}
}
index += byte_count;
}
}
public void MultiBlockWrite(List<DataParameter> w_address, List<DataParameter> b_address)
{
List<byte> bytes = new List<byte> {
// 头部信息
0x50, 0x00,
0x00,
0xFF,
0xFF, 0x03,
0x00,
0x40, 0x00,// 长度需要调整
0x10, 0x00,
0x06, 0x14,
0x00, 0x00,// 子指令
(byte)w_address.Count,
(byte)b_address.Count,
};
foreach (var address in w_address.Union(b_address))
{
byte[] addr_bytes = this.GetAddress(address.Address, address.Area);
// 拼接请求的地址
bytes.AddRange(addr_bytes);
bytes.Add((byte)address.Area);
if (address.Datas!.Count % 2 > 0)
throw new Exception("数据准备有误");
if (new Areas[] { Areas.X, Areas.Y, Areas.M }.Contains(address.Area))
{
if (address.Datas == null || address.Datas.Count % 16 != 0)
throw new Exception("需要写入的数据有异常,无法写入");
byte[] data_bytes = new byte[address.Datas.Count / 8];
int index = 0;
// 将16个状态字节,转换成两个字节,一个字节8个位进行汇总
for (int i = 0; i < address.Datas.Count; i++)
{
if (i > 0 && i % 8 == 0) index++;
byte bit = (byte)(address.Datas[i] == 0x01 ? 0x01 : 0x00);
data_bytes[index] |= (byte)(bit << (i % 8));
}
bytes.AddRange(data_bytes);// 将
}
else
{
if (address.Datas == null || address.Datas.Count != 4)
throw new Exception("需要写入的数据有异常,无法写入");
bytes.AddRange(address.Datas);
}
}
int len = w_address.Union(b_address).Sum(a => a.Datas!.Count / 2 + 5) + 8;
bytes[7] = (byte)(len % 256);
bytes[8] = (byte)(len / 256 % 256);
byte[] resp = this.SendAndReceive(bytes.ToArray());
// 检查响应状态
this.CheckResponse(resp);
}
多块的代码未测试
7. PLC启停
public enum ExecuteType
{
Normal = 0x01,
Force = 0x03
}
public enum CleanMode
{
Normal = 0x00,
WithoutLock = 0x01,
All = 0x02
}
// PLC 启停
public void PlcRun(ExecuteType et = ExecuteType.Normal, CleanMode cm = CleanMode.Normal)
{
byte[] bytes = new byte[] {
// 头部
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x0A,0x00,// 长度需要调整
0x10,0x00,
0x01,0x10,
0x00,0x00,
(byte)et,0x00,// 是否强制执行:0x01不强制执行 0x03强制执行
(byte)cm,// 清除模式 :00 不清空;01 清空锁存以外;02 清空全部
0x00// 默认值
};
byte[] resp = this.SendAndReceive(bytes);
this.CheckResponse(resp);
}
public void PlcStop(ExecuteType et = ExecuteType.Normal)
{
byte[] bytes = new byte[] {
// 头部
0x50,0x00,
0x00,
0xFF,
0xFF,0x03,
0x00,
0x08,0x00,// 长度需要调整
0x10,0x00,
0x02,0x10,
0x00,0x00,
(byte)et,0x00,// 是否强制执行:0x01不强制执行 0x03强制执行
};
byte[] resp = this.SendAndReceive(bytes);
this.CheckResponse(resp);
}
8. 测试
static void CustomLibTest()
{
Mc3E mc3E = new Mc3E("192.168.174.128", 6000);
mc3E.Open();
// 数据转换测试
List<ushort> values = new List<ushort>() {
111,222,333,444
};
byte[] v_bytes = mc3E.GetBytes<ushort>(values.ToArray());
mc3E.Write(v_bytes, Areas.D, "100");
//byte[] v_bytes = mc3E.Read(Areas.D, "100", 4);
//byte[] v_bytes = mc3E.Read("D100", 4);
//byte[] v_bytes = mc3E.Read("X1A", 16, RequestType.BIT);
var vs = mc3E.GetDatas<bool>(v_bytes);
// 批量读取测试--------------------------------------------
byte[] result = mc3E.Read(Base.Areas.D, "100", 10);
// 007B
result = mc3E.Read(Base.Areas.X, "100", 10, Base.RequestType.BIT);
result = mc3E.Read(Base.Areas.X, "1A0", 3, Base.RequestType.BIT);
// 10个状态
Console.WriteLine(string.Join(" ", result.Select(b => b.ToString("X2"))));
// 批量写入测试----------------------------------------------
mc3E.Write(new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 },
Base.Areas.X, "8", Base.RequestType.BIT);
mc3E.Write(new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 },
Base.Areas.D, "8", Base.RequestType.WORD);
mc3E.Write(new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 },
Base.Areas.Y, "abde", Base.RequestType.WORD);
// 随机读取测试----------------------------------------------
List<DataParameter> w = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="100"},
new DataParameter{ Area=Areas.X,Address="10"},// 地址按照16的倍数进行指定
};
List<DataParameter> dw = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="120"},
new DataParameter{ Area=Areas.M,Address="16"},// 地址按照16的倍数进行指定
};
mc3E.RandomRead(w, dw);
// 随机位写入测试----------------------------------------------
List<DataParameter> bit = new List<DataParameter>
{
new DataParameter{ Area=Areas.X,Address="1A",Datas=new List<byte>{ 0x01} },
new DataParameter{ Area=Areas.M,Address="10",Datas=new List<byte>{ 0x01} },// 地址按照16的倍数进行指定
};
mc3E.RandomWriteBit(bit);
// 随机字写入测试----------------------------------------------
List<DataParameter> word = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="10",Datas=new List<byte>{ 0x01,0x02} },
new DataParameter{ Area=Areas.M,Address="32",Datas=new List<byte>{ 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 } },// 地址按照16的倍数进行指定
};
List<DataParameter> dword = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="20",Datas=new List<byte>{ 0x01,0x02,0x03,0x04} },
new DataParameter{ Area=Areas.X,Address="20",Datas=new List<byte>{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 } },// 地址按照16的倍数进行指定
};
mc3E.RandomWrite(word, dword);
// 多块成批读取测试
List<DataParameter> wordBlock = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="10",Count=3},
new DataParameter{ Area=Areas.M,Address="0",Count=2},// 地址按照16的倍数进行指定
};
List<DataParameter> bitBlock = new List<DataParameter>
{
new DataParameter{ Area=Areas.Y,Address="0",Count=2 },
new DataParameter{ Area=Areas.X,Address="20",Count=1},// 地址按照16的倍数进行指定
};
mc3E.MultiBlockRead(wordBlock, bitBlock);
wordBlock = new List<DataParameter>
{
new DataParameter{ Area=Areas.D,Address="10",Datas=new List<byte>{ 0x01,0x02,0x03,0x04} },
new DataParameter{ Area=Areas.M,Address="0",Datas=new List<byte>{ 0xFF,0xFF} },// 地址按照16的倍数进行指定
};
bitBlock = new List<DataParameter>
{
new DataParameter{ Area=Areas.Y,Address="0",Datas=new List<byte>{ 0xFF,0xFF} },
new DataParameter{ Area=Areas.X,Address="20",Datas=new List<byte>{ 0xFF,0xFF} },// 地址按照16的倍数进行指定
};
mc3E.MultiBlockWrite(wordBlock, bitBlock);
mc3E.PlcRun(cm: CleanMode.All);
mc3E.PlcStop();
}
