协议介绍:
广泛用于异步文件传输协议,分为XModem和1k-XModem两种,前者使用128字节的数据块,后者使用1024字节(即1k字节)的数据块。
XModem校验和协议:
字段定义:
XModem信息包格式
- 标准XModem协议(每个数据包含128字节数据)帧格式 | SOH | 信息包序号 | 信息包序号的补码 | 数据区段 | 校验和 | | —- | —- | —- | —- | —- |
---------------------------------------------------------------------------| Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132 ||-------------------------------------------------------------------------||Start Of Header|Packet Number|~(Packet Number)| Packet Data | Check Sum |---------------------------------------------------------------------------
- 1k-XModem(每个数据包含1024字节数据)帧格式 | STX | 信息包序号 | 信息包序号的补码 | 数据区段 | 校验和 | | —- | —- | —- | —- | —- |
-----------------------------------------------------------------------------------| Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132-Byte133 ||---------------------------------------------------------------------------------||Start Of Header|Packet Number|~(Packet Number)| Packet Data | 16-bit CRC |-----------------------------------------------------------------------------------
数据包说明:
对于标准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 |
<a name="9VeJW"></a>### CRC16的计算:```cint 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;elsecrc = crc << 1;} while (--i);}return (crc);}
Xmodem协议代码:
#include "BA_UART_CONFIG.h"#include "BA_XModem.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 25static int last_error = 0;void out_buff(unsigned char *buff, int size){int arg = 0;UART_HANDLER uart = getUartHandler(5);writeSysUart(uart, buff, size, &arg);}struct sysUartWaitArgStruct sysXmodemUartArg ={OS_OPT_PEND_BLOCKING,1000,NULL,};int in_buff(unsigned char *buff, int time_out){int arg = 0;int qSize = 0;int readSize = 0;UART_HANDLER uart = getUartHandler(5);last_error = 0;sysXmodemUartArg.timeout = time_out;if(RETURN_RESULT_ERROR_NOERR ==ctrlSysUart(uart, DEVICE_CONTROL_WAIT_EVENT, (UART_ARG)(&sysXmodemUartArg))){qSize = uart->recvDQ.q.curSize;if(qSize > 0){readSize = readSysUart(uart, buff, qSize, &arg);}}if(readSize == 0)last_error = 1;return (readSize);}int calcrc(const unsigned 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;elsecrc = crc << 1;} while (--i);}return (crc);}static int check(int crc, const unsigned char *buf, int sz){if(crc){unsigned short crc = calcrc(buf, sz);unsigned short tcrc = (buf[sz]<<8)+buf[sz+1];if (crc == tcrc)return 1;}else{int i = 0;unsigned char cks = 0;for(i = 0; i < sz; i ++){cks += buf[i];}if (cks == buf[sz])return 1;}return 0;}//recv_buff_size == 384int xmodemReceive(unsigned char *dest, int destsz){unsigned char xbuff[140];int bufsz = 0;int crc = 0;unsigned char trychar = 'C';unsigned char packetno = 1;int c = 0;int len = 0;int retry = 0;int retrans = MAXRETRANS;int recvSize = 0;for(;;){for(retry = 0; retry < 16; retry ++){if(trychar){xbuff[0] = trychar;out_buff(xbuff, 1);}recvSize = in_buff(xbuff, (DLY_1S)<<1);c = xbuff[0];if (last_error == 0){switch(c){case SOH:bufsz = 128;goto start_recv;case STX:{xbuff[0] = CAN;out_buff(xbuff, 1);}return -1;case EOT:{xbuff[0] = ACK;out_buff(xbuff, 1);}return len;case CAN:in_buff(xbuff, DLY_1S);c = xbuff[0];if(c == CAN){{xbuff[0] = ACK;out_buff(xbuff, 1);}return -1;}break;default:break;}}}if (trychar == 'C'){trychar = NAK;continue;}{xbuff[0] = CAN;out_buff(xbuff, 1);out_buff(xbuff, 1);out_buff(xbuff, 1);}return -2;start_recv:if(trychar == 'C')crc = 1;trychar = 0;if(recvSize != (bufsz + (crc ? 1 : 0) + 4))goto reject;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){{xbuff[0] = CAN;out_buff(xbuff, 1);out_buff(xbuff, 1);out_buff(xbuff, 1);}return -3;}{xbuff[0] = ACK;out_buff(xbuff, 1);}continue;}reject:{xbuff[0] = NAK;out_buff(xbuff, 1);}}}//send_buff_size == 140int xmodemTransmit(unsigned char *src, int srcsz){unsigned char xbuff[140];int bufsz = 0;int crc = -1;unsigned char packetno = 1;int i = 0;int c = 0;int len = 0;int retry = 0;for(;;){for( retry = 0; retry < 16; ++retry){in_buff(xbuff, (DLY_1S)<<1);c = xbuff[0];if(last_error == 0){switch(c){case 'C':crc = 1;goto start_trans;case NAK:crc = 0;goto start_trans;case CAN:in_buff(xbuff, DLY_1S);c = xbuff[0];if(c == CAN){{xbuff[0] = ACK;out_buff(xbuff, 1);}return -1;}break;default:break;}}}{xbuff[0] = CAN;out_buff(xbuff, 1);out_buff(xbuff, 1);out_buff(xbuff, 1);}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 = calcrc(&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 ++){out_buff(xbuff, bufsz + 4 + (crc ? 1 : 0));in_buff(xbuff, DLY_1S);c = xbuff[0];if(last_error == 0){switch(c){case ACK:packetno ++;len += bufsz;goto start_trans;case CAN:in_buff(xbuff, DLY_1S);c = xbuff[0];if(c == CAN){{xbuff[0] = ACK;out_buff(xbuff, 1);}return -1;}break;case NAK:break;default:break;}}}{xbuff[0] = CAN;out_buff(xbuff, 1);out_buff(xbuff, 1);out_buff(xbuff, 1);}return -4;}else{for(retry = 0; retry < 10; retry ++){{xbuff[0] = EOT;out_buff(xbuff, 1);}in_buff(xbuff, (DLY_1S)<<1);c = xbuff[0];if(c == ACK)break;}return ((c == ACK) ? len : -5);}}}}
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
参考文章:
