关于串口通信文本框不显示或显示慢的解决办法

C# 关于串口通信文本框不显示或显示慢的解决办法)

笔者写了一个串口通信类,却碰到了一个问题就是显示速度非常的慢,文本框要四五分钟的时间才会显示串口数据的变化,查了几百篇文章都没有提到这个问题,于是在这里记录一下也许会帮到你,有网友私信给我让我加QQ号,在这里如果有什么问题可以加我的QQ:778576519以便共同提高。

先上图

在这里插入图片描述

可以看到是一个物联网汽车信号采集卡上传的信号
接收数据格式为
在这里插入图片描述注意:不支持项显示 井号,这里一但要使用井号就会0与1的变化,如<左转灯>如果使用就会有变化开灯是<1>关灯<0>如果不使用就一直是井号。

串口类代码

由于笔者注释的比较详细直接看代码吧
下面展示一些 内联代码片

using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using Accord.Statistics;

namespace VehicleCollector.CalssDocuments
{

   	class SerialPortCaptrue
    {
        #region 串口连接方法

        private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke
        public SerialPort sd;
        public bool SerialPortConnect(string portName, string baudRate, string dataBits, string parity, string stopBits)
        {
            Parity iParity_1;
            switch (parity)
            {
                case "奇校验": iParity_1 = Parity.Odd; break;
                case "偶校验": iParity_1 = Parity.Even; break;
                case "无校验": iParity_1 = Parity.None; break;
                default: iParity_1 = Parity.None; break;
            }
            StopBits istopBits_1;
            switch (stopBits)
            {
                case "1": istopBits_1 = StopBits.One; break;
                case "1.5": istopBits_1 = StopBits.OnePointFive; break;
                case "2": istopBits_1 = StopBits.Two; break;
                default: istopBits_1 = StopBits.One; break;

            }
            sd = new SerialPort();
            sd.PortName = portName;//串口名称
            sd.BaudRate = Convert.ToInt32(baudRate);//波特率
            sd.DataBits = Convert.ToInt32(dataBits);//数据位
            sd.Parity = iParity_1;//校验位
            sd.StopBits = istopBits_1;//停止位
            sd.ReceivedBytesThreshold = 1;//设置有多少个字节后才触发接收事件
            sd.ReadTimeout = -1;//读超时
            sd.ReadBufferSize = 4096;//设置缓存区的大小
            sd.DataReceived += new SerialDataReceivedEventHandler(PortReceived);//订阅串口接收事件
            //sd.DtrEnable = true;
            //sd.RtsEnable = false;
            bool stat = false;
            //sd.Open();
            if (sd.IsOpen)
            {
                stat = true;
            }
            else
            {
                stat = false;
            }
            return stat;//返回串口是否打开状态 如是是False 没打开 如果True则打开
        }
        #endregion


        public string receive = "";//数据接收
        public string ReadLineStr;
        private static readonly object objLock = new object();//加锁
        #region 串口接收事件

