上位机---QT

本历程是IAP二级启动系统,OTA升级使用QT编写的上位机。

QT新手写的比较烂,,,,,,,,

使用QT的版本:

mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"           // 主窗口类
#include "ui_mainwindow.h"        // UI界面文件
#include <QDebug>                 // 调试输出
#include <QTextCodec>             // 字符编码
#include <QMessageBox>            // 消息对话框
#include <QThread>                // 多线程
#include <QHostAddress>           // 网络地址
#include <QSerialPortInfo>        // 串口信息
#include <QFileDialog>            // 文件对话框
#include <QNetworkInterface>      // 网络接口
#include <QTimer>                 // 定时器

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_filePath("")
    , tcpserver(new QTcpServer(this))
    , tcpSocket(nullptr)
    , tcpBlockNum(1)
    , tcpPos(0)
    , tcpReadyForTransfer(false)       // 初始化为未准备好传输
    , tcpHandshakeReceived(false)      // 初始化为未收到握手信号
{
    ui->setupUi(this);
    COM = new QSerialPort(this);

    // 初始化串口列表
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ui->COM_Number->addItem(info.portName());
    }

    // 设置默认TCP参数
    ui->TCP_IP->setPlainText("0.0.0.0");  // 使用0.0.0.0监听所有接口
    ui->TCP_PORT->setPlainText("5001");   // 使用5001端口

    // 更新网络信息到textBrowser_2
    updateNetworkInfo();

    // 连接串口接收信号
    connect(COM, SIGNAL(readyRead()), this, SLOT(Ser_COM_RX()));

    // 连接TCP服务器新连接信号
    //在Qt中,newConnection是QTcpServer类的一个信号。当有一个新的连接到来时,QTcpServer会发出这个信号。
    connect(tcpserver, SIGNAL(newConnection()), this, SLOT(newTcpConnection()));
}

MainWindow::~MainWindow()
{
    delete ui;
    if (tcpSocket) tcpSocket->close();
    if (tcpserver) tcpserver->close();
}

void MainWindow::updateNetworkInfo()
{
    QString networkInfo = "=== 网络信息 ===\n";
    networkInfo += "可用IP地址:\n";

    foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) {
        if (interface.flags() & QNetworkInterface::IsUp &&
            !(interface.flags() & QNetworkInterface::IsLoopBack)) {

            networkInfo += "接口: " + interface.humanReadableName() + "\n";

            foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) {
                if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
                    networkInfo += "  IP: " + entry.ip().toString() +
                                 "  掩码: " + entry.netmask().toString() + "\n";
                }
            }
        }
    }

    networkInfo += "\n推荐设置:\n";
    networkInfo += "- 使用 0.0.0.0 监听所有接口\n";
    networkInfo += "- 或使用上面列出的具体IP地址\n";

    ui->textBrowser_2->append(networkInfo);
}

// ========== 串口功能 ==========

/**
 * @brief 刷新串口列表
 * 扫描系统可用串口并更新到下拉框
 */
void MainWindow::on_button_Refresh_com_clicked()
{
    ui->COM_Number->clear();
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ui->COM_Number->addItem(info.portName());
    }
}

/**
 * @brief 打开/关闭串口
 * 配置串口参数并控制串口开关状态
 */
void MainWindow::on_Button_Open_COM_clicked()
{
    QSerialPort::BaudRate baudRate;
    QSerialPort::StopBits stopBits = QSerialPort::OneStop;
    QSerialPort::DataBits dataBits = QSerialPort::Data8;
    QSerialPort::Parity checkBits = QSerialPort::NoParity;
    QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl;

    // 设置波特率 currentText获得文本中的内容
    if(ui->COM_Baud_Rate->currentText() == "9600"){
        baudRate = QSerialPort::Baud9600;
    }
    else if(ui->COM_Baud_Rate->currentText() == "115200"){
        baudRate = QSerialPort::Baud115200;
    }
    else{
        QMessageBox::warning(this, "参数错误", "仅支持9600/115200波特率!");
        return;
    }

    // 配置串口参数
    COM->setPortName(ui->COM_Number->currentText());
    COM->setBaudRate(baudRate);
    COM->setStopBits(stopBits);
    COM->setDataBits(dataBits);
    COM->setParity(checkBits);
    COM->setFlowControl(flowControl);

    // 打开串口逻辑
    if(ui->Button_Open_COM->text() == "打开串口"){
        if(COM->open(QIODevice::ReadWrite)){
            ui->Button_Open_COM->setText("关闭串口");
            ui->pushButton->setStyleSheet("background-color: red;");
        }else{
            QMessageBox::critical(this, "错误", "串口打开失败:\r\n" + COM->errorString());
        }
    }
    // 关闭串口逻辑 text 单行文本,简单控件返回的是QString类型的文本。
    else if(ui->Button_Open_COM->text() == "关闭串口"){
        if(COM->isOpen()){
            COM->close();
            ui->Button_Open_COM->setText("打开串口");
            ui->pushButton->setStyleSheet("background-color: black;");
        }
    }
}

