QT串口读取Serial->readAll()踩过的坑


Chapter1 QT串口读取Serial->readAll()踩过的坑

原文链接:https://blog.csdn.net/weixin_43491568/article/details/103183541

博主在制作一个QT软件与单片机进行数据传输(16进制的数据,结束标志是\r\n)的时候数据一直拿不完全。经过多日的研究终于成功了。

坑一:

QByteArray data;
data = Serial->readAll();//拿串口中的数据

因为串口设置的是8位数据位,因此拿到的数据是32位,但是博主想要的是“00 10 00 00 00 00 01 00 28 00 0A 01 01 01 01 00 \r\n”。但是QT使用qDebug()拿到是,如图(这样的数据不方便处理):

修改的代码如下:

QByteArray data;
data = Serial->readAll();//拿串口中的数据
temp.append(data);
if(temp.contains('\n'))//只有等到\n的时候才能进入
{
		xxxx//
		temp.clear();
 }

这样处理后拿到的数据是:如图
在这里插入图片描述

坑二

单片机发送数据的时候需要加一个延时函数,不然还是会出现类似坑一的问题。

未完待续

Chapter2 [QT串口上位机BUG解决]json解析数据bug以及接收数据问题

原文链接:https://blog.csdn.net/sinat_58149788/article/details/130455112

问题描述

主要是串口每次只能打印32个字节的数据,如果多了就会把数据放到缓冲区,这导致要二次打印,使json格式不连续,就构不成json格式,如下图。这样就不能解析出json数据了。
在这里插入图片描述

原因分析:

主要原因就是QT上位机数据最对打印32位,这导致json格式不完整,这样就不能解析了。

解决方案:

这里我有两个办法分别是对应的数据采集端(单片机)一次只发送32位数据。第二个是上位机进行字符串拼接

一、是数据采集端(单片机mcu)来解决

我采集电压电流是通过Stm32F103zet6在通过I2C与INA226通信,下面是数据采集端(单片机mcu)程序,这里的解决办法是设置一个标志位,该标志位用取反函数让其有两种状态,0和1.
然后我在用if判断不同状态发送不同的json格式数据,并且保证发送的数据长度必须小于32个8位数据。

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "USART.h"
#include "stdio.h"
#include "Wire.h"
#include "AHT10.h"
#include "SW3526.h"
#include "INA226.h"
//本案例是以一个AHT10温湿度传感器来显示I2C协议!
float Temp;
float Humi;
float ADC_Voltage;
u8 USART_FLAGS;
int main(void){
	USART_Begin(115200);
		//AHT10_Reset();
	//AHT10_Init();
	INA226_Init();
  //SW3526_Init();
	//printf("INA226_ID=0x%x\r\n",INA226_Get_Addr(0xFF)>>8);	

	while(1){
		//AHT10_Read_Data(&Temp,&Humi);
	  
		//SW3526_Init();
		//I2CS_ACK();
		//I2CS_ACK();
		//SW3526_Send_8_Reg(0x3A,0x01);
		//ADC_Voltage=SW3526_Read_8_Reg(0x3B)<<4|SW3526_Read_8_Reg(0x3C);
		//SW3526_Send_8_Reg(0x3A,0x01);
		//ADC_Voltage=SW3526_Read_8_Reg(0x30)*16*10;
		//SW3526_Fast_charge_protocol();
		//SW3526_Buck_ON_OFF(1);
		//SW3526_Fast_charge_protocol();
		//SW3526_Send_8_Reg(0x05,0x00);
		//printf("输出协议=%d\r\n",SW3526_Read_8_Reg(0x06));
		//printf("V=%.2f\r\n",SW3526_Buck_OUTPUT_Voltage()/1000);
		//Delay_ms(500);
		//printf("Temp=%.2f\r\nHumi=%.2f\r\n",Temp,Humi);
		
		USART_FLAGS=~USART_FLAGS;
		if(USART_FLAGS==0){
			printf("{\"current\":%d,\"voltage\":%d}",(int)(INA226_Get_Current_dat()),(int)(INA226_Get_Voltage_dat()));
			Delay_ms(400);
		}else{
			printf("{\"power\":%d}",(int)INA226_Get_Power_dat());
			Delay_ms(10);
		}
	  //printf("Voltage=%.4fV\r\n",INA226_Get_Voltage_dat()/1000);
	  //printf("CURRENT=%.2fmA\r\n",INA226_Get_Current_dat());
		
	 // printf("CURRENT=%.4fW\r\n",INA226_Get_Power_dat());		
	}
}

