C#winform解析串行端口数据

问题描述

我是一个手持复制粘贴的新手,试图从旧的Avery-Weightronix 7820秤中获取数据。我通过RJ-232电缆将其连接起来,并设法从秤上获得响应。我按照本教程https://www.studentcompanion.co.za/creating-a-serial-port-interface-with-c/进行了设置并开始运行。

一旦我开始使用它,我想简化它,并实质上将计算机用作远程显示器。我对串行连接参数进行了硬编码,并设置了代码以每秒发送一次重量请求,以便可以在winform中显示它。

稍后,我计划扩展它,以便获得多个项目的总重量。我计划称量一个项目,将重量复制到一个总计中,在秤上放一个新项目,然后重复。

我之所以陷入困境,是因为我无法始终如一地解析从秤返回的数据,并且因为循环锁定了UI。

当我发送W <CR>这是当前重量的请求时,示例响应为:

<LF> 0001.10lb <CR> <LF> 00 <CR><ETX>

<ETX> =文本结尾字符(Ø3十六进制),<LF> =换行符(ØA十六进制),<CR> =回车符(ØD十六进制)。

秤的响应是固定长度的,但是当我进入调试模式时,在代码的前几个周期中未收到响应。然后它到达以后。将其输出到RTF格式时可以,但是当我尝试提取子字符串时,如果没有数据,则会出现错误

此外,UI锁定,我唯一能做的就是停止执行。通过阅读,看来我应该实现线程,但是我不确定如何重新组织代码来实现这一点。

对于如何解决这些问题,我将不胜感激。

这是我的代码

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;

namespace ScaleView
{
  public partial class Form1 : Form
  {
    public Form1()
    {
        InitializeComponent();
    }

    private void groupBox1_Enter(object sender,EventArgs e)
    {

    }


    private void Form1_Load(object sender,EventArgs e)
    {
        //updatePorts();           //Call this function everytime the page load 
                                 //to update port names
        CheckForIllegalCrossthreadCalls = false;
    }
    

    private SerialPort ComPort = new SerialPort();  //Initialise ComPort Variable as SerialPort
    private void connect()
    {
        bool error = false;

            
        ComPort.PortName = "COM3";
        ComPort.Baudrate = int.Parse("9600");      //convert Text to Integer
        ComPort.Parity = (Parity)Enum.Parse(typeof(Parity),"Even"); //convert Text to Parity
            ComPort.DataBits = int.Parse("7");        //convert Text to Integer
            ComPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits),"1");  //convert Text to stop bits

            try  //always try to use this try and catch method to open your port. 
                 //if there is an error your program will not display a message instead of freezing.
            {
                //Open Port
                ComPort.open();
                ComPort.DataReceived += SerialPortDataReceived;  //Check for received data. When there is data in the receive buffer,//it will raise this event,we need to subscribe to it to kNow when there is data
                                                                 //MessageBox.Show(this,"Connected",MessageBoxButtons.OK);
            }
            catch (UnauthorizedAccessException) { error = true; }
            catch (System.IO.IOException) { error = true; }
            catch (ArgumentException) { error = true; }

            if (error) MessageBox.Show(this,"Could not open the COM port. Most likely it is already in use,has been removed,or is unavailable.","COM Port unavailable",MessageBoxButtons.OK,MessageBoxIcon.Stop);

        
       
        //if the port is open,Change the Connect button to disconnect,enable the send button.
        //and disable the groupBox to prevent changing configuration of an open port.
        if (ComPort.IsOpen)
        {
            btnConnect.Text = "disconnect";
        }
    }
    // Call this function to close the port.
    private void disconnect()
    {
        ComPort.Close();
        btnConnect.Text = "Connect";
        
    }
    //whenever the connect button is clicked,it will check if the port is already open,call the disconnect function.
    // if the port is closed,call the connect function.
    private void btnConnect_Click_1(object sender,EventArgs e)

    {
        if (ComPort.IsOpen)
        {
            disconnect();
        }
        else
        {
            connect();
            rtxtDataArea.AppendText("Connected\n");
            sendData();
        }
    }

    private void btnClear_Click(object sender,EventArgs e)
    {
        //Clear the screen
        rtxtDataArea.Clear();
    }
    // Function to send data to the serial port
    private void sendData()
    {
        bool error = false;
        while(ComPort.IsOpen)        //if text mode is selected,send data as tex
        {
            
            try
            {
                // Convert string of hex digits (in this case representing W<CR>) to a byte array
                string hextext = "57 0D";
                byte[] data = HexStringToByteArray(hextext);

                // Send the binary data out the port
                ComPort.Write(data,data.Length);
                System.Threading.Thread.Sleep(3000);

                rtxtDataArea.ForeColor = Color.Blue;   //write Hex data in Blue
                string response = ComPort.ReadLine();
                int charfrom = 1;
                int charto = 9;
                //string weight = response.Substring(charfrom,charto - charfrom);
                rtxtDataArea.AppendText(response + "TEST\n");
                
            }
            catch (FormatException) { error = true; }

            // Inform the user if the hex string was not properly formatted
            catch (ArgumentException) { error = true; }

            if (error) MessageBox.Show(this,"Not properly formatted hex string: \n" + "example: E1 FF 1B","Format Error",MessageBoxIcon.Stop);

        }
    }
    //Convert a string of hex digits (example: E1 FF 1B) to a byte array. 
    //The string containing the hex digits (with or without spaces)
    //Returns an array of bytes. </returns>
    private byte[] HexStringToByteArray(string s)
    {
        s = s.Replace(" ","");
        byte[] buffer = new byte[s.Length / 2];
        for (int i = 0; i < s.Length; i += 2)
            buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i,2),16);
        return buffer;
    }

    private void btnSend_Click(object sender,EventArgs e)
    {
        sendData();
    }
    //This event will be raised when the form is closing.
    private void Form1_FormClosing(object sender,FormClosingEventArgs e)
    {
        if (ComPort.IsOpen) ComPort.Close();  //close the port if open when exiting the application.
    }
    //Data recived from the serial port is coming from another thread context than the UI thread.
    //Instead of reading the content directly in the SerialPortDataReceived,we need to use a delegate.
    delegate void SetTextCallback(string text);
    private void SetText(string text)
    {
        //invokerequired required compares the thread ID of the calling thread to the thread of the creating thread.
        // if these threads are different,it returns true
        if (this.rtxtDataArea.Invokerequired)
        {
            rtxtDataArea.ForeColor = Color.Green;    //write text data in Green colour

            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d,new object[] { text });
        }
        else
        {
            this.rtxtDataArea.AppendText(text);
        }
    }
    private void SerialPortDataReceived(object sender,SerialDataReceivedEventArgs e)
    {
        var serialPort = (SerialPort)sender;
        var data = serialPort.ReadExisting();
        SetText(data);
    }

    private void rtxtDataArea_TextChanged(object sender,EventArgs e)
    {

    }
}
}

解决方法

同时删除ouitry-catch方法中的MessageBox.Show(...)块和connect()sendData()块中的try-catch将捕获所有异常并将其发送到UI线程。

删除new Thread(() => {行,因为它绕过了内置保护。 CheckForIllegalCrossThreadCalls = false;方法可能比serialPort.ReadLine();更好,因为serialPort.ReadExisting();将等待一整行数据。

这应该使您对代码的外观有所了解:

ReadLine()