【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十六章 C++入门

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


第六篇 嵌入式GUI 开发篇

在嵌入式上,我们少不了界面的开发,一种是用安卓,一种是用QT,那么安卓对CPU的性能要求比较高,不是所有的CPU都可以运行,但是QT对CPU要求不高,甚至可以在单片机上来运行,而且QT是一个非常优秀的跨平台工具,一套代码我们可以在多个平台上来运行,比如Windows,Android,Linux等,换一套编译器即可更换不同的平台。所以非常的方便和有趣。接下来我们就来学习上嵌入式上的QT开发,因为QT开发需要C++基础,不过大家不用担心,QT上用的C++并不多,手册上的C++知识大家掌握了即可开始QT的学习,大家不必担心。

第七十六章 C++入门

本章内容对应视频讲解链接(在线观看):

C++基础(上) → C++基础(上)_哔哩哔哩_bilibili

C++基础(下) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=4

76 .1 c++基础

c++是c语言的升级版,在c的基础上增加了很多功能。是一种高级语言,常见后缀:cpp,c++,cc等。

g++编译:gcc是一个通用命令,它会根据不同的参数调用不同的编译器或链接器,GCC 为了让操作简单推出g++命令用来编译 C++。

C++命名空间:中大型软件项目在多人合作开发时会出现变量或函数的命名冲突,C++ 引入了命名空间来解决变量重复定义。声明命名空间 std:using namespace std,后续如果有未指定命名空间的符号,那么默认使用 std,cin、cout ,endl都位于命名空间 std。

#include <iostream>
namespace A{
        int num = 1;
}
namespace B{
        int num = 2;
}
int main(int argc, const char *argv[])
{
        int m = A::num;
        std::cout << m << std::endl;
        m = B::num;
        std::cout << m << std::endl;
        return 0;
}

76 .2 c++类和对象

1.什么是面向对象,什么又是面向过程

c语言就是面向过程的,c++就是面向对象的,面向对象特点:封装,继承,多态。

举例:a+b

直接计算a+b就是面向过程。

面向对象就是给a+b穿上了一层衣服。不是直接计算a+b。

2.c++的类

类大家可以把他看成c语言结构体的升级版。类的成员不仅可以是变量,也可以是函数,类可以看作是一种数据类型,这种数据类型是一个包含成员变量和成员函数的集合。

如:

class student
{
public:
//成员函数
       char name[64];
       char age;
       //成员函数
   void do(){
       Cout << "function" <<endl;
}
};

对象:类的实例化

直接定义:

student my; // student就是类 my就是对象

在堆里面定义。

student *my = new student;

删除对象

delete my; 目的是释放堆里面的内存

my = NULL; 变成空指针,杜绝成为野指针。

访问类的成员

 	student my;
    student *my1 = new student;
    my.age = 18;
    my1->age =19;

    cout << my.age << endl;
    cout << my1->age << endl;

访问方法和C语言结构体是一样的,普通变量通过"." 指针通过"->"。

4.类的函数成员

因为类里面的成员不仅可以是变量,也可以是函数。

第一步:在类里面声明

第二步:实现这个函数。我们可以直接在类里面写,也可以写在类的外面。

直接写在类的里面

class student
{
public:
       char name[64];
       int age;
 
       void test(){
           cout << 123 << endl;
       };
};

写到类的外面

class student
{
public:
       char name[64];
       int age;
 
       void test();
};
 
void student::test(){  //student:表示属于这个类里面的函数,不加的话会被识别成普通函数。
    cout << 123 << endl;
};
 
};

5.访问修饰符

类的访问修饰符就是对类的成员进行权限管理。

public: 表示函数和变量是公开的,任何人都可以访问。

private: 表示函数和变量只能在自己的类里面自己访问自己,不能通过对象来访问。

能不能强行访问?可以的。

protected:表示函数和变量只能在自己的类里面自己访问自己,但是可以被派生类来访问的。

76 .3 函数的重载

1.类函数的重载特性

类函数的重载特性就是说我们可以在类里面定义同名的函数,但是参数不同的函数。

class student
{
public:
       char name[64];
       int age;
 
       void test();
       void test(int a);
 
private:
       int  haha;
 
};

重载函数在调用的时候,会根据参数的类型,然后去匹配相应的函数进行调用。

76 .4 构造函数和析构函数

析构函数:假如我们定义了析构函数,当对象被删除或者生命周期结束的时候,就会触发析构函数。

构造函数:假如我们定义了构造函数,就会触发这个构造函数。

我们要怎么定义析构函数和构造函数?

1.析构函数和构造函数的名字必须和类名一模一样。

2.析构函数要在前面加上一个~

举例:

class student
{
public:
       student();
       ~student();
       char name[64];
       int age;
 
