• 串口


    串口的write跟read在同一线程中执行
    如果此时正在write,但是收到readyRead()信号,会中断write,去执行readyRead()信号触发的槽函数。

    在多线程串口通信中,如果设计不好,会造成访问缓冲指令时,线程锁,死锁。

    对QSerialPort的读写操作需要在同一个线程,不能跨线程操作。

    ---------------------------------------------------------------
    设计一个通用的模型:

    -------------------------------------------------------------------

    模型一:

    数据:
    待发送指令队列,在线程中遍历这个队列,每次pop一条指令,通知串口线程,发送该指令。发送成功之后,将该指令插入到已发送指令队列。

    已发送指令队列,等待响应消息,根据序列号,匹配到 发送消息 响应消息。匹配成功,则从已发送队列中删除该指令

    线程:
    线程1:
    生成指令,插入到“待发送指令队列”


    线程2:
    遍历“待发送指令队列”, 每次pop一条指令,通知串口线程,发送该指令。该线程阻塞等待。串口线程如果发送成功将该指令插入到已发送指令队列。串口线程发送结束之后,唤醒该线程。


    线程3:
    串口线程。写函数。绑定信号槽的读函数。

    写函数 由线程2得到发送指令之后,触发执行,在该函数中,判断如果发送成功将该指令插入到已发送指令队列。并记录发送时间。发送接收后,去唤醒线程2。

    读函数,由readyRead()信号触发。注意,该信号会中断写函数。所以不要在读函数中,处理接收到的数据。读函数 只负责读取数据,拼接数据包,得到一个完整的数据包之后,通知
    其他线程去处理该数据包。

    线程4:
    解析数据包函数。
    由线程3的读函数得到了一个完整数据包之后,触发该线程的解析数据包函数。

    解析数据包函数,从 “已发送指令队列” 中 根据序列号,匹配 发送,接收 指令。 匹配成功,则“已发送指令队列” 删除 已被发送且得到了响应消息的指令。

    超时响应判断:在线程2中判断,遍历“已发送指令队列” ,判断已经发送出的消息经过的事件是否超过了 超时时间。

    -----------------------------------------------------------------------------------------------------------------------------------------------
    模型二:

    数据:
    待发送指令队列,在线程中遍历这个队列,每次pop一条指令,通知串口线程,发送该指令。发送成功之后,将该指令插入到已发送指令队列。

    已发送指令队列,等待响应消息,根据序列号,匹配到 发送消息 响应消息。匹配成功,则从已发送队列中删除该指令

    已接收指令队列,串口收到响应消息之后,插入到该队列中,去匹配“已发送指令队列” ,匹配成功,则删除 指令。

    线程:
    线程1:
    生成指令,插入到“待发送指令队列”


    线程2:
    遍历“待发送指令队列”, 每次pop一条指令,通知串口线程,发送该指令。该线程阻塞等待。串口线程如果发送成功将该指令插入到已发送指令队列。串口线程发送结束之后,唤醒该线程。


    线程3:
    串口线程。写函数。绑定信号槽的读函数。

    写函数 由线程2得到发送指令之后,触发执行,在该函数中,判断如果发送成功将该指令插入到已发送指令队列。并记录发送时间。发送接收后,去唤醒线程2。

    读函数,由readyRead()信号触发。注意,该信号会中断写函数。所以不要在读函数中,处理接收到的数据。读函数 只负责读取数据,拼接数据包,得到一个完整的数据包之后,插入到
    “已接收指令队列”

    线程4:
    解析 接收指令队列 函数。

    遍历 “接收指令队列”
    解析数据包,从 “已发送指令队列” 中 根据序列号,匹配 发送,接收 指令。 匹配成功, 从两个队列中 删除 指令。

    超时响应判断:在线程4中判断。


    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    模型三、

    数据:
    待发送指令队列,在线程中遍历这个队列,每次pop一条指令,通知串口线程,发送该指令。发送成功之后,将该指令插入到已发送指令队列。

    已发送指令队列,等待响应消息,根据序列号,匹配到 发送消息 响应消息。匹配成功,则从已发送队列中删除该指令

    线程:
    线程1:
    生成指令,插入到“待发送指令队列”


    线程2:
    遍历“待发送指令队列”, 每次pop一条指令,通知串口线程,发送该指令。假定串口指令马上发送且总是成功发送(串口是打开状态即认为是发送成功)。
    在线程2中插入指令到已发送指令队列。并记录发送时间,检查响应超时使用。


    线程3:
    串口线程。写函数。绑定信号槽的读函数。

    写函数 由线程2得到发送指令之后,触发执行。

    读函数,由readyRead()信号触发。注意,该信号会中断写函数。
    解析数据包,从 “已发送指令队列” 中 根据序列号,匹配 发送,接收 指令。 匹配成功,则“已发送指令队列” 删除 已被发送且得到了响应消息的指令。

    超时响应判断:在线程2中判断,遍历“已发送指令队列” ,判断已经发送出的消息经过的事件是否超过了 超时时间。

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------

    为简单起见,选择模型三的方案


    超时响应判断:
    已发送指令 超时的判断。

    可以在线程2中判断,遍历“已发送指令队列” 计算指令已经被发送出去的时间,如果超过了 设置超时时间 则说明 响应消息超时了。

    也可以在线程4中判断。


    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    网上的一些Qt串口的资料:

    1、https://blog.csdn.net/Ryanpinwei/article/details/52203668
    readyRead()信号不产生解决方法,控制管脚状态,serial.setDataTerminalReady(true);

    2、其他程序员设计的程序:
    https://www.cnblogs.com/jobgeo/p/6903424.html
    https://bbs.csdn.net/topics/392015864
    http://blog.sina.com.cn/s/blog_4bd0c9aa0102vyag.html (使用第三方Posix_QextSerialPort,缺点:QextSerialPort的CPU占用率高。)
    https://www.cnblogs.com/hanford/p/6048325.html 如何使用:软件流控制(XON/XOFF),硬件流控制(RTS/CTS)

    http://www.qtcn.org/bbs/read-htm-tid-58951.html(串口操作(打开,关闭,读 or 写)一定要放在同一个线程进行,否则线程间冲突)

    https://www.jianshu.com/p/7ada20132204 (QtSerialPort,QSerialPortInfo 类中的接口函数介绍)

    //返回可读数据的字节数
    qint64 QSerialPort::bytesAvailable()

    //如果串口当前正忙,返回true
    bool QSerialPortInfo::isBusy() const

    //如果串口可用,返回串口的制造商的名字
    QString QSerialPortInfo::manufacturer() const

    3、一些代码技巧
    协议为7个字节
    if(serial->waitForReadyRead(200))
    {
      qDebug()<<"Count:"<<count++;
      QByteArray dd = serial->readAll();
      while (dd < 7)
      {
        msleep(500);
        dd = dd + serial->readAll();
      }
      qDebug()<<dd.toHex();
      qDebug()<<"Bytes"<<dd.size()<<" Time:"<<QDateTime::currentMSecsSinceEpoch()<<endl<<endl;
      serial->clear();
      msleep(500);

    }

    //循环每次读取100字节,直到读取完串口缓冲区。
    // This slot is connected to QSerialPort::readyRead()
    void QSerialPortClass::readyReadSlot()
    {
      while (!port.atEnd()) {
        QByteArray data = port.read(100);
        ....
      }
    }

    ----------------------------------------------------------------------------------

    业务控制,使用的一些代码

    1、休眠 QThread::msleep(100); //usecs microseconds 微妙

    2、耗时操作可能会导致readall函数一直读不到数据, 加入QApplication.processEvents()尝试解决。

    3、消息序列号的生成:

    QAtomicInteger<long> m_sequence;

    void BaseDevice::PackageFrame(unsigned char addr, unsigned char cmd, Frame& frm)
    {
        m_sequence.ref();
        long seq = m_sequence.load();
        if (seq % 0xffff == 0 || seq % 0xffff == 1)
        {
            m_sequence.ref();
            m_sequence.ref();
            seq = m_sequence.load();
        }
    
    
        memset(&frm, 0, sizeof(Frame));
       //........
        frm.m_sequence = (INT16U)(seq % 0xffff);
    }

    4、记录消息发送的时间戳

    CmdCB *p;

    //QT方式获取时间戳
    p->m_startTick = QTime::currentTime().msecsSinceStartOfDay();

    //windows api获取时间戳
    p->m_startTick = ::GetTickCount();



  • 相关阅读:
    三国杀三字经
    window.open参数表
    捕获FormRequest安全错误的类
    SqlServer之Convert 函数应用格式化日期
    数据库区分大小写的问题
    时间字符串的比较
    char码值对应列表大全
    web.config 连接字符串 加密解密,代码方式
    关于app_offline.htm的用法
    关闭窗口的时候弹出确认对话框
  • 原文地址:https://www.cnblogs.com/zhangxuan/p/11819452.html
Copyright © 2020-2023  润新知