高速串口的数据接收

高速串口的数据接收(一)

针对高速串口接收数据的做了一些优化,下面是最开始的代码,存在数据接收时掉帧,数据延时等问题,现在我们将下面代码逐步优化和讲解。

void MyQSerialPort::run()
{
    if(!rev_ser->isOpen()) { return; }
    Fixed_data_back m_data;
    QByteArray buffer,temp;
    int bufferlen = sizeof(Fixed_data_back);
    while(!stop)
    {
         if(runningFlag) {
             if(rev_ser->bytesAvailable())
             {
                uint16_t trycount = 5;
                uint16_t lastlen = 0;
                rev_ser->waitForReadyRead(50);
                buffer = rev_ser->read(bufferlen);
                lastlen = bufferlen - buffer.length();
                while(lastlen) {
                QThread::usleep(100);
                rev_ser->waitForReadyRead(50);
                temp = rev_ser->read(lastlen);
                buffer += temp;
                lastlen = bufferlen - buffer.length();
                trycount--;
                if((lastlen != 0) && trycount == 0) { break; }
             }
                if(buffer.length() == bufferlen) {
                    memcpy((void*)(&m_data),(void*)(buffer.data()),bufferlen);
                    if(CheckSumGuideData((uint8_t*)(&m_data))) {
                        //qDebug()<<QStringLiteral("接收正确");
                    }else {
                        qDebug()<<QStringLiteral("接收错误");
                    }
                    buffer.clear();
                   } 
                }
        }
}

上述代码优化如下:
1、设用信号和槽-Qt框架的信号和槽机制是专门为了事件驱动的编程设计的,在“QSerialPort”中,有一个“readyRead()”信号,每当有新的数据可读时会发射这个信号,这比轮询方式更加高效,也更符合Qt的设计哲学。
2、移除不必要的循环等待-使用“QSerialPort”的“waitForReadyRead()”函数通常不推荐用于生产环境,因为它会阻塞事件循环,我们可以利用信号和槽来异步处理数据。
3、减少对“usleep()”的依赖-在GUI应用程序中,长时间的睡眠是一个坏习惯,因为它会阻塞执行线程。即使在工作线程中,使用“usleep()”也可能导致对串口响应不够灵敏。我们可以通过Qt的事件循环来优雅地处理这个问题。
4、异常处理-应该检查每个串口操作是否成功,并在必要时进行错误处理。
5、线程安全-如果“stop”和“runningFlag”被多个线程访问,需要确保它们的访问是线程安全的,比如使用互斥锁或者Qt的原子操作。

class MyQSerialPort : public QObject {
    Q_OBJECT
public:
    MyQSerialPort(QSerialPort *serialPort, QObject *parent = nullptr) :
        QObject(parent), serial(serialPort) {
        // 连接信号和槽
        connect(serial, &QSerialPort::readyRead, this, &MyQSerialPort::handleReadyRead);
    }

    ~MyQSerialPort() {
        serial->close();
    }

public slots:
    void startRead() {
        runningFlag = true;
    }

    void stopRead() {
        runningFlag = false;
    }

private slots:
    void handleReadyRead() {
        if (!runningFlag) return;

        QByteArray data = serial->readAll();
        processData(data);
    }

private:
    void processData(const QByteArray &data) {
        // 处理数据
        if (data.size() < sizeof(Fixed_data_back)) {
            // 数据不完整,可以存储起来等待下次读取更多
            buffer.append(data);
            if (buffer.size() >= sizeof(Fixed_data_back)) {
                // 现在我们有足够的数据进行处理
                Fixed_data_back m_data;
                memcpy(&m_data, buffer.constData(), sizeof(Fixed_data_back));
                buffer.clear();
                // 校验和处理
                if(CheckSumGuideData(reinterpret_cast<uint8_t*>(&m_data))) {
                    // 数据校验成功
                } else {
                    qDebug()<<QStringLiteral("接收错误");
                }
            }
        } else {
            // 直接处理数据
        }
    }

    QSerialPort *serial;
    QByteArray buffer;
    bool runningFlag = false;
};

上面代码进行了如下优化:

  • 使用信号和槽来代替手动循环和等待数据。
  • 不再需要"usleep()"或者“waitForReadyRead()”。
  • 使用成员函数“handleReadyRead()”来响应串口的"readyRead()"信号。
  • 如果接收到的数据不完整,会被存储在“buffer”中,直到有足够的数据可以处理。
    这段代码更加简洁,遵循了Qt的编程范式,应该提供更稳定和高效的串口读取操作。
    在Qt中,通常不需要直接创建并使用"QThread"的实例来处理串口读写。因为“QSerialPort”已经在内部处理好了异步读写的问题,你只需要关心如何设置串口参数,以及如何连接信号与槽来处理数据即可。
    如果你需要从UI线程之外操作串口(比如,UI线程需要保持响应用户操作,而串口操作可能比较耗时),可以将串口处理的逻辑放在一个单独的类中(比如你的“MyQSerialPort”类),然后在主线程中实例化这个类,并将它移动到一个新的“QThread”中。
    以下是一个简单的示例说明如何创建一个线程并在其中运行“MyQSerialPort”类的实例:
#include <QCoreApplication>
#include <QSerialPort>
#include <QThread>
#include "MyQSerialPort.h"  // 假设这是你的MyQSerialPort类的头文件

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QSerialPort *serialPort = new QSerialPort();  // 创建串口对象
    // 设置串口参数(端口名、波特率等)

    MyQSerialPort *mySerialPortWorker = new MyQSerialPort(serialPort);  // 创建你的串口处理对象
    QThread *thread = new QThread();  // 创建一个新线程

    mySerialPortWorker->moveToThread(thread);  // 将串口处理对象移动到新线程

    // 当线程启动时,开始串口读取操作
    QObject::connect(thread, &QThread::started, mySerialPortWorker, &MyQSerialPort::startRead);

    // 当线程结束时,进行清理
    QObject::connect(thread, &QThread::finished, mySerialPortWorker, &QObject::deleteLater);
    QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);

    // 开始线程
    thread->start();

    // 退出程序时,确保线程正确结束
    int ret = a.exec();
    thread->quit();
    thread->wait();
    delete serialPort;  // 释放串口资源

    return ret;
}

