C#串口通信03 多个读取操作

C#串口通信03 多个读取操作

Amiron 249 2022-04-27

前言

根据前两个例子,我们现在已经能凑活地接收到一条完整的响应数据了。我们想再添加一个读取按钮,并将返回的响应数据处理一下,将其填到我们预期的输入框中。在“我一言,你一语”的业务场景中,我们可以定义一个标识变量,在接收事件中进行区分各个操作的解析方法。接下来,我们继续完善接收数据流程,并添加新需求。

环境

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

结构

SerialPortDemo-03
└── CRC.cs
└── MarinForm.cs
└── Program.cs

界面

简单的测试界面

定义变量

private SerialPort serialPort = new SerialPort();

// 接收数据缓存
private MemoryStream memoryBuffer = new MemoryStream();

// 串口打开标识
private bool open = false;

// 操作标识
private int action = -1; // 0 读取; 1 请求解析读取

// 按钮控制列表
private List<Button> buttonList = new List<Button>();

控件初始化

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;

	// 按钮加入控制列表
	buttonList.Add(buttonRead);
	buttonList.Add(buttonRead1);
}

打开串口

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 <= 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();
                    // 操作标识重置
                    action = -1;

                    // 按钮恢复
                    Invoke(new Action(() => buttonList.ForEach(button => button.Enabled = true)));

                    return;
                }

                // 判断操作
                switch (action)
                {
                    case 0:
                        // 接收事件与界面并不是同一个线程;所以需要用Invoke()方法同步更新界面
                        Invoke(new Action(() => textBoxLog.AppendText("收到响应:" + StringLib.GetHexStringFromByteArray(temp) + "\r\n")));
                        MessageBox.Show("读取完成");
                        break;

                    case 1:
                        // 接收事件与界面并不是同一个线程;所以需要用Invoke()方法同步更新界面
                        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 -- 校验位

                            byte[] data = new byte[6];
                            Array.Copy(temp, 3, data, 0, 6);

                            // 将数据填到对应输入框中
                            textBoxAddress1.Text = ShortLib.GetShortFromByteArray(data).ToString();
                            textBoxAddress2.Text = ShortLib.GetShortFromByteArray(data, 2).ToString();
                            textBoxAddress3.Text = ShortLib.GetShortFromByteArray(data, 4).ToString();
                            textBoxLog.AppendText("收到响应:" + StringLib.GetHexStringFromByteArray(memoryBuffer.ToArray()) + "\r\n");

                            MessageBox.Show("读取完成");
                        }));
                        break;
                }

                // 缓存清空
                memoryBuffer = new MemoryStream();
                // 操作标识重置
                action = -1;

                // 按钮恢复
                Invoke(new Action(()=> buttonList.ForEach(button => button.Enabled = true)));

            }
            catch (Exception)
            {
                throw;
            }
        }

发送读取命令

private void buttonRead_Click(object sender, EventArgs e)
{
	 try
            {
                // 按钮失效
                buttonList.ForEach(button => button.Enabled = false);
                // 定义操作标识
                action = 0;
             
                // 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 };

                textBoxLog.AppendText("发送请求:" + StringLib.GetHexStringFromByteArray(request) + "\r\n");
                serialPort.Write(request, 0, request.Length);
            }
            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, 0x54, 0x0B };

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

                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个寄存器作为测试使用,然后将寄存器地址2的值改为2。

Modbus Slave

运行程序

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

调试信息

长度和校验位

源码

码云


# c#