       void test();
       void test(int a);
 
private:
       int  haha;
};
 
student::student(){
    cout << "hello" << endl;
}
 
student::~student(){
    cout << "bye" << endl;
}

构造函数是可以被重载的。

析构函数不能被重载。

3.类的继承

什么是类的继承?

类的继承允许我们在新的类里面继承父类的public还有protected部分,private是不能被继承的。

当我们觉得这个类不好的时候,可以使用类的继承,添加我们需要的功能。·

格式:

class 儿子:public 爸爸{
public:
........
Protected:
}

例子:

class mystudent:public student
{
public:
       int grade;
};

如果在子类里面去访问父类的成员?

也是通过.和->来访问的。

76 .5 虚函数和纯虚函数

虚函数:有实际定义的,允许派生类对他进行覆盖式的替换,virtual来修饰。

纯虚函数:没有实际定义的虚函数就是纯虚函数。

举例:

virtual void test(); //虚函数

virtual void testa(){} //纯虚函数

怎么定义一个虚函数?

用virtual来修饰,虚函数是用在类的继承上的。

虚函数的优点?

可以预留接口,实现分工合作。

76 .1 安装Qtcreator

本章内容对应视频讲解链接(在线观看):

在Windows上搭建QT开发环境 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=2

Qtcreator下载地址:https://download.qt.io/archive/qt/5.12/5.12.10/,进入选择版本号界面,本教程使用的是5.12.10,也推荐读者选择此版本。进入如下图界面后,选择安装包,我们在windows下学习Qt,所以选择qt-opensource-windows-x86-5.11.1.exe,点击即可下载。

下载后右键点击exe文件,选择以管理员身份运行。注册账号,点击下一步(或next),选择安装路径。选择下一步,勾选需要用到的组件,本阶段教程需要勾选以下七个选项:

选择完后一直点下一步,安装完成后如下图:

76 .2 创建工程

创建一个QT工程步骤:

步骤一:

步骤二:

填写工程名字,不要有中文路径:

步骤三:填写类名:

创建成功后如下图:

工程目录下的.pro工程文件分析:

点击forms,然后双击ui文件,就可以进入ui编辑器。

ui编辑器面板介绍:

76 .3 信号和槽

本章内容对应视频讲解链接(在线观看):

QT信号和槽 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=6

信号就是指控件发出的特定的信号。槽就是槽函数的意思,信号和槽都位于类中,不是C++标准代码。我们可以把槽函数绑定在某一个控件的信号上。当需要调用外部函数时,发送一个信号,此时与该信号相关联的槽便会被调用,槽其实就是一个函数,槽与信号的关联要由程序员来完成,关联方法有自动关联和手动关联。

76.3.1 自动关联

使用Qt信号和槽的自动关联,可加快开发速度,一般用于同一个窗体之间的控件关联,槽函数格式非常关键,格式为:

void on_<窗口部件名称>_<信号名称>(<signal parameters>);

自动关联步骤:

步骤一:手动选择相应的控件,然后右键->转到槽。

选择信号类型:

自动关联会在.h文件声明槽函数。槽函数只能声明到private slots或者public slots 下面。按住Ctrl+鼠标左键,跳转到.cpp文件对应的函数功能实现部分。填写功能代码,我们在槽函数内用qDebug打印信息。

添加完成qDebug内容之后,点击构建,运行:

每次点击,按钮都会发信号,对应的槽函数就会执行,结果如下图:

76.3.2 手动关联

信号和槽机制是QT的核心机制,要精通QT编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是QT的核心特性,也是QT区别于其它工具包的重要地方。此外如果遇到不懂的函数或类,可以先选中,然后按F1键,即可查看介绍。

虽然Qt有自动关联功能,但涉及到多个窗体和复杂事件的时候,只能使用手动关联,手动关联使用connect这个函数。

函数定义:

connect(const QObject *sender, const char *signal,

const QObject *receiver, const char *member,

Qt::ConnectionType = Qt::AutoConnection);

参数含义:

sender:发送对象;

singal:发送对象里面的一个信号,格式一般为SIGNAL(信号);

receiver:接收对象;

member:接收对象里面的槽函数,格式一般为SLOT(信号)。

ConnectionType:设置信号和槽的同步异步,一般使用默认值Qt::AutoConnection,可不填。

通常只传递前四个参数,如下所示:

connect(A,SIGNAL(B),C,SLOT(D));
当对象A发出B信号时候,就
会触发对象C的槽函数D

signals 是 QT 的关键字,而非 C/C++ 的。signals 关键字指出进入了信号声明区,随后即可声明自己的信号。

slots 槽函数是普通的 C++ 成员函数,当与其关联的信号被发射时,这个槽函数就会被调用。槽函数有的参数个数和类型,在对应的信号函数中必须一一对应,即信号函数的参数个数必须多于或等于槽函数的个数。

