前言
书接上文,我们写了一个简陋的例子,在一般情况下还能凑合的用。但是,在接收较长的数据时会有分包现象出现。
在“我一言,你一语”的业务场景中,我们发送一条请求后,可以按照一定的规则来组装接收到的响应数据。比如一个简陋的规则:根据Modbus RTU报文格式可以知道一条请求报文相对应的响应报文长度。
环境
IDE: VS 2019
环境: .NET Core 3.1
依赖: System.IO.Ports、thinger.DataConvertLib
测试工具: Modbus Slave
结构
SerialPortDemo-02
└── MarinForm.cs
└── Program.cs
界面
定义变量
private SerialPort serialPort = new SerialPort();
// 接收数据缓存
private MemoryStream memoryBuffer = new MemoryStream();
// 串口打开标识
private bool open = false;
// 响应长度
private short length = -1;
控件初始化
private void MainForm_Load(object sender, EventArgs e)
{
// 获取当前可识别的串口
string[] port = System.IO.Ports.SerialPort.GetPortNames();
// 初始化界面
comboBoxPortName.Items.AddRange(port);
// 如果有数据显示第0个
comboBoxPortName.SelectedIndex = comboBoxPortName.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 buttonOpen_Click(object sender, EventArgs e)
{
try
{
if(!open)
{
// 订阅串口接收事件
serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPortDataReceived);
serialPort.PortName = comboBoxPortName.Text;
serialPort.BaudRate = int.Parse(comboBoxBaudRate.Text);
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Parity = Parity.None;
serialPort.Open();
buttonOpen.Text = "关闭串口";
open = true;
}
else
{
serialPort.Close();
buttonOpen.Text = "打开串口";
open = false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
接收事件
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 == length)
{
// 长度重置
length = -1;
// 接收事件与界面并不是同一个线程;所以需要用Invoke()方法同步更新界面
Invoke(new Action(() => textBoxLog.AppendText("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()) + "\r\n")));
}
}
catch (Exception)
{
throw;
}
}
发送读取命令
private void buttonRead_Click(object sender, EventArgs e)
{
try
{
// 0x01 -- 从机地址; 0x03 -- 功能码; 0x00 -- 高位起始地址
// 0x00 -- 低位起始地址; 0x00 -- 高位读取寄存器个数
// 0x09 -- 低位读取寄存器个数
// 0x85 0xCC -- 校验位
byte[] request = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x64, 0x44, 0x21 };
//byte[] request = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x9, 0x85, 0xCC };
// 缓存清空
memoryBuffer = new MemoryStream();
// 定义响应数据达到要求的长度
length = (short)(ShortLib.GetShortFromByteArray(request, 4) * 2 + 5);
Debug.WriteLine("length:" + length);
textBoxLog.AppendText("发送请求:" + StringLib.GetHexStringFromByteArray(request) + "\r\n");
serialPort.Write(request, 0, request.Length);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
测试
启动Modbus Slave
使用Modbus Slave连接到虚拟串口中,并开启100个寄存器作为测试使用。