在单片机上实现Ymodem协议接收文件的实现

目录

1. Y-Modem协议介绍

1.1 Y-Modem协议简介

1.2 Y-Modem协议格式

1.2.1 Y-Modem起始帧

1.2.2 Y-Modem数据帧

1.2.3 Y-Modem结束帧

1.2.4 Y-Modem命令

 2. Tera Term软件使用

2.1 Tera Term软件的使用

3. 单片机接收端实现代码

 

本篇文章主要介绍的是通过Y-Modem协议如何实现上位机和下位机之间的文件传输,发送端使用的是Tera Term软件来完成协议发送端流程。软件下载地址:Tera Term Open Source Project (teratermproject.github.io)

1. Y-Modem协议介绍

1.1 Y-Modem协议简介

Y-Modem 是一种串行文件传输协议,用于通过串口进行文件传输。它是由 Chuck Forsberg 开发的,继承了 X-Modem 协议的特性,并在此基础上进行了改进。以下是对 Y-Modem 协议的基本介绍以及其相对于其他协议(如 X-Modem)的优势:

(1)块大小更大:Y-Modem 的默认块大小为 1024 字节,是 X-Modem 128 字节块大小的八倍。这意味着在相同的条件下,Y-Modem 可以比 X-Modem 更快地传输数据,尤其是对于大文件而言,效率优势更加明显;

(2)元数据传输:Y-Modem 可以在数据传输之前发送文件名、文件大小、修改日期等元数据。这使得接收端在传输开始前就可以获知文件的相关信息,方便管理和验证文件的完整性;

(3)多文件传输:Y-Modem 支持一次会话传输多个文件。在文件传输结束后,发送端可以继续发送下一个文件,而不需要重新初始化传输会话。这对于批量传输文件非常有用,减少了每次传输前的初始化时间;

(4)错误检测和纠正:Y-Modem 使用 CRC(循环冗余校验)来检测数据块的错误,相比 X-Modem 的校验和(checksum)方法,CRC 提供了更好的错误检测能力。Y-Modem 还支持重新传输错误数据块,提高了传输的可靠性。

1.2 Y-Modem协议格式

1.帧格式

名称帧头包号包号反码信息块校验
简写SOH/STXPNXPNDATACRC
字节数1111024/1282

(1)帧头

帧头SOH(0X01)STX(0X02)
信息块长度128字节1024字节

(2) 包序号

数据包序号只有一个字节,计数范围是0~255,超过这个范围则从0开始计算;

(3) 帧长度

<1>以SOH开始的数据包,信息块是128字节,该类型帧的总长度是1+1+1+2+128 = 133字节;

<2>以STX开始的数据包,信息块是1024字节,该类型帧的总长度是1+1+1+2+1024 = 1-29字节;

(4) 校验

采用CRC16校验算法,校验值2个字节,传输时CRC高八位在前,低八位在后;CRC计算数据为信息块数据,不包含帧头、包号、包号反码;

2.Y-Modem握手信号

握手信号由接收方发起,在发送方开始传输文件之前,接收方需要发送YMODEM_C(字符C,ASCII码为0x43)命令,发送方收到之后,开始传输起始帧;

1.2.1 Y-Modem起始帧

Ymodem起始帧并不直接传输文件内容,而是先将文件名和文件大小置于数据帧中传输;起始帧是以SOH 133字节长度帧传输,格式如下:

帧头包号包号反码文件名称文件大小填充区校验高位校验地位
SOH0x000xffFileName+0x00FileSize+0x00NULL(0x00)CRC-HCRC-L

起始帧的数据格式如下:

SOH 00 FF filename[ ] filezise[ ] NUL[ ] CRCH CRCL