但是这里会出现一个问题当串口发送另外一个数据时电流,电压会短暂显示0,这里我们可以缩短单片机发送功率的数据延时来减少上位机的电流。电压出现0的问题。或者是修改上位机代码,如图二。
在这里插入图片描述
上位机的写入lcd_Number的数据,因为单片机是分时发送数据,当发送power的数据时,电流、电压的json解析失败,那存放电流,电压的数据肯定为 0
这时我们用一个if判断,当读取的电压、电流 == 0时,就不将值写入到lcd_Number。这样就完美的解决了这个问题
下面是QT串口上位机的串口接收数据代码的修改

QByteArray Widget::DateRead()  //接收数据
{



    QByteArray temp=serialPort->readAll();


    QString str=ui->recelives->toPlainText();
    str=QString::fromLocal8Bit(temp);
    ui->recelives->appendPlainText(str);



    //解析 JSON格式
   // QString json_str="{\"current\":15,\"voltage\":29,\"yan\":29}";
    QString receive =QString::fromLocal8Bit(temp.constData());
    qDebug() << receive ;
    qDebug() << receive.length();
    QJsonDocument doc=QJsonDocument::fromJson(receive.toUtf8());
    QJsonObject obj=doc.object();

    QJsonValue Current =obj.value("current");
    QJsonValue Voltage =obj.value("voltage");

    QString Current_cp;
    Current_cp.sprintf("%d",Current.toInt());
    QString Voltage_cp;
    Voltage_cp.sprintf("%d",Voltage.toInt());
    //qDebug() << Current_cp ;
   // qDebug() << Voltage_cp ;
    //ui->current_dat->display(Current.toInt());
   // ui->voltage_dat->dosplayVoltage.toInt()();
    if(Current.toInt()!=0 && Voltage.toInt()!=0){
    ui->current_dat->display(Current_cp);
    ui->voltage_dat->display(Voltage_cp);
    }
    temp.clear();
    return temp;
}

二、上位机进行字符串拼接

博主水平有限,现在还不好解决

Chapter3 QT 串口接收数据不完整解决思路

原文链接:https://blog.csdn.net/qq_44084616/article/details/131240790

一、问题描述

在串口通信时,会经常遇到,在接收一帧数据时,QSerialPort::readyRead()会触发多次,即接收一包数据需要多次接收才能完整得到数据帧。

二、解决思路

延迟接收: QSerialPort::readyRead()触发时不要立刻去接收数据,而是等待readyRead的最后一次触发时读数据。
如何等待最后一次readyRead: 用一个单触发定时器,readyRead触发时启动定时器,当定时器timeout(),可以认为是最后一次readyRead()。

三、贴代码思路

//!
    m_serial = new QSerialPort;
    connect(m_serial,&QSerialPort::readyRead,this,&MainWindow::slot_serialport_readyRead);

    //! 轮询定时器-周期性发送数据
    pTimerSend = new QTimer(this);
    pTimerSend->setTimerType(Qt::PreciseTimer);
    connect(pTimerSend, &QTimer::timeout, this, &MainWindow::slot_com_timeout_send);
    // pTimerSend->start(200); //串口连接成功后启动,此处仅为体现轮询周期为200ms

    //! 串口模式-数据延迟接收-保证数据完整
    pTimerRecv = new QTimer(this);
    pTimerRecv->setTimerType(Qt::PreciseTimer);
    pTimerRecv->setSingleShot(true); //只触发一次
    connect(pTimerRecv, &QTimer::timeout, this, &MainWindow::slot_serialport_delay_recv_timeout);