如果需要保证读取到完整的数据包,并且在处理后还能继续读取剩下的数据,可以在处理数据之后调整"buffer",而不是完全清空它。你需要确保"buffer"中的数据始终是完整的数据包。当处理完一个完整的数据包之后,应当只移除已经处理过的那部分数据。
下面是如何调整“processData”函数来处理这种情况的实例:

void processData(const QByteArray &data) {
    // 将新接收的数据添加到buffer中
    buffer.append(data);
    
    // 当buffer中的数据足够组成至少一个完整的数据包时,进行处理
    while (buffer.size() >= sizeof(Fixed_data_back)) {
        // 从buffer的开始部分复制一个数据包大小的数据
        Fixed_data_back m_data;
        memcpy(&m_data, buffer.constData(), sizeof(Fixed_data_back));

        // 检查这个数据包
        if(CheckSumGuideData(reinterpret_cast<uint8_t*>(&m_data))) {
            // 数据校验成功,处理数据
            // ...

            // 从buffer中移除已经处理过的数据
            buffer = buffer.mid(sizeof(Fixed_data_back));
        } else {
            qDebug() << QStringLiteral("接收错误");
            // 如果校验失败,决定如何处理错误。一种做法是移除错误的数据包,
            // 另一种是保持buffer不变,等待更多数据。
            
            // 例如,如果你想要丢弃错误的数据包:
            buffer = buffer.mid(sizeof(Fixed_data_back));
            // 或者,如果你想要等待更多数据,可能是因为数据包的一部分尚未到达:
            break;
        }
    }
}

