协议介绍:

广泛用于异步文件传输协议,分为XModem和1k-XModem两种,前者使用128字节的数据块,后者使用1024字节(即1k字节)的数据块。

XModem校验和协议:

字段定义:

0x01 //Xmodem数据头
0x02 //1K-Xmodem数据头
0x04 //发送结束
0x06 //认可响应
0x15 //不认可响应
0x18 //撤销传送
0x1A //填充数据包

XModem信息包格式

  • 标准XModem协议(每个数据包含128字节数据)帧格式 | SOH | 信息包序号 | 信息包序号的补码 | 数据区段 | 校验和 | | —- | —- | —- | —- | —- |
  1. ---------------------------------------------------------------------------
  2. | Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132 |
  3. |-------------------------------------------------------------------------|
  4. |Start Of Header|Packet Number|~(Packet Number)| Packet Data | Check Sum |
  5. ---------------------------------------------------------------------------
  • 1k-XModem(每个数据包含1024字节数据)帧格式 | STX | 信息包序号 | 信息包序号的补码 | 数据区段 | 校验和 | | —- | —- | —- | —- | —- |
  1. -----------------------------------------------------------------------------------
  2. | Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132-Byte133 |
  3. |---------------------------------------------------------------------------------|
  4. |Start Of Header|Packet Number|~(Packet Number)| Packet Data | 16-bit CRC |
  5. -----------------------------------------------------------------------------------

数据包说明:

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

如何启动传输:

传输由接收方启动,方法是向发送方发送”C”或者NAK(注意,这里提到的NAK是用来启动传输的。以下我们会看到NAK还可以用来对数据产生重传的机制)。接收方发送NAK信号表示接收方打算用累加和校验;发送字符”C”则表示接收方想打算使用CRC校验(具体校验规则下文XModem源码,源码胜于雄辩)。

传输过程:

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

如何结束传输:

如果发送方正常传输完全部数据,需要结束传输,正常结束需要发送方发送EOT字节通知接收方。接收方回以ACK进行确认。当然接收方也可强制停止传输,当接收方发送CAN字节给发送方,表示接收方想无条件停止传输,发送方收到CAN后,不需要再发送EOT确认(因为接收方已经不想理它了,呵呵)。

特殊处理:

虽然数据包是以SOH 来标志一个信息包的起始的,但在SOH位置上如果出现EOT则表示数据传输结束,再也没有数据传过来。
接收方首先应确认数据包序号的完整性,通过对数据包序号取补,然后和数据包序号的补码异或,结果为0表示正确,结果不为0则发送NAK请求重传。
接收方确认数据包序号正确后,然后检查是否期望的序号。如果不是期望得到的数据包序号,说明发生严重错误,应该发送一个 CAN 来中止传输。
如果接收到的数据包的包序号和前一包相同,那么接收方会忽略这个重复包,向发送方发出 ACK ,准备接收下一个包。
接收方确认了信息包序号的完整性和是正确期望的后,只对128字节的数据区段进行算术和校验,结果与帧中最后一个字节(算术校验和)比较,相同发送 ACK,不同发送NAK。

超时处理:

接收方等待一个信息包的到来所具有的超时时限为10 秒,每个超时后发送 NAK。
当收到包时,接收过程中每个字符的超时间隔为 1 秒。
为保持“接收方驱动”,发送方在等待一个启动字节时不应该采用超时处理。
一旦传输开始,发送方采用单独的 1 分钟超时时限,给接收方充足的时间做发送ACK,NAK,CAN之前的必须处理。

校验和的说明:

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

  • 累加校验和:

对N个字节的数据进行累加和运算,然后对256取余数,此种校验方式的校验数据长度为1个字节。
由于校验和只占一个字节,如果累加的和超过255将从零开始继续累加。
https://blog.csdn.net/m0_37697335/article/details/83867199

  • CRC校验:

对N个字节的数据进行CRC计算,然后取低16位,此种校验方式的校验数据长度为2个字节
**

  • 校验和方式的XModem传输流程 ```matlab

| 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 |

  1. <a name="9VeJW"></a>
  2. ### CRC16的计算:
  3. ```c
  4. int calcrc(char *ptr, int count)
  5. {
  6. int crc;
  7. char i;
  8. crc = 0;
  9. while (--count >= 0)
  10. {
  11. crc = crc ^ (int) *ptr++ << 8;
  12. i = 8;
  13. do
  14. {
  15. if (crc & 0x8000)
  16. crc = crc << 1 ^ 0x1021;
  17. else
  18. crc = crc << 1;
  19. } while (--i);
  20. }
  21. return (crc);
  22. }

