QT实现串口通信步骤以及问题记录,小白文,大佬轻锤,欢迎指错。

【串口通信参考文章】这篇更为详细,部分代码是从这扒的

下面是UI界面,主要需求:通过串口或网口方式收发数据,读取下位机状态以及对其进行控制。

串口部分主要控件:两个QTextBrowser记录收发数据,串口开关,QLabel制作开关指示灯

(控制界面还未全部完成)

1. 工程文件及头文件添加代码

工程文件xxx.pro中添加:

#串口通信
QT       +=serialport

头文件xxx.h中添加:

//串口通信
#include <QSerialPort>
#include <QSerialPortInfo>

2. 参数设置

    //串口变量↓QSerialPort     *serial;              //定义全局的串口对象QStringList     baudList;             //波特率QStringList     parityList;           //校验位QStringList     dataBitsList;         //数据位QStringList     stopBitsList;         //停止位QStringList     flowControlList;      //控制流

3. 串口初始化函数

void MainWindow::SerialPortInit()
{serial = new QSerialPort;                       //申请内存,并设置父对象// 获取计算机中有效的端口号,然后将端口号的名称给端口选择控件foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){serial->setPort(info);                      // 在对象中设置串口if(serial->open(QIODevice::ReadWrite))      // 以读写方式打开串口{//ui->UI界面你添加的控件名->控件方法ui->PortBox->addItem(info.portName());  // 添加计算机中的端口qDebug() << "串口打开成功";serial->close();                        // 关闭} else{qDebug() << "串口打开失败,请重试";}}// 参数配置,波特率serial->setBaudRate(QSerialPort::Baud19200);// 校验位,校验默认选择无serial->setParity(QSerialPort::NoParity);// 数据位,数据位默认选择8位serial->setDataBits(QSerialPort::Data8);// 停止位,停止位默认选择1位serial->setStopBits(QSerialPort::OneStop);// 控制流,默认选择无serial->setFlowControl(QSerialPort::NoFlowControl);
}
  • 这里的参数配置 (如波特率数值19200) 需要与下位机一致。

4.开关按钮以及显示灯

void MainWindow::on_OpenSerialButton_clicked()
{
//用不上这段代码
#if 0if(uSocket->isOpen()){//按钮被点击时,无论当前状态,如果开启则关闭uSocket->close();// 关闭状态,按钮显示“打开串口”ui->UDP_ConnectButton->setText("打开\n网口");// 关闭状态,颜色为绿色ui->UDP_ConnectButton->setStyleSheet("color: green;");// 关闭,显示灯为红色UDP_LEDChange(true);ui->OperatorLog->append("<b><font color=\"#B22222\">"+TimeStamp()+"UDP已关闭!</b>");ui->OperatorLog->append("<font color=\"#B22222\">原因:串口操作");}
#endifif(serial->isOpen())                                        // 如果串口打开,则将其关闭{serial->clear();serial->close();// 关闭状态,按钮显示“打开串口”ui->OpenSerialButton->setText("打开\n串口");// 关闭状态,字体颜色改为绿色ui->OpenSerialButton->setStyleSheet("color: green;");// 关闭,显示灯改为红色LED(true);ui->OperatorLog->append(TimeStamp()+"<b><font color=\"#B22222\">"+TimeStamp()+"串口已关闭 0</b>");ui->ControlBox->setEnabled(false);}else                                                        // 如果串口关闭,则将其打开{//当前选择的串口名字serial->setPortName(ui->PortBox->currentText());//用ReadWrite 的模式尝试打开串口,无法收发数据时,发出警告if(!serial->open(QIODevice::ReadWrite)){QMessageBox::warning(this,tr("提示"),tr("串口打开失败!"),QMessageBox::Ok);return;}// 打开状态,按钮显示“关闭串口”ui->OpenSerialButton->setText("关闭\n串口");// 打开状态,字体颜色改为红色ui->OpenSerialButton->setStyleSheet("color: red;");// 打开,显示灯改为绿色LED(false);ui->OperatorLog->append(TimeStamp()+"<b><font color=\"#32CD32\">"+TimeStamp()+"串口已开启</b>");ui->ControlBox->setEnabled(true);}
}

