前言
根据前面的例子发现,我们在对一条响应数据进行解析的时候,需要根据协议关心每个数据对应类型的长度。在数据长了之后,解析起来很麻烦,在组装写入请求时尤甚。所以需要一个可以直接返回所需类型数据的类。
环境
IDE: VS 2019
环境: .NET Core 3.1
依赖: System.IO.Ports、thinger.DataConvertLib
测试工具: Modbus Slave
结构
SerialPortDemo-05
└── ByteBuffer.cs
└── CRC.cs
└── MarinForm.cs
└── MySerialPort.cs
└── Program.cs
界面
ByteBuffer
定义变量
//固定长度的临时数组
private byte[] TEMP_BYTE_ARRAY = new byte[1024];
//当前数组长度
public int CURRENT_LENGTH = 0;
//当前指针位置
public int CURRENT_POSITION = 0;
//最后返回数组
private byte[] RETURN_ARRAY;
压入数据
public void pushByteArray(byte[] byteArray)
{
byteArray.CopyTo(TEMP_BYTE_ARRAY, CURRENT_LENGTH);
CURRENT_LENGTH += byteArray.Length;
}
public void pushByte(byte value)
{
TEMP_BYTE_ARRAY[CURRENT_LENGTH++] = value;
}
public void pushShort(short value)
{
byte[] temp = ByteArrayLib.GetByteArrayFromShort(value);
TEMP_BYTE_ARRAY[CURRENT_LENGTH++] = temp[0];
TEMP_BYTE_ARRAY[CURRENT_LENGTH++] = temp[1];
}
弹出数据
/// <summary>
/// 弹出一个byte类型数据,当前指针提升1位
/// </summary>
/// <returns></returns>
public byte popByte()
{
byte value = ByteLib.GetByteFromByteArray(TEMP_BYTE_ARRAY, CURRENT_POSITION);
CURRENT_POSITION++;
return value;
}
/// <summary>
/// 弹出一个short类型数据,当前指针提升2位
/// </summary>
/// <returns></returns>
public short popShort()
{
if(CURRENT_POSITION + 1 >= CURRENT_LENGTH )
{
return 0;
}
short value = ShortLib.GetShortFromByteArray(TEMP_BYTE_ARRAY, CURRENT_POSITION);
CURRENT_POSITION += 2;
return value;
}
返回数组
public byte[] toByteArray()
{
RETURN_ARRAY = new byte[CURRENT_LENGTH];
Array.Copy(TEMP_BYTE_ARRAY, 0, RETURN_ARRAY, 0, CURRENT_LENGTH);
return RETURN_ARRAY;
}
MySerialPort
定义变量
private SerialPort serialPort = new SerialPort();
// 接收数据缓存
private MemoryStream memoryBuffer = new MemoryStream();
// 串口打开标识
public bool open = false;
// 接收事件
public event Action<byte[]> receiveEvent;
// 输出日志事件
public event Action<string> showLogEvent;
public int action = -1; // 0 读; 1写;
控件初始化
public void initBy(ComboBox comboxPortName, ComboBox comboBoxBaudRate)
{
string[] port = System.IO.Ports.SerialPort.GetPortNames();
comboxPortName.Items.AddRange(port);
// 如果有数据显示第0个
comboxPortName.SelectedIndex = comboxPortName.Items.Count > 0 ? 0 : -1;
comboBoxBaudRate.Items.Add(1200);
comboBoxBaudRate.Items.Add(4800);
comboBoxBaudRate.Items.Add(9600);
comboBoxBaudRate.Items.Add(14400);
comboBoxBaudRate.Items.Add(19200);
comboBoxBaudRate.Items.Add(38400);
comboBoxBaudRate.SelectedItem = 9600;
}
串口接收事件
private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
serialPort = (SerialPort)sender;
byte[] bytebuffer = new byte[serialPort.BytesToRead];
serialPort.Read(bytebuffer, 0, bytebuffer.Length);
// 将接收到的数据写入缓存
memoryBuffer.Write(bytebuffer);
Debug.WriteLine("-------");
Debug.WriteLine("mem length:" + memoryBuffer.Length);
// 判断缓存长度 至少包括数据长度
if (memoryBuffer.Length <= 2)
{
return;
}
// 整包数据长度
byte length = 0;
switch(action)
{
case 0:
length = (byte)(ByteLib.GetByteFromByteArray(memoryBuffer.ToArray(), 2) + 5); // 读响应长度
break;
case 1:
length = (byte)8; // 写响应长度
break;
}
Debug.WriteLine("length:" + length);
// 判断缓存长度
if (memoryBuffer.Length < length)
{
return;
}
byte[] temp = new byte[length - 2];
byte[] tempCRC = new byte[2];
Array.Copy(memoryBuffer.ToArray(), temp, temp.Length);
Array.Copy(memoryBuffer.ToArray(), temp.Length, tempCRC, 0, 2);
byte[] needCheckTemp = CRC.crc16(temp);
Debug.WriteLine(StringLib.GetHexStringFromByteArray(temp) + " / " + StringLib.GetHexStringFromByteArray(tempCRC) + " ");
Debug.WriteLine(StringLib.GetHexStringFromByteArray(needCheckTemp));
Debug.WriteLine(!(tempCRC[0] == needCheckTemp[length - 2] && tempCRC[1] == needCheckTemp[length - 1]));
Debug.WriteLine(tempCRC[0] + " " + needCheckTemp[length - 2] + " " + tempCRC[1] + " " + needCheckTemp[length - 1]);
// 数据长度满足条件,判断校验位
if (!(tempCRC[0] == needCheckTemp[length - 2] && tempCRC[1] == needCheckTemp[length - 1]))
{
// 缓存清空
memoryBuffer = new MemoryStream();
action = -1;
return;
}
Debug.WriteLine("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()));
showLogEvent?.Invoke("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()));
receiveEvent?.Invoke(memoryBuffer.ToArray());
// 缓存清空
memoryBuffer = new MemoryStream();
action = -1;
}
catch (Exception)
{
throw;
}
}
打开关闭串口
public void openBy(string portName, int baudRate)
{
try
{
serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPortDataReceived);
serialPort.PortName = portName;
serialPort.BaudRate = baudRate;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Parity = Parity.None;
serialPort.Open();
open = true;
}
catch (Exception)
{
throw;
}
}
public void close()
{
try
{
serialPort.Close();
open = false;
}
catch (Exception)
{
throw;
}
}
发送读写请求
public void sendReadRequest(byte[] buffer)
{
try
{
action = 0;
write(buffer);
}
catch (Exception)
{
throw;
}
}
public void sendWriteRequest(byte[] buffer)
{
try
{
action = 1;
write(buffer);
}
catch (Exception)
{
throw;
}
}
串口写操作
private void write(byte[] buffer)
{
try
{
byte[] request = CRC.crc16(buffer);
memoryBuffer = new MemoryStream();
Debug.WriteLine("发送请求:" + StringLib.GetHexStringFromByteArray(request));
showLogEvent?.Invoke("发送请求:" + StringLib.GetHexStringFromByteArray(request));
serialPort.Write(request, 0, request.Length);
}
catch (Exception)
{
throw;
}
}
MainForm
定义变量
private MySerialPort serialPort = new MySerialPort();
private int action = -1; // 0 读取; 1 请求解析读取;2 写入
// 按钮控制列表
private List<Button> buttonList = new List<Button>();
控件初始化
private void MainForm_Load(object sender, EventArgs e)
{
serialPort.initBy(comboxPortName, comboBoxBaudRate);
serialPort.showLogEvent += (message) => Invoke(new Action(() => textBoxLog.AppendText(message + "\r\n")));
serialPort.receiveEvent += receive;
// 按钮加入控制列表
buttonList.Add(buttonRead);
buttonList.Add(buttonRead1);
buttonList.Add(buttonWrite);
}
串口接收事件
private void receive(byte[] buffer)
{
try
{
switch (action)
{
case 0:
MessageBox.Show("读取完成");
break;
case 1:
// 响应的数据
// 01 03 06 00 00 00 02 00 00 80 B5
// 0x01 -- 从机地址; 0x03 -- 功能码; 0x06 -- 数据长度
// 0x00 0x00 -- 第一个寄存器的值
// 0x00 0x02 -- 第二个寄存器的值
// 0x00 0x00 -- 第三个寄存器的值
// 0x80 0xB5 -- 校验位
ByteBuffer byteBuffer = new ByteBuffer(buffer);
byteBuffer.popByte(); // 0x01 -- 从机地址;
byteBuffer.popByte(); // 0x03 -- 功能码;
byteBuffer.popByte(); // 0x06 -- 数据长度
Invoke(new Action(() =>
{
textBoxAddress1.Text = byteBuffer.popShort().ToString(); // 0x00 0x00 -- 第一个寄存器的值
textBoxAddress2.Text = byteBuffer.popShort().ToString(); // 0x00 0x02 -- 第二个寄存器的值
textBoxAddress3.Text = byteBuffer.popShort().ToString(); // 0x00 0x00 -- 第三个寄存器的值
}));
MessageBox.Show("请求解析读取");
break;
case 2:
MessageBox.Show("写入成功");
break;
}
action = -1;
// 按钮恢复
Invoke(new Action(() => buttonList.ForEach(button => button.Enabled = true)));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
打开串口
private void buttonOpen_Click(object sender, EventArgs e)
{
try
{
if (!serialPort.open)
{
serialPort.openBy(comboxPortName.Text, int.Parse(comboBoxBaudRate.Text));
buttonOpen.Text = "关闭串口";
}
else
{
serialPort.close();
buttonOpen.Text = "打开串口";
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
发送读取请求
private void buttonRead_Click(object sender, EventArgs e)
{
try
{
// 按钮失效
buttonList.ForEach(button => button.Enabled = false);
action = 0;
byte[] request = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x64 };
serialPort.sendReadRequest(request);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
发送读取请求1
private void buttonRead1_Click(object sender, EventArgs e)
{
try
{
// 按钮失效
buttonList.ForEach(button => button.Enabled = false);
action = 1;
byte[] request = new byte[] { 0x01, 0x03, 0x00, 0x01, 0x00, 0x03 };
serialPort.sendReadRequest(request);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
发送写入请求
private void buttonWrite_Click(object sender, EventArgs e)
{
try
{
// 按钮失效
buttonList.ForEach(button => button.Enabled = false);
action = 2;
//byte[] request = new byte[] { 0x01, 0x10, 0x00, 0x01, 0x00, 0x03, 0x06 };
// 响应的数据
// 0x01, 0x10, 0x00, 0x01, 0x00, 0x03, 0x06
// 0x01 -- 从机地址; 0x10 -- 功能码;
// 0x00 0x01 -- 起始地址
// 0x00 0x03 -- 寄存器个数
// 0x06 -- 字节计数
ByteBuffer requestBuffer = new ByteBuffer();
requestBuffer.pushByteArray(new byte[] { 0x01, 0x10, 0x00, 0x01, 0x00, 0x03, 0x06 });
requestBuffer.pushShort(short.Parse(textBoxAddressWrite1.Text));
requestBuffer.pushShort(short.Parse(textBoxAddressWrite2.Text));
requestBuffer.pushShort(short.Parse(textBoxAddressWrite3.Text));
serialPort.sendWriteRequest(requestBuffer.toByteArray());
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}