/**
 * @brief 发送串口文本
 * 将发送区文本通过串口发送
 */
void MainWindow::on_COM_TX_clicked()
{
    //toPlainText() 多行纯文本,不含格式,返回的是纯文本,不包含任何格式信息。
    //ui->textEdit->toPlainText()返回一个QString,然后调用toLatin1()将其转换为Latin-1编码的字节数组,
    //因为QSerialPort的write函数需要的是QByteArray或const char*。
    if (COM->isOpen()) {
        COM->write(ui->textEdit->toPlainText().toLatin1());
    } else {
        QMessageBox::warning(this, "提示", "请先打开串口!");
    }
}

/**
 * @brief 清除发送区
 * 清空发送文本框内容
 */
void MainWindow::on_COM_TX_Clear_clicked()
{
    ui->textEdit->clear();
}

/**
 * @brief 串口数据接收
 * 实时接收并显示串口数据,使用GB2312编码处理中文
 */
void MainWindow::Ser_COM_RX()
{
    /*
    isEmpty:用于检查容器是否为空。
    创建一个 GB2312 编码的文本编码器
    将 QByteArray 中的 GB2312 编码数据转换为 Unicode 的 QString
    insertPlainText(): 插入纯文本,不保留格式
    功能: 将光标移动到文本末尾,实现自动滚动
    */
    QByteArray buf = COM->readAll();
    if (!buf.isEmpty()) {
        QTextCodec *codec = QTextCodec::codecForName("GB2312");
        QString str = codec->toUnicode(buf);
        ui->textBrowser->insertPlainText(str);
        ui->textBrowser->moveCursor(QTextCursor::End);
    }
}

/**
 * @brief 打开bin文件
 * 选择并读取bin文件到内存
 */
void MainWindow::on_open_bin_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this, "选择bin文件", "", "bin文件 (*.bin);;所有文件 (*)");
    if (!filePath.isEmpty()) {
        // QString m_filePath;   保存到全局变量中去,其他函数也要使用
        m_filePath = filePath;
        QFile file(m_filePath);
        if (file.open(QIODevice::ReadOnly)) {  //QIODevice::ReadOnly 指定打开的模式:只读
            m_fileData = file.readAll();
            file.close();
            QMessageBox::information(this, "提示", "已选择bin文件:\r\n" + filePath + "\r\n文件大小:" + QString::number(m_fileData.size()) + "字节");
        } else {
            QMessageBox::critical(this, "错误", "文件打开失败:" + file.errorString());
        }
    }
}

/**
 * @brief 字节数组转十六进制字符串
 * @param data 输入的字节数组
 * @return 格式化的十六进制字符串
 */
QString MainWindow::byteArrayToHexString(const QByteArray &data)
{
    QString hexStr;
    for (int i = 0; i < data.size(); i++) {
        hexStr += QString("%1 ").arg((unsigned char)data.at(i), 2, 16, QChar('0')).toUpper();
        if ((i + 1) % 16 == 0) {
            hexStr += "\n";
        }
    }
    return hexStr;
}

/**
 * @brief Xmodem CRC16校验计算
 * @param data 需要计算校验的数据
 * @return CRC16校验值
 */