emit Qt定义的一个宏,作用是发射信号,与此信号关联的槽函数就会被调用。

例程:我们在widget.h中添加以下内容自定义一个信号和一个槽函数

signals:
    void my_Signal(void);    //自定义的信号

private slots:
    void my_Solt(void);   //自定义的槽函数	

添加完成如下图所示:

并在widget.cpp实现槽函数:

void Widget::my_Solt(void)
{
    qDebug("按下");
}

然后在widget.cpp中绑定信号和槽:

connect(this,SIGNAL(my_Signal()),this,SLOT(my_Solt()));

在widget.ui中创建按钮,并转到槽,自动关联的槽函数如下图:

发射信号

这样,点击按钮,就会发射自定义的信号my_Signal(),与my_Signal()相关联的this对象槽函数my_Solt就会被调用,槽函数就会输出打印信息,如下图:

部分核心代码如下:

Widget.h:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    void my_Signal(void);    //自定义的信号

private slots:
    void my_Solt(void);   //自定义的槽函数
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

Widget.cpp:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(this,SIGNAL(my_Signal()),this,SLOT(my_Solt()));
}

Widget::~Widget()
{
    delete ui;
}
void Widget::my_Solt(void)
{
    qDebug("按下");
}

void Widget::on_pushButton_clicked()
{
    emit my_Signal();
}

76 .4 给界面添加图片

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(上) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=7

76.4.1 添加资源

选中项目名称,右键单击--->选择添加新文件

在弹出窗口中选择Qt--->Qt Resource File ,选择Choose

填写写资源名称,

例如填写 picture后,在工程下的Resources会出现picture.qrc文件,成功后如下图。

双击picture.qrc,点击"Add Frefix",如下图所示:

76.4.2 添加图片

我们首先将要添加的图片复制到工程目录下。然后点击点击"Add files",

选中图片,点击"打开",进入资源编辑器,在资源编辑器中会看到添加的图片,然后保存。

以此点开Resources下的各个文件夹,即可看到添加的图片,此时图片已经添加到工程。

76.4.3 Label添加图片

在ui文件添加Label组件,添加完成如下图所示:

然后我们右击该组件->选择改变样式表,

弹出对话框,选择添加资源的小三角->border image,

选择要添加的图片,如下图:

点击OK,apply,OK,即可完成添加,如下图:

76 .5 界面布局

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(中) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=8

76.5.1 水平布局

Horizontal Layout 水平方向布局,组件自动在水平方向上分布

使用时先选中组件,然后点击水平布局即可完成,可看到组件变为水平排列。如下图:

76.5.2 垂直布局

Vertical Layout 垂直方向布局,组件自动在垂直方向上分布,操作方法和水平布局一致,在布局之后组件垂直排列。

我们点击打破布局按钮,重新选择要布局的组件,然后点击垂直布局按钮,如下图:

76.5.3 栅格布局

Grid Layout 网格状布局,网状布局大小改变时,每个网格的大小都改变

我们发现布局之后各个组件都是紧挨着的,这时候可以用"弹簧"控件来控制组件位置。

Horizontal Spacer 一个用于水平分隔的空格

完成后如下图:

Vertical Spacer 一个用于垂直分隔的空格,拖拽组件如下图:

选中点击垂直布局,完成后如下图:

76 .6 界面切换

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(下) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=9

本节通过实验介绍通过创建窗口对象的方式实现界面切换:

步骤一:

在主界面ui文件添加pushButton按钮,然后右击转到槽函数。

然后右击工程,点击新建一个窗口如下图所示:

随后在,工程下创建新的Qt设计师界面类,如下图:

我们选择Widget,用户可以根据需要选择,然后输入类名windowRun。

创建完成后如下图:

步骤二:在widget窗口的头文件widget.h添加windowrun头文件,并创建win窗口,添加内容如下:

#include "windowrun.h"

windowRun *win;

添加完成如下图所示(注意,这两个添加的位置不在同一处):

步骤三:在widget.cpp的按钮槽函数on_pushButton_clicked()中添加以下内容:

win = new windowRun(); //显示新窗口

win->setGeometry(this->geometry());//设置win窗口尺寸与此窗口尺寸相同

win->show();//显示

添加完成之后如下图所示:

运行程序后,点击按钮后即可跳转到第二个界面。

76 .7 Qt串口编程

本章内容对应视频讲解链接(在线观看):

QT上位机开发之串口助手(上) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=10

QT上位机开发之串口助手(下) → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=11

本节我们使用Qt来编写一个简单的上位机。

实验介绍:组装ui界面,使用Qt提供的串口类来实现串口收发功能,需要掌握的相关 Qt知识有以下几点:

