协议介绍:
广泛用于异步文件传输协议,分为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的计算:
```c
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);
}
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 25
static 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;
else
crc = 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 == 384
int 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 == 140
int 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
参考文章: