串口的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();



posted on 2019-11-08 12:59  邶风  阅读(668)  评论(0编辑  收藏  举报