QSerialPort是 Qt5中的附加模块,提供了基本的功能,包括配置、I/O操作、获取和设置RS-232引脚的信号,要链接QtSerialPort模块,需要在.pro文件中添加+=serialport。

QSerialPort封装了丰富的成员变量来对应串口属性,还有很多操作串口的成员函数,常用的成员函数有setPort()或setPortName(),setBaudRate(),setDataBits(),setStopBits(),setParity()等,可以用这些函数设置要访问的串口设备。本实验使用了readyRead()信号,当有数据到来时会触发类对象的readyRead()信号,然后利用它的成员函数 readAll()读取。

类QSerialPortInfo可以获取可用的串口信息,如端口名称,系统位置,产品号,描述,制造商等信息。我们把它获取到的端口信息交给QSerialPort类对象。

76.7.1 界面布局

步骤一:将控件拖到ui界面上

接收框使用Plain Text Edit,发送框使用lineEdit,属性选择组件使用Combo Box。

步骤二:属性设置栏布局,以串口号为例,依次水平布局属性选择位。

然后全部选中属性选择框,点垂直布局

效果如下图:

步骤三:功能栏布局,在按钮间添加弹簧,点击水平布局。

选中Lbel,发送框和功能按钮,点击垂直布局:

如下图:

选中属性栏和右侧组件,然后点击水平布局,如下图:

完成后:

再仿照上边的方法将下方的功能部分和接收框垂直布局:

添加完组件后,更改接收框为只读:点击接收框,在QTextEdit里标记readOnly。

在右上角更改ui界面对象名,界面组装完成后可以根据需要自行修改,

双击属性选择框添加属性:

如下图:

76.7.2 实现串口功能

1.编辑工程文件(后缀为 .pro的文件)在QT += core gui后添加 serialport。

2.自动获取串口

使用QSerialPortInfo:::availablePorts()获取当前串口,该函数返回容器类Qlist<QSerialPortInfo>,用Qt定义的关键字foreach遍历容器Qlist里的串口信息,并将串口信息放到QStringList的类对象serialNamePort,显示到ui的串口组件。

{
{
    ui->setupUi(this);
      QStringList serialNamePort;
   //遍历:availablePorts()返回的串口信息
          foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){
        serialNamePort << info.portName();
    }
    ui->serialCb->addItems(serialNamePort);
}

编译后点击串口选择框,会出现已经连接的串口。

3.打开串口功能和属性设置

步骤一:实例化串口类QSerialPort对象serialPort,对串口的操作就是对serialPort对象的操作,调用QSerialPort封装的成员变量(属性)和成员函数(功能)就能控制串口。

class Example : public QMainWindow
{
public:
..........
QSerialPort * serialPort;
..........
};
 ui(new Ui::Example)
{
    ui->setupUi(this);
......
     serialPort = new QSerialPort;
    ......
}

步骤二:填充波特率,数据位,停止位,校验位等属性。获取ui组件传递过来的串口信息,将串口属性填充到serialPort对象。

步骤三:打开串口,判断是否打开成功。

/*打开按钮*/
void Example::on_openCb_clicked()
{
    QSerialPort::BaudRate bauRate;   //波特率
    QSerialPort::DataBits dataBits;  //数据位
    QSerialPort::StopBits stopBits;  //停止位
    QSerialPort::Parity   checkBits; //校验位
 
    //设置波特率
    if     (ui->baudCb->currentText() == "4800"  )    { bauRate = QSerialPort::Baud4800;  }
    else if(ui->baudCb->currentText() == "9600"  )    { bauRate = QSerialPort::Baud9600;  }
    else if(ui->baudCb->currentText() == "115200")    { bauRate = QSerialPort::Baud115200;}
 
    //设置数据位
    if    (ui->dataCb->currentText() == "5")   { dataBits = QSerialPort::Data5;}
    else if(ui->dataCb->currentText() == "6")   { dataBits = QSerialPort::Data6;}
    else if(ui->dataCb->currentText() == "7")   { dataBits = QSerialPort::Data7;}
    else if(ui->dataCb->currentText() == "8")   { dataBits = QSerialPort::Data8;}
 
    //设置停止位
    if     (ui->stopCb->currentText() == "1"  ) { stopBits = QSerialPort::OneStop;         }
    else if(ui->stopCb->currentText() == "1.5" ) { stopBits = QSerialPort::OneAndHalfStop;  }
    else if(ui->stopCb->currentText() == "2"   ) { stopBits = QSerialPort::TwoStop;         }
 
    //设置校验位
    if(ui->checkCb->currentText() == "none"  )  { checkBits = QSerialPort::NoParity; }
 
    //填充串口对象的属性值
    serialPort->setPortName(ui->serialCb->currentText());
    serialPort->setBaudRate(bauRate);
    serialPort->setDataBits(dataBits);
    serialPort->setStopBits(stopBits);
    serialPort->setParity(checkBits);
 
    //设置好属性后打开串口
    if(serialPort->open(QIODevice::ReadWrite) == true){
        QMessageBox::information(this,"提示","成功");
    }else{
        QMessageBox::critical(this,"提示","失败");
    }
}

