【QT Creator学习记录】上位机与下位机网口UDP通信

UI界面图

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

工程文件xxx.pro中添加:

#网口通信
QT       += network

头文件xxx.h中添加:

//UDP
#include <QUdpSocket>
#include <QNetworkDatagram>

2. 接收数据

(1) uSocket初始化

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

 
    /*   UDP   -注意逻辑-  */
    //创建QUdpSocket对象
    uSocket = new QUdpSocket(this);
    //绑定本地IP和端口号,IPLocal与PortLocal为自定义的全局变量
    uSocket->bind(QHostAddress(IPLocal),PortLocal);
    //默认关闭
    uSocket->close();
    //收到数据时,会触发readyRead()信号,自定义readPendingDatagrams()进行读取数据
    connect(uSocket,&QUdpSocket::readyRead, this,&MainWindow::UDP_DataReceived);

}
/*     UDP网口    ↓*/
    //默认本地端口号
    quint16 PortLocal =8089;
    //默认本地IP地址
    QString IPLocal ="192.168.10.200";

QT官方文档:bind函数的定义和所需参数, 咱直接传个IP地址和端口号就行

// ### Qt6: make the first one virtual
    bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
    bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);

问题记录:关于QUdpSocket数据类型的 bind () 与 close () 与 open ()

之前将IP与端口号绑定后,正常接收数据,关闭网口再打开发现收不到数据了😂

后面发现UDP关闭后,再打开需要重新绑定,即便uSocket对象一直存在。

另外open的时候注意,记得写open方式,QT官方文档:

virtual bool open(OpenMode mode);
public:
    enum OpenModeFlag {
        NotOpen = 0x0000,
        ReadOnly = 0x0001,
        WriteOnly = 0x0002,
        ReadWrite = ReadOnly | WriteOnly,
        Append = 0x0004,
        Truncate = 0x0008,
        Text = 0x0010,
        Unbuffered = 0x0020
    };
    Q_DECLARE_FLAGS(OpenMode, OpenModeFlag)

而且,记得传参数时加上QIODevice::    (我之前没加,一直尝试,不知道哪出了问题,坑。)

uSocket->open(QIODevice::ReadWrite);

结论:close后open要重新bind

(2)UDP接收数据

//UDP接收
void MainWindow::UDP_DataReceived(){


    //while循环中读取数据,只要有数据,就一直读取并处理。
    while (uSocket->hasPendingDatagrams()) {

        QNetworkDatagram datagram = uSocket->receiveDatagram();
        UDP_DataHex.append(datagram.data().toHex());

        //报文头判断,true则继续,false则将数据丢弃
        if(UDP_DataHex.at(0)=='f'||UDP_DataHex.at(1)=='f'){
            count +=1;
            //qDebug()<<"UDP test count:"<< count;
            if(count==10){

                DataRecevied_Hex=UDP_DataHex;
                //数据模拟(连通单片机时应注释掉)
                DataRecevied_Hex="ffb27f02d9022600e114";

                //校验位判断,true则输出,false则将数据丢弃
                if(Parity_DataReceived()){
                    ui->IPDestination->setText(datagram.senderAddress().toString());
                    ui->PortDestination->setText(QString::number(datagram.senderPort()));
                    ui->UDP_DataReceived->append(Line75Percent+TimeStamp() + DataFactory(DataRecevied_Hex));

                    //AngleCurrent();

                }else{}
            //UDP_DataHex数据输出后,清空内容,并将计数器归零
            count=0;
            UDP_DataHex="";
            }else{}
        }else{
            //UDP_DataHex头两个字符不是ff,则清空内容,并将计数器归零
            count=0;
            UDP_DataHex="";
        }

    }

}

3. 接收数据校验

将接受到的数据进行校验和,如何最后接收到的数据最后两位与计算后所得相同,返回true。

bool MainWindow::Parity_DataReceived(){

    bool judge;

    //校验位计算与转换
    int ache=0;
                            //↓除去校验位本身的长度
    for(int i=0;i<DataRecevied_Hex.size()-2; i+=2){

        ache += HexToDec(DataRecevied_Hex.at(i)).toInt()*16 + HexToDec(DataRecevied_Hex.at(i+1)).toInt();
        //qDebug() << "test"<< ache;

    }
    QString Parity =QString::number(ache, 16);

    if(Parity.size()==1){
        Parity.prepend("000");
    }
    else if(Parity.size()==2){
        Parity.prepend("00");
    }
    else if(Parity.size()==3){
        Parity.prepend("0");
    }

    Parity.remove(0,2);
    QString str =DataRecevied_Hex.at(18);
    str.append(DataRecevied_Hex.at(19));
    if(Parity==str){
        qDebug() << "校验成功,校验位为:" << Parity;
        return judge = true;
    }else{
        ui->UDP_DataReceived->append("<font color=\"#B22222\">数据校验失败,接收校验位为:"+str+ "  应为:"+Parity);
        qDebug() << "校验失败,数据校验位为:" << str << "计算校验位为:" << Parity;
        return judge = false;
    }
}

4. UDP数据发送


