【北京迅为】《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"));
}

运行结果:

相关推荐
云卓科技1 小时前
无人机之中继通信技术篇
科技·嵌入式硬件·安全·机器人·无人机
潇洒的电磁波2 小时前
半波正弦信号的FFT变换
嵌入式硬件
Lin2012302 小时前
STM32 + CubeMX + 硬件SPI + W5500 +TcpClient
stm32·单片机·嵌入式硬件
极客小张2 小时前
基于STM32的智能温室环境监测与控制系统设计(代码示例)
c语言·arm开发·stm32·单片机·嵌入式硬件·物联网·毕业设计
玉树临风江流儿2 小时前
STM32标准库-待机模式
stm32·单片机·嵌入式硬件
小狗爱吃黄桃罐头7 小时前
江协科技STM32学习- P31 I2C通信协议
stm32·i2c·江科大
小狗爱吃黄桃罐头9 小时前
江协科技STM32学习- P36 SPI通信外设
stm32·江科大
物联高科10 小时前
高效节能电动机在电厂中的应用研究
单片机·嵌入式硬件·汽车·能源·制造
编码追梦人11 小时前
如何编写STM32的RTC程序
stm32