//! 串口接收信号
void MainWindow::slot_serialport_readyRead()
{
	//! 定时器重新启动,直到该函数不再触发(超过50ms),定时器触发
    pTimerRecv->start(50);	//4K数据,50ms的延迟接收完全没问题
}

//! 串口延迟接收
void MainWindow::slot_serialport_delay_recv_timeout()
{
    QByteArray Recv = m_serial->readAll();
    //! qDebug()<<"=="<<Recv;
    //! qDebug()<<"###"<<Recv.toHex(' ');
    //! 处理接收的数据
    com_recv_data_process(Recv);
}

四、注意事项

延迟接收时长问题: 延迟时长应小于轮询周期
通信状况复杂不适用:若通信的串口中会打印调试信息或其他信息时,延迟接收可能出现问题,例如:由于串口中有打印的调试信息,readyRead()信号不断发生,导致pTimerRecv不断重启而无法触发延迟接收函数,从而导致收不到或收到远超应收字节数的数据。
五、总结
以上描述方式仅适合干净的通信环境,若想一劳永逸的适配各种环境,这里提供一个思路。如下:开辟一个缓存,实时接收串口数据(仅存),开一个子线程,识别缓存中的数据,去掉无法识别的数据,取出已识别数据。

Chapter4 QT 中串口接收数据完整解决方案

原文链接:https://blog.csdn.net/weixin_45013621/article/details/125939331

串口是在QT中比较常用的一个类,通常也会有这样或者那样的问题,比如说串口的配置问题,我明明在引用了QSerialPort但是为什么不能用呢?等等。

今天要分享的问题就如标题所言,在使用QSerialPort类读取串口消息时,有时候会出现接收不全的现象,针对这个问题进行的解决。

通常情况下大家使用串口接收消息是这样做的

QSerialPort *m_serialport = new QSerialPort();
connect(m_serialport, &QSerialPort::readyRead, this, &BllProjecr::ReadData);
void BllProject::ReadData()
{
    QString str;
    QByteArray buffer = m_serialport->readAll();
    str += buffer; 
    //然后把str拿去解析。
}

串口传输数据有时候会有延迟 。

比如说串口本来该传的数据是“Vete is so Cool\r”,但是呢,由于传输延迟问题可能就是分两次传的该信息 得到的就是“Vete is”和“ so Cool\r”。对于串口回传信息的解析造成一点困扰。

所以呢就要想办法解决这个问题。这里给出的解决方案是利用QTimer定时器来进行读取。

QTimer *m_timer = new QTimer();
QSerialPort *m_serialport =new QSerialPort();
QByteArray m_buffer;
connect(m_serialport ,&QSerialport::readyRead,this&BllProject::bufferData);
connect(m_timer,&QTimer::timeout,this,&BllProject::ReadData);
void BllProject::bufferData()
{
     m_timer->start(50);
     m_buffer.append(m_serialport->readAll());
}
void BllProject::ReadData()
{
    m_timer->stop();
    // 把m_buffer 拿去解析。
    m_buffer.clear();
}

上面这个方法就是针对此问题的改进,给信息来一个50ms的缓冲时间,消除串口数据传输的时间差,这样就可以收到一次下发指令之后串口返回的完整数据了。

Chapter5 Qt串口接收数据不全

原文链接:https://blog.csdn.net/weixin_44965579/article/details/129026072

运行环境
版本:Qt5.14

IDE:QtCreator

问题描述

我使用的是readyRead()信号触发接收函数,使用serial->readAll()进行数据读取。在一台电脑上使用虚拟串口工具生成串口,使用串口助手每10ms发送一个262个字符的数据,不会发生接收数据不全的问题。

接着使用两台电脑,两个串口模块连接进行测试,同样速率和数据,接收到的数据会一段一段的到达。

处理方法

  1. 如果数据有帧头和帧尾,使用全局缓冲区,将接收到的数据放进缓冲区,判断是否满足整包,满足再进行包处理,不满足一直接数据
QByteArray Port_buffer;//需要定义在.h类里作为全局变量,不能放函数里
//下面是.cpp里
QByteArray data = serial->readAll();
Port_buffer.append(data);
if(Port_str.startsWith("FFFAFFFA")&&Port_str.contains("ED\r\n"))//帧头FFFAFFFA,帧尾ED
{
    //缓冲区里数据以FFFAFFFA开头,并且包含一个ED\r\n,表明至少有一个完整包
    index_ED = Port_buffer.indexOf("ED\r\n");//定位到帧尾的位置
    QByteArray pack = Port_buffer.left(index_ED+4);//取一个包的数据
    Port_buffer.remove(0,index_ED+4);
    //去空格、检查数据包
    //数据处理
}else if(!Port_str.startsWith("FFFAFFFA")&&Port_buffer.contains("FFFAFFFA"))
    {
        //不以FFFAFFFA开头,说明数据不完整,如果里面包含数据头,则直接删除到数据头的位置
        Port_buffer.remove(0,Port_buffer.indexOf("FFFAFFFA"));
    }

上面方式存在一个问题:使用虚拟串口本地测,不会发生数据分段接收,每次都是一个完整的数据进来,上面代码每次只允许一次,会发生Port_buffer里一直存数据包,时间长了就蹦了,处理方法:可以把接收改成定时处理。但是虚拟串口也只是理想情况,我的软件肯定是通过串口线连接到另外一台设备,所以这样应该也是可以的
在这里插入图片描述

上图data是从串口中取出的数据,直到存满完整数据才进行数据处理
在这里插入图片描述

上图使用虚拟串口,每次触发readyread都至少有一整个数据包(可能因为界面还在刷新数据等原因导致串口里滞留多个数据包),但是只触发一次读取数据,导致缓冲区里数据处理不完,这里可以用定时处理方式解决。

2.如果没有帧头帧尾,可以判断是否以\r\n结尾,判断是否收到一个完整数据包

if(Port_str.endsWith("\r\n"))//修改成endsWith("\r\n")就可以判断是否结束
{
 
}

其他

考虑过增加串口接收线程,但是代码会很复杂,上图里每2ms就能触发一次读数据,说明绰绰有余。我的界面刷新数据也是每次接收都刷新,还画个曲线图,再加上每10ms发个包,都能接收的过来。

建议:除非需要多个串口同时接数据,需要使用到多线程。如果只有一个串口接数据,就别折腾多线程了。

Chapter6 Qt中接收串口数据不完整、分段的解决方法

原文链接:https://blog.csdn.net/weixin_48424192/article/details/109483013

场景:

最近在串口通信时碰到了一个问题,向485串口发送指定报文,会收到一条关于压力数值的数据。但将其qDebug打印出来却发现数据被分成了两部分依次打印,之后通过验证确定了问题出在readyRead的信号与槽这一部分。

在槽函数的入口处加入 qDebug()<<“-”; 可以发现 “-” 连同分段的数据被打印了两次,也就是说在收到readyRead的信号后,串口的数据被分成两段,各执行了一次readMyCom槽函数。

解决方法:

在网上搜索后,发现有很多解决方法,比如通过数据的帧头帧尾进行判断是否为完整数据帧、通过帧头和数据帧长来判断完整帧、加入定时器将延时读取的数据存入缓冲区然后timeout后再一次性读取数据,这里我主要介绍定时器延时读取的方法。

改写连接函数:

在readyRead信号对应的槽函数中设置定时器,并将读取到的数据储存在缓冲区中,待定时器timeout再一并读取缓冲区中的数据,缺点是如果极高频率的串口通讯可能会有问题。

//改写前的connect
//connect(my_serialport,&QSerialPort::readyRead,this,&MainWindow::readMyCom);

//改写后的connect
connect(my_serialport,&QSerialPort::readyRead,this,[=]()
{
   timer->start(100);//设置100毫秒的延时
   QByteArray buffer.append(my_serialport->readAll());//将读到的数据放入缓冲区
});
connect(timer,&QTimer::timeout,this,&MainWindow::readMyCom);//timeout执行真正的读取操作

