blogernice

导航

XModem协议

XModem协议介绍:
XModem是一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即1k字节的数据块。

一、XModem校验和协议
1. XModem信息包格式
XModem协议最早由Ward Christensen在20世纪70年代提出并实现的,传输数据单位为信息包,信息包格式如下:

---------------------------------------------------------------------------
|     Byte1     |    Byte2    |     Byte3      |Byte4~Byte131|  Byte132   |
|-------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data |  Check Sum |
---------------------------------------------------------------------------

2. 校验和的计算
所有的数据字节都将参与和运算,由于校验和只占一个字节,如果累加的和超过255将从零开始继续累加。

3. 字段定义
<SOH> 01H
<EOT> 04H
<ACK> 06H
<NAK> 15H
<CAN> 18H

4. 校验和方式的XModem传输流程
传输流程如图所示:

------------------------------------------------------------------------------
|               SENDER                |          |          RECIEVER         |
|                                     |  <---    |  NAK                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
|                                     |  <---    |  ACK                      |
| EOT                                 |  --->    |                           |
|                                     |  <---    |  ACK                      |
------------------------------------------------------------------------------

对 于发送方仅仅支持校验和的传输方式,接收方应首先发送NAK信号来发起传输,如果发送方没有数据发送过来,需要超时等待3秒之后再发起NAK信号 来进行数据传输。对于数据传输正确,接收方需要发送ACK信号来进行确认,如果数据传输有误,则发送NAK信号,发送方在接收到NAK信号之后需要重新发 起该次数据传输,如果数据已近传输完成,发送方需要发送EOT信号,来结束数据传输。

5. 如何取消数据传输
当接收方发送CAN表示无条件结束本次传输过程,发送方收到CAN后,无需发送EOT来确认,直接停止数据的发送。

二、XModem-CRC16协议
1. XModem-CRC16信息包格式
XModem协议在90年代做过一次修改,将132字节处的校验和改成双字节的CRC16校验,CRC16校验的信息包格式如下:

------------------------------------------------------------------------------
|     Byte1     |    Byte2    |     Byte3      |Byte4~Byte131|Byte132~Byte133|
|----------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data |   16Bit CRC   |
------------------------------------------------------------------------------

2. CRC16的计算
比较复杂,表示看不懂,以后有时间再研究吧,先给出一份源代码:

int calcrc(char *ptr, int count)
{
	int crc;
	char i;

	crc = 0;
	while (--count >= 0)
	{
		crc = crc ^ (int) *ptr++ << 8;
		i = 8;
		do
		{
			if (crc & 0x8000)
				crc = crc << 1 ^ 0x1021;
			else
				crc = crc << 1;
		} while (--i);
	}

	return (crc);
}

需要注意的是,在发送方,CRC是高字节在前,低字节在后。

3. CRC16校验的XModem传输流程
传输流程如图所示:

---------------------------------------------------------------------------
|               SENDER             |          |           RECIEVER        |
|                                  |  <---    |  'C'                      |
|                                  |          |  Time out after 3 second  |
|                                  |  <---    |  'C'                      |
| SOH|0x01|0xFE|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| .                                |          |  .                        |
| .                                |          |  .                        |
| .                                |          |  .                        |
|                                  |  <---    |  ACK                      |
| EOT                              |  --->    |                           |
|                                  |  <---    |  ACK                      |
---------------------------------------------------------------------------

和校验和方式不同的是,当接收方要求发送方以CRC16校验方式发送数据时以'C'来请求,发送方对此做出应答,流程就如上图所示。当 发送方仅仅支 持校验和方式时,则接收方要发送NAK来请求,要求以校验和方式来发送数据,如果仅仅支持CRC16校验方式,则只能发送'C'来请求。如果两者都支持的 话,优先发送'C'来请求,流程如图所示:

------------------------------------------------------------------------------
|               SENDER                |          |           RECIEVER        |
|                                     |  <---    |  'C'                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  'C'                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
|                                     |  <---    |  ACK                      |
| EOT                                 |  --->    |                           |
|                                     |  <---    |  ACK                      |
------------------------------------------------------------------------------

最后,如果信息包中的数据如果不足128字节,剩余的部分要以0x1A(Ctrl-Z)来填充。

三、1k-XModem协议
1k-XModem协议同XModem-CRC16协议差不多,只是数据块长度变成了1024字节即1k,同时每个信息报的第一个字节的SOH变成了STX,STX定义为 <STX> 0x02,能有效的加快数据传输速率。

 

研究Xmodem协议必看的10个问题

 Xmodem 协议作为串口数据传输主要的方式之一,恐怕只有做过bootloader的才有机会接触一下,网上有关该协议的内容要么是英语要么讲解不详细。笔者以前写 bootloader时研究过1k-Xmodem,参考了不少相关资料。这里和大家交流一下我对Xmodem的理解,多多指教!


1.Xmodem协议是什么?
  XMODEM协议是一种串口通 信中广泛用到的异步文件传输协议。分为标准Xmodem和1k-Xmodem两种,前者以128字节块的形式传输数据,后者字节块为1k即1024字节, 并且每个块都使用一个校验和过程来进行错误检测。在校验过程中如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送一个确认字 节(ACK)。由于Xmodem需要对每个块都进行认可,这将导致性能有所下降,特别是延时比较长的场合,这种协议显得效率更低。除了Xmodem,还有 Ymodem,Zmodem协议。他们的协议内容和Xmodem类似,不同的是Ymodem允许批处理文件传输,效率更高;Zmodem则是改进的了 Xmodem,它只需要对损坏的块进行重发,其它正确的块不需要发送确认字节。减少了通信量。

2.Xmodem协议相关控制字符
SOH       0x01
STX        0x02      
EOT    0x04
ACK     0x06
NAK      0x15   
CAN       0x18
CTRLZ       0x1A  

3.标准Xmodem协议(每个数据包含有128字节数据)帧格式
_____ ____________  ___________________ __________ ____________
| SOH  | 信息包序号       |  信息包序号的补码           |数据区段       |  校验和             |
|_____|____________|___________________|__________|____________|

4.1k-Xmodem(每个数据包含有1024字节数据)帧格式
  _______________________________________________________________
| STX | 信息包序号  |  信息包序号的补码 |数据区段  |  校验和 

5.数据包说明
对 于标准Xmodem协议来说,如果传送的文件不是128的整数倍,那么最后一个数据包的有效内容肯定小于帧长,不足的部分需要用CTRL-Z(0x1A) 来填充。这里可能有人会问,如果我传送的是bootloader工程生成的.bin文件,mcu收到后遇到0x1A字符会怎么处理?其实如果传送的是文本 文件,那么接收方对于接收的内容是很容易识别的,因为CTRL-Z不是前128个ascii码,不是通用可见字符,如果是二进制文件,mcu其实也不会把 它当作代码来执行。哪怕是excel文件等,由于其内部会有些结构表示各个字段长度等,所以不会读取多余的填充字符。否则Xmodem太弱了。对于1k- Xmodem,同上理。

6.如何启动传输?
传输由接收方启动,方法是向发送方发送"C"或者NAK(注意哦,这里提到的NAK是用来启动传输的。以下我们会看到NAK还可以用来对数据产生重传的机制)。接收方发送NAK信号表示接收方打算用累加和校验;发送字符"C"则表示接收方想打算使用CRC校验。

7.传输过程
当接收方发送的第一个"C"或者NAK到达发送方,发送方认为可以发送第一个数据包,传输已经启动。发送方接着应该将数据以每次128字节的数据加上包头,包号,包号补码,末尾加上校验和,打包成帧格式传送。
发 送方发了第一包后就等待接收方的确认字节ACK,收到接收方传来的ACK确认,就认为数据包被接收方正确接收,并且接收方要求发送方继续发送下一个包;如 果发送方收到接收方传来的NAK(这里,NAK用来告诉发送方重传,不是用来启动传输)字节,则表示接收方请求重发刚才的数据包;如果发送方收到接收方传 来的CAN字节,则表示接收方请求无条件停止传输。

8.如何结束传输?
如果发送方正常传输完全部数据,需要结束传输,正常结束需要发送方发送EOT字节通知接收方。接收方回以ACK进行确认。当然接收方也可强制停止传输,当接收方发送CAN
字节给发送方,表示接收方想无条件停止传输,发送方收到CAN后,不需要再发送 EOT确认。

9.特殊处理
    虽然数据包是以 SOH 来标志一个信息包的起始的,但在SOH 位置上如果出现EOT则表示数据传输结束,再也没有数据传过来。接收方首先应确认数据包序号的完整性,通过对数据包序号取补,然后和数据包序号的补码异 或,结果为0表示正确,结果不为0则发送NAK请求重传。接收方确认数据包序号正确后,然后检查是否期望的序号。如果不是期望得到的数据包序号,说明发生 严重错误,应该发送一个 CAN来中止传输。

如果接收到的数据包的包序号和前一包相同,那么接收方会忽略这个重复包,向发送方发出 ACK ,准备接收下一个包。接收方确认了信息包序号的完整性和是正确期望的后,只对128 字节的数据区段进行算术和校验,结果与帧中最后一个字节(算术校验和)比较,相同发送 ACK,不同发送 NAK。

10.校验和的说明
Xmodem协议支持2种校验和,它们是累加和与CRC校验。
当接收方一开始启动传输时发送的是NAK,表示它希望以累加和方式校验。
当接收方一开始启动传输时发送的是字符“C”,表示它希望以CRC方式校验。
  
可 能有人会问,接收方想怎么校验发送方都得配合吗,难道发送方必须都支持累加和校验和CRC校验?事实上Xmodem要求支持CRC的就必须同时支持累加 和,如果发送方只支持累加和,而接收方用字符“C”来启动,那么发送方只要不管它,当接收方继续发送“C”,三次后都没收到应答,就自动会改为发送 NAK,因为它已经明白发送方可能不支持CRC校验,现在接收方改为累加和校验和发送方通讯。发送方收到NAK就赶紧发送数据包响应。

 
 
xmodem源代码
 
#i nclude "crc16.h"
#define SOH  0x01
#define STX  0x02
#define EOT  0x04
#define ACK  0x06
#define NAK  0x15
#define CAN  0x18
#define CTRLZ 0x1A

#define DLY_1S 1000
#define MAXRETRANS 25
static int last_error = 0;

#i nclude "string.h"

void port_outbyte(unsigned char trychar)
{
    unsigned char buf[2];
    buf[0] = trychar;
    lowLevel_write(buf,1);
}

unsigned char port_inbyte(unsigned int time_out)
{
    unsigned char ch;
    int i;
    last_error = 0;

    if(lowLevel_read(&ch,1) == 1)
        return ch;

    last_error = 1;
    return ch;
}

static int check(int crc, const unsigned char *buf, int sz)
{
    if (crc)
    {
        unsigned short crc = crc16_ccitt(buf, sz);
        unsigned short tcrc = (buf[sz]<<8)+buf[sz+1];
        if (crc == tcrc)
            return 1;
    }
    else
    {
        int i;
        unsigned char cks = 0;
        for (i = 0; i < sz; ++i)
        {
            cks += buf[i];
        }
        if (cks == buf[sz])
            return 1;
    }
    return 0;
}

static void flushinput(void)
{
 //while (port_inbyte(((DLY_1S)*3)>>1) >= 0)
    ;
}

int xmodemReceive(unsigned char *dest, int destsz)
{
    unsigned char xbuff[1030];
    unsigned char *p;
    int bufsz, crc = 0;
    unsigned char trychar = 'C';
    unsigned char packetno = 1;
    int i, c, len = 0;
    int retry, retrans = MAXRETRANS;

    for(;;)
    {
        for( retry = 0; retry < 16; ++retry)
        {
            if (trychar)
                port_outbyte(trychar);
            c = port_inbyte((DLY_1S)<<1);
            if (last_error == 0)
            {
                switch (c)
                {
                case SOH:
                    bufsz = 128;
                    goto start_recv;
                case STX:
                    bufsz = 1024;
                    goto start_recv;
                case EOT:
                    flushinput();
                    port_outbyte(ACK);
                    return len;
                case CAN:
                    c = port_inbyte(DLY_1S);

                    if (c == CAN)
                    {
                        flushinput();
                        port_outbyte(ACK);
                        return -1;
                    }
                    break;
                default:
                    break;
                }
            }
        }
        if (trychar == 'C')
        {
            trychar = NAK;
            continue;
        }
        flushinput();
        port_outbyte(CAN);
        port_outbyte(CAN);
        port_outbyte(CAN);
        return -2;
start_recv:
        if (trychar == 'C') crc = 1;
            trychar = 0;
        p = xbuff;
        *p++ = c;
        for (i = 0;  i < (bufsz+(crc?1:0)+3); ++i)
        {
            c = port_inbyte(DLY_1S);

            if (last_error != 0)
                goto reject;
            *p++ = c;
        }

        if (xbuff[1] == (unsigned char)(~xbuff[2]) &&
            (xbuff[1] == packetno || xbuff[1] == (unsigned char)packetno-1) &&
            check(crc, &xbuff[3], bufsz))
        {
            if (xbuff[1] == packetno)
            {
                int count = destsz - len;
                if (count > bufsz)
                    count = bufsz;
                if (count > 0)
                {
                    memcpy (&dest[len], &xbuff[3], count);
                    len += count;
                }
                ++packetno;
                retrans = MAXRETRANS+1;
            }
            if (--retrans <= 0)
            {
                flushinput();
                port_outbyte(CAN);
                port_outbyte(CAN);
                port_outbyte(CAN);
                return -3;
            }
            port_outbyte(ACK);
            continue;
        }
reject:
        flushinput();
        port_outbyte(NAK);
    }
}

