QT完成UDP网络上位机设计,接收STM32自定义报文数据的处理和显示

QT 同时被 2 个专栏收录
4 篇文章 0 订阅
37 篇文章 3 订阅

设计需求

在原设计中,没有网络部分的设计,所以硬件部分是没有设计网络模块。

为了增加网络功能,通过对预留串口的芯片手册查找,发现了预留的PA9和PA10口,可以复用为uart1串口。
在这里插入图片描述

有了串口可以通信。那么就可以使用 uart转网口模块,实现网络功能。

正点原子以太网转串口模块 调试和使用方法(实战详解)

模块的使用方法这篇文章详细的进行了说明,本文章将从整体设计进行阐述。

UDP

本设计使用PC上位机和stm32的网络通信为UDP通信

QT的设计也是基于之前写的这个程序

QT编写的UDP上位机

TCP和UDP的区别

  1. TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。

  2. 也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。

  3. 因为上面的特性,所以TCP需要区分客户端和服务器,UDP是不需要区分的

UI界面设计

对于板子的部分,硬件和代码都已经实现了,只是后面给数据加上报文协议就可以了。重点就放在了上位机的设计。

首先,设计出来上位机,实现UDP上位机能够正常接收网络数据的功能,再来考虑报文协议等问题。

之前写过一个TCP上位机的设计文章,设计UDP的过程大同小异,可以进行参考,链接在这里

Qt 设计实现TCP服务器和客户端上位机(实战详解)

界面设计

首先是界面的设计,板子采集的数据主要有功率 温度 还有风机状态这三个数据量,所以设计显示界面如图所示。
在这里插入图片描述
对于UDP服务,需要设置本地的端口号,以及目标的端口号还有目标的IP

UDP代码设计与实现

打开UDP

单机 “打开UDP” 的PushButton时候,应该实现初始化udp的socket协议,接收和读写数据的功能。主要实现的功能是 读取UI界面中输入的本地端口号,绑定本地端口到到socket,监听数据是否接收到数据,读写数据,数据显示到指定位置

/*
 * 点击打开按钮,绑定端口号到socket
 * 有了这部分,开启服务,然后输入端口号,就可以接收到数据了
 */
void Widget::on_openpushButton_clicked()
{
    /* 绑定本地端口号 */
    if(( udpSocket->bind(ui->locaportlineEdit_2->text().toInt()) ) == true)
    {
         QMessageBox::information(this, "提示", "UDP端口号绑定成功!");
    }else{
         QMessageBox::warning(this, "警告", "UDP端口号绑定失败!");
    }


    connect(udpSocket, SIGNAL(readyRead()),
            this, SLOT(readyRead_Slot()));
}

void Widget::readyRead_Slot()
{
    /* 等待接收到数据 */
    while (udpSocket->hasPendingDatagrams()) {

        QByteArray datagram;
        /* 调整数组大小一致 */
        datagram.resize(udpSocket->pendingDatagramSize());

        udpSocket->readDatagram(datagram.data(), datagram.size());      // 读取接收到的数据

        QString buf;
        buf = datagram.data();                                          // 转化成字符串类型
        ui->recvplainTextEdit->appendPlainText(buf);                    // 把内容显示在 接收窗口
    }
}

关闭UDP

/* 关闭UDP服务 */
void Widget::on_closepushButton_2_clicked()
{
    udpSocket->close();
}

发送数据

这里主要设计,读取UI界面中输入的目标端口号和目标IP地址,以及输入的数据存放到缓存中,最后就是发送指令

/* 发送数据 */
void Widget::on_pushButton_3_clicked()
{
    quint16 port;                                       // 目标端口号地址

    QString sendbuff;                                   // 发送数据的缓存

    QHostAddress address;                               // 目标IP地址

    address.setAddress(ui->aimiplineEdit_4->text());

    sendbuff = ui->sendlineEdit->text();

    port = ui->aimportlineEdit_3->text().toUInt();

    udpSocket->writeDatagram(sendbuff.toLocal8Bit().data(), sendbuff.length(), address, port);
}

关闭UDP

/* 清空接收窗口的内容 */
void Widget::on_pushButton_clicked()
{
    ui->recvplainTextEdit->clear();
}

上位机板间通信协议

因为要传输三个数据,直接发送过来接收,数据都挤在一起发送过来肯定是没有办法处理的,所以,要进行协议的设计,这里的报文协议设计采用比较简单使用的方法,规则如表所示
在这里插入图片描述
这里的校验方式采用比较简单实用的和校验

