QT串口助手(五):文件操作

作者:zzssdd2

E-mail:zzssdd2@foxmail.com

一、前言

开发环境:Qt5.12.10 + MinGW

功能

  • 文件的发送
  • 数据的保存

知识点

  • QFile类的使用
  • QTimer类的使用
  • 文本的转码与编码识别
  • QPushButtonQProgressBar控件的使用
    在这里插入图片描述

二、功能实现

本章功能主要包含两个方面,一是通过串口发送选定的文本文件,二是将接收的数据保存为本地文本文件。最后还有对《QT串口助手(三):数据接收》章节内容进行一个补充扩展。

2.1、文件打开

选择文件按钮点击后,触发点击信号对应的槽函数,在槽函数中进行文件的打开与读取:

/*选择并打开文件*/
QString curPath = QDir::currentPath();  //系统当前目录
QString dlgTitle = "打开文件";  //对话框标题
QString filter = "文本文件(*.txt);;二进制文件(*.bin *.dat);;所有文件(*.*)"; //文件过滤器
QString filepath = QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter);
QFileInfo fileinfo(filepath);

if (filepath.isEmpty())
{
    QMessageBox::warning(this,"警告","文件为空");
    return;
}
//文件路径显示到发送框
ui->Send_TextEdit->clear();
ui->Send_TextEdit->appendPlainText(filepath);

QFile file(filepath);
if (!file.exists())
{
    QMessageBox::warning(this,"警告","文件不存在");
    return;
}
if (!file.open(QIODevice::ReadOnly))
{
    QMessageBox::warning(this,"警告","文件打开失败");
    return;
}

2.2、编码判断

因为应用程序默认使用的编码为UTF-8,如果打开GBK格式编码的文件就会乱码,所以需要判断文件的编码,如果不是UTF-8则需要对文件进行编码转换。

/* 设置应用程序的编码解码器 */
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
QTextCodec::setCodecForLocale(codec);
  • [static] QTextCodec *QTextCodec::codecForName(const char *name)

    Searches all installed QTextCodec objects and returns the one which best matches name; the match is case-insensitive. Returns 0 if no codec matching the name name could be found.

  • [static] void QTextCodec::setCodecForLocale(QTextCodec *c)

    Set the codec to c; this will be returned by codecForLocale(). If c is a null pointer, the codec is reset to the default.

    This might be needed for some applications that want to use their own mechanism for setting the locale.

    Warning: This function is not reentrant.

/* 判断编码 */
QTextCodec::ConverterState state;
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
FileText = codec->toUnicode(data.constData(),data.size(),&state);
//若有无效字符则是GBK编码
if (state.invalidChars > 0) 
{
    //转码后返回
    FileText = QTextCodec::codecForName("GBK")->toUnicode(data);
}
else
{
    FileText =  data;
}

对文件进行上述的处理后,如果是GBK编码则也能够正确的读取了。

  • QString QTextCodec::toUnicode(const char *input, int size, QTextCodec::ConverterState *state = nullptr) const

    Converts the first size characters from the input from the encoding of this codec to Unicode, and returns the result in a QString.

    The state of the convertor used is updated.

  • QString QTextCodec::toUnicode(const QByteArray &a) const

    Converts a from the encoding of this codec to Unicode, and returns the result in a QString.

在这里插入图片描述

2.3、文件读取

文件打开后,需要对文件类型进行判断,然后进行文件数据的读取:

/*判断文件类型*/
int type = 0;
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(filepath);
if (mime.name().startsWith("text/")) 
{
    type = 1;	//文本文件
} 
else if (mime.name().startsWith("application/")) 
{
    type = 2;	//二进制文件
}

QMimeType QMimeDatabase::mimeTypeForFile(const QString&fileName, QMimeDatabase::MatchMode mode = MatchDefault) const

Returns a MIME type for the file named fileName using mode.

This is an overloaded function.

QMimeType 类描述文件或数据的类型,由 MIME 类型字符串表示,获取到文件类型后接下来就知道应该使用什么方法读取文件内容了。常见文件类型如下:

描述(startsWith)类型示例
text普通文本text/plain, text/html, text/css, text/javascript
image图像文件(包含动态gif)image/gif, image/png, image/jpeg, image/bmp, image/webp
audio音频文件audio/wav, audio/mpeg, audio/midi, audio/webm, audio/ogg
video视频文件video/mp4, video/x-flv, video/webm, video/ogg
application二进制数据application/xml, application/pdf
/*读取文件*/
switch(type)
{
    case 1:
    {
        //QIODevice读取普通文本
        QByteArray data = file.readAll();
        file.close();
        if (data.isEmpty())
        {
            QMessageBox::information(this, "提示", "文件内容空");
            return;
        }
        /* 判断编码 */
    }
    break;
    case 2:
    {
        int filelen = fileinfo.size();
        QVector<char> cBuf(filelen);//储存读出数据
        //使用QDataStream读取二进制文件
        QDataStream datain(&file);
        datain.readRawData(&cBuf[0],filelen);
        file.close();
        //char数组转QString
        FileText = QString::fromLocal8Bit(&cBuf[0],filelen);
    }
    break;
}
  • QByteArray QIODevice::readAll()

    Reads all remaining data from the device, and returns it as a byte array.

    This function has no way of reporting errors; returning an empty QByteArray can mean either that no data was currently available for reading, or that an error occurred.

  • int QDataStream::readRawData(char *s, int len)

    Reads at most len bytes from the stream into s and returns the number of bytes read. If an error occurs, this function returns -1.

    The buffer s must be preallocated. The data is not decoded.

  • 关于QVector:QVector类是一个提供动态数组的模板类。QVector是Qt的通用容器类之一,它将其项存储在相邻的内存位置并提供基于索引的快速访问。例如上面代码定义了一个大小为filelen的char类型的数组用来储存读取的二进制数据(相对与普通数组而言,QVector数组项可以动态增加,能够避免访问越界等问题)。

  • QT中使用QTextStream或QIODevice类读写普通文本文件,使用QDataStream类读写二进制文本文件

最后将读取到的文件大小信息和内容显示到接收框并标记有待发送文件:

//显示文件大小信息
QString info = QString("%1%2").arg("文件大小为:").arg(FileText.length());
ui->Receive_TextEdit->clear();
ui->Receive_TextEdit->append(info);
//显示文件内容
if (ui->HexDisp_checkBox->isChecked()) 
{
    ui->Receive_TextEdit->setPlainText(FileText.toUtf8().toHex(' ').toUpper());
} 
else 
{
    ui->Receive_TextEdit->setPlainText(FileText);
}
//设置显示焦点在最顶部
ui->Receive_TextEdit->moveCursor(QTextCursor::Start,QTextCursor::MoveAnchor);

/*标记有文件发送*/
isSendFile = true;
FrameCount = 0;
ProgressBarValue = 0;

2.4、文件发送

此时在标记了有文件发送的情况下,点击发送按钮则是发送文件:

/*
    函   数:on_Send_Bt_clicked
    描   述:发送按键点击信号槽
    输   入:无
    输   出:无
*/
void Widget::on_Send_Bt_clicked()
{
    if (isSerialOpen != false)
    {
        if (isSendFile)	//发送文件数据
        {
            if (ui->Send_Bt->text() == "发送") 
            {
                ui->Send_Bt->setText("停止");
                SendFile();
            } 
            else 
            {
                ui->Send_Bt->setText("发送");
                FileSendTimer->stop();
            }
        }
        else	//发送发送框数据
        {
            SerialSendData(SendTextEditBa);
        }
    }
    else
    {
        QMessageBox::information(this, "提示", "串口未打开");
    }
}

/*
    函   数:SendData
    描   述:定时器发送文件
    输   入:无
    输   出:无
*/
void Widget::SendFile(void)
{
    /*按设置参数发送*/
    FrameLen = ui->PackLen_lineEdit->text().toInt(); // 帧大小
    FrameGap = ui->GapTim_lineEdit->text().toInt();  // 帧间隔
    int textlen = Widget::FileText.size();           // 文件大小
    if (FrameGap <= 0 || textlen <= FrameLen)
    {
        //时间间隔为0 或 帧大小≥文件大小 则直接一次发送
        serial->write(FileText.toUtf8());
        ui->Send_Bt->setText("发送");
    }
    else
    {
        //按设定时间和长度发送
        FrameNumber = textlen / FrameLen; // 包数量
        LastFrameLen = textlen % FrameLen; // 最后一包数据长度
        //进度条步进值
        if (FrameNumber >= 100) 
        { 
            ProgressBarStep = FrameNumber / 100;
        } 
        else 
        {
            ProgressBarStep = 100 / FrameNumber;
        }
        //设置定时器
        FileSendTimer->start(FrameGap);
    }
}