int xmodemTransmit(unsigned char *src, int srcsz)
{
    unsigned char xbuff[1030];
    int bufsz, crc = -1;
    unsigned char packetno = 1;
    int i, c, len = 0;
    int retry;

    for(;;)
    {
        for( retry = 0; retry < 16; ++retry)
        {
            c = port_inbyte((DLY_1S)<<1);
            if (last_error == 0)
            {
                switch (c)
                {
                case 'C':
                    crc = 1;
                    goto start_trans;
                case NAK:
                    crc = 0;
                    goto start_trans;
                case CAN:
                    c = port_inbyte(DLY_1S);
                    if (c == CAN)
                    {
                        port_outbyte(ACK);
                        flushinput();
                        return -1;
                    }
                    break;
                default:
                    break;
                }
            }
        }
        port_outbyte(CAN);
        port_outbyte(CAN);
        port_outbyte(CAN);
        flushinput();
        return -2;

        for(;;)
        {
start_trans:
            xbuff[0] = SOH; bufsz = 128;
            xbuff[1] = packetno;
            xbuff[2] = ~packetno;
            c = srcsz - len;
            if (c > bufsz)
                c = bufsz;
            if (c >= 0)
            {
                memset (&xbuff[3], 0, bufsz);
                if (c == 0)
                {
                    xbuff[3] = CTRLZ;
                }
                else
                {
                    memcpy (&xbuff[3], &src[len], c);
                    if (c < bufsz)
                        xbuff[3+c] = CTRLZ;
                }
                if (crc)
                {
                    unsigned short ccrc = crc16_ccitt(&xbuff[3], bufsz);
                    xbuff[bufsz+3] = (ccrc>>8) & 0xFF;
                    xbuff[bufsz+4] = ccrc & 0xFF;
                }
                else
                {
                    unsigned char ccks = 0;
                    for (i = 3; i < bufsz+3; ++i)
                    {
                        ccks += xbuff[i];
                    }
                    xbuff[bufsz+3] = ccks;
                }
                for (retry = 0; retry < MAXRETRANS; ++retry)
                {
                    for (i = 0; i < bufsz+4+(crc?1:0); ++i)
                    {
                        port_outbyte(xbuff[i]);
                    }
                    c = port_inbyte(DLY_1S);
                    if (last_error == 0 )
                    {
                        switch (c)
                        {
                        case ACK:
                            ++packetno;
                            len += bufsz;
                            goto start_trans;
                        case CAN:
                            c = port_inbyte(DLY_1S);
                            if ( c == CAN)
                            {
                                port_outbyte(ACK);
                                flushinput();
                                return -1;
                            }
                            break;
                        case NAK:
                        default:
                            break;
                        }
                    }
                }
                port_outbyte(CAN);
                port_outbyte(CAN);
                port_outbyte(CAN);
                flushinput();
                return -4;
            }
            else
            {
                for (retry = 0; retry < 10; ++retry)
                {
                    port_outbyte(EOT);
                    c = port_inbyte((DLY_1S)<<1);
                    if (c == ACK)
                        break;
                }
                flushinput();
                return (c == ACK)?len:-5;
            }
        }
    }
}

posted on 2018-10-25 17:15  blogernice  阅读(764)  评论(0编辑  收藏  举报