单片机上实现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/STX | PN | XPN | DATA | CRC |
字节数 | 1 | 1 | 1 | 1024/128 | 2 |
(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字节长度帧传输,格式如下:
帧头 | 包号 | 包号反码 | 文件名称 | 文件大小 | 填充区 | 校验高位 | 校验地位 |
---|---|---|---|---|---|---|---|
SOH | 0x00 | 0xff | FileName+0x00 | FileSize+0x00 | NULL(0x00) | CRC-H | CRC-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/STX | PN | XPN | DATA | CRC-H | CRC-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填充;
帧头 | 包号 | 包号反码 | 数据区 | 校验高位 | 校验低位 |
---|---|---|---|---|---|
SOH | 0x00 | 0xff | 0x00 | 0x00 | 0x00 |
1.2.4 Y-Modem命令
命令 | 命令码 | 备注 |
---|---|---|
YMODEM_SOH | 0x01 | 133字节长度帧 |
YMODEM_STX | 0x02 | 1024字节长度帧 |
YMODEM_EOT | 0x04 | 文件传输结束命令 |
YMODEM_ACK | 0x06 | 接收正确应答命令 |
YMODEM_CAN | 0x18 | 取消传输命令, 连续发送5个该命令 |
YMODEM_C | 0x43 | 字符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;
}
作者:小李要努力写代码