首页 > 代码库 > Qt5串口编程详解【新版】

Qt5串口编程详解【新版】

Qt5的串口比Qt4的好用得多,Qt4的貌似没有集成官方库。

之前我也写过Qt5的串口,不过有一些缺陷,这次试图改进。转载请保留链接:http://blog.csdn.net/qq363692146/article/details/26049355

本文发表于2014.5.17。


如果在linux下,记得使用root权限,可以用root权限打开可执行文件,或者用root权限打开Qt Creator。(原因是串口常常需要特权,有些串口有特权也只能度而不能写,这个可能是Qt本身的问题)【至于安卓端,其实没试过。】【用root打开Qt Creator似乎是一个一劳永逸的方法,这样每次运行都以root权限,但是一些配置文件也会被改变为root权限。下一次再以普通权限打开Qt Creator就会提示错误,需要输入这样命令 sudo chmod 777 `find 对应目录`,这样能将目录的每个文件去掉权限限制】



好了,准备开始。



先建立Qt Widgets Application,只要注意尽量不用中文路径即可,一直下一步,直到完成。


接着修改“项目名.pro”文件,增加这一单独行。(这个项目文件可以根据需要裁剪和增加,以增快编译效率和设置选项,而这一行表示“我想用到串口”)

QT       += serialport



进入mainwindow.ui,给它一个Combo Box(用于选择哪一个串口,一台电脑常常有多个串口)、一个label(用于显示串口是否正常)、一个Text Edit(用于接收数据)、一个lineEdit(用于发送数据)、一个Push Button(当按下一次,就发送一次),这里为了方便读者理解,与博文统一,请读者先别急着修改它们的ObeectName,至于布局,请随意。

给Combo Box一个槽,来改变串口(右键,转到槽,选择currentIndexChange(QString))。//当改变选择的选项则触发,可得到选项名

给Push Button一个槽,用于发送(右键,选择槽,选择第一个函数)。//当按下触发




在mainWindow.h文件中

加入头文件:(这里QSerialPort是必须的,而QSerialPortInfo是可选的,它能提供本机可用串口列表(列表的每一个元素都自动获取了对应的端口号、波特率、校验位等信息!),如果不使用它则需要手动编码,设置对应的串口、波特率等信息,最后完成之后单独拿出来讲,见下面的黄色阴影字体部分)

#include <QSerialPort>
#include <QSerialPortInfo>
接着,给它一个private变量(表示串口及其操作函数)、一个private函数(用于初始化)、一个连接槽,(用于接收串口过来的数据,发送是不需要槽的,原因是发送是主动、随时的,而接收是被动、等待的):

private变量:

QSerialPort serial;

一个private函数:

void initSeialPort();

连接槽:

private slots:
    void serialRead();


进入mainWindow.cpp中

在构造函数中:

在ui->setupUi(this);之后加入这一句:

initSeialPort();

在析构函数中:

在delete ui;之前加入这一句:

serial.close();


接下来补齐这些没有函数体为空的函数,为了方便可以直接复制(仔细浏览,看看它们已经定义了没有,或者是否ui触发的槽选择错误,如果有问题,请投诉):

initSerialPort函数

先连接槽,然后获取可用的infos(所有本地存在串口信息),如果都不可用,提示“无效”,如果存在,则为Combo Box添加选项,第一项是“串口”(只是提示,由于combo Box的选项从无到有,将触发一次 on_comboBox_currentIndexChanged(const QString &arg1) 槽 ,见下面橙色阴影字体),这个选项之后是其他的串口的端口号作为选项。

void MainWindow::initSeialPort()
{

    connect(&serial,SIGNAL(readyRead()),this,SLOT(serialRead()));	//连接槽

    //get name for choose
    QList<QSerialPortInfo>  infos = QSerialPortInfo::availablePorts();
    if(infos.isEmpty())
    {
        ui->comboBox->addItem("无效");
        return;
    }
    ui->comboBox->addItem("串口");
    foreach (QSerialPortInfo info, infos) {
        ui->comboBox->addItem(info.portName());
    }
}