        /// <summary>
        ///串口接收事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PortReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (Closing) return; //如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
            try
            {
                
                Thread.Sleep(100);  //(毫秒)由于线程读取速度太快必须等待一定时间
                                    //否则文本框显示会等四五分钟时间才会看到变化
                                  //这个地方笔者搞了两个星期没有找到原因最初值是(5ms)现在改为100ms     
                if (sd.BytesToRead > 0)
                {
                    ReadLineStr = "";
                    //ReadLineStr = sd.ReadLine();
                    ReadLineStr = sd.ReadExisting(); //数据接收内容
                    if (ReadLineStr[0] != '$')
                    {
                        return;
                    }

                    if (ReadLineStr.EndsWith("\n"))
                    {
						lock (objLock)//加锁
                        {
                            string[] Serial_Info = ReadLineStr.Split(','); //按照逗号分隔把$ODB各种信号分隔到字符数组中
                            receive = string.Join(",", Serial_Info); //数组转成字符串
                            if (receive != "")
                            {
                                Task tsk = Task.Run(() => 
                                {
                                   Receiving_show(receive);
                                });
                            }
                        }
                    }
                    sd.DiscardInBuffer(); //清空缓存区数据

                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public string Receiving_show(string receive_str)
        {
            string str;
            string re = "";
            int NN = receive_str.IndexOf('\n');//取字符串最后\n的索引值如果为空返回值是-1
            if (receive_str == null && NN < 39 && NN == -1)//如果为空容易报错这里加上返回防止报错
            {
                return "";
            }
            str = receive_str;
            if (receive != null && NN > 0)
            {
                string st = receive_str.Substring(0, 7);//取字符串前7个字符从O开始
                if (st == "$OBD-RT")
                {
                    string s = receive_str.Substring(0, NN);//取字符串回车符前所有的字符
                    string[] StrArray = s.Split(new Char[] { ',' });//将字符装入字符串数组中

                    //这里只能一个一个的取字符没有规律
                    PublicCons.K_KeyOnOff_Str = StrArray[1];                //钥匙状态
                    PublicCons.K_EngineSpeed_Str = StrArray[3];             //发动机转速
                    PublicCons.K_RunningSpeed_Str = StrArray[4];            //行驶速度
                    PublicCons.K_Gears_Str = StrArray[6];                   //档位信号A0 绿色线
                    PublicCons.K_SteeringCorner_Str = StrArray[7];          //方向盘转角
                    PublicCons.K_Gas_Str = StrArray[8];                     //油门加速踏板
                    PublicCons.K_StopMove_Str = StrArray[13];               //当前故障码数量转闯动信号
                    PublicCons.K_MainDoor_Str = StrArray[15];               //主驾驶车门
                    PublicCons.K_CopilotBrake_Str = StrArray[24];           //定义天窗转副刹信号
                    PublicCons.K_SafetyBelt_Str = StrArray[26];             //主驾驶安全带
                    PublicCons.K_Clutch_Str = StrArray[28];                 //离合信号
                    PublicCons.K_Speaker_Str = StrArray[29];                //喇叭信号
                    PublicCons.K_HandBrake_Str = StrArray[31];              //手刹信号
                    PublicCons.K_BrakeSignal_Str = StrArray[32];            //刹车信号
                    PublicCons.K_LeftLight_Str = StrArray[33];              //左转灯 
                    PublicCons.K_RightLight_Str = StrArray[34];             //右转灯 
                    PublicCons.K_ParkLight_Str = StrArray[35];              //位置灯
                    PublicCons.K_DippedHeadlight_Str = StrArray[36];        //近光灯 
                    PublicCons.K_HighBeam_Str = StrArray[37];               //远光灯
                    PublicCons.K_FrontFogLamp_Str = StrArray[38];           //前雾灯
                    PublicCons.K_RearFogLamp_Str = StrArray[39];            //后雾灯
                    re = string.Join(",", StrArray); //数组转成字符串
                    PublicCons.Receive_Str = re;

                }
            }

            return re;
        }


        #region 打开串口
        public bool SerialOpen()
        {
            try
            {

                if (sd.PortName != PublicCons.PortStr_1)
                {
                    MessageBox.Show(string.Format("端口不存在请检查设置{0}", PublicCons.PortStr_1.ToString()));
                    return false;
                }

                if (!sd.IsOpen)//如果没有打开
                {
                    sd.Open();
                    sd.Handshake = Handshake.None;//通信协议
                    sd.DtrEnable = false;//如果为 true,则启用数据终端就绪 (DTR);否则为 false。
                                         //默认为 false。如果用TRUE程序假死不会返回取数据
                    //Thread.Sleep(50);
                    //sd.DtrEnable = true;
                    /*sd.RtsEnable = true;
                    Thread.Sleep(50);*/
                    sd.RtsEnable = false;
                }

                if (sd.IsOpen)//如果打开串口
                {

                    sd.Close();
                    Thread.Sleep(100);  //(毫秒)等待一定时间,      
                    sd.Open();
                    sd.Handshake = Handshake.None;//通信协议
                    sd.DtrEnable = false;//如果为 true,则启用数据终端就绪 (DTR);否则为 false。
                                         //默认为 false。如果用TRUE程序假死不会返回取数据
                    //Thread.Sleep(50);
                    //sd.DtrEnable = true;
                    /*sd.RtsEnable = true;
                    Thread.Sleep(50);*/
                    sd.RtsEnable = false;


                }
            }
            catch (Exception e)
            {
                //throw e;
                MessageBox.Show("错误" + e.Message);

            }
            return sd.IsOpen;
        }
        #endregion
        private bool Listening = false;//等待监听线程结束
        public void SerialClose()
        {
            Closing = true;
            while (Listening) Application.DoEvents();
            //sd.DtrEnable = true;
            //sd.RtsEnable = false;
            sd.Close();
            Closing = false;
            

        }
        #endregion
    }
}

刚开始笔者以为是多线程的原因造成的,于是在多线程上花了很多的时间可是问题却不是出在多线程上,大多数博客上说访问ui资源,所以需要使用invoke方式同步ui笔者也没有错啊那问题出在那里。
现在看笔者调用文本框的代码:

下面展示一些 内联代码片

private static readonly object objLock_Txt = new object();//加锁
        private void timer_SerialPort_Tick(object sender, EventArgs e)//串口时钟
        {
            if (isConnect)
            {
                DateTime dt = DateTime.Now;
                lab_SerialPortConn.Text = "采集卡连接成功";
                lock (objLock_Txt)//加锁
                {
                    if (txt_SerialPortRecevideData.InvokeRequired)//如果不在一个线程上InvokeRequired返回值为真
                    {
                        this.BeginInvoke(new Action(() =>//C# 3.0以后代替委托的新方法
                        {

                            txt_SerialPortRecevideData.AppendText(dt.ToString("yyyy-M-d-HH:mm:ss ") + PublicCons.Receive_Str + "\r\n"); //向文本添加数据+ "\r\n"; //注意:回车换行必须这样写,单独使用"\r"和"\n"都不会有效果

                            if (txt_SerialPortRecevideData.TextLength > 20000)//文本框超过20000个字节清空
                            {
                                txt_SerialPortRecevideData.Clear();
                            }
                        }));
                    }
                    else
                    {
                        txt_SerialPortRecevideData.AppendText(dt.ToString("yyyy-M-d-HH:mm:ss ") + PublicCons.Receive_Str + "\r\n"); //向文本添加数据+ "\r\n"; //注意:回车换行必须这样写,单独使用"\r"和"\n"都不会有效果

                        if (txt_SerialPortRecevideData.TextLength > 20000)//文本框超过20000个字节清空
                        {
                            txt_SerialPortRecevideData.Clear();
                        }
                    }
                    ShowTxt();
                    //txt_Refresh();
                }
            }
            else
            {
                lab_SerialPortConn.Text = "采集卡未连接";
            }
            

        }

txt_SerialPortRecevideData 是文本框
PublicCons.Receive_Str 是静态变量。
由于解析出来的完整数据要被多个地方调用这里必须要加锁(lock)不加锁会出现争夺资源的现象影响执行效率问题,搞不好还会假死。
费了两个星期的时间又是用事件,委托等方法都没有解决在文本框显示慢的问题,以至于笔者差点放弃。最终笔者无意间调整了线程睡眠时间突然一下子好了。最初笔者调整线程睡眠时间为5ms 无意间调整为500ms反正没事调着玩没有想到的是发现可以正常使用了,后慢慢调整到100ms.问题最终得到解决。
下面展示一些 内联代码片