设置一个定时器,将文件按照设定的帧大小帧间隔来发送:

/*文件帧发送定时器信号槽*/
FileSendTimer = new QTimer(this);
connect(FileSendTimer,SIGNAL(timeout()),this,SLOT(File_TimerSend()));

/*
    函   数:File_TimerSend
    描   述:发送文件定时器槽函数
    输   入:无
    输   出:无
*/
void Widget::File_TimerSend(void)
{
    if (FrameCount < FrameNumber)
    {
        serial->write(FileText.mid(FrameCount * FrameLen, FrameLen).toUtf8());
        FrameCount++;
        //更新进度条
        ui->progressBar->setValue(ProgressBarValue += ProgressBarStep);
    }
    else
    {
        if (LastFrameLen > 0)
        {
            serial->write(FileText.mid(FrameCount * FrameLen, LastFrameLen).toUtf8());
            ui->progressBar->setValue(100);
        }
        /*发送完毕*/
        FileSendTimer->stop();
        FrameCount = 0;
        ProgressBarValue = 0;
        FrameNumber = 0;
        LastFrameLen = 0;
        QMessageBox::information(this, "提示", "发送完成");
        ui->progressBar->setValue(0);
        ui->Send_Bt->setText("发送");
    }
}

QString QString::mid(int position, int n = -1) const

Returns a string that contains n characters of this string, starting at the specified position index.

Returns a null string if the position index exceeds the length of the string. If there are less than n characters available in the string starting at the given position, or if n is -1 (default), the function returns all characters that are available from the specified position.

Example:

 QString x = "Nine pineapples";
 QString y = x.mid(5, 4);            // y == "pine"
 QString z = x.mid(5);               // z == "pineapples"

2.5、数据保存

保存数据按钮按下时,将接收框数据按文本方式进行保存:

/*
    函   数:on_SaveData_Button_clicked
    描   述:保存数据按钮点击槽函数
    输   入:无
    输   出:无
*/
void Widget::on_SaveData_Button_clicked()
{
    QString data = ui->Receive_TextEdit->toPlainText();

    if (data.isEmpty())
    {
        QMessageBox::information(this, "提示", "数据内容空");
        return;
    }

    QString curPath = QDir::currentPath();            //获取系统当前目录
    QString dlgTitle = "保存文件";                     //对话框标题
    QString filter = "文本文件(*.txt);;所有文件(*.*)";  //文件过滤器
    QString filename = QFileDialog::getSaveFileName(this,dlgTitle,curPath,filter);
    if (filename.isEmpty())
    {
        return;
    }
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly))
    {
        return;
    }

    /*保存文件*/
    QTextStream stream(&file);
    stream << data;
    file.close();
}

QTextStream::QTextStream(FILE *fileHandle, QIODevice::OpenModeopenMode = QIODevice::ReadWrite)

Constructs a QTextStream that operates on fileHandle, using openMode to define the open mode. Internally, a QFile is created to handle the FILE pointer.

This constructor is useful for working directly with the common FILE based input and output streams: stdin, stdout and stderr. Example:

 QString str;
 QTextStream in(stdin);
 in >> str;

三、扩展

这里对接收模块的功能进行一个补充。上面说了应用程序默认编码是UTF-8,那么如果在ascii显示模式下收到的数据是GBK格式的编码就会造成显示乱码,所以需要对接收数据进行编码判断,如果是GBK编码则进行转换显示。

/*
    函   数:SerialPortReadyRead_slot
    描   述:readyRead()信号对应的数据接收槽函数
    输   入:无
    输   出:无
*/
void Widget::SerialPortReadyRead_slot()
{
    /*读取串口收到的数据*/
    QByteArray bytedata = serial->readAll();
    
    //......省略
    
    if(ui->HexDisp_checkBox->isChecked() == false)  //ascii显示
    {
        /* 判断编码 */
        QTextCodec::ConverterState state;
        //调用utf8转unicode的方式转码,获取转换结果
        QString asciidata = QTextCodec::codecForName("UTF-8")->toUnicode(bytedata.constData(),bytedata.size(),&state);
        //存在无效字符则是GBK,转码后返回
        if (state.invalidChars > 0) 
        {
            asciidata = QTextCodec::codecForName("GBK")->toUnicode(bytedata);
        } 
        else 
        {
            asciidata = QString(bytedata);
        }
        
        //......省略
    }
    
    //......省略
}