quint16 MainWindow::xmodemCrc16(const QByteArray &data)
{
    quint16 crc = 0x0000;
    quint16 poly = 0x1021;
    for (char byte : data) {
        crc ^= (quint16)(unsigned char)byte << 8;
        for (int i = 0; i < 8; i++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ poly;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

/**
 * @brief 串口发送bin文件
 * 使用Xmodem协议通过串口发送bin文件
 */
void MainWindow::on_send_bin_clicked()
{   //true - 字符串为空(长度为0) false - 字符串不为空
    if (m_filePath.isEmpty() || m_fileData.isEmpty()) {
        QMessageBox::warning(this, "提示", "请先选择bin文件!");
        return;
    }
    if (!COM->isOpen()) {
        QMessageBox::warning(this, "提示", "请先打开串口!");
        return;
    }

    // 断开串口接收信号,避免干扰Xmodem协议
    disconnect(COM, SIGNAL(readyRead()), this, SLOT(Ser_COM_RX()));

    bool handshakeSuccess = false;
    QByteArray recvBuffer;
    const int maxWaitMs = 15000;
    const int checkIntervalMs = 100;
    int elapsedMs = 0;

    ui->textBrowser->append("等待STM32发送'C'信号(0x43)...");

    // 等待握手信号'C'
    while (elapsedMs < maxWaitMs && !handshakeSuccess) {
        if (COM->bytesAvailable() > 0) {
            QByteArray newData = COM->readAll();
            recvBuffer.append(newData);

            QString hexLog = "收到数据(十六进制):";
            for (uint8_t b : newData) {
                hexLog += QString("%1 ").arg(b, 2, 16, QChar('0')).toUpper();
            }
            ui->textBrowser->append(hexLog);
        }

        // 检查是否收到握手信号'C'
        if (recvBuffer.indexOf(char(0x43))) {
            handshakeSuccess = true;
            COM->readAll();
            recvBuffer.clear();
            ui->textBrowser->append("已收到'C'(0x43),握手成功!");
            break;
        }

        QThread::msleep(checkIntervalMs);
        elapsedMs += checkIntervalMs;
    }

    if (!handshakeSuccess) {
        ui->textBrowser->append("未收到'C'(0x43),超时!");
        QMessageBox::critical(this, "失败", "未收到STM32的握手信号'C'(0x43)");
        connect(COM, SIGNAL(readyRead()), this, SLOT(Ser_COM_RX()));
        return;
    }

    // 开始发送数据块
    int blockNum = 1;
    int pos = 0;
    const int blockSize = 128;
    bool sendSuccess = true;
    const int maxRetries = 5;

    QMessageBox::information(this, "提示", "握手成功!开始发送文件...");

    while (pos < m_fileData.size() && sendSuccess) {
        int retries = 0;
        bool blockSent = false;

        // 单个数据块的重发机制
        while (retries < maxRetries && !blockSent) {
            QByteArray block = m_fileData.mid(pos, blockSize);
            if (block.size() < blockSize) {
                block.append(QByteArray(blockSize - block.size(), 0x1A));  // 填充0x1A
            }

            // 构造Xmodem数据包
            QByteArray xmodemBlock;
            xmodemBlock.append(0x01);  // SOH
            xmodemBlock.append((char)blockNum);
            xmodemBlock.append((char)(blockNum ^ 0xFF));  // 包号补码
            xmodemBlock.append(block);
            quint16 crc = xmodemCrc16(block);
            xmodemBlock.append((char)(crc >> 8));    // CRC高字节
            xmodemBlock.append((char)(crc & 0xFF));  // CRC低字节

            COM->write(xmodemBlock);
            COM->waitForBytesWritten(1000);

            // 等待ACK/NAK响应
            if (COM->waitForReadyRead(2000)) {
                QByteArray ack = COM->readAll();
                if (ack.contains(0x06)) {  // ACK
                    blockNum++;
                    pos += blockSize;
                    blockSent = true;
                } else if (ack.contains(0x15)) {  // NAK
                    retries++;
                    QMessageBox::warning(this, "警告", "数据包" + QString::number(blockNum) + "重发(" + QString::number(retries) + "/" + QString::number(maxRetries) + ")");
                } else {
                    retries++;
                    QMessageBox::warning(this, "警告", "未知响应,重发数据包" + QString::number(blockNum));
                }
            } else {
                retries++;
                QMessageBox::warning(this, "警告", "超时无响应,重发数据包" + QString::number(blockNum));
            }
        }

        if (retries >= maxRetries) {
            sendSuccess = false;
            QMessageBox::critical(this, "失败", "数据包" + QString::number(blockNum) + "重发超限");
        }
    }

    // 发送结束包
    if (sendSuccess) {
        QMessageBox::information(this, "提示", "发送结束包EOT...");
        COM->write(QByteArray(1, 0x04));  // EOT
        COM->waitForBytesWritten(1000);

        if (COM->waitForReadyRead(3000)) {
            QByteArray ack = COM->readAll();
            if (ack.contains(0x06)) {
                QMessageBox::information(this, "成功", "文件发送完成!STM32已确认");
            } else {
                QMessageBox::warning(this, "警告", "文件发送完成,但未收到结束包确认");
            }
        } else {
            QMessageBox::warning(this, "警告", "发送结束包后超时无响应");
        }
    }

    // 重新连接串口接收信号
    connect(COM, SIGNAL(readyRead()), this, SLOT(Ser_COM_RX()));
}

// ========== TCP功能 ==========

/**
 * @brief 新TCP连接
 * 处理新的TCP客户端连接
 */
void MainWindow::newTcpConnection()
{
    if (tcpSocket) {
        tcpSocket->close();
        delete tcpSocket;
    }
    //用于获取下一个挂起的连接作为已连接的 QTcpSocket 对象。
    tcpSocket = tcpserver->nextPendingConnection();
    ui->textBrowser_2->append("=== TCP客户端已连接 ===");
    ui->textBrowser_2->append(QString("客户端地址:%1:%2").arg(tcpSocket->peerAddress().toString()).arg(tcpSocket->peerPort()));

    // 重置传输状态
    tcpBlockNum = 1;
    tcpPos = 0;
    tcpReadyForTransfer = false;   // 新连接时未准备好传输
    tcpHandshakeReceived = false;  // 新连接时未收到握手信号

    // 连接信号
    /*
    readyRead()信号:当网络套接字有新的数据可读时,这个信号被发射。这意味着有数据从对方发送过来,
    已经到达本地缓冲区,可以读取了。连接这个信号后,一旦有数据到达,
    就会触发tcpReadyRead()槽函数,我们可以在槽函数中读取并处理这些数据。

    disconnected()信号:当对方关闭连接或者连接意外断开时,这个信号被发射。连接这个信号后,
    一旦连接断开,就会触发tcpDisconnected()槽函数,我们可以在槽函数中进行清理工作,比如释放资源、更新界面状态等。
    */
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcpReadyRead()));
    connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcpDisconnected()));

    ui->textBrowser_2->append("TCP连接已建立,等待用户操作...");
    ui->textBrowser_2->append("请选择bin文件并点击'TCP发送'按钮开始传输");
}