其中包号为固定为0;包号反码与包号相反,提供包号反码可以帮助判断数据是否正确(即判断是否是取反关系);FileName为文件名称,文件名称后必须加0x00作为结束,例如我要传一个名为foo.c的文件,大小为1KByte即1024byte,他在数据帧中的存放格式为:66 6F 6F 2E 63 00;FileSize为文件大小值,文件大小值后必须加0x00作为结束,刚刚距离的文件大小转化为十六进制为0x400,在数据帧中的存放是:34 30 30 00 ;NUL[ ]表示剩下的字节都用00填充,数据部分大小为128字节,除去文件名与文件大小占用的空间外,剩余的字节全部用00填充;CRCH CRCL分别表示16位CRC校验码的高8位与低8位;

1.2.2 Y-Modem数据帧

Y-Modem数据帧传输,在信息块填充有效数据;

帧头包号包号反码有效数据校验高位校验低位
SOH/STXPNXPNDATACRC-HCRC-L

YModem的数据帧中会预留1024字节空间用来传输文件数据,它跟起始帧接收差不多,如下:

STX/SOH 01 FE DATA[1024] CRCH CRCL

对于最后一包数据处理,SOH帧和STX帧有不同处理:

(1)对于SOH帧,若余下数据小于128字节,则以0x1A填充,最后一帧长度仍为133字节;

(2)对于STX帧需要考虑以下几种情况:

·余下数据等于1024字节,以1029长度帧发送;

·余下数据小于1024字节,但大于128字节,以1029字节帧长度发送,无效数据以0x1A填充;

·余下数据等于128字节,以133字节帧长度发送;

·余下数据小于128字节,以133字节帧长度发送,无效数据以0x1A填充;

1.2.3 Y-Modem结束帧

Y-Modem的结束帧采用SOH 133字节长度帧传输,该帧不携带数据(空包),即数据区、校验都用0x00填充;

帧头包号包号反码数据区校验高位校验低位
SOH0x000xff0x000x000x00

1.2.4 Y-Modem命令

命令命令码备注
YMODEM_SOH0x01133字节长度帧
YMODEM_STX0x021024字节长度帧
YMODEM_EOT0x04文件传输结束命令
YMODEM_ACK0x06接收正确应答命令
YMODEM_CAN0x18取消传输命令, 连续发送5个该命令
YMODEM_C0x43字符C

例:

 2. Tera Term软件使用

我们的电脑本机作为我的上位机,单片机作为下位机,电脑是发送方,单片机作为接收方。在实现Y-Modem协议的过程中,上位机使用软件Tera Term,这个软件帮我们模拟完成了Y-Modem协议发送的流程,所以我们只需要编写实现Y-Modem协议的的接收代码,烧录到单片机中即可。

2.1 Tera Term软件的使用

 

首先要确保传输波特率等基本信息与单片机一

在确保基本信息匹配后要保

Y-modem协议传输演示

首先要确保传输波特率等基本信息与单片机一致

在确保基本信息匹配后要保存配置

Y-modem协议传输演示

 

 

3. 单片机接收端实现代码

#define SOH 0x01
#define STX 0x02
#define EOT 0x04
#define ACK 0x06
#define BSP 0x08
#define NAK 0x15
#define CAN 0x18
#define CRC_NONE        0   /* No CRC checking */
#define CRC_ADD8        1   /* Add of all data bytes */
#define CRC_CRC16       2   /* CCCIT CRC16 */
#define MAX_CRCS        3

#define SECOND                  1000
#define MAX_RETRIES             20
#define MAX_RETRIES_WITH_CRC    5
#define MAX_CAN_BEFORE_ABORT    5

static int xy_gets(struct xyz_ctxt *proto, unsigned char *buf, int len, uint64_t timeout)
{
    int          i;
    signed char  ch;

    for(i=0; i<len; i++)
    {
        if( pdFALSE == proto->xGetCharFunc(NULL, &ch, (TickType_t)timeout) )
        {
            return i;

        }

        buf[i] = (unsigned char)ch;
        printf("0x%02X ", buf[i]);
    }
    printf("\r\n");
    return len;
}



