本历程是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