  private void PortReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (Closing) return; //如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
            try
            {
                
                Thread.Sleep(100);  //(毫秒)由于线程读取速度太快必须等待一定时间
                                    //否则文本框显示会等四五分钟时间才会看到变化
                                    //这个地方笔者搞了两个星期没有找到原因最初值是(5ms)现在改为100ms  
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
您可以在接收到数据时使用 `QTextEdit` 控件的 `insertPlainText()` 方法来显示数据,这样就不会自动换行了。示例代码如下: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建串口对象 self.serial = QSerialPort(self) self.serial.readyRead.connect(self.on_serial_ready_read) # 创建文本编辑框 self.text_edit = QTextEdit(self) self.setCentralWidget(self.text_edit) # 打开串口 port_name = "COM1" # 串口名 baud_rate = QSerialPort.Baud9600 # 波特率 data_bits = QSerialPort.Data8 # 数据位 parity = QSerialPort.NoParity # 校验位 stop_bits = QSerialPort.OneStop # 停止位 self.serial.setPortName(port_name) self.serial.setBaudRate(baud_rate) self.serial.setDataBits(data_bits) self.serial.setParity(parity) self.serial.setStopBits(stop_bits) if self.serial.open(QSerialPort.ReadWrite): print(f"串口 {port_name} 已打开") else: print(f"串口 {port_name} 打开失败") def on_serial_ready_read(self): data = self.serial.readAll() self.text_edit.insertPlainText(data) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` 在上面的代码中,我们使用了 `QTextEdit` 控件的 `insertPlainText()` 方法来显示接收到的数据。这个方法不会自动换行,所以数据会在同一行上连续显示

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杏雨1969

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值