static inline void xy_putc(struct xyz_ctxt *proto, char c)
{
    proto->xPutCharFunc(c);
}

static void xy_block_ack(struct xyz_ctxt *proto)
{
    char c = block_ack[proto->mode][proto->crc_mode];

    if (c)
        xy_putc(proto, c);
}

static void xy_block_nack(struct xyz_ctxt *proto)
{
    char c = block_nack[proto->mode][proto->crc_mode];

    if (c)
        xy_putc(proto, c);

    proto->total_retries++;
}

static int check_crc(unsigned char *buf, int len, int crc, int crc_mode)
{
    unsigned char crc8 = 0;
    uint16_t crc16;
    int i;

    switch (crc_mode) {
    case CRC_ADD8:
        for (i = 0; i < len; i++)
            crc8 += buf[i];
        return crc8 == crc ? 0 : -EBADMSG;
    case CRC_CRC16:
        crc16 = crc16_checksum(buf, len);
        xy_dbg("crc16: received = %x, calculated=%x\n", crc, crc16);
        return crc16 == crc ? 0 : -EBADMSG;
    case CRC_NONE:
        return 0;
    default:
        return -EBADMSG;
    }
}

/**
 * xy_read_block - read a X-Modem or Y-Modem(G) block
 * @proto: protocol control structure
 * @blk: block read
 * @timeout: maximal time to get data
 *
 * This is the pivotal function for block receptions. It attempts to receive one
 * block, ie. one 128 bytes or one 1024 bytes block.  The received data can also
 * be an end of transmission, or a cancel.
 *
 * Returns :
 *  >0           : size of the received block
 *  0            : last block, ie. end of transmission, ie. EOT
 * -EBADMSG      : malformed message (ie. sequence bi-bytes are not
 *                 complementary), or CRC check error
 * -EILSEQ       : block sequence number error wrt previously received block
 * -ETIMEDOUT    : block not received before timeout passed
 * -ECONNABORTED : transfer aborted by sender, ie. CAN
 */
static ssize_t xy_read_block(struct xyz_ctxt *proto, struct xy_block *blk, uint64_t timeout)
{
    ssize_t data_len = 0;
    int rc;
    unsigned char hdr = 0, seqs[2]={0}, crcs[2]={0};
    int crc = 0;
    bool hdr_found = 0;

    while (!hdr_found) {
        rc = xy_gets(proto, &hdr, 1, timeout);
        xy_dbg("read 0x%x(%c) -> %d\n", hdr, hdr, rc);
        if (rc <= 0)
            goto timeout;

        switch (hdr) {
        case SOH:
            data_len = 128;
            hdr_found = 1;
            proto->total_SOH++;
            break;
        case STX:
            data_len = 1024;
            hdr_found = 1;
            proto->total_STX++;
            break;
        case CAN:
            rc = -ECONNABORTED;
            if (proto->total_CAN++ > MAX_CAN_BEFORE_ABORT)
                goto out;
            break;
        case EOT:
            rc = 0;
            blk->len = 0;
            goto out;
        default:
            break;
        }
    }

    blk->seq = 0;
    rc = xy_gets(proto, seqs, 2, timeout);
    if (rc < 0)
        goto out;
    blk->seq = seqs[0];
    if (255 - seqs[0] != seqs[1])
        return -EBADMSG;

    rc = xy_gets(proto, blk->buf, data_len, timeout);
    if (rc < 0)
        goto out;
    blk->len = rc;


    switch (proto->crc_mode) {
    case CRC_ADD8:
        rc = xy_gets(proto, crcs, 1, timeout);
        crc = crcs[0];
        break;
    case CRC_CRC16:
        rc = xy_gets(proto, crcs, 2, timeout);
        crc = (crcs[0] << 8) + crcs[1];
        break;
    case CRC_NONE:
        rc = 0;
        break;
    }
    if (rc < 0)
        goto out;

    rc = check_crc(blk->buf, data_len, crc, proto->crc_mode);
    if (rc < 0)
        goto out;
    return data_len;

timeout:
    return -ETIMEDOUT;
out:
    return rc;
}