4.收发串口数据功能

读数据:每当数据流从串口到达系统一次,就会传到Qt应用程序一次,readyRead信号就会触 发一次,所以可以用前面章节讲的信号和槽机制将readyRead信号和槽函数绑定,然后 就可以在槽函数中读取串口数据。槽函数中使用readAll()读取数据,使用带换行功能的appendPlainText()显示到ui的接收窗口。

//类中声明槽函数
private slots:
  void serialPortReadyRead_Solt(void);
//readyRead信号和槽函数绑定
connect(serialPort,SIGNAL(readyRead()),this,SLOT(serialPortReadyRead_Solt()));
 
//读串口
void Example::serialPortReadyRead_Solt(void)
{
    QString buf;
    buf = QString(serilaPort->readAll());
    ui->recvEdit->appendPlainText(buf);
}

写数据:获取ui 界面填写的信息,ui->sendEdit->text(),使用QSerialPort的成员函数 write将数据写到串口。

void Widget::on_sendBt_clicked()
{
    serilaPort->write(ui->sendEdit->text().toLocal8Bit().data());
}

5.关闭串口功能

使用QSerialPort的成员函数close()关闭串口。

void Widget::on_closeBt_clicked()
{
serilaPort->close();
}

6.清空发送栏数据

调用ui组件lineEdit的成员函数clear即可清空数据

void Widget::on_clearBt_clicked()
{
    ui->recvEdit->clear();
}

编译测试,结果如下图:

76 .8 Qt程序打包和部署

本章内容对应视频讲解链接(在线观看):

把QT程序打包成Windows软件 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=12

因为我们要把写好的程序发给用户来用,写好的源码也不方便给别人看,所以要把程序进行打包部署。

步骤一:点击左下角的电脑图标将Debug模式切换到Release模式。

release模式:发布版本,不对源代码进行调试,基本没有调试信息。

debug模式:调试版本,有很多调试信息。

步骤二:找到release模式构建的文件夹。

步骤三:修改可执行程序图标。先把图标加到工程所在文件夹。然后在pro文件里面添加

RC_ICONS=serial_iocn.ico

注意:图标的格式必须为.ico这个格式的,其他格式不行。

步骤四:封包操作。需要用到QT的控制台,点击电脑左下角,在搜索栏搜索qt,即可看到 qt控制台,双击即可打开,如下图 :

我们需要电脑桌面上创建一个新的文件夹,注意千万不要有中文路径。然后把exe文件拷贝到我们新创建的文件夹里面,在控制台进入可执行文件所在的目录,如下图:

步骤五:使用windeployqt工具把库加到我们新创建的这个文件夹里面。

格式:windeployqt exe 文件的名称,如下图:

打包成功后,进入执行文件路径,双击程序就可以打开,如下图:

76 .9 Qt网络编程

本章内容对应视频讲解链接(在线观看):

QT网络编程之TCP通信 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=13

QT网络编程之UDP通信 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=14

网络编程有TCP和UDP,TCP编程需要用到两个类:QTcpServer和QTcpSocket。

|-------|------------|----------------|
| 区别 |||
| | TCP | UDP |
| 是否连接 | 面向连接 | 面向非连接 |
| 传输可靠性 | 可靠 | 不可靠 |
| 应用场合 | 少量数据,如传输文件 | 传输大量数据,如传输视频语音 |

76.9.1 TCP实现服务器和客户端

TCP协议(Transmission Control Protocol)是一种面向连接的,可靠的,基于字节流的传输层通信协议,传输数据稳定可靠。

在 help索引中搜索到如下图两个重要类:

服务器编程中两个类都会用到,客户端编程中只会用到QTcpSocket对象。

本实验中对QTcpServer类的基本使用:

(1)监听客户端连接。

(2)每当有新的客户端连接服务器的时候,会自动触发信号,

(3)根据当前连接上的新的客户端创建一个Socket对象,将数据的收发动作交给socket套 接字去处理。

(4)关闭服务器close();

对QTcpSocket类的基本使用:

(1)服务器端:有新连接时获取连接状态,绑定socket 。

(2)客户端:通过socket连接服务器,连接成功触发信号。

(3)当有数据到来时会触发信号,用readAll()读取。

(4)通过读写socket收发数据。

具体步骤:

步骤一:创建工程,在工程文件.pro中添加network,如下图:

步骤二:设计ui界面,

  1. 在属性编辑栏设置主窗口大小:
  1. 添加组件