U8 TX_CheckSum(U8 *buf, U8 len) //buf为数组,len为数组长度
{ 
    U8 i, ret = 0;
    for(i=0; i<len; i++)
    {
        ret += *(buf++);
    }
     ret = ~ret;
    return ret;
}
U8 RX_CheckSum(U8 *buf, U8 len) //buf为数组,len为数组长度
{ 
    U8 i, ret = 0;
     for(i=0; i<len; i++)
    {
        ret += *(buf++);
    }
    ret = ret;
    return ret+1;
}

有了报文协议,这样就可以通过报文数据来区分不同的数据,数据的报文类型定义如下
在这里插入图片描述

板子通信代码设计

因为使用了串口转网口模块,所以实际操作中直接发送数据到串口就可以了。

串口的发送函数,根据报文协议,生成对应的报文结构,然后通过串口发送出去数组。

uint8_t usart1_send(uint8_t cmd, uint8_t *datas,uint16_t dataLen)
{
	uint16_t len = 0;
	uint8_t rxDatas[10] = {0};
	uint8_t dataCheck;
	head[1] = cmd;
	head[2] = dataLen & 0xff;
	head[3] = (dataLen >> 8) & 0xff;
	head[4] = TX_CheckSum(head + 1,3);
	
	dataCheck = TX_CheckSum(datas,dataLen);
	usart1_send_data(head, 5);
	usart1_send_data(datas,dataLen);
	usart1_send_data(&dataCheck,1);
	usart1_send_data(&tail,1);
	
	return 0;
}

本来应该有一个主从的应答校验重发,就是这样,因为时间关系就没有做
在这里插入图片描述

UDP上位机数据处理与显示

在这里插入图片描述


void Widget::readyRead_Slot()
{
    /* 等待接收到数据 */
    while (udpSocket->hasPendingDatagrams()) {

        QByteArray datagram, buf, tempAdcx, tempTemper;
        /* 调整数组大小一致 */
        datagram.resize(udpSocket->pendingDatagramSize());

        udpSocket->readDatagram(datagram.data(), datagram.size());      // 读取接收到的数据

        buf = datagram;
        int len = buf.length();
        int i = 0, tempFan;
        while(len--)
        {
            if(buf[i] == 0xAA)
            {
                if(buf[i+1] == 0x01){
                    tempAdcx[0] = buf[i+5];
                    tempAdcx[1
                            ] = buf[i+6];
                }else if(buf[i+1] == 0x02){
                    tempTemper[0] = buf[i+5];
                }else if(buf[i+1] = 0x03){
                    tempFan = buf[i+5];
                }
            }
            i++;
        }

        QString strtempAdcx;
        bool ok;
        strtempAdcx = tempAdcx.toHex().data();
        //qDebug() << strtempAdcx.toInt(&ok, 16) <<endl;
        int adcx = strtempAdcx.toInt(&ok, 16);

        ui->gonglvlabel_6->setText(QString::number(adcx, 10));
        ui->wendulabel_8->setText(QString::number(tempTemper[0]));
        if(tempFan == 1){
            ui->fengjilabel_10->setText("故障");
        }else{
            ui->fengjilabel_10->setText("正常");
        }

    }
}

这里本来也应该有一个和校验,来检验数据有没有因为传输发生错误,在这个版本中没有加入,直接验证报文头然后提取数据进行一个读取了。

读取到的数据以QByteArray的类型保存,所以要进行格式的转换,才能最终显示为需要的十进制结果。

HEX形式转十进制显示QString::number

传送过来的报文数据,是以HEX字节的形式存在数组中,比如我们的数据,如果是1234,那么就是0x04D2,十六进制的格式,这个数据现在存放在上位机的buf中,buf的类型为QByteArray


QByteArray类有丰富的函数封装,可以进行一个格式的转换
在这里插入图片描述

QByteArray str("FF");
  bool ok;
  int hex = str.toInt(&ok, 16);     // hex == 255, ok == true
  int dec = str.toInt(&ok, 10);     // dec == 0, ok == false

在QT的帮助中有例程可以查看,所以把数据转换成十进制数可以解决了

但是这里有一个问题,我需要把数据显示在label类型的显示界面中
在这里插入图片描述
这里可以看到
在这里插入图片描述
输入的内容需要时QString类型的数据,所以直接把转换好的十进制数据放进去是不可以编译通过的。

这个时候就需要使用QString的一个函数number
在这里插入图片描述

ui->gonglvlabel_6->setText(QString::number(adcx, 10));
ui->wendulabel_8->setText(QString::number(tempTemper[0]));

大功告成~

  • 0
    点赞
  • 0
    评论
  • 12
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值