Xmodem协议代码:

  1. #include "BA_UART_CONFIG.h"
  2. #include "BA_XModem.h"
  3. #define SOH 0x01
  4. #define STX 0x02
  5. #define EOT 0x04
  6. #define ACK 0x06
  7. #define NAK 0x15
  8. #define CAN 0x18
  9. #define CTRLZ 0x1A
  10. #define DLY_1S 1000
  11. #define MAXRETRANS 25
  12. static int last_error = 0;
  13. void out_buff(unsigned char *buff, int size)
  14. {
  15. int arg = 0;
  16. UART_HANDLER uart = getUartHandler(5);
  17. writeSysUart(uart, buff, size, &arg);
  18. }
  19. struct sysUartWaitArgStruct sysXmodemUartArg =
  20. {
  21. OS_OPT_PEND_BLOCKING,
  22. 1000,
  23. NULL,
  24. };
  25. int in_buff(unsigned char *buff, int time_out)
  26. {
  27. int arg = 0;
  28. int qSize = 0;
  29. int readSize = 0;
  30. UART_HANDLER uart = getUartHandler(5);
  31. last_error = 0;
  32. sysXmodemUartArg.timeout = time_out;
  33. if(RETURN_RESULT_ERROR_NOERR ==
  34. ctrlSysUart(uart, DEVICE_CONTROL_WAIT_EVENT, (UART_ARG)(&sysXmodemUartArg)))
  35. {
  36. qSize = uart->recvDQ.q.curSize;
  37. if(qSize > 0)
  38. {
  39. readSize = readSysUart(uart, buff, qSize, &arg);
  40. }
  41. }
  42. if(readSize == 0)
  43. last_error = 1;
  44. return (readSize);
  45. }
  46. int calcrc(const unsigned char *ptr, int count)
  47. {
  48. int crc;
  49. char i;
  50. crc = 0;
  51. while (--count >= 0)
  52. {
  53. crc = crc ^ (int) *ptr++ << 8;
  54. i = 8;
  55. do
  56. {
  57. if (crc & 0x8000)
  58. crc = crc << 1 ^ 0x1021;
  59. else
  60. crc = crc << 1;
  61. } while (--i);
  62. }
  63. return (crc);
  64. }
  65. static int check(int crc, const unsigned char *buf, int sz)
  66. {
  67. if(crc)
  68. {
  69. unsigned short crc = calcrc(buf, sz);
  70. unsigned short tcrc = (buf[sz]<<8)+buf[sz+1];
  71. if (crc == tcrc)
  72. return 1;
  73. }
  74. else
  75. {
  76. int i = 0;
  77. unsigned char cks = 0;
  78. for(i = 0; i < sz; i ++)
  79. {
  80. cks += buf[i];
  81. }
  82. if (cks == buf[sz])
  83. return 1;
  84. }
  85. return 0;
  86. }
  87. //recv_buff_size == 384
  88. int xmodemReceive(unsigned char *dest, int destsz)
  89. {
  90. unsigned char xbuff[140];
  91. int bufsz = 0;
  92. int crc = 0;
  93. unsigned char trychar = 'C';
  94. unsigned char packetno = 1;
  95. int c = 0;
  96. int len = 0;
  97. int retry = 0;
  98. int retrans = MAXRETRANS;
  99. int recvSize = 0;
  100. for(;;)
  101. {
  102. for(retry = 0; retry < 16; retry ++)
  103. {
  104. if(trychar)
  105. {
  106. xbuff[0] = trychar;
  107. out_buff(xbuff, 1);
  108. }
  109. recvSize = in_buff(xbuff, (DLY_1S)<<1);
  110. c = xbuff[0];
  111. if (last_error == 0)
  112. {
  113. switch(c)
  114. {
  115. case SOH:
  116. bufsz = 128;
  117. goto start_recv;
  118. case STX:
  119. {
  120. xbuff[0] = CAN;
  121. out_buff(xbuff, 1);
  122. }
  123. return -1;
  124. case EOT:
  125. {
  126. xbuff[0] = ACK;
  127. out_buff(xbuff, 1);
  128. }
  129. return len;
  130. case CAN:
  131. in_buff(xbuff, DLY_1S);
  132. c = xbuff[0];
  133. if(c == CAN)
  134. {
  135. {
  136. xbuff[0] = ACK;
  137. out_buff(xbuff, 1);
  138. }
  139. return -1;
  140. }
  141. break;
  142. default:
  143. break;
  144. }
  145. }
  146. }
  147. if (trychar == 'C')
  148. {
  149. trychar = NAK;
  150. continue;
  151. }
  152. {
  153. xbuff[0] = CAN;
  154. out_buff(xbuff, 1);
  155. out_buff(xbuff, 1);
  156. out_buff(xbuff, 1);
  157. }
  158. return -2;
  159. start_recv:
  160. if(trychar == 'C')
  161. crc = 1;
  162. trychar = 0;
  163. if(recvSize != (bufsz + (crc ? 1 : 0) + 4))
  164. goto reject;
  165. if(xbuff[1] == (unsigned char)(~xbuff[2]) &&
  166. (xbuff[1] == packetno || xbuff[1] == (unsigned char)packetno - 1) &&
  167. check(crc, &xbuff[3], bufsz))
  168. {
  169. if(xbuff[1] == packetno)
  170. {
  171. int count = destsz - len;
  172. if (count > bufsz)
  173. count = bufsz;
  174. if (count > 0)
  175. {
  176. memcpy(&dest[len], &xbuff[3], count);
  177. len += count;
  178. }
  179. packetno ++;
  180. retrans = MAXRETRANS+1;
  181. }
  182. if(-- retrans <= 0)
  183. {
  184. {
  185. xbuff[0] = CAN;
  186. out_buff(xbuff, 1);
  187. out_buff(xbuff, 1);
  188. out_buff(xbuff, 1);
  189. }
  190. return -3;
  191. }
  192. {
  193. xbuff[0] = ACK;
  194. out_buff(xbuff, 1);
  195. }
  196. continue;
  197. }
  198. reject:
  199. {
  200. xbuff[0] = NAK;
  201. out_buff(xbuff, 1);
  202. }
  203. }
  204. }
  205. //send_buff_size == 140
  206. int xmodemTransmit(unsigned char *src, int srcsz)
  207. {
  208. unsigned char xbuff[140];
  209. int bufsz = 0;
  210. int crc = -1;
  211. unsigned char packetno = 1;
  212. int i = 0;
  213. int c = 0;
  214. int len = 0;
  215. int retry = 0;
  216. for(;;)
  217. {
  218. for( retry = 0; retry < 16; ++retry)
  219. {
  220. in_buff(xbuff, (DLY_1S)<<1);
  221. c = xbuff[0];
  222. if(last_error == 0)
  223. {
  224. switch(c)
  225. {
  226. case 'C':
  227. crc = 1;
  228. goto start_trans;
  229. case NAK:
  230. crc = 0;
  231. goto start_trans;
  232. case CAN:
  233. in_buff(xbuff, DLY_1S);
  234. c = xbuff[0];
  235. if(c == CAN)
  236. {
  237. {
  238. xbuff[0] = ACK;
  239. out_buff(xbuff, 1);
  240. }
  241. return -1;
  242. }
  243. break;
  244. default:
  245. break;
  246. }
  247. }
  248. }
  249. {
  250. xbuff[0] = CAN;
  251. out_buff(xbuff, 1);
  252. out_buff(xbuff, 1);
  253. out_buff(xbuff, 1);
  254. }
  255. return -2;
  256. for(;;)
  257. {
  258. start_trans:
  259. xbuff[0] = SOH;
  260. bufsz = 128;
  261. xbuff[1] = packetno;
  262. xbuff[2] = ~packetno;
  263. c = srcsz - len;
  264. if(c > bufsz)
  265. c = bufsz;
  266. if(c >= 0)
  267. {
  268. memset(&xbuff[3], 0, bufsz);
  269. if (c == 0)
  270. {
  271. xbuff[3] = CTRLZ;
  272. }
  273. else
  274. {
  275. memcpy(&xbuff[3], &src[len], c);
  276. if (c < bufsz)
  277. xbuff[3 + c] = CTRLZ;
  278. }
  279. if(crc)
  280. {
  281. unsigned short ccrc = calcrc(&xbuff[3], bufsz);
  282. xbuff[bufsz + 3] = (ccrc>>8) & 0xFF;
  283. xbuff[bufsz + 4] = ccrc & 0xFF;
  284. }
  285. else
  286. {
  287. unsigned char ccks = 0;
  288. for(i = 3; i < bufsz + 3; i ++)
  289. {
  290. ccks += xbuff[i];
  291. }
  292. xbuff[bufsz + 3] = ccks;
  293. }
  294. for(retry = 0; retry < MAXRETRANS; retry ++)
  295. {
  296. out_buff(xbuff, bufsz + 4 + (crc ? 1 : 0));
  297. in_buff(xbuff, DLY_1S);
  298. c = xbuff[0];
  299. if(last_error == 0)
  300. {
  301. switch(c)
  302. {
  303. case ACK:
  304. packetno ++;
  305. len += bufsz;
  306. goto start_trans;
  307. case CAN:
  308. in_buff(xbuff, DLY_1S);
  309. c = xbuff[0];
  310. if(c == CAN)
  311. {
  312. {
  313. xbuff[0] = ACK;
  314. out_buff(xbuff, 1);
  315. }
  316. return -1;
  317. }
  318. break;
  319. case NAK:
  320. break;
  321. default:
  322. break;
  323. }
  324. }
  325. }
  326. {
  327. xbuff[0] = CAN;
  328. out_buff(xbuff, 1);
  329. out_buff(xbuff, 1);
  330. out_buff(xbuff, 1);
  331. }
  332. return -4;
  333. }
  334. else
  335. {
  336. for(retry = 0; retry < 10; retry ++)
  337. {
  338. {
  339. xbuff[0] = EOT;
  340. out_buff(xbuff, 1);
  341. }
  342. in_buff(xbuff, (DLY_1S)<<1);
  343. c = xbuff[0];
  344. if(c == ACK)
  345. break;
  346. }
  347. return ((c == ACK) ? len : -5);
  348. }
  349. }
  350. }
  351. }