void Widget::readMyCom()
{
   time->stop();//关闭定时器
   QByteArray data = buffer;//读取缓冲区数据
   buffer.clear();//清除缓冲区
   ....//数据处理操作
}

Chapter7 Qt–官方串口库串口数据接收不完整解决方法总结(一)

原文链接:https://blog.csdn.net/qq_28877125/article/details/103520013

问题

Qt 官方串口库QSerialPort的 readyRead() 信号,只要有数据就抛出,这就导致一条数据分多次抛出。由于正常的数据没有固定的开头和结尾,这就导致无法获取正常的一组数据。

方法一:确定一个完整的开始标志和结尾标志

解决思路:增加接收延时功能,把多次读取的数据保存到缓冲区,延时结束,一次性读取数据
示例:

QSerialPort *serialPort= new QSerialPort();
connect(serialPort, SIGNAL(readyRead()), this, SLOT(slotReadData()));

void MainWindow::slotReadData()
{
    static QByteArray sumData;
    QByteArray tempData = serial->readAll();

    if(!tempData.isEmpty())
    {
        sumData.append(tempData);
        if(sumData.contains("\n")) // 检测到换行符
        {
            do_DataHandler(sumData); // 数据解析

            sumData.clear();
        }
    }
    tempData.clear();
}

void MainWindow::do_DataHandler(QByteArray BufferData)
{
    /* *
     *
     * 筛选出“{x,yyyyyy}”格式的数据
     *
     * */

    //异常类:无头且变量为空,已丢失头部,数据不可靠,直接返回
    if ((!BufferData.contains("{"))&(PasteData.isNull()))
    {
        return;
    }
    //第一种:有头无尾,先清空原有内容,再附加
    if ((BufferData.contains("{"))&(!BufferData.contains("}")))
    {
        PasteData.clear();
        PasteData.append(BufferData);
    }
    //第二种:无头无尾且变量已有内容,数据中段部分,继续附加即可
    if ((!BufferData.contains("{"))&(!BufferData.contains("}"))&(!PasteData.isNull()))
    {
        PasteData.append(BufferData);
    }
    //第三种:无头有尾且变量已有内容,已完整读取,附加后输出数据,并清空变量
    if ((!BufferData.contains("{"))&(BufferData.contains("}"))&(!PasteData.isNull()))
    {
        PasteData.append(BufferData);
        ReadData = PasteData;
        PasteData.clear();
    }
    //第四种:有头有尾(一段完整的内容),先清空原有内容,再附加,然后输出,最后清空变量
    if ((BufferData.contains("{"))&(BufferData.contains("}")))
    {
        PasteData.clear();
        PasteData.append(BufferData);
        ReadData = PasteData;
        PasteData.clear();
    }

    QString MidStr;
    QStringList list= ReadData.split("{");

    for(int i=0; i < list.length();i++)
    {
        if(!list.at(i).isEmpty())
        {
            MidStr = list.at(i);
            if(!MidStr.isEmpty())
            {
                MidStr.insert(0, '{');
                //qDebug() << "ReadData:" << ReadData;
                qDebug() << "MidStr:" << MidStr;
            }
        }
    }
    ReadData.clear();
}

方法二:定时接收

解决思路:增加接收延时功能,把多次读取的数据保存到缓冲区,延时结束,一次性读取数据

示例:

QByteArray baRcvData;

QSerialPort *serialPort= new QSerialPort();
QTimer *timer = new QTimer();
connect(serialPort, SIGNAL(readyRead()), this, SLOT(slotReadData()));
connect(timer, SIGNAL(timeout()), this, SLOT(timeUpdate())));

void MainWindow::slotReadData()
{
	timer->start(100);//启动定时器,接收100毫秒数据(根据情况设定)
	baRcvData.append(serialPort->readAll());
} 

void MainWindow::timeUpdate()
{
	timer->stop();
	if(baRcvData.length()!=0)
	{
		qDebug()<<baRcvData;
	}
	baRcvData.clear();
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值