// 开关显示灯
void  MainWindow::LED(bool changeColor)
{if(changeColor == false){// 显示绿色ui->LED->setStyleSheet("background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(0, 229, 0, 255), stop:1 rgba(255, 255, 255, 255));border-radius:12px;");}else{// 显示红色ui->LED->setStyleSheet("background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(255, 0, 0, 255), stop:1 rgba(255, 255, 255, 255));border-radius:12px;");}
}
  • #if 1表示直到#endif之间的这段代码正常开启,#if 0表示关闭。                                                         可以理解为高效将此代码块注释掉。(文中的#if  到#endif内容可以忽略)
  • QMessageBox是提示窗口,需要在头文件或者源文件内添加#include <QMessageBox>
  • TimeStamp()是自写的时间戳函数,返回当前时间,精确小数点后两位。
  • SetStyleSheet字体样式设置,类似html中的格式,颜色可在rgb调色板中寻找。这是平时方找颜色时常用的网站
  • 关于函数名on_OpenSerialButton_clicked(),这是个槽函数,前缀为on_,后缀为_clicked(),中间是控件的名称,这种格式写槽函数便可以省去写connect函数。
//时间戳
QString MainWindow::TimeStamp(){QString str;QTime time = QTime::currentTime();QString msec = QString::number(time.msec());//如果毫秒恰好为整数0,则添加个0,保持秒数为两位if(msec.size()==1){msec.append("0");}//msec.left(2)表示取毫秒数的前两位str.append("◆"+time.toString("hh:mm:ss:")+msec.left(2)+"◆→ ");return str;}

5. 串口数据接收

(1)接收函数

//串口接收
void MainWindow::Serial_DataReceived()
{QByteArray data;//下位机发送if(serial->bytesAvailable()>=  10 ) // 10为一次需要读取的字节数{data = serial->readAll();                      // 读取数据qDebug() << "initial data test"<< data;//将接收到的QByteArray数据转为16进制,DataRecevied_Hex为QString全局变量DataRecevied_Hex = data.toHex();//注释//ui->Serial_DataReceived->append( TimeStamp()+ DataFactory(DataRecevied_Hex));//注释//AngleCurrent();//这里可以直接将读取到的数据显示到你指定的控件,例如ui->控件名->append(内容);//注意内容类型,文中用的控件是QTextBrowser,append的内容需要是QString类型的//控件有两种方式,一种是代码生成,一种是ui界面绘制,文中多数采取的是ui界面绘制。}}

(2)窗口内调用串口初始化函数 ,并写上串口接收信号槽,便能读取数据。

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);SerialPortInit();/**********************************************
*
*                 ↓  信号槽  ↓
*
**********************************************//*   Serial   */// 串口接收数据//信号槽,connect(发送方,发送的信号,接收方,触发的函数)  //下面这段既串口为发送方,信号则是串口接收到数据,接收方this,触发Serial_DataReceived函数connect(serial,&QSerialPort::readyRead,this,&MainWindow::Serial_DataReceived);//上文提到的on_OpenSerialButton_clicked,如果写成connect的方式,则为//connect(OpenSerialButton,&QPushButton::clicked,this,&MainWindow::OpenSerialButton);//既(控件名,控件被点击时发送信号,接收方this,触发函数),将触发函数写成on_控件名_clicked则可省略,方便许多。}
//串口接收
void MainWindow::Serial_DataReceived()
{QByteArray data;if(serial->bytesAvailable()>=  10 ) // 10为一次需要读取的字节数{data = serial->readAll();                      // 读取数据qDebug() << "initial data test"<< data;DataRecevied_Hex = data.toHex();ui->Serial_DataReceived->append( TimeStamp()+ DataFactory(DataRecevied_Hex));AngleCurrent();}}

接收数据方法有很多,但多数都存在一定问题,

或是数据读取不全,或是读取顺序不对、时快时慢。

最后在网上找到了这个办法,目前为止没出错。

问题记录:关于 字节 与 16进制(Hex)

之前一直没绕明白为什么1字节可以表示两个16进制数,饶了很久。

个人理解16进制是人类语言,需要转换成计算机能理解的0与1电信号。

1字节(Byte)= 8 比特(bit),既00000000。

想表示一个16进制数,需要至少16种(0~F)二进制组合来区分,

对应二进制就至少需要4个bit,即0000。

结论:1个16进制位=0.5个字节

所以接收到的10字节内容,既80bit,每4位表示一个16进制,可以表示20位。

6. 串口数据发送

本文数据发送,需要将用户输入的高低角与方位角角度数值转为16进制QByteArray类型。

(1)角度更改按钮

void MainWindow::on_Control_AngleChange_clicked(){//读取输入控件接收到的数据QString SdecFW = ui->AngleFWC->currentText();QString SdecGD = ui->AngleGDC->currentText();//QString转Doubledouble decGD = SdecGD.toDouble();double decFW = SdecFW.toDouble();// 接口协议是用两个字节表示-180°至+180°,即0至65535表示度数,0000=FFFF=0°// 0~7FFF(0-32767)从小到大表示0~180°,// FFFF~8000(65535~32768)从大到小表示0至-180度,// 系数coefficientGD = 0.0054,coefficientFW = 0.0053if(decGD>=0){decGD = decGD/coefficientGD;}else{decGD = decGD/coefficientGD+65535;}if(decFW>=0){decFW = decFW/coefficientFW;}else{decFW = decFW/coefficientFW+65535;}//double转为int,再转为16进制表示的QStringQString hexGD =QString::number(int(decGD), 16);QString hexFW =QString::number(int(decFW), 16);//高低字节转换,因为下位机要求,发送字节要先发低位,后发高位,例如ff00,需要变为00ffhexGD=gdwChange(hexGD);hexFW=gdwChange(hexFW);#if 0//判断输入角度与当前角度是否一致if(hexGD!=DataSend_Hex.mid(12,4)){ui->OperatorLog->append(TimeStamp()+"指令:[俯仰]高低角变更为:"+SdecGD+"°");}if(hexFW!=DataSend_Hex.mid(16,4)){ui->OperatorLog->append(TimeStamp()+"指令:[旋回]方位角变更为:"+SdecFW+"°");}#endif//全局变量发送数据QString DataSend_Hex="ffb200000000000000000c000000";//将转换后的高低角和旋回角的字符串替换原字符串DataSend_Hex.replace(12,8,hexGD+hexFW);//角度数据截取覆盖//全局变量接收数据QString DataRecevied_Hex="00000000000000000000";QString ReceivedAngle =DataRecevied_Hex.left(12);if(ReceivedAngle==""){ReceivedAngle="ffb200000000";}//因为接收的数据和发送的数据前12位需要保持一致,都是表示报文头发送地址与当前角度值//将之前接收到的数据直接截取替换就可以DataSend_Hex.replace(0,12,ReceivedAngle);#if 0//判断使能是否已开启if(ui->EnableButton->isChecked()){}else{EnableButton_on();}#endif//计算发送数据校验位Parity_DataSend();//发送数据SendMode();#if 0//使能自动关闭->判断if(CountdownSwitch){Delay(10);if(ManualControlEnableButton){EnableButton_off();}}#endif}

(2)高低字节转换

QString MainWindow::gdwChange(QString s){//初始0°或为空字符串if(s=="0"||s==""){s="0000";} //10进制转为16进制的String类型后,需要补位,2位前面补00以此类推。 //即使是1°,转换后也是200左右,需要至少2位16进制表示,所以除0°外size不会为1。//append为添加至字符串末尾,prepend为添加至字符串开头,//remove为移除部分字符串,例如F123,remove(2,1)则代表从第2位开始,移除1位,移除后为F13if(s.size()==2){s.append("00"); //填充+高低位位置变化}else if(s.size()==3){s.prepend("0");QString ache;ache.append(s.at(2));ache.append(s.at(3));s.remove(2,2);s.prepend(ache);}else if(s.size()==4){QString ache;ache=s.at(2);ache.append(s.at(3));s.remove(2,2);s.prepend(ache);}return s;
}

(3)校验位


//校验位计算与转换
void MainWindow::Parity_DataSend(){//本文接口协议校验方式为校验和计算,既末尾校验字节等于除校验位的所有字节相加,然后取后两位//如ff b2 7f 02 d9 02 26 00 e1 14,则最后的14是通过ff + b2 + 7f + ...得出的。(414后两位)  int ache=0;//↓除去校验位本身的长度for(int i=0;i<DataSend_Hex.size()-2; i+=2){//全局变量QString DataSend_Hex="ffb200000000000000000c000000";//先将16进制QString转10进制QString,再转为int类型,最后根据基数和位权计算ache += HexToDec(DataSend_Hex.at(i)).toInt()*16 + HexToDec(DataSend_Hex.at(i+1)).toInt();//at是Qstring的方法,读取字符串某一位,效率高,但只限读取无法更改。//HexToDec是自定义函数,用于将16进制字符转为10进制}//最后得到的int数值再转为16进制的QString类型(转来转去只是为了计算,(╯▔皿▔)╯恼)QString Parity =QString::number(ache, 16);//判断校验位的位数,不足补0if(Parity.size()==1){Parity.prepend("000");}else if(Parity.size()==2){Parity.prepend("00");}else if(Parity.size()==3){Parity.prepend("0");}//将最后两位(最后一字节)替换。(协议规定发送时是发14字节,接收时接收10字节)//replace是自带的字符串替换函数,replace(从第26位开始,取两位,替换内容)Parity.remove(0,2);DataSend_Hex.replace(26,2,Parity);//qDebug可以理解为只显示于是控制台的cout,经常拿来测试运行后的数据值qDebug() << "校验位更改:" << DataSend_Hex;}

(4)进制转换

分别是16转2,16转10,2转16,只能一位一位的转,挺笨重的方法。

/* ↓     进制转换     */
QString MainWindow::HexToBinary(QString s){if(s=="0"){s="0000";}else if (s=="1") {s="0001";}else if (s=="2") {s="0010";}else if (s=="3") {s="0011";}else if (s=="4") {s="0100";}else if (s=="5") {s="0101";}else if (s=="6") {s="0110";}else if (s=="7") {s="0111";}else if (s=="8") {s="1000";}else if (s=="9") {s="1001";}else if (s=="a"||s=="A") {s="1010";}else if (s=="b"||s=="B") {s="1011";}else if (s=="c"||s=="C") {s="1100";}else if (s=="d"||s=="D") {s="1101";}else if (s=="e"||s=="E") {s="1110";}else if (s=="f"||s=="F") {s="1111";}return s;
}QString MainWindow::HexToDec(QString s){if(s=="0"){s="0";}else if (s=="1") {s="1";}else if (s=="2") {s="2";}else if (s=="3") {s="3";}else if (s=="4") {s="4";}else if (s=="5") {s="5";}else if (s=="6") {s="6";}else if (s=="7") {s="7";}else if (s=="8") {s="8";}else if (s=="9") {s="9";}else if (s=="a"||s=="A") {s="10";}else if (s=="b"||s=="B") {s="11";}else if (s=="c"||s=="C") {s="12";}else if (s=="d"||s=="D") {s="13";}else if (s=="e"||s=="E") {s="14";}else if (s=="f"||s=="F") {s="15";}return s;}QString MainWindow::BinaryToHex(QString s){if(s=="0000"){s="0";}else if (s=="0001") {s="1";}else if (s=="0010") {s="2";}else if (s=="0011") {s="3";}else if (s=="0100") {s="4";}else if (s=="0101") {s="5";}else if (s=="0110") {s="6";}else if (s=="0111") {s="7";}else if (s=="1000") {s="8";}else if (s=="1001") {s="9";}else if (s=="1010") {s="a";}else if (s=="1011") {s="b";}else if (s=="1100") {s="c";}else if (s=="1101") {s="d";}else if (s=="1110") {s="e";}else if (s=="1111") {s="f";}return s;}
/* ↑     进制转换     */

(5)发送方式


//最终数据发送[判断是 串口发送 or UDP发送]
void MainWindow::SendMode(){QByteArray dataByte =QByteArray::fromHex(DataSend_Hex.toLatin1());//发送方式判断,哪个端口打开了就用哪个发送,文中开关按钮设置了互斥,所以串口和网口不会同时打开。if(serial->isOpen()){//串口写入qDebug() << "串口发送数据:"<< dataByte;//数据写入serial->write(dataByte);//全局变量行间距const QString Line75Percent="<p style='line-height:75%'>";ui->Serial_DataSend->append(Line75Percent+TimeStamp()+"<font color=\"#05BDFF\">"+DataFactory(DataSend_Hex)+"</font>"+"</p>");}
#if 0else if(uSocket->isOpen()){qDebug() << "UDP发送数据:";QString IPDestination = ui->IPDestination->text();quint16 PortDestination = ui->PortDestination->text().toInt();qDebug() << "IPDestination:"<<IPDestination;qDebug() << "PortDestination:"<<PortDestination;uSocket->writeDatagram(dataByte,QHostAddress(IPDestination),PortDestination);ui->UDP_DataSend->append(Line75Percent+TimeStamp()+"<font color=\"#05BDFF\">"+DataFactory(DataSend_Hex)+"</font>"+"</p>");}else{qDebug() << "请打开串口或UDP";}
#endif
};

(6)数据加工

DataFactory就是将内容加空格,并转为大写。

//数据加工[Data=DataReceived_Hex or DataSend_Hex]
QString MainWindow::DataFactory(QString Data){QString str=Data;//如果字符串不是20位或者28位,说明数据有误,直接打死。if(str.size()!=20 && str.size()!=28){qDebug() << "数据出错了";return 0;}//加空格,这里要注意每次添加后,添加的空格本身也占长度,所以coefficient每次加3,而不是加2int coefficient = 0;for(int i=0;i<Data.size()/2;i++){str.insert(coefficient," ");coefficient +=3;}//字符串转大写,toLower是转小写,QString自带函数str = str.toUpper();//测试[接通下位机时应注释]//str ="FF FF FF FF FF FF FF FF FF FF FF FF FF FF";//str ="CC CC CC CC CC CC CC CC CC CC CC CC CC CC";return str;
};

7. 总结

串口通信步骤:读取串口,设置参数,获得数据,数据类型转换,使用数据

感觉耗的大部分时间都在数据转换这一部分,真是把上学那会落得都补回来了

【QT Creator学习记录】(一)上位机与下位机串口通信相关推荐

  1. 【QT Creator学习记录】(四)限制输入输出与正则表达式

    [想法/需求] 如图,两个角度的输入限制分别为-20°至80度.-165°至165°,小数点后最多允许两位小数. 两个控件名分别为AngleGDC与AngleFWC. [实现] //正则表达式//声明 ...

  2. 关于Qt上位机与下位机stm32数据传输的解析问题(一)

    在制作上位机中,我们常常要把单片机上的数据,比如曲线图.电机速度.信号频率幅值等显示在上位机软件中,那么就需要下位机方将数据不断传给上位机以在Qt的QLCDNumber或者Qchart不断刷新,这是上 ...

  3. QT5实现串口收发数据(上位机与下位机通信)

    最近帮老师做一个应用程序,通过上位机与下位机进行串口通信,最后实现实时绘图,通过几天努力,成功实现蓝牙串口通信. 参考博客1 注意:代码中一些与串口无关代码,可以忽略掉 一.QT5串口基础知识 1. ...

  4. 基于STM32C8T6、ESP8266-01S、JavaWeb、JSP、Html、JavaScript、Android、服务器和客户端设计、上位机和下位机设计等技术融合的物联网智能监控系统设计与实现

    系列文章目录 第一章ESP8266的java软件仿真测试 第二章ESP8266硬件与软件测试 第三章ESP8266客户端与Java后台服务器联调 第四章ESP8266客户端与JavaWeb服务器联调 ...

  5. 上位机与下位机程序配合是 下位机程序崩溃

    最近作为一个菜鸟,一直测试同事用QT写好的上位机与下位机程序, 结果配置一个模块,连续点击多次下载时,下位机出现程序崩溃的情况.如图 根据提示得知 在下位机线程没有结束的时候,我通过上位机就把内容情况 ...

  6. 上下位机通讯协议_上位机与下位机的区别通讯

    上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接 ...

  7. 上位机和下位机的概念,理解如何实现PC从PLC中读取数据?

    市面上的PLC有上百种, 西门子的, 三菱的, 欧姆龙的等等. 上位机和下位机的理解: 上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/ ...

  8. 打开单片机世界的大门——上位机控制下位机实例详解

    上位机控制下位机实例详解 一.基本概念 上位机与下位机 串口 数据表达 二.下位机程序 三.上位机程序 四.总结 一.基本概念 在开始讲解前,先来看几个基本概念,如果是有基础的大佬,请直接跳到下一节. ...

  9. 超详细Klipper 上位机与下位机配置

    (适用多数Mega2560芯片打印机主板,本文使用香橙派ZERO2作为上位机) 上位机:ZERO2 下位机:打印机主板 下载镜像系统 首先,去Armbian官网下载Buster系统镜像:Armbian ...

最新文章

  1. Docker与FastDFS的安装命令及使用
  2. 【热点】因这个配置错误导致全球互联网流量下降了 3.5%
  3. java 不同包_Java项目中不同包的命名及作用
  4. h5红包雨代码_【多管闲事】非专业人士H5学习指北:从门还没入到放弃 | 叙一来闲...
  5. 安装Redis教程(详细过程)
  6. 51单片机智能小车蓝牙
  7. PD虚拟机的三种网络模式
  8. Mycat(7):分片详解之枚举
  9. 导数求函数最大值和最小值习题
  10. 初级——如何进行Android单元测试
  11. 微波反应试验温度、时间、压力及功率控制
  12. ORB-SLAM3从理论到代码实现(二):Optimizer局部地图优化
  13. 微信小程序:数独挑战之九宫格-中级-第一题解题思路
  14. 存储市场竞争加剧 美光科技再次光荣绽放
  15. 免费虚拟现实运用开发平台VR Juggler
  16. 【微服务】SpringAMQP
  17. shellcode免杀;CS上线;获取微信聊天记录
  18. 数据结构-寻找二叉树两节点的最近公共祖先(Java)
  19. Sony ST27i 获取root权限
  20. 2010年最骚最贱最有深度的100句话!!![

热门文章

  1. 几何光学学习笔记(13)- 4.2双平面镜成像
  2. oracle采购业务流程,ORACLE EBS 采购的业务流程
  3. 主机屋虚拟主机php版本,5款免费虚拟主机管理系统(云虚拟主机管理)
  4. 【单片机基础】89C52单片机串口通信
  5. 《弟子规》全集译文,为孩子的明天存下来!
  6. vtkImageCheckerboard生成棋盘格,跟随鼠标移动显示
  7. Github的远程项目如何下载到本地
  8. 分布式系统常见问题总结
  9. [Win10+Excel365]尽管已启用VBA宏,Excel还是无法运行宏
  10. 【ZZULIOJ】1070: 小汽车的位置