在上述代码中,我们不再简单地清除整个“buffer”,而是在处理完一个“Fixed_data_back”结构后,使用“QByteArray::mid()”函数来移除那部分已经处理过的数据。这样,“buffer”中剩余的数据仍然保留,可以用来检测更多的完整数据包。
此外,如果检测到错误的数据包(即校验失败),需要你决定是丢弃它还是保留在“buffer”中,等待可能的额外数据。在某些情况下,如果你怀疑接收到的数据只是暂时不完整,你可能希望等待更多数据的到来,而不是立即丢弃它。
上述processData函数可能会出现被调用传入数据时由于processData函数中的while还没执行完成导致数据未及时接收,即如果处理数据(比如校验和处理)的时间过长,可能会导致在处理期间到达的新数据没有被及时读取,从而可能导致数据缓冲区溢出或数据丢失。
为了解决这个问题,有几个策略可以考虑:
1、减少数据处理时间
优化“processData”函数以减少数据处理所需的时间。例如,可以优化“CheckSumGuidData”函数,或者减少在循环中执行的操作数量,这有助于减少数据处理的时间,从而减少对新数据处理的延迟。
2、使用单独的处理线程
将数据处理逻辑放在单独的线程中执行。这样、主线程可以专注于串口读取数据,而处理数据的工作在另一个线程中进行,两者之间通过线程安全的队列或其他机制进行通信。

void processData(const QByteArray &data) {
    buffer.append(data);

    // 检查是否有足够的数据
    while (buffer.size() >= sizeof(Fixed_data_back)) {
        // 将数据传送到处理线程
        QByteArray package = buffer.left(sizeof(Fixed_data_back));
        QMetaObject::invokeMethod(processingThread, "handleData", Q_ARG(QByteArray, package));

        // 移除已经传送的数据
        buffer.remove(0, sizeof(Fixed_data_back));
    }
}

在这个策略中,“processingThread”是负责数据处理的线程,而“handleData”是在该线程中执行数据处理的方法。
3、数据分段处理
如果可能,尝试分段处理缓冲区的数据,而不是一次处理完所有数据。例如,可以在每次事件循环中只处理一定数量的数据包,然后返回时间循环,以便及时处理新到达的数据。

void processData(const QByteArray &data) {
    buffer.append(data);

    int packetsProcessed = 0;
    while (buffer.size() >= sizeof(Fixed_data_back) && packetsProcessed < MAX_PACKETS_PER_LOOP) {
        // 处理数据...

        packetsProcessed++;
    }
}

在这里,“MAX_PACKETS_PER_LOOP”是你定义的每次事件循环中可以处理的最大数据包数量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RS422通信协议是一种常用的串行通信协议,使用差分信号传输数据,具有高速传输、抗干扰能力强等优点,常用于长距离数据传输。以下是一个简单的C语言实现RS422通信协议的收发示例代码。 首先,需要打开串口设备文件,并进行串口的初始化,示例代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> int rs422_init(char *devname, int baudrate) { int fd; struct termios options; fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); if(fd < 0) { perror("open failed"); return -1; } fcntl(fd, F_SETFL, 0); tcgetattr(fd, &options); cfsetispeed(&options, baudrate); cfsetospeed(&options, baudrate); options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CRTSCTS; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_iflag &= ~(INLCR | ICRNL | IGNCR); options.c_oflag &= ~(ONLCR | OCRNL); options.c_cc[VTIME] = 0; options.c_cc[VMIN] = 1; tcsetattr(fd, TCSANOW, &options); return fd; } ``` 接下来,可以使用read和write函数进行数据的收发,示例代码如下: ```c int rs422_send(int fd, unsigned char *data, unsigned char len) { int nbytes; nbytes = write(fd, data, len); if(nbytes < 0) { perror("write failed"); return -1; } return 0; } int rs422_receive(int fd, unsigned char *data, unsigned char len) { int nbytes; nbytes = read(fd, data, len); if(nbytes < 0) { perror("read failed"); return -1; } return nbytes; } ``` 需要注意的是,以上代码仅供参考,实际使用时需要根据具体的应用场景进行修改和优化。同时,对于一个完整的RS422通信应用程序,还需要考虑各种异常情况的处理,如错误码的处理、超时的处理等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值