在这里插入图片描述

四、总结

本章主要讲解对文件的操作。除了需要了解在QT中对文件类的常用操作之外,个人认为还有比较重要的两个知识点:1是文本编码的判断和转码处理,2是对于二进制文本的读取。

LONG CSCOMMDlg::OnCommunication(WPARAM ch, LPARAM port) { g_ucRecvBuf[g_ulRecvLen++]=UCHAR(ch); if(g_ulRecvLen>=51) { memcpy(&g_ADData,g_ucRecvBuf,51); //return 1; } // return 1; if(g_ulRecvLen<4) return 1; //1表示接收数据长度不够 ULONG ulCheckSum =0; CMDHEADER header; header = *((CMDHEADER *)g_ucRecvBuf); //如果不相等,将Buffer中的每个数据前移动一个位置 if(CMD_SYN_WORD!= header.ulSyncWord) { memmove(g_ucRecvBuf,g_ucRecvBuf+1,3); return 2;//2表示没有接收到同步字 } if(g_ulRecvLen<sizeof(CMDHEADER)) return 3; //3表示没有接收到帧头 CString strTemp; CString strFileName; char szFilePath[MAX_PATH]={0}; switch(header.cmdType) { case CMD_FILEINFO://文件信息(包括名称,大小)命令 if(g_ulRecvLen<header.ulPackLen) return 4; //没有收到完整的文件信息命令 if(g_ulRecvLen==(sizeof(CMDHEADER)+header.ulPackLen)) { FILEINFO fileInfo; fileInfo=*((FILEINFO *)g_ucRecvBuf); ulCheckSum = CheckSum(fileInfo.ucFileName,fileInfo.ulFileNameLen) +fileInfo.lFileLen; if(ulCheckSum!=fileInfo.ulCheckSum) { fileInfo.header.cmdState = CMD_STATE_ERROR; g_lErrorPackNum++; //strTemp.Format("%d",g_lErrorPackNum); } else { fileInfo.header.cmdState = CMD_STATE_OK; //增加接收文件的计时功能 memset(&g_tRecvBegin,0,sizeof(time_t)); time(&g_tRecvBegin); m_bRecvFileIsBegin = TRUE; } m_nCurFileRecvLen=0; m_nFileRecvLen = fileInfo.lFileLen; //文件长度 fileInfo.header.cmdType =CMD_FILEINFO_RESP; m_Port.WriteToPort((char*)&fileInfo.header,sizeof(CMDHEADER)); memcpy(szFilePath,fileInfo.ucFileName,fileInfo.ulFileNameLen); strFileName.Format("%s",szFilePath); m_strCurFilePath= m_strSaveFileDir+strFileName; if(NULL!=g_file.m_hFile) g_file.Close(); g_file.Open(m_strCurFilePath,CFile::modeCreate|CFile::modeReadWrite); TRACE("1,RECV FILEINFO CMD,File Name = %s,g_ulRecvLen = %d\n", fileInfo.ucFileName,g_ulRecvLen); // if(m_nFileRecvLen1024*1024) // { // strTemp.Format("%0.3fKB",m_nFileRecvLen/1024.0/1024.0); // } // //显示"传输总耗时" // const ULONG ulFrameBytes = g_nBaud/10 -(sizeof(CMDHEADER)+4); // long lComm_TimeSum = m_nFileRecvLen/ulFrameBytes; // //传输总耗时间,单位:秒 // // WORD wHour =0; // WORD wMinute =0; // WORD wSecond =0; // // wSecond = lComm_TimeSum%60; // wMinute = lComm_TimeSum/60%60; // wHour = lComm_TimeSum/60/60%60; // // if((0==wSecond)&&(0==wMinute)&&(0==wHour)) // wSecond =1; // strTemp.Format("%02d:%02d:%02d",wHour,wMinute,wSecond); g_ulRecvLen =0; } break; case CMD_FILEDATA: //发送文件中数据的命令 if(g_ulRecvLen<header.ulPackLen) return 5;//没有收到完整的文件数据帧命令 if(g_ulRecvLen == (sizeof(CMDHEADER)+header.ulPackLen)) { FILEDATA fileData; fileData = *((FILEDATA *)g_ucRecvBuf); fileData.pucBuf = new UCHAR[fileData.header.ulPackLen-4]; memset(fileData.pucBuf,0,fileData.header.ulPackLen-4); memcpy(fileData.pucBuf,g_ucRecvBuf+sizeof(CMDHEADER)+4,fileData.header.ulPackLen-4); ulCheckSum=CheckSum(fileData.pucBuf, fileData.header.ulPackLen-4); if(ulCheckSum!= fileData.ulCheckSum) { fileData.header.cmdState =CMD_STATE_ERROR; g_lErrorPackNum++; strTemp.Format("%d",g_lErrorPackNum); //m_editErrorFrames } else fileData.header.cmdState =CMD_STATE_OK; fileData.header.cmdType = CMD_FILEDATA_RESP; g_file.Write(fileData.pucBuf,fileData.header.ulPackLen-4); //fileData.header.ulPackLen-4, DELETE_ARRAYOBJS(fileData.pucBuf); g_ulRecvLen=0; m_Port.WriteToPort((char*)&fileData.header,sizeof(CMDHEADER)); m_nCurFileRecvLen+=fileData.header.ulPackLen-4; strTemp.Format("%0.2f",(float)m_nCurFileRecvLen/(float)m_nFileRecvLen*100); strTemp+=_T("%"); float step = (float)m_nCurFileRecvLen/(float)m_nFileRecvLen*100; // m_progress.SetPos((int)step); /*if(m_nFileRecvLen1024*1024) { strTemp.Format("%0.3fKB",m_nFileRecvLen/1024.0/1024.0); }*/ } break; case CMD_FILEEOF://文件发送结束命令 if(g_ulRecvLen<header.ulPackLen) return 6;//没有发到完整的文件发送结束命令 if(g_ulRecvLen == (sizeof(CMDHEADER)+header.ulPackLen)) { FILEEOF fileEOF; fileEOF= *((FILEEOF *)g_ucRecvBuf); fileEOF.pucBuf = new UCHAR[fileEOF.header.ulPackLen-4]; memset(fileEOF.pucBuf,0,fileEOF.header.ulPackLen-4); memcpy(fileEOF.pucBuf,g_ucRecvBuf+sizeof(CMDHEADER)+4,fileEOF.header.ulPackLen-4); ulCheckSum = CheckSum(fileEOF.pucBuf,fileEOF.header.ulPackLen-4); if(ulCheckSum!=fileEOF.ulCheckSum) { fileEOF.header.cmdState=CMD_STATE_ERROR; g_lErrorPackNum++; strTemp.Format("%d",g_lErrorPackNum); } else { fileEOF.header.cmdState =CMD_STATE_OK; m_bRecvFileIsBegin = FALSE;//接收文件完成,停止显示时间 } fileEOF.header.cmdType =CMD_FILEEOF_RESP; g_file.Write(fileEOF.pucBuf,fileEOF.header.ulPackLen-4); g_file.Close(); //解决大文件发送时,第二个文件打开错误原因是没“置0”原因 g_file.m_hFile=NULL; DELETE_ARRAYOBJS(fileEOF.pucBuf); g_ulRecvLen=0; m_Port.WriteToPort((char*)&fileEOF.header,sizeof(CMDHEADER)); m_nCurFileRecvLen += fileEOF.header.ulPackLen-4; if(m_nFileRecvLen<=0) { strTemp=_T("0%"); } else { strTemp.Format("%0.2f", (float)m_nCurFileRecvLen/(float)m_nFileRecvLen*100); strTemp+=_T("%"); } if(m_nFileRecvLen1024*1024) { strTemp.Format("%0.3fKB",m_nFileRecvLen/1024.0/1024.0); } } break; //=================处理接收端的响应信息 begin 信息头================= case CMD_FILEINFO_RESP://发送文件信息命令后,发到接收端的响应信息 if(CMD_STATE_OK==header.cmdState) { m_bFileInfoIsOK = TRUE; g_ulRecvLen = 0; g_bIsRecvData = TRUE; } else if(CMD_STATE_ERROR==header.cmdState) { m_bFileInfoIsOK = FALSE; g_ulRecvLen=0; SendFileInfo(); } break; //=================处理接收端的响应信息 文件数据================= case CMD_FILEDATA_RESP://发送文件体命令后,发到接收端的响应信息 if(CMD_STATE_OK==header.cmdState) m_bFileDataIsOK = TRUE; else if(CMD_STATE_ERROR==header.cmdState) m_bFileDataIsOK = FALSE; g_ulRecvLen =0; g_bIsRecvData = TRUE; break; //=================处理接收端的响应信息 文件结尾================= case CMD_FILEEOF_RESP://发送文件尾命令后,发到接收端的响应信息 if(CMD_STATE_OK==header.cmdState) { m_bFileEOFIsOK = TRUE; g_bFileSendEnd = TRUE; } else if (CMD_STATE_ERROR == header.cmdState) { m_bFileEOFIsOK = FALSE; m_bSendFileIsBegin = FALSE; } g_ulRecvLen = 0; g_bIsRecvData = TRUE; break; //====================默认处理================= default:break; } //设置信号量 SetEvent(m_hRecvMsgEvent); return 0; //返回0表示正确 }
ExMod Serial Assistant使用说明 ExMod串口调试助手主要功能是发送大量串口数据,设计目标是满足上位机串口编程调试之用,是个人把在上位机编程中常用的模拟调试功能汇总到一个调试软件中而成。除普通串口调试助手功能外,还包括发送文件中数据、Modbus Rtu Master常用功能。其中文件格式支持Excel、CSV、文本文件3种格式。配合虚拟串口软件,只要确定了通讯协议,即可脱离设备调试软件。可适用于自定义协议、Modbus Rtu协议。 使用说明: 软件主界面包括3个功能区,“发送任意串口数据”以上为功能区1,用于基本串口数据发送,还包括计算CRC校验码并与数据同时发送功能。 功能区2 包括“读取Holding寄存器”、“读取输入寄存器”、“写入多个寄存器”,用于单条Modbus数据读取、发送。 剩余部分为第3功能区,“发送文件中数据”用于大量串口数据发送。其下有多个发送选项,包括发送范围、发送间隔、数据格式。“延时”用于设置多条数据间发送间隔。“Modbus数据”选择表示文件中数据按Modbus格式发送,不选择表示为普通串口数据。如果文件中所有数据为同一寄存器地址,需在“寄存器地址”中输入地址,如果多条数据地址不同,可在文件中编辑寄存器地址,此时需选择“第一列为地址”。Excel、CSV中第一列好理解,文本文件在软件中也按CVS格式处理,即文本文件中第1个逗号之前视为第一列。 发送文件中数据如果选择“Modbus数据”,仅支持“WriteMultipleRegisters”命令发送发送串口数据如果16进制数据不为整字节(Modbus时为整字),会在数据前补0。 接收数据中的显示内容如果长度超过0.1M会自动保存到以当前时间命名的文本文件中。也可以手动保存以便以后调试时使用。
Qt C 串口助手是一个基于Qt库和C语言开发的串口通信辅助工具。它可以实现与串口设备的连接、数据的发送和接收、调试信息的显示等功能。 首先,Qt C 串口助手通过Qt库提供的QSerialPort类,简化了串口通信的编程过程。我们可以使用QSerialPort类来打开指定的串口设备,并设置波特率、数据位、校验位等参数。同时,还可以监听串口收到的数据。 其次,在串口助手的界面中,我们可以通过按钮或菜单选项来进行串口的连接和断开。当串口成功连接后,我们可以将待发送的数据输入到发送文本框中,然后点击发送按钮将数据发送出去。同时,接收区域会实时显示串口接收到的数据。这样方便了用户对串口操作和数据的收发。 此外,Qt C 串口助手还提供了数据的调试功能。用户可以选择以ASCII码或十六进制的形式来显示接收到的数据。这对于调试串口设备时特别有用。用户还可以选择是否在接收区域显示时间戳,以便记录每条数据的接收时间。 最后,Qt C 串口助手还具备其他辅助功能,比如清除接收区、保存接收到的数据到本地文件、设置串口超时时间等。这些功能的设计使得串口助手更加实用和便捷。 综上所述,Qt C 串口助手是一个功能完善的串口通信辅助工具。它采用Qt库和C语言开发,具备串口连接、数据发送和接收、调试信息显示等功能,帮助用户轻松实现串口通信
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值