接收窗口: Plain Text Edit

发送窗口,IP地址窗口,端口号窗口:Line Edit

打开服务器,关闭服务器:Push Button

拖拽完成后逐个布局,根据需要设置组件大小,这里端口号框设置成了最小200

按钮布局:拖拽按钮和弹簧,然后点击水平布局。

然后选中全部组件,点击栅格布局:

最后更改组件名称注释,完成后如图:

步骤三:服务器端编程:

1.创建QTcpServer对象

2.创建监听端口,使得客户端可以使用这个端口访问服务器,使用listen函数。

bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);

第一个参数是绑定指定的地址(即本机服务器IP),第二个参数是绑定的本服务器端口号。

监听某个端口时,如果有新连接进来就发出newConnection()信号。

3.当服务器对象被访问时,会发出newConnection()信号,所以为该信号添加槽函数并用一个QTcpSocket对象接受客户端的访问。

4.当socket接收缓冲区有新数据到来时,会发出readyRead()信号,为该信号添加槽函数,使用readyRead()读取。

5.socket发送数据可直接调用write()成员函数。

6.关闭端口号。

代码如下:

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
 
namespace Ui {
class TcpServer;
}
 
class TcpServer : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit TcpServer(QWidget *parent = 0);
    ~TcpServer();
 
    QTcpServer * tcpServer;
    QTcpSocket * tcpSocket;
public slots:
    void newConnection_Slot(void);
    void readyRead_Solt(void);
private slots:
    void on_openBu_clicked();
    void on_sendBu_clicked();
    void on_closeBu_clicked();
 
private:
    Ui::TcpServer *ui;
};

#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QString>
TcpServer::TcpServer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TcpServer)
{
    ui->setupUi(this);
    tcpServer = new QTcpServer(this);
    tcpSocket = new QTcpSocket(this);
    //连接信号与槽函数进行绑定
    connect(tcpServer,SIGNAL(newConnection()),SLOT(newConnection_Slot()));
}
 
//连接信号槽函数
void TcpServer::newConnection_Slot(void)
{
    //连接客户端后socket
    tcpSocket = tcpServer->nextPendingConnection();
    //套接字的接收数据信号与都数据槽函数连接
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Solt()));
}
//读取数据
void  TcpServer::readyRead_Solt(void)
{
    QString buf;
    //读取
    buf = tcpSocket->readAll();
    ui->recvEdit->appendPlainText(buf);
}
 
TcpServer::~TcpServer()
{
    delete ui;
}
 
//打开
void TcpServer::on_openBu_clicked()
{
    //监听
    tcpServer->listen(QHostAddress::Any,ui->portEdit->text().toUInt());
}
 
//发送数据
void TcpServer::on_sendBu_clicked()
{
    tcpSocket->write(ui->sendEdit->text().toLocal8Bit().data());
}
 
//关闭
void TcpServer::on_closeBu_clicked()
{
    tcpSocket->close();
}

步骤四:客户端编程

1.创建QTcpSocket套接字对象

2.使用套接字对象的成员函数去请求连接服务器。

void connectToHost(const QHostAddress &address, quint16 port, openMode mode = ReadWrite);

第一个参数为服务器IP地址,第二个参数为服务器端口号。第三个参数为打开方式,默认为可读可写

函数功能:请求连接服务器连接成功后发出connected()信号,绑定槽函数connected_Solt()去操作socket。

3.使用write函数向服务器发送数据,当socket接收缓冲区有新数据到来时

会发出readyRead()信号,为该信号添加槽函数以读取数据。

4.断开与服务器的连接。

class TcpClient : public QMainWindow
{
.......
private slots:
    void on_openBt_clicked();
    void connected_Solt(void);
    void readyRead_Solt(void);
    void on_sendEdit_2_clicked();
    void on_closeBt_clicked();
};
 
TcpClient::TcpClient(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TcpClient)
{
    ui->setupUi(this);
    //创建socket 对象
    tcpSocket = new QTcpSocket(this);
}
 
TcpClient::~TcpClient()
{
    delete ui;
}
//打开(连接服务器)
void TcpClient::on_openBt_clicked()
{
    tcpSocket->connectToHost(ui->ipEdit->text(),ui->portEdit->text().toUInt());
    connect(tcpSocket,SIGNAL(connected()),this,SLOT(connected_Solt()));
}
//等待数据到来
void TcpClient::connected_Solt(void)
{
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Solt()));
}
//读取数据
void TcpClient::readyRead_Solt(void)
{
    ui->recvEdit->appendPlainText(tcpSocket->readAll());
}
//发送
void TcpClient::on_sendEdit_2_clicked()
{
    tcpSocket->write(ui->sendEdit->text().toLocal8Bit().data());
}
//关闭
void TcpClient::on_closeBt_clicked()
{
    tcpSocket->close();
}

