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