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个字符的数据,不会发生接收数据不全的问题。
接着使用两台电脑,两个串口模块连接进行测试,同样速率和数据,接收到的数据会一段一段的到达。
处理方法
- 如果数据有帧头和帧尾,使用全局缓冲区,将接收到的数据放进缓冲区,判断是否满足整包,满足再进行包处理,不满足一直接数据
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();
}