当前位置:   article > 正文

使用QT基于YMODEM协议实现串口文件发送(和xshell互通)_串口ymodem文件传输

串口ymodem文件传输

在这里插入图片描述

背景

项目需要用QT实现一个YMODEM文件传输的功能,目标下位机是MCU嵌入式设备,且下位机程序已经经过xshell传输文件的验证。

YMODEM 简介

YMODEM协议是一个文件传输协议,常用于嵌入式设备。本文不对YMODEM做过多的阐述,阅读需建立在你已经对YMODEM有一定了解的基础上。如果要了解YMODEM协议,推荐几个地址:

维基百科 YMODEM
Ymodem 协议详解
YMODEM协议简介
YMODEM协议中文翻译

但要注意的是这些文章都有一些小的细节性的错误,文章的评论区有人指出了,需要注意甄别。

YMODEM通信协议:

发送端----------------------------------------------------------------接收端

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

SOH 00 FF “foo.c” "1064’’ NUL[118] CRC CRC >>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

STX 01 FE data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 02 FD data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 03 FC data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 04 FB data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

SOH 05 FA data[100] 1A[28] CRC CRC>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NAK

EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

SOH 00 FF NUL[128] CRC CRC >>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

核心代码

文件发送的核心代码,主要步骤是:

  1. 启动YMODEM传输,并建立通道。
  2. 发送首帧数据,即文件名和文件大小信息。
  3. 按照1024或者128字节发送文件数据。
  4. 结束传输。