Xmodem.h

/**

**/
#ifndef __XMODEM_H__
#define __XMODEM_H__

#ifdef __cplusplus
extern "C" {
    #endif
    #undef EXTERN
    #ifdef __XMODEM_C__
    #define EXTERN
    #else
    #define EXTERN extern
    #endif

    #include "config.h"
    #ifdef XMODEM_ENABLE

    /* Xmodem协议的相关控制符定义 */
    #define SOH                    0x01
    #define STX                    0x02
    #define EOT                    0x04
    #define ACK                    0x06
    #define NAK                    0x15
    #define CAN                    0x18
    #define CTRLZ                0x1A
    #define TRY_TIMEOUT            0x01

    /*Expoorted types--------------------------------*/
    /*Expoorted macro--------------------------------*/
    /*Expoorted functions--------------------------------*/
    EXTERN void XMODEM_Handler(uint8_t data);

    #endif
    #ifdef __cplusplus
}
#endif
#endif

Xmodem.c

#define __XMODEM_C

#include "Xmodem.h"

#ifdef XMODEM_ENABLE

/*Private define-----------------------------------------------*/

#define XMODEM_RX_STATE_STA            0    /*帧头*/
#define XMODEM_RX_STATE_NUM            0    /*序号*/
#define XMODEM_RX_STATE_DAT            0    /*数据*/
#define XMODEM_RX_STATE_CHK            0    /*校验*/

