计算机系统应用教程网站

网站首页 > 技术文章 正文

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

btikc 2024-09-22 01:32:17 技术文章 26 ℃ 0 评论

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.

对于软件而言,因为驱动接口固定,软件也相对好写,因此串口通讯也是嵌入式常见开发模式,但是对于受控设备类型五花八门,往往编程代码也是不尽相同。

串口参数处理

QSerialPort 是Qt用于串口处理类,在Linux/Windows能稳定工作,在Android实测也能按Linux操作。

按其文档,在HPUX等各种Unix平台也能使用,甚至MacOSX下,只要驱动正常是也是可以用。

Windows平台的串口端口名一般是 COM1 ~COMXX

而Linux 的命令比较自由,完全看驱动自己命名。比如/dev/ttyS0 ,如果是usb转串口往往,设备名称往往是 /dev/ttyUSB0 之类,使用时注意当usb串口进行插拔,其设备名会发现变化比如变成 /dev/ttyUSB1


半双工设备处理

很多单片机设备在进行设置时,局限于设备性能,在上一条指令未处理完之前,再发送下一条指令会不作响应,除非等到设备发送返回结果。比如我手头某家信号发生器(DDS)采用文本指令,生成信号往往需要调用多个指令设置不同参数,(频率,振幅等),它是一个STM32 单片机响应,因此在处理上一条指令,必须等到其响一个回车符,才能发送下一条指令。因此这类设备的串口是半双工的模式。

QSerialPort 处理这类设备,在发送命令后,必须要使用waitForReadyRead();一直等待设备的响应,为了保险往往还要多次等待。成功处理代码如下。

QByteArray W4ComDev::sendWaitRecv(const QByteArray&data,int recvTimeout)
{

    int ret =  mSerialPort.write(data);
     qDebug()<< __func__ << "ret "<<ret;
    if(ret<=0)
        return QByteArray();


     mSerialPort.waitForBytesWritten(recvTimeout);

    for(int i=0;i<3;i++){
        if(mSerialPort.waitForReadyRead(recvTimeout))
            break;
    }

    QByteArray result =  mSerialPort.readAll();

    if(textMode())
        qDebug() << "recv text \""<<result<<"\"";
    else
      qDebug() << "recv result  "<<result.toHex(' ');

    return result;
}

在处理时,要注意两点

在等待的过程中,是阻塞系统的执行的,因此为防止设备没有响应把整个软件卡死,必须要放在线程当中执行。

因为一次要发送多个队列,为了简化上层软件处理,可以设计一个命令队列,应用只需要一次把所有命令发送到队列,串口线程在逐条进行处理。

为此我队列可以直接用Qt自带队列类做了一个加锁队列

ifndef CONCURRENTQUEUE_H
#define CONCURRENTQUEUE_H

#include <QMutex>
#include <QWaitCondition>
#include <QQueue>

template<class T>
class ConcurrentQueue
{
public:
    ConcurrentQueue(int size=100){
       mQueueSize = size;
    }
    bool isFull(){
        if (-1 == mQueueSize)
            return false;

        mMutex.lock();
        int count = mQueue.count();
        mMutex.unlock();
        return count >= mQueueSize;
    }
    bool isEmpty(){
        mMutex.lock();
        bool empty = mQueue.isEmpty();
        mMutex.unlock();
        return empty;
    }
    void clean(){
        mMutex.lock();
        mQueue.clear();
        mNotFull.wakeAll();
        mMutex.unlock();
    }

    //取数据,如果没有,则直接退出
    T tryPull(){
        QMutexLocker locker(&mMutex);
        if (mQueue.count() == 0 )
            return T();

        return  mQueue.dequeue();

    }
   //加入数据,如果满则进入等待
    void pendPush(const T&t){
        QMutexLocker locker(&mMutex);

     //   if (mQueue.count() == mQueueSize)
      //        mNotFull.wait(&mMutex);
        mQueue.enqueue(t);

        mNotEmpty.wakeAll();


    }

    //取数据,如果没有则进入等待
    T pendPull(int timeout = 0){
        QMutexLocker locker(&mMutex);
        if (mQueue.count() == 0){
              if(timeout > 0){
                 if(!mNotEmpty.wait(&mMutex,timeout))
                      return T();
              }

              return T();
        }
//        else
//             mNotEmpty.wait(&mMutex);
        T i = mQueue.dequeue();


         mNotFull.wakeAll();
        return i;
    }

    void push(const T& t){
        mMutex.lock();
        mQueue.enqueue(t);
        mMutex.unlock();
    }
    T pull(){
        mMutex.lock();
        T i = mQueue.dequeue();
        mMutex.unlock();
        return i;
    }

    int count(){

        QMutexLocker locker(&mMutex);
        int count = mQueue.count();

        return count;
    }

    int queueSize() { return mQueueSize;}


protected:
    int mQueueSize = 100;
    QQueue<T> mQueue;
    QWaitCondition mNotEmpty;
    QWaitCondition mNotFull;
    QMutex mMutex;
    //mutable QReadWriteLock RWlock; //优点可以多线程同时读,比QMutex更高效
};

#endif // CONCURRENTQUEUE_H

使用时如下定义即可

ConcurrentQueue<QByteArray> txCmdQueue;


少量数据传输

比如BLE透传模块,这类设备往往每次传输不到20byte,而且数据往往是二进制。这样用

QSerialPort的信号readReady来异步接收数据往往不能及时收到。我的理解是QSerialPort需要接收到一定长度,或者收到回车符才会触发事件。这样会造成上位机软件处理不及时。

这种情况处理,需要采用同步接收方式,使用一个线程不断使用readAll()方法来处理,再结合

void ComRecvThread::recvTest()
{
    QByteArray data;
    while(!needClose())
    {
        if(mDevice->waitForReadyRead(50)){
            // qApp->processEvents();
             data = mDevice->readAll(); //读取串口数据
        if(!data.isEmpty())
        {
        
            qDebug() << __func__ << "recv "<< data.toHex(' ');
            emit readReady(data);
        }
        else {
            qDebug() << "not recv";
        }

        QThread::msleep(30);
    }
    }

}

实测这样能及时收到反应。

大量数据连续数据

有一些工业控制设备,如信号基站等,在运行中会不断发送设备状态数据。数据量大而且往往连续不断发送。

这种情况用同步方法或用异步事件均可以。但这种情况会出现一些问题,一些协议包往往较长,一次readAll()无法读取,或者一个协议包正好在两次接收分别收上来。这就牵涉到分包和拼包的问题。

这个一般的做法是根据协议格式进行单独处理,但是这种处理需要另开一个buffer来按字节逐次检测,效率偏低也不通用。

这种情况QDataStream 就派上用场了,这个类非常好用。请参考我关于QDataStream处理文章。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表