Ymodem::Code YmodemFileTransmit::callback(Status status, uint8_t* buff, uint32_t* len)
{
    switch (status) {
        case StatusEstablish: {
                if (file->open(QFile::ReadOnly) == true) {
                    QFileInfo fileInfo(*file);

                    fileSize  = fileInfo.size();
                    fileCount = 0;

                    strcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());
                    strcpy((char*)buff + fileInfo.fileName().toLocal8Bit().length() + 1,
                           QByteArray::number(fileInfo.size()).data());
//                    char source = 0x20;
//                    strcpy((char*)buff + fileInfo.fileName().toLocal8Bit().length() + 1 +
//                           QByteArray::number(fileInfo.size()).size(), &source);
                    *len = YMODEM_PACKET_SIZE;

                    YmodemFileTransmit::status = StatusEstablish;

                    transmitStatus(StatusEstablish);

                    return CodeAck;
                } else {
                    YmodemFileTransmit::status = StatusError;

                    writeTimer->start(WRITE_TIME_OUT);

                    return CodeCan;
                }
            }

        case StatusTransmit: {
                if (fileSize != fileCount) {
                    if ((fileSize - fileCount) > YMODEM_PACKET_1K_SIZE) {
                        qint64 r = file->read((char*)buff, YMODEM_PACKET_1K_SIZE);
                        fileCount += r;
                        *len = YMODEM_PACKET_1K_SIZE;
                    } else {

                        qint64 r = file->read((char*)buff, YMODEM_PACKET_SIZE);
                        fileCount += r;

                        *len = YMODEM_PACKET_SIZE;
                    }

                    progress = (int)(fileCount * 100 / fileSize);
                    YmodemFileTransmit::status = StatusTransmit;

                    transmitProgress(progress);
                    transmitStatus(StatusTransmit);

                    return CodeAck;
                } else {
                    YmodemFileTransmit::status = StatusTransmit;

                    transmitStatus(StatusTransmit);

                    return CodeEot;
                }
            }

        case StatusFinish: {
                file->close();

                YmodemFileTransmit::status = StatusFinish;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeAck;
            }

        case StatusAbort: {
                file->close();

                YmodemFileTransmit::status = StatusAbort;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        case StatusTimeout: {
                YmodemFileTransmit::status = StatusTimeout;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        default: {
                file->close();

                YmodemFileTransmit::status = StatusError;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

文件接收的核心代码,主要步骤是:

  1. 开启YMODEM文件接收,建立通道。
  2. 解析首帧数据,即解析文件名和文件大小信息。
  3. 按照发送者的发送大小(1024或者128字节)解析文件数据。
  4. 接收完成,结束传输。
Ymodem::Code YmodemFileReceive::callback(Status status, uint8_t* buff, uint32_t* len)
{
    switch (status) {
        case StatusEstablish: {
                QByteArray b = QByteArray((char*)buff, 133).toHex();
                qDebug() << "recvbuff:" <<  b;
                if (buff[0] != 0) {
                    int  i         =  0;
                    char name[128] = {0};
                    char size[128] = {0};

                    for (int j = 0; buff[i] != 0 && buff[i] != 0x20; i++, j++) {
                        qDebug() << buff[i];
                        name[j] = buff[i];
                    }

                    i++;
                    //SOH 00 FF foo.c 3232 NUL[118] CRCH CRCL
                    //0或者空格,xshell 在文件名称和文件大小中间发送的是空格,即0x20,标准YMODEM协议要求文件名称以'\0'(也就是0)结尾,所以如果要实现和xshell互通,此处要兼容
                    for (int j = 0; buff[i] != 0 && buff[i] != 0x20; i++, j++) {
                        qDebug() << buff[i];
                        size[j] = buff[i];
                    }

                    fileName  = QString::fromLocal8Bit(name);
                    fileSize  = QString(size).toULongLong();
                    fileCount = 0;
                    qDebug() << "StatusEstablish::fileName:" << fileName ;
                    qDebug() << "StatusEstablish::fileSize:" << fileSize ;
                    file->setFileName(filePath + fileName);

                    if (file->open(QFile::WriteOnly) == true) {
                        YmodemFileReceive::status = StatusEstablish;

                        receiveStatus(StatusEstablish);

                        return CodeAck;
                    } else {
                        YmodemFileReceive::status = StatusError;

                        writeTimer->start(WRITE_TIME_OUT);

                        return CodeCan;
                    }
                } else {
                    YmodemFileReceive::status = StatusError;

                    writeTimer->start(WRITE_TIME_OUT);

                    return CodeCan;
                }
            }

        case StatusTransmit: {
                if ((fileSize - fileCount) > *len) {
                    qint64 w = file->write((char*)buff, *len);
                    fileCount += *len;
                } else {

                    qint64 w = file->write((char*)buff, fileSize - fileCount);
                    fileCount += fileSize - fileCount;
                }

                progress = fileSize == 0 ? 0 : (int)(fileCount * 100 / fileSize);
                YmodemFileReceive::status = StatusTransmit;

                receiveProgress(progress);
                receiveStatus(StatusTransmit);

                return CodeAck;
            }

        case StatusFinish: {
                file->close();

                YmodemFileReceive::status = StatusFinish;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeAck;
            }

        case StatusAbort: {
                file->close();

                YmodemFileReceive::status = StatusAbort;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        case StatusTimeout: {
                YmodemFileReceive::status = StatusTimeout;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        default: {
                file->close();

                YmodemFileReceive::status = StatusError;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

在进行YMODEM 上位机开发时,有两个坑需要注意,否则大概率掉坑里。

1号坑:

下位机那边反馈,使用xshell给MCU发送文件正常,使用我们的软件一开始就卡住。

经过对xshell抓包分析,发现xshell默认配置会在发送YMODEM前先发送"rb -E"指令,我们的下位机收到无法解析的指令后,会默认回复’C’,所以xshell收到’C’后开始了正常的文件发送。

但是根据YMODEM协议标准(可以看上面的通信协议),在启动文件传输后,发送端是静止的,只需要等待接收端发送第一个’C’, 所以我们的上位机并没有做,导致启动发送就卡住了。修改代码,在开始发送前,先发送任意字符,触发接收端的’C’回复,修改后测试正常了。

所以如果打开也涉及到YMODEM开发,需要注意和你们的下位机同事沟通清楚’C’字符在什么时候回复,有没有按照标准来。

2号坑:

在YMODEM传输文件名和文件大小时,YMODEM协议中明确要求了文件名必须以null(即’\0’)作为结束符,求证:

见 wikipedia 的 References 文章中的 Chapter 1 ,
The pathname shall be a null terminated ASCII string as described below.

但抓包发现xshell在文件名和文件大小中间填充的是空格(即0x20),如果这个时候接收端说xshell和我是互通的来证明自己没问题,就欲哭无泪了。因为他很有可能解析文件名和文件大小时判断的是0x20,而不是’\0’。所以,此处也要跟下位机沟通清楚,他们是如何解析文件名和文件大小的,如果没有判断’\0’,文件名和文件大小肯定就不对了。

结束补充

Demo截图:
在这里插入图片描述
参考:

SerialPortYmodem

对其在xshell兼容方面作了改动和优化。

Demo及源代码下载地址:
https://download.csdn.net/download/u012534831/88625925

其他代码我打包上传到csdn资源中,关注公号后在后台留言需要下载的资源,我看到后免费发给你,并可以得到我的免费解答。 原创不易,谢谢支持。

在这里插入图片描述

关注公众号 QTShared,带你探索更多QT相关知识。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/779899
推荐阅读
相关标签
  

闽ICP备14008679号