C#串口通信02 接收数据可能不完整

C#串口通信02 接收数据可能不完整

Amiron 414 2022-04-25

前言

书接上文,我们写了一个简陋的例子,在一般情况下还能凑合的用。但是,在接收较长的数据时会有分包现象出现。
一个请求,两个响应
在“我一言,你一语”的业务场景中,我们发送一条请求后,可以按照一定的规则来组装接收到的响应数据。比如一个简陋的规则:根据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个寄存器作为测试使用。

Modbus Slave

运行程序

一个请求,一个响应

调试信息

分包的数据被组装了起来

源码

码云


# c#