当每次combo Box选项变化,将触发下面的函数:

arg1为该选择的Combo Box选项名称(第一次触发为“串口”,之后被用户选择为需要的串口号,根据上面的addItem函数可以逆推)

根据arg1查找可用的串口列表,如果找不到(例如arg1为“串口”这个字符串,当然找不到)则提示“[出错]”,否则打开并提示"[已开启]"。

这里serial先关后开,防止先前的串口忘了关闭(因为每改变一次选项就要开启一次,那么当然要把上一次的串口给关了才行)。

void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
    QSerialPortInfo info;
    QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();
    int i = 0;
    foreach (info, infos) {
        if(info.portName() == arg1) break;
        i++;
    }
    if(i != infos.size ()){//can find
        ui->label->setText("[已开启]");
        serial.close();
        serial.setPort(info);
        serial.open(QIODevice::ReadWrite);         //读写打开
    }
    else
    {
        serial.close();
        ui->label->setText("[出错]");
    }
}



下面两个函数便不详述:

void MainWindow::serialRead()
{
    ui->textEdit->append(serial.readAll());
}

toLatin1可用转换QString到QByteArray类型:

void MainWindow::on_pushButton_clicked()
{
    if(ui->lineEdit->text().isEmpty())
        return;
    serial.write(ui->lineEdit->text().toLatin1());
}



好已经完成全部工作,测试一下吧。




补充一下自己摸索的其他方法,作为阅读即可:


问题1:如何去掉QSerialPortInfo头文件,并手动设置串口信息,这里举个例子:

//注意这里serial是指针形式,而且下面的顺序有一定要求:
//下面这些设置都是针对单片机11.0592的晶振的,请根据具体情况设置:

serial = new QSerialPort("COM4");       //串口号,一定要对应好,大写!!!串口号一般要修改。
    serial->open(QIODevice::ReadWrite);      //读写打开  
    serial->setBaudRate(QSerialPort::Baud9600);  //波特率  
    serial->setDataBits(QSerialPort::Data8); //数据位  
    serial->setParity(QSerialPort::NoParity);    //无奇偶校验  
    serial->setStopBits(QSerialPort::OneStop);   //无停止位  
    serial->setFlowControl(QSerialPort::NoFlowControl);  //无控制  


问题2:如何解决传接收断断续续的问题(在某些情况串口无意地经常把一条数据拆成多条数据,这时候多次触发了serialRead槽,得到不想要的结果):

解决方法:

1.对于固定长度的数据,假如有数据长度不正确,就请求重发。

2.对于不固定长度的,可以在数据后面加上特殊符号作为识别,这里将举例(当特殊符号为‘~‘)【不过这个代码没考虑多线程:static变量存在着】:

void MainWindow::serialRead()  
{  
    static QByteArray allData;         //静态变量!!在串口只得到一部分的时候用来累加数据  
    QByteArray dataTemp;            	//本次接收的数据,可能是前半部分数据或者后半部分数据(甚至是被分割为多段数据的其中一部分数据),或者也可能是完整数据。
    while (!serial->atEnd()) {  
        QByteArray dataTemp = serial->readAll();                //因为串口是不稳定的,也许读到的是部分数据而已,但也可能是全部数据  
        if( dataTemp.data()[dataTemp.length() - 1] == ‘~‘){     //当临时数据最后一位是‘~‘,代表一条数据读完了  
            allData += dataTemp;                                //总数据加上本次数据  
            allData.resize(allData.size() - 1);                 //删除结尾的~符号  
            qCritical() << allData;                             //这时候allData将是你要的数据  
            allData.clear();                                    //清除数据!!!!!  
        }  
        else                                                  //当最后一位数据不是‘~‘,即未读完  
            allData += dataTemp;                               //每次累加这部分数据,因为还没发完  
    }  
}