QT串口编程 - 阻塞从机示例(Blocking Slave)
Blocking Slave演示了如何在非GUI线程中使用QSerialPort的同步API为串行接口创建应用程序。

QSerialPort支持两种通用的编程方法:
异步(非阻塞)方法。当控件返回到Qt的事件循环时,将调度并执行操作。操作完成后,QSerialPort会发出信号。例如,QSerialPort :: write()
立即返回。当数据发送到串行端口时,QSerialPort发出bytesWritten()
。
同步(阻塞)方法。在非GUI和多线程应用程序中,可以调用waitFor ...()
函数(即QSerialPort :: waitForReadyRead()
)以挂起调用线程,直到操作完成为止。
在此示例中,演示了同步方法。终端示例说明了异步方法。
本示例的目的是演示一种模式,您可以使用该模式来简化串行编程代码,而又不会失去用户界面的响应能力。使用Qt的阻塞式串行编程API通常会导致代码更简单,但是由于其阻塞行为,它只能在非GUI线程中使用,以防止用户界面冻结。但是与许多人的看法相反,将线程与QThread一起使用并不一定会给应用程序增加难以管理的复杂性。
该应用程序是一个从站,演示了与主应用程序Blocking Master Example配对的工作。
从应用程序将通过串行端口从主应用程序接收请求,并发送响应。
我们将从处理串行编程代码的SlaveThread类开始。
class SlaveThread : public QThread
{
Q_OBJECT
public:
explicit SlaveThread(QObject *parent = nullptr);
~SlaveThread();
void startSlave(const QString &portName, int waitTimeout, const QString &response);
void run() Q_DECL_OVERRIDE;
signals:
void request(const QString &s);
void error(const QString &s);
void timeout(const QString &s);
private:
QString portName;
QString response;
int waitTimeout;
QMutex mutex;
bool quit;
};
SlaveThread是QThread子类,它提供用于接收来自Master的请求的API,并且具有用于传递响应和报告错误的信号。
您应该调用startSlave()
来启动Slave应用程序。 该方法将所需的参数传输到SlaveThread,以配置和启动串行接口。 当从主机收到SlaveThread的任何请求时,都会发出request()
信号。 如果发生任何错误,将发出error()
或timeout()
信号。
请务必注意,从主GUI线程调用了startSlave()
,但是将从SlaveThread的线程访问响应数据和其他参数。 SlaveThread的数据成员是同时从不同的线程读取和写入的,因此建议使用QMutex同步访问。
void SlaveThread::startSlave(const QString &portName, int waitTimeout, const QString &response)
{
QMutexLocker locker(&mutex);
this->portName = portName;
this->waitTimeout = waitTimeout;
this->response = response;
if (!isRunning())
start();
}
startSlave()
函数存储串行端口名称,超时和响应数据,并且QMutexLocker
锁定互斥锁以保护这些数据。 然后,我们启动线程,除非它已经在运行。 QWaitCondition :: wakeOne()
将在后面讨论。
void SlaveThread::run()
{
bool currentPortNameChanged = false;
mutex.lock();
QString currentPortName;
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
}
int currentWaitTimeout = waitTimeout;
QString currentRespone = response;
mutex.unlock();
在run()函数中,首先获取互斥锁,然后从成员数据中获取串行端口名称,超时和响应数据,然后再次释放该锁。 在任何情况下都不应在获取这些数据的过程的同时调用startSlave()方法。 QString是可重入的,但不是线程安全的,因此不建议从一个启动,调用和超时或另一启动,响应数据中读取串行端口名称。 SlaveThread一次只能处理一个启动。
在循环进入之前,我们在堆栈上构造的QSerialPort对象进入run()函数:
QSerialPort serial;
while (!quit) {
这使我们可以在运行循环时创建一个对象,也意味着该对象的所有方法都将在run()
线程的上下文中执行。
在循环中,检查当前启动的串行端口名称是否已更改。 如果有,请重新打开并重新配置串行端口。
if (currentPortNameChanged) {
serial.close();
serial.setPortName(currentPortName);
if (!serial.open(QIODevice::ReadWrite)) {
emit error(tr("Can't open %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
}
if (serial.waitForReadyRead(currentWaitTimeout)) {
循环将继续等待请求数据:
// read request
QByteArray requestData = serial.readAll();
while (serial.waitForReadyRead(10))
requestData += serial.readAll();
警告:应该在每次read()
调用阻塞方法之前使用方法waitForReadyRead()
,因为它处理所有I / O例程而不是Qt事件循环。
如果读取数据时发生错误,则发出timeout()
信号。
} else {
emit timeout(tr("Wait read request timeout %1")
.arg(QTime::currentTime().toString()));
}
成功读取后,尝试发送响应并等待传输完成:
// write response
QByteArray responseData = currentRespone.toLocal8Bit();
serial.write(responseData);
if (serial.waitForBytesWritten(waitTimeout)) {
QString request(requestData);
emit this->request(request);
警告:对于阻塞方法,在每次write()
调用之后都应使用waitForBytesWritten()
方法,因为它处理所有I / O例程而不是Qt事件循环。
如果在写入数据时发生错误,则发出timeout()
信号。
} else {
emit timeout(tr("Wait write response timeout %1")
.arg(QTime::currentTime().toString()));
}
发出成功写入后,包含从主应用程序接收的数据的request()
信号:
emit this->request(request);
接下来,线程切换为读取串行接口的当前参数,因为它们已经被更新,并从头开始运行循环。
mutex.lock();
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
} else {
currentPortNameChanged = false;
}
currentWaitTimeout = waitTimeout;
currentRespone = response;
mutex.unlock();
}
网友评论