/**
 * @brief TCP数据接收
 * 处理TCP客户端发送的数据
 */
void MainWindow::tcpReadyRead()
{
    if (!tcpSocket) return;

    QByteArray buf = tcpSocket->readAll();
    if (buf.isEmpty()) return;

    // 显示接收到的原始数据
    QString hexLog = "TCP收到数据(十六进制):";
    for (int i = 0; i < buf.size(); i++) {
        hexLog += QString("%1 ").arg((unsigned char)buf.at(i), 2, 16, QChar('0')).toUpper();
    }
    ui->textBrowser_2->append(hexLog);
    ui->textBrowser_2->append(QString("数据长度:%1 字节").arg(buf.size()));

    // 检查每个字节
    for (int i = 0; i < buf.size(); i++) {
        /*
            buf.at(i) 表示访问容器 buf 中索引为 i 的元素
            与 buf[i] 的功能类似,都是获取指定位置的元素
        */
        unsigned char byte = buf.at(i);

        if (byte == 0x43) {  // 'C' - 握手信号
            ui->textBrowser_2->append(">>> 收到握手信号'C' (0x43) <<<");
            tcpHandshakeReceived = true;  // 标记已收到握手信号

            // 只有在用户点击了发送按钮后才开始传输
            if (tcpReadyForTransfer && tcpHandshakeReceived) {
                ui->textBrowser_2->append("开始TCP Xmodem传输...");
                sendTcpXmodemBlock();
            } else {
                ui->textBrowser_2->append("已收到握手信号,等待用户点击'TCP发送'按钮...");
            }
        }
        else if (byte == 0x06) {  // ACK
            ui->textBrowser_2->append(">>> 收到ACK (0x06) <<<");
            if (tcpReadyForTransfer) {
                tcpBlockNum++;
                tcpPos += 128;
                ui->textBrowser_2->append(QString("发送下一块,包号:%1,位置:%2/%3")
                                       .arg(tcpBlockNum).arg(tcpPos).arg(m_fileData.size()));

                if (tcpPos >= m_fileData.size()) {
                    // 文件传输完成,发送EOT
                    tcpSocket->write(QByteArray(1, 0x04));
                    ui->textBrowser_2->append("文件发送完成,发送EOT包 (0x04)");
                    QMessageBox::information(this, "成功", "文件传输完成!");
                    tcpReadyForTransfer = false;  // 传输完成,重置状态
                } else {
                    sendTcpXmodemBlock();
                }
            }
        }
        else if (byte == 0x15) {  // NAK
            ui->textBrowser_2->append(">>> 收到NAK (0x15) <<<");
            if (tcpReadyForTransfer) {
                ui->textBrowser_2->append(QString("重发包号:%1").arg(tcpBlockNum));
                sendTcpXmodemBlock();
            }
        }
        else if (byte == 0x04) {  // EOT
            ui->textBrowser_2->append(">>> 收到EOT (0x04) <<<");
            ui->textBrowser_2->append("STM32已确认传输完成");
            tcpReadyForTransfer = false;  // 传输完成,重置状态
        }
        else {
            ui->textBrowser_2->append(QString("未知字节:0x%1").arg(byte, 2, 16, QChar('0')).toUpper());
        }
    }
}