//最终数据发送[判断是 串口发送 or UDP发送]
void MainWindow::SendMode(){

    QByteArray dataByte =QByteArray::fromHex(DataSend_Hex.toLatin1());

    //发送方式判断
    if(serial->isOpen()){
        //串口写入
        qDebug() << "串口发送数据:"<< dataByte;
        serial->write(dataByte);
        ui->Serial_DataSend->append(Line75Percent+TimeStamp()+"<font color=\"#05BDFF\">"+DataFactory(DataSend_Hex));
    }else if(uSocket->isOpen()){
        qDebug() << "UDP发送数据:"; 

        //读取用户输入的IP地址与端口号
        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));
    }else{
        qDebug() << "请打开串口或UDP";
    }

};

数据发送用的函数QT官方文档:

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

writeDatagram(数据,QHostAddress(IP地址),端口)

5. 控件QLineEdit输入限制

实际应用时,应该限制用户所输入的内容(如只能输入IP地址):

(1)方法一:正则表达式

首先这种方法点号是不驻留的,其次输入时无法跳过点号完全删除其他数字

例如255.123.255.255,是没办法直接删除123的,必须将后面内容全部删除,才能更改123

ui->IPLocal->setValidator(new QRegExpValidator(QRegExp("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b")));

(2)方法二: setInputMask函数

ui->IPLocal->setInputMask("000.000.000.000; ");

官方文档内容: 

void setInputMask(const QString &inputMask);

其中"000.000.000.000; "的含义就是限制IP地址输入,这种方法点号会驻留就如UI界面中显示的一样,无法删除,但不存在方法一的问题。

6. 用户更改IP地址

首先判断输入是否为空,不为空再判断IP地址格式是否正确

//本地IP地址与端口号更改
void MainWindow::on_UDP_IPChange_clicked(){

    if(ui->IPLocal->text()!=""&&IsIP(ui->IPLocal->text())){
        IPLocal = ui->IPLocal->text();
    }
    if(ui->PortLocal->text()!=""){
       PortLocal = ui->PortLocal->text().toInt();
    }
    uSocket->close();
    uSocket->bind(QHostAddress(IPLocal),PortLocal);
    uSocket->open(QIODevice::ReadWrite);
    qDebug() <<"IP地址更改test:"+ IPLocal+"   "+PortLocal;
};

 QRegExp为QT的正则表达式,我直接趴下来的,具体表达式的含义可以自行查阅。

//是否为IP地址判断
bool  MainWindow::IsIP(QString currentIP)
{
    QRegExp rxp("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b");
    if(!rxp.exactMatch(currentIP))
    {
        QMessageBox::information(this, tr("错误"), tr("IP地址错误"));
        return false;
    }
    return true;
}

7. 总结

相比串口通信,UDP参数不多,主要就四个,本机IP、本机端口、目标IP、目标端口。

经过串口的各种数据转换洗礼,这里反而轻松不少,只是要注意UDP接收数据容易出现问题,要想办法验证数据头和校验位。(其实保险起见,串口也应该验证一下的,(╯▔皿▔)╯再说吧)

  • 13
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
编写上位机串口通信通常使用Qt框架。Qt是一个跨平台的C++图形用户界面应用程序开发框架,它提供了丰富的库和工具,用于创建GUI应用程序和与硬件进行通信。 以下是在Qt中编写上位机串口通信的一般步骤: 1. 安装Qt框架:首先需要在您的开发环境中安装Qt框架,并配置相应的开发环境。 2. 创建Qt应用程序:使用Qt Creator或其他IDE创建一个新的Qt应用程序。 3. 配置串口设备:使用Qt提供的串口设备API(例如QSerialPort)找到您的目标设备的串口信息。这通常需要在硬件设备的文档中找到串口的名称、波特率、数据位、停止位和校验位等信息。 4. 打开串口设备:使用QSerialPort类打开指定的串口设备。 5. 读取和写入数据:使用QSerialPort类的read()和write()方法来读取和写入数据。这些方法允许您从串口设备读取数据并发送数据到串口设备。 6. 关闭串口设备:在完成通信后,使用QSerialPort类的close()方法关闭串口设备。 7. 显示通信结果:将通信结果显示在Qt应用程序的用户界面上,以便用户可以查看通信过程和结果。 以下是一个简单的示例代码片段,用于在Qt中打开串口设备并读取数据: ```cpp #include <QSerialPort> #include <QDebug> void readFromSerial() { QSerialPort serial; serial.setPortName("COM1"); // 设置串口名称 serial.setBaudRate(QSerialPort::Baud9600); // 设置波特率 serial.setDataBits(QSerialPort::Data8); // 设置数据位 serial.setParity(QSerialPort::NoParity); // 设置校验位 serial.setStopBits(QSerialPort::OneStop); // 设置停止位 serial.setFlowControl(QSerialPort::NoFlowControl); // 设置流控制 if (serial.open(QIODevice::ReadWrite)) { // 打开串口设备 while (true) { char c = serial.readLine(10); // 读取一行数据(最多10个字节) if (!c.isEmpty()) { qDebug() << "Received data:" << c; // 输出接收到的数据 } else { break; // 如果没有接收到数据,退出循环 } } serial.close(); // 关闭串口设备 } else { qDebug() << "Failed to open serial port"; // 输出打开串口失败的消息 } } ``` 这是一个非常基础的示例,它只读取一行数据并将其输出到控制台。在实际应用中,您可能需要将通信结果显示在Qt应用程序的用户界面上,并处理各种错误和异常情况。 请注意,上述代码仅适用于Windows操作系统上的串口通信。如果您使用的是其他操作系统(如Linux或macOS),则需要使用不同的API和库来访问串口设备。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值