C#串口通信04 SerialPort相关功能和界面分离

C#串口通信04 SerialPort相关功能和界面分离

Amiron 220 2022-05-01

前言

根据前一个例子,我们现在已经能凑活地进行多个读取操作了。现在的情况是,串口通信相关的代码和界面相关的代码都在一起,特别乱,需要让串口的相关操作与界面分离开来。

环境

IDE: VS 2019
环境: .NET Core 3.1
依赖: System.IO.Ports、thinger.DataConvertLib
测试工具: Modbus Slave

结构

SerialPortDemo-04
└── CRC.cs
└── MarinForm.cs
└── MySerialPort.cs
└── Program.cs

界面

简单的测试界面

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 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;

}

打开串口

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;
	}
}

串口接收事件

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 = (byte)(ByteLib.GetByteFromByteArray(memoryBuffer.ToArray(), 2) + 5);

		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();

			return;
		}

		Debug.WriteLine("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()));
		showLogEvent?.Invoke("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()));

		receiveEvent?.Invoke(memoryBuffer.ToArray());

	}
	catch (Exception)
	{
		throw;
	}
}

串口写操作

public 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 请求解析读取

// 按钮控制列表
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);
}

串口接收事件

private void receive(byte[] buffer)
{
	try
	{
		switch (action)
		{
			case 0:
				MessageBox.Show("读取完成");
				break;

			case 1:
				Invoke(new Action(() =>
				{                            // 响应的数据
					// 01 03 06 00 00 00 02 00 00 80 B5
					// 0x01 -- 从机地址;  0x03 -- 功能码; 0x06 -- 数据长度
					// 0x00 0x00 -- 第一个寄存器的值
					// 0x00 0x02 -- 第二个寄存器的值
					// 0x00 0x00 -- 第三个寄存器的值
					// 0x80 0xB5 -- 校验位
					textBoxAddress1.Text = ShortLib.GetShortFromByteArray(buffer, 3).ToString();
					textBoxAddress2.Text = ShortLib.GetShortFromByteArray(buffer, 5).ToString();
					textBoxAddress3.Text = ShortLib.GetShortFromByteArray(buffer, 7).ToString();
				}));

				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.write(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.write(request);
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
	
}

测试

启动Modbus Slave

使用Modbus Slave连接到虚拟串口中,并开启100个寄存器作为测试使用,然后将寄存器地址2的值改为2。

Modbus Slave

运行程序

地址2的值与Modbus Slave中的值相对应

源码

码云
封面


# c#