C#串口通信06  以同步编程方式读取数据

C#串口通信06 以同步编程方式读取数据

Amiron 515 2022-05-03

前言

前面的例子都是以响应式的方式编写业务代码的,与我们平时所接触的同步编程方式有所不同。在传统的同步编程方式中,调用一个方法,会等待其响应返回。个人感觉响应式编程相对于同步编程方式,在流程控制方面的有时可能不太方便。所以,想用同步编程方式来读取数据。大体思路是点击按钮后,在视图线程中阻塞,直到等待串口接收到完整数据后再继续下面的操作;用async/await解决视图线程阻塞后,界面会卡住的问题。

环境

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

结构

SerialPortDemo-06
└── 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 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;
		}

		if(length <= 2)
		{
			// 缓存清空
			memoryBuffer = new MemoryStream();
			action = -1;
			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());

		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 async Task<ByteBuffer> sendReadRequest(byte[] buffer)
{
	try
	{
		action = 0;

		return await write(buffer); ;

	}
	catch (Exception)
	{
		throw;
	}

}

public async Task<ByteBuffer> sendWriteRequest(byte[] buffer)
{
	try
	{
		action = 1;

		return await write(buffer); 
	}
	catch (Exception)
	{
		throw;
	}
}

串口写操作

private async Task<ByteBuffer> write(byte[] buffer)
{
	try
	{
		byte[] request = CRC.crc16(buffer);

		// 缓存清空
		memoryBuffer = new MemoryStream();

		//Debug.WriteLine("length:" + length);
		Debug.WriteLine("发送请求:" + StringLib.GetHexStringFromByteArray(request));
		showLogEvent?.Invoke("发送请求:" + StringLib.GetHexStringFromByteArray(request));

		ByteBuffer byteBuffer = new ByteBuffer();

		// 订阅事件,将接收到的消息传递过来
		receiveEvent = (b) => byteBuffer.pushByteArray(b);

		serialPort.Write(request, 0, request.Length);

		// 等待消息传递
		await Task.Run(() =>
		{
			while (true)
			{
				if (byteBuffer.CURRENT_LENGTH > 0) return;
			}
		});

		return byteBuffer;
	}
	catch (Exception)
	{
		throw;
	}
}

MainForm

定义变量

private MySerialPort serialPort = new MySerialPort();

控件初始化

private void Form1_Load(object sender, EventArgs e)
{
	serialPort.initBy(comboxPortName, comboBoxBaudRate);

	serialPort.showLogEvent += (message) => Invoke(new Action(() => textBoxLog.AppendText(message + "\r\n")));
}

打开串口

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 async void buttonRead_Click(object sender, EventArgs e)
{
	try
	{
		ByteBuffer byteBuffer = new ByteBuffer(new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x64 });

		await serialPort.sendReadRequest(byteBuffer.toByteArray());

		MessageBox.Show("读取成功");
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}

}

发送读取请求1

private async void buttonRead1_Click(object sender, EventArgs e)
{
	try
	{
		byte[] request = new byte[] { 0x01, 0x03, 0x00, 0x01, 0x00, 0x03 };

		ByteBuffer byteBuffer = await serialPort.sendReadRequest(request);
		// 响应的数据
		// 01 03 06 00 00 00 02 00 00 80 B5
		// 0x01 -- 从机地址;  0x03 -- 功能码; 0x06 -- 数据长度
		// 0x00 0x00 -- 第一个寄存器的值
		// 0x00 0x02 -- 第二个寄存器的值
		// 0x00 0x00 -- 第三个寄存器的值
		// 0x80 0xB5 -- 校验位

		byteBuffer.popByte(); // 0x01 -- 从机地址;
		byteBuffer.popByte(); // 0x03 -- 功能码;
		byteBuffer.popByte(); // 0x06 -- 数据长度

		textBoxAddress1.Text = byteBuffer.popShort().ToString(); // 0x00 0x00 -- 第一个寄存器的值
		textBoxAddress2.Text = byteBuffer.popShort().ToString(); // 0x00 0x02 -- 第二个寄存器的值
		textBoxAddress3.Text = byteBuffer.popShort().ToString(); // 0x00 0x00 -- 第三个寄存器的值


		MessageBox.Show("请求解析读取");
	}

	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

发送写入请求

private async void buttonWrite_Click(object sender, EventArgs e)
{
	try
	{
		ByteBuffer requestBuffer = new ByteBuffer();
		//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 -- 字节计数
		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));

		await serialPort.sendWriteRequest(requestBuffer.toByteArray());

		MessageBox.Show("写入成功");
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

测试

启动Modbus Slave

Modbus Slave

运行程序,读取


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

运行程序,写入


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

源码

码云
封面


# c#