编译运行成功,使用服务器和客户端通信如下图:

76.9.2 UDP实现服务器和客户端

UDP协议是开放式,无连接,不可靠的传输层通信协议,但它收发数据的速度相对于TCP快很多,常用在传输音视频等数据量非常大的场合。

udp网络编程只需要使用一个类QUdpSocket。

本实验中对QUdpSocket的基本使用:

1.创建QUdpSocket对象。

2.绑定端口号

3.数据到来触发readyRead()信号。

4.读取发送数据。

5.关闭。

具体步骤:

步骤一:组装ui界面,和TCP章节搭建UI界面方法一致。

步骤二:编写代码

1.创建QUdpSocket对象,使用bind函数绑定端口号和套接字,数据报到来后会发出信 号readyRead(),在绑定的槽函数内去读取数据。

2.读取数据,数据到来hasPendingDatagrams()返回true,再用pendingDatagramSize()获取数据报的长度,如果数据没有被读取

完,hasPendingDatagrams()就会返回true,直至数据都被读取完。

readDatagram(data,size);

参数data为读取的数据,size为数据长度。

3.发送数据,使用writeDatagram函数,

writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);

Data:发送的数据。

Len:发送的数据长度。

Host:目标IP地址。

Port:目标端口号。

4.关闭socket套接字。

代码如下:

udp.h

#include <QMainWindow>
#include <QUdpSocket>
 
namespace Ui {
class Udp;
}
 
class Udp : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit Udp(QWidget *parent = 0);
    ~Udp();
    QUdpSocket * udpSocket;
private slots:
    void on_pushButton_clicked();
    void readyRead_Slot(void);
    void on_pushButton_3_clicked();
    void on_pushButton_2_clicked();
 
private:
    Ui::Udp *ui;
};

udp.cpp:

Udp::Udp(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::Udp)
{
    ui->setupUi(this);
    udpSocket = new QUdpSocket(this);
}
 
Udp::~Udp()
{
    delete ui;
}
/*
 * 打开按钮
 */
void Udp::on_pushButton_clicked()
{
    //绑定本端口的端口号
    if(udpSocket->bind(ui->cliEdit->text().toUInt()) == true){
        QMessageBox::information(this,"提示","成功");
    }else{
        QMessageBox::information(this,"提示","失败");
    }
    //绑定数据信号和槽函数
    connect(udpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Slot()));
}
/*
 *读取数据槽函数
 */
void Udp::readyRead_Slot()
{
    QString buf;
    QByteArray array;
    //hasPendingDatagrams()返回true时表示至少有一个数据报在等待被读取
    while(udpSocket->hasPendingDatagrams()){
       //获取数据
        array.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(array.data(),array.size());
        buf = array.data();
        ui->recvEdit->appendPlainText(buf);
    }
}
 
/*
 * 发送数据
 */
void Udp::on_pushButton_3_clicked()
{
    quint16 port;
    QString sendBuff;
    QHostAddress address;
 
    address.setAddress(ui->ipEdit->text());//目标机地址
    port = ui->portEdit->text().toInt();//目标机端口号
    sendBuff = ui->sendEdit->text();//发送的数据
    //发送
    udpSocket->writeDatagram(sendBuff.toLocal8Bit().data(),sendBuff.length(),address,port);
}
 
/*
 *关闭
 */
void Udp::on_pushButton_2_clicked()
{
    udpSocket->close();
}

步骤三:运行测试,收发功能正常如下图:

76 .10 Qt定时器

本章内容对应视频讲解链接(在线观看):

QT时间编程之QT时钟 → https://www.bilibili.com/video/BV1tp4y1i7EJ?p=15

实验目标:实现计时器功能,并且点击打点按钮将当前时间打印出来。

用到的类有QTimer和QTime,QTimer是一个计时器类,相当于秒表,QTimer是一个时间类,相当于手表。

76.10.1 实验步骤

步骤一:界面布局:

拖拽组件,在属性编辑栏设置大小,然后选中按钮,点击水平布局;

在属性编辑栏设置Label的最小高度为50,选中全部组件,点击栅格布局,如下图:

根据实际情况调整大小,更改对象名后如下图:

步骤二:创建计时器类对象timer和时间类time,设置初始时间为0。

    class TimerP : public QMainWindow
    {
    Q_OBJECT
 
    public:
    explicit TimerP(QWidget *parent = 0);
    ~TimerP();
    QTimer timer;
QTime time;
..........
    };

步骤三:开启计时器对象,设置定时时间,时间到后会发出 timeout() 信号,绑定此信号和自定义的槽函数timeOut_Slot()。

void start(int msec);

