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