static int check_blk_seq(struct xyz_ctxt *proto, struct xy_block *blk, int read_rc)
{
    if (blk->seq == ((proto->next_blk - 1) % 256))
        return -EALREADY;
    if (blk->seq != proto->next_blk)
        return -EILSEQ;
    return read_rc;
}

extern char * strsep(char **stringp, const char *delim);
static int parse_first_block(struct xyz_ctxt *proto, struct xy_block *blk)
{
    int filename_len;
    char *str_num;

    filename_len = (int)strlen((char *)blk->buf);
    xy_dbg("Filename length: %d\n", filename_len);
    if (filename_len > blk->len)
        return -EINVAL;
    strncpy(proto->filename, (char *)blk->buf, sizeof(proto->filename));
    xy_dbg("Parsed filename: %s\n", proto->filename);
    str_num = (char *)blk->buf + filename_len + 1;
    printf("String number start: %s\n", str_num);
    strsep(&str_num, " ");
    proto->file_len = (int)strtoul((char *)blk->buf + filename_len + 1, NULL, 10);
    printf("Parsed file length: %d\n", proto->file_len);
    //g_files_flag = 1;
    return 1;
}

static int xy_get_file_header(struct xyz_ctxt *proto)
{
    struct xy_block blk;
    int tries, rc = 0;

    memset(&blk, 0, sizeof(blk));
    proto->state = PROTO_STATE_GET_FILENAME;
    proto->crc_mode = CRC_CRC16;

    for (tries = 0; tries < MAX_RETRIES; tries++)
    {
        xy_putc(proto, invite_filename_hdr[proto->mode][proto->crc_mode]);

        rc = xy_read_block(proto, &blk, 3*SECOND);
        printf("in the xy_get_file_header rc's value is %d \r\n", rc);
        printf("Block buffer (raw): ");
        for (int i = 0; i < blk.len; i++)
        {
        	printf("%02X ", blk.buf[i]);
        }
        printf("\n");
        switch (rc)
        {
        case -ECONNABORTED:
            goto fail;
        case -ETIMEDOUT:
        case -EBADMSG:
            break;
        case -EALREADY:
        default:
            proto->next_blk = 1;
            xy_block_ack(proto);
            proto->state = PROTO_STATE_NEGOCIATE_CRC;
            rc = parse_first_block(proto, &blk);
            return rc;
        }

        if (rc < 0 && tries++ >= MAX_RETRIES_WITH_CRC)
            proto->crc_mode = CRC_ADD8;
    }
    rc = -ETIMEDOUT;
fail:
    proto->total_retries += tries;
    return rc;

}

static int xy_await_header(struct xyz_ctxt *proto)
{
    int rc;

    rc = xy_get_file_header(proto);
    printf("In the xy_await_header rc's value is %d\r\n", rc);
    if (rc < 0)
        return rc;
    proto->state = PROTO_STATE_NEGOCIATE_CRC;
    printf("header received, filename=%s, file length=%d\r\n", proto->filename, proto->file_len);
    lfs_file_open(&lfs, &file, proto->filename, LFS_O_WRONLY | LFS_O_CREAT);
    if ( !proto->filename[0] )
        proto->state = PROTO_STATE_FINISHED_XFER;

    proto->nb_received = 0;
    return rc;
}

static void xy_finish_file(struct xyz_ctxt *proto)
{
    proto->state = PROTO_STATE_FINISHED_FILE;
}