/**
 * @brief TCP连接断开
 * 处理TCP客户端断开连接
 */
void MainWindow::tcpDisconnected()
{
    ui->textBrowser_2->append("=== TCP客户端已断开连接 ===");
    if (tcpSocket) {
        tcpSocket->deleteLater();
        tcpSocket = nullptr;
    }

    // 重置传输状态
    tcpBlockNum = 1;
    tcpPos = 0;
    tcpReadyForTransfer = false;
    tcpHandshakeReceived = false;
}

/**
 * @brief 启动/停止TCP监听
 * 控制TCP服务器的启动和停止
 */
void MainWindow::on_TCP_Start_clicked()
{
    if (tcpserver->isListening()) {
        tcpserver->close();
        ui->TCP_Start->setText("启动监听");
        ui->textBrowser_2->append("=== TCP服务器已关闭 ===");
        return;
    }
    //.trimmed() 是 QString 类的成员函数,用于去除字符串两端的空白字符。
    QString ip = ui->TCP_IP->toPlainText().trimmed();
    QString portText = ui->TCP_PORT->toPlainText().trimmed();

    if (ip.isEmpty() || portText.isEmpty()) {
        QMessageBox::warning(this, "参数错误", "请输入有效的IP地址和端口号!");
        return;
    }

    bool ok;
    quint16 port = portText.toUShort(&ok);
    if (!ok || port == 0) {
        QMessageBox::warning(this, "参数错误", "端口号必须是1-65535之间的数字!");
        return;
    }

    QHostAddress hostAddress;

    // 处理特殊IP地址
    if (ip == "0.0.0.0") {
        hostAddress = QHostAddress::Any;
        ui->textBrowser_2->append("使用 0.0.0.0 监听所有网络接口");
    } else if (ip == "127.0.0.1" || ip == "localhost") {
        hostAddress = QHostAddress::LocalHost;
        ui->textBrowser_2->append("使用 127.0.0.1 仅限本地连接");
    } else {
        if (!hostAddress.setAddress(ip)) {
            QMessageBox::warning(this, "参数错误",
                "IP地址格式不正确!\n请使用:\n- 0.0.0.0(监听所有接口)\n- 127.0.0.1(仅本地连接)\n- 或上面显示的本机IP地址");
            return;
        }
    }

    // 先关闭之前的连接
    if (tcpSocket) {
        tcpSocket->close();
        tcpSocket->deleteLater();
        tcpSocket = nullptr;
    }

    // 重置传输状态
    tcpBlockNum = 1;
    tcpPos = 0;
    tcpReadyForTransfer = false;
    tcpHandshakeReceived = false;

    // 设置服务器选项
    tcpserver->setMaxPendingConnections(1);

    if (tcpserver->listen(hostAddress, port)) {
        ui->TCP_Start->setText("停止监听");
        QString listenInfo = QString("=== TCP服务器启动成功 ===\n监听地址:%1:%2").arg(ip).arg(port);
        if (ip == "0.0.0.0") {
            listenInfo += "(所有网络接口)";
        }
        ui->textBrowser_2->append(listenInfo);
        ui->textBrowser_2->append("等待客户端连接...");
    } else {
        QString errorMsg = QString("TCP服务器启动失败:\r\n%1\r\n").arg(tcpserver->errorString());

        // 提供具体解决方案
        if (tcpserver->errorString().contains("permission", Qt::CaseInsensitive)) {
            errorMsg += "可能的原因:端口号小于1024需要管理员权限\n";
            errorMsg += "解决方案:使用大于1024的端口号(如5001、8080等)";
        } else if (tcpserver->errorString().contains("address in use", Qt::CaseInsensitive)) {
            errorMsg += "可能的原因:端口被占用\n";
            errorMsg += "解决方案:更换端口号或关闭占用该端口的程序";
        } else if (tcpserver->errorString().contains("not available", Qt::CaseInsensitive)) {
            errorMsg += "可能的原因:IP地址不可用\n";
            errorMsg += "解决方案:使用 0.0.0.0 或上面显示的可用IP地址";
        }

        ui->textBrowser_2->append(errorMsg);
        QMessageBox::critical(this, "错误", errorMsg);
    }
}