#define XMODEM_TX_STATE_WAIT_STA    0
#define XMODEM_TX_STATE_WAIT_DAT    1
#define XMODEM_TX_STATE_WAIT_ACK    2
#define XMODEM_TX_STATE_WAIT_EOT    3


/*Private variables-----------------------------------------------*/
uint8_t XmodemData[1025];                    /*Xmodem数据包的有效数据缓存*/
uint16_t XmodemSize = 0;                        /*Xmodem数据包的有效数据长度*/
uint8_t XmodemStart = 0;                    /*Xmodem接收和发送数据的状态*/
uint16_t XmodemIndex = 0;                    /*有效数据的下标或当累计数值用*/
uint8_t XmodemNumber[2];                    /*Xmodem数据包序列号*/
uint8_t XmodemCheckData[2];                    /*Xmodem有效数据的校验值*/
uint8_t XmodemCheckType = 0;                /*Xmodem校验类型:0:CRC, 1:SUM*/

/*Private variables-----------------------------------------------*/
uint8_t XmodemFileBuffer[2000];                /*Xmodem发送测试数据*/
uint16_t XmodemSentLength = 0;                /*Xmodem已发送的有效数据长度*/
uint16_t XmodemFileLength = 2000;            /*Xmodem整个发送的有小数据长度*/



/**
*@bried 对Xmodem接收到的数据包进行CRC校验值的计算
**/
uint16_t XMODEM_CalcCheckCRC(uint8_t *data, uint16_t size)
{
    uint32_t CRC = 0;
    uint16_t length = size + 2;

    while(length--)
    {
        uint8_t dat = (length < 2) ? 0 : *data++;

        for(uint8_t i=0; i<8; i++)
        {
            CRC <<= 1;
            if(dat & 0x00080) CRC += 0x0001;
            dat<<=1;
            if(CRC & 0x10000) CRC ^= 0x1021;
        }
    }

    CRC &= 0xFFFu;
}


// 参考资料
https://mbb.eet-china.com/blog/3889467-415666.html

参考文章:

KERMIT,XMODEM,YMODEM,ZMODEM传输协议小结

Xmodem通信协议实例