int xymodem_handle(struct xyz_ctxt *proto)
{
    struct xy_block blk;
    int rc = 0, xfer_max, len = 0, again = 1, remain;
    int crc_tries = 0, same_blk_retries = 0;
    char invite;
    char fpath[64];

    __NOP();

    while (again) {
        __NOP();
        switch (proto->state) {
            case PROTO_STATE_GET_FILENAME:
                crc_tries = 0;
                __NOP();
                rc = xy_await_header(proto);

                if (rc < 0)
                    goto out;
                continue;
            case PROTO_STATE_FINISHED_FILE:
            	g_files_flag = 1;
            	proto->state = PROTO_STATE_FINISHED_XFER;

                xy_putc(proto, ACK);

                continue;
            case PROTO_STATE_FINISHED_XFER:
                again = 0;
                rc = 0;
                goto out;
            case PROTO_STATE_NEGOCIATE_CRC:
                invite = invite_file_body[proto->mode][proto->crc_mode];
                proto->next_blk = 1;
                if (crc_tries++ > MAX_RETRIES_WITH_CRC)
                    proto->crc_mode = CRC_ADD8;

                xy_putc(proto, invite);


                /* Fall through */
            case PROTO_STATE_RECEIVE_BODY:
                rc = xy_read_block(proto, &blk, 3*SECOND);
                if (rc > 0) {
                    rc = check_blk_seq(proto, &blk, rc);
                    proto->state = PROTO_STATE_RECEIVE_BODY;
                }
                break;
        }

        if (proto->state != PROTO_STATE_RECEIVE_BODY)
            continue;

        switch (rc) {
            case -ECONNABORTED:
                goto out;
            case -ETIMEDOUT:
                if (proto->mode == PROTO_YMODEM_G)
                    goto out;
                xy_block_nack(proto);
                break;
            case -EBADMSG:
            case -EILSEQ:
                if (proto->mode == PROTO_YMODEM_G)
                    goto out;
                xy_block_nack(proto);
                break;
            case -EALREADY:
                xy_block_ack(proto);
                break;
            case 0:
                xy_finish_file(proto);
                break;
            default:
                remain = proto->file_len - proto->nb_received;
                if (is_xmodem(proto))
                    xfer_max = blk.len;
                else
                    xfer_max = min(blk.len, remain);

                blk.buf[xfer_max] = '\0';
                printf(">>>File contains %d bytes: %s\n", xfer_max, blk.buf);
                lfs_file_write(&lfs, &file, blk.buf, xfer_max);
                lfs_sync(&lfs);
                //rc = write(proto->fd, blk.buf, xfer_max);
                proto->next_blk = ((blk.seq + 1) % 256);
                proto->nb_received += rc;
                len += rc;
                xy_block_ack(proto);
                break;
        }
        if (rc < 0)
            same_blk_retries++;
        else
            same_blk_retries = 0;
        if (same_blk_retries > MAX_RETRIES)
            goto out;
        if (g_files_flag)
        {
        	printf("File transfer completed.\n");
            break;
        }
    }
out:
    return rc;
}

static int xymodem_open(struct xyz_ctxt *proto, int mode)
{
    if( !proto )
        return -1;

    memset(proto, 0, sizeof(*proto));

    proto->mode = mode;
    proto->crc_mode = CRC_CRC16;

    proto->xGetCharFunc = xSerialGetChar;
    proto->xPutCharFunc = xSerialPutChar;

    if (is_xmodem(proto)) {
        proto->state = PROTO_STATE_NEGOCIATE_CRC;
    } else {
        proto->state = PROTO_STATE_GET_FILENAME;
    }

    return 0;
}

static void xymodem_close(struct xyz_ctxt *proto)
{
    printf("\nxyModem - %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n",
           proto->total_SOH, proto->total_STX,
           proto->total_CAN, proto->total_retries);
}

int do_load_ymodem(void)
{
    struct xyz_ctxt proto;
    int rc = 0;

    xymodem_open(&proto, PROTO_YMODEM);
    printf("Open the ymodem protocol okay\r\n");

    g_files_flag = 0;



        do {
            rc = xymodem_handle(&proto);
        } while (rc > 0);


    printf(" the firmware file upload over.\r\n");

    xymodem_close(&proto);

    return rc < 0 ? rc : 0;
}


 

  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值