函数功能:开启定时器,时间到后发出timeout信号,并重新计时。

参数msec含义:定时时间,单位毫秒。

    TimerP::TimerP(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TimerP)
    {
      ui->setupUi(this);
//信号timeout与槽函数绑定
      connect(&timer,SIGNAL(timeout()),this,SLOT(timeOut_Slot()));
      time.setHMS(0,0,0,0);
        ui->showTime->setText("00:00:00:000");
    }

/*
 *开始定时
 */
void TimerP::on_starBu_clicked()
{
     timer.start(3);
}

步骤四:槽函数timeOut_Slot()内处理时间类对象,使每次计时时间结束后,时间对象能增加 相同的时间,实现计时功能。

QTime addMSecs(int ms) const;

参数msec含义:增加的时间值,单位毫秒。

函数功能:返回一个当前时间对象之后ms毫秒之后的时间对象。

	/*
	 * 计时
	 */
	void TimerP::timeOut_Slot()
	{
   	 //qDebug("timt out");
   	 time = time.addMSecs(3);
   	 ui->showTime->setText(time.toString("hh:mm:ss.zzz"));
	}

步骤五:打点记录功能,使用全局变量记录排名,并显示到界面。

/*
 * 记录
 */
void TimerP::on_bitBu_clicked()
{
     QString temp;
     i=i+1;
     temp.sprintf("%d: ",i);
     ui->bitTime->append(temp);
     ui->bitTime->append(time.toString("hh:mm:ss.zzz"));
}

76.10.2 部分代码

timerp.h:
class TimerP : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit TimerP(QWidget *parent = 0);
    ~TimerP();
    QTimer timer;
    QTime time;
 
private slots:
    void on_starBu_clicked();//开始计时按钮槽函数
    void timeOut_Slot();//定时时间到槽函数
    void on_closeBu_clicked();//关闭按钮槽函数
    void on_resetBu_clicked();//重置按钮槽函数
    void on_bitBu_clicked();//打点记录按钮槽函数
 
private:
    Ui::TimerP *ui;
};

timerp.cpp:
#include <QTimer>
#include <QTime>
 
static int i;
TimerP::TimerP(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TimerP)
{
    ui->setupUi(this);
    connect(&timer,SIGNAL(timeout()),this,SLOT(timeOut_Slot()));
    time.setHMS(0,0,0,0);
    ui->showTime->setText("00:00:00:000");
}
 
TimerP::~TimerP()
{
    delete ui;
}
 
void TimerP::on_starBu_clicked()
{
    timer.start(3);
}
/*
 * 处理时间类对象
 */
void TimerP::timeOut_Slot()
{
    //qDebug("timt out");
    time = time.addMSecs(3);
    ui->showTime->setText(time.toString("hh:mm:ss.zzz"));
}
/*
 * 关闭
 */
void TimerP::on_closeBu_clicked()
{
    timer.stop();
    i=0;
}
/*
 * 重置
 */
void TimerP::on_resetBu_clicked()
{
    timer.stop();
    time.setHMS(0,0,0,0);
    ui->showTime->setText("00:00:00:000");
    ui->bitTime->clear();
    i=0;
}
 
/*
 * 记录
 */
void TimerP::on_bitBu_clicked()
{
    QString temp;
    i=i+1;
    temp.sprintf("%d: ",i);
    ui->bitTime->append(temp);
    ui->bitTime->append(time.toString("hh:mm:ss.zzz"));
}

运行结果:

相关推荐
深圳市青牛科技实业有限公司 小芋圆6 分钟前
【青牛科技】 GC2803:白色家电与安防领域的卓越驱动芯片可替代ULN2803
科技·单片机·嵌入式硬件·电脑·白色家电·电动卷帘门·工业设备
Arciab1 小时前
51单片机入门:LED灯控制(01)
单片机·led·51
LaoZhangGong1232 小时前
Linux第95步_Linux内核中的INPUT子系统
linux·运维·数据库·经验分享·stm32·input·stm32mp127
BreezeJuvenile4 小时前
STM32总体架构简单介绍
stm32·单片机·嵌入式硬件
scgg4 小时前
STM32抢占优先级不生效
stm32·疑问
可乐鸡翅好好吃5 小时前
STM32的中断(什么是外部中断和其他中断以及中断号是什么)
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·51单片机
没有余地 EliasJie7 小时前
深度学习图像视觉 RKNN Toolkit2 部署 RK3588S边缘端 过程全记录
人工智能·嵌入式硬件·深度学习
嵌入式大圣7 小时前
单片机在电路板中的应用
单片机·嵌入式硬件
小A1599 小时前
STM32完全学习——使用标准库完成PWM输出
stm32·单片机·学习
小A15910 小时前
STM32完全学习——使用标准库完成定时器中断
stm32·单片机·学习