/**
 * @brief TCP发送bin文件
 * 通过TCP使用Xmodem协议发送bin文件
 * 修复:只有在点击此按钮后才开始传输
 */
void MainWindow::on_TCP_Send_clicked()
{
    if (m_filePath.isEmpty() || m_fileData.isEmpty()) {
        QMessageBox::warning(this, "提示", "请先选择bin文件!");
        return;
    }
    if (!tcpSocket || !tcpSocket->isOpen()) {
        QMessageBox::warning(this, "提示", "请先建立TCP连接!\n确保STM32客户端已连接");
        return;
    }

    // 重置传输状态
    tcpBlockNum = 1;
    tcpPos = 0;
    tcpReadyForTransfer = true;  // 标记已准备好传输

    ui->textBrowser_2->append("=== 用户已点击'TCP发送'按钮 ===");
    ui->textBrowser_2->append("等待STM32发送'C'信号开始TCP Xmodem传输...");
    ui->textBrowser_2->append("请在STM32端选择:[5]LwIP以太网下载A区程序 或 [6]LwIP以太网下载B区程序");

    // 如果已经收到握手信号,立即开始传输
    if (tcpHandshakeReceived) {
        ui->textBrowser_2->append("检测到已有握手信号,立即开始传输...");
        sendTcpXmodemBlock();
    }

    // 设置超时检测
    QTimer::singleShot(15000, this, [this]() {
        if (tcpPos == 0 && tcpSocket && tcpSocket->isOpen() && tcpReadyForTransfer) {
            ui->textBrowser_2->append("等待'C'信号超时!请检查:");
            ui->textBrowser_2->append("1. STM32端程序已运行");
            ui->textBrowser_2->append("2. STM32端已选择以太网下载选项");
            ui->textBrowser_2->append("3. 网络连接正常");
            QMessageBox::warning(this, "超时",
                "未收到STM32的握手信号'C',请确认:\n"
                "1. STM32端程序已运行\n"
                "2. STM32端已选择以太网下载选项\n"
                "3. 网络连接正常");
            tcpReadyForTransfer = false;  // 超时后重置状态
        }
    });
}

/**
 * @brief 停止TCP服务器
 * 停止TCP服务器并断开所有连接
 */
void MainWindow::on_TCP_Stop_clicked()
{
    if (tcpserver->isListening()) {
        tcpserver->close();
        ui->TCP_Start->setText("启动监听");
        ui->textBrowser_2->append("=== TCP服务器已关闭 ===");
    }
    if (tcpSocket) {
        tcpSocket->close();
        tcpSocket->deleteLater();
        tcpSocket = nullptr;
    }

    // 重置传输状态
    tcpBlockNum = 1;
    tcpPos = 0;
    tcpReadyForTransfer = false;
    tcpHandshakeReceived = false;
}

/**
 * @brief 发送TCP Xmodem数据块
 * 发送单个Xmodem协议数据块到TCP客户端
 */
void MainWindow::sendTcpXmodemBlock()
{
    if (!tcpSocket || m_fileData.isEmpty() || tcpPos >= m_fileData.size() || !tcpReadyForTransfer) {
        return;
    }

    // 准备数据块
    QByteArray block = m_fileData.mid(tcpPos, 128);
    if (block.size() < 128) {
        block.append(QByteArray(128 - block.size(), 0x1A));  // 填充0x1A
    }

    // 构造Xmodem数据帧
    QByteArray xmodemFrame;
    xmodemFrame.append(0x01);  // SOH
    xmodemFrame.append((char)tcpBlockNum);
    xmodemFrame.append((char)(tcpBlockNum ^ 0xFF));  // 包号补码
    xmodemFrame.append(block);
    quint16 crc = xmodemCrc16(block);
    xmodemFrame.append((char)(crc >> 8));    // CRC高字节
    xmodemFrame.append((char)(crc & 0xFF));  // CRC低字节

    // 发送数据帧
    tcpSocket->write(xmodemFrame);

    QString frameInfo = QString("发送TCP Xmodem块:包号%1,长度%2字节,位置%3/%4")
                       .arg(tcpBlockNum).arg(xmodemFrame.size()).arg(tcpPos).arg(m_fileData.size());
    ui->textBrowser_2->append(frameInfo);

    // 显示发送的数据帧内容(前几个字节)
    QString frameHex = "数据帧头:";
    for (int i = 0; i < qMin(8, xmodemFrame.size()); i++) {
        frameHex += QString("%1 ").arg((unsigned char)xmodemFrame.at(i), 2, 16, QChar('0')).toUpper();
    }
    if (xmodemFrame.size() > 8) {
        frameHex += "...";
    }
    ui->textBrowser_2->append(frameHex);
}

mainwindow.h

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QTcpServer>
#include <QTcpSocket>
#include <QFile>
#include <QTimer>
#include <QNetworkInterface>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // 串口功能槽函数
    void on_button_Refresh_com_clicked();    // 刷新串口列表
    void on_Button_Open_COM_clicked();       // 打开/关闭串口
    void on_COM_TX_clicked();                // 发送串口文本
    void on_COM_TX_Clear_clicked();          // 清除发送区
    void Ser_COM_RX();                       // 串口数据接收
    void on_open_bin_clicked();              // 打开bin文件
    void on_send_bin_clicked();              // 串口发送bin文件

    // TCP功能槽函数
    void newTcpConnection();                 // 新TCP连接
    void tcpReadyRead();                     // TCP数据接收
    void tcpDisconnected();                  // TCP连接断开
    void on_TCP_Start_clicked();             // 启动/停止TCP监听
    void on_TCP_Send_clicked();              // TCP发送bin文件
    void on_TCP_Stop_clicked();              // 停止TCP服务器

private:
    Ui::MainWindow *ui;
    QSerialPort *COM;                  // 串口对象
    QString m_filePath;                // 选中的bin文件路径
    QByteArray m_fileData;             // bin文件原始数据

    // TCP相关成员
    QTcpServer *tcpserver;             // TCP服务器
    QTcpSocket *tcpSocket;             // 已连接的TCP客户端socket
    int tcpBlockNum;                   // TCP Xmodem包号
    int tcpPos;                        // TCP Xmodem文件读取位置
    bool tcpReadyForTransfer;          // TCP是否准备好传输(已点击发送按钮)
    bool tcpHandshakeReceived;         // TCP是否已收到握手信号

    // Xmodem工具函数
    quint16 xmodemCrc16(const QByteArray &data);  // CRC16校验计算
    QString byteArrayToHexString(const QByteArray &data);  // 字节数组转十六进制字符串
    void sendTcpXmodemBlock();         // 发送单个TCP Xmodem数据块
    void updateNetworkInfo();          // 更新网络信息
};
#endif // MAINWINDOW_H
相关推荐
她说彩礼65万2 小时前
C# Lambda 表达式
开发语言·c#
Bug快跑-12 小时前
Java、C# 和 C++ 并发编程的深度比较与应用场景
java·开发语言·前端
2501_941111462 小时前
高性能计算集群部署
开发语言·c++·算法
864记忆2 小时前
Qt 对 JSON和XML文件的操作详解
xml·qt·json
普通网友2 小时前
模板编译期机器学习
开发语言·c++·算法
普通网友2 小时前
C++与机器学习框架
开发语言·c++·算法
普通网友2 小时前
C++安全编程指南
开发语言·c++·算法
学困昇2 小时前
C++11中的右值引用和移动语义
开发语言·c++
有梦想的攻城狮2 小时前
初识Rust语言
java·开发语言·rust