本帖最后由 xld0932 于 2022-4-29 11:17 编辑
#申请原创# @21小跑堂
简介 串行通讯的文件传输协议主要有Xmodem、Ymodem、Zmodem、KERMIT等等。其中Xmodem协议是Ward Chritensen于1978年创建的用于调制解调纠错的协议,它是一种被广泛使用的异步文件传输协议。
Xmodem传输以数据包为单位,一个数据包由一个字节的开始字符、一个字节的包序号、一个字节的包序号补码、数据包内容以及数据校验这几部分组成。其中数据包序号下标是从0x01开始向上递增的,累加到0xFF后将循环反复。
根据开始字符的区分,可以将Xmodem协议分为标准Xmodem和Xmodem-1k两个版本。标准Xmodem版本协议数据包内容长度固定为128字节,Xmodem-1k版本协议的数据包内容长度固定为1024字节。规定传输多少字节的数据包内容是由发送方决定的,当发送方发送的开始字符为SOH(0x01),则表示后面数据包内容长度固定为128字节,若发送方发送的开始字符为STX(0x02),则表示后面数据包内容长度固定为1024字节;在实际数据发送中,每个传输数据包的长度都是固定的,如果数据包内容长度不足128/1024字节,则会使用CTRLZ(0x1A)来补全数据包内容。
标准Xmodem协议(每个数据包含有128字节数据)帧格式: SOH + 数据包序号 + 数据包序号补码 + 数据包内容(128字节) + 数据校验
Xmodem-1k协议(每个数据包含有1024字节数据)帧格式: STX + 数据包序号 + 数据包序号补码 + 数据包内容(1024字节) + 数据校验
Xmodem协议的数据检验方式也有2种,一种是累加和校验方式,对传输的数据进行累加和运算,然后对256取余数即可,这种校验方式的校验数据长度为1个字节;另外一种是CRC校验方式,对传输的数据进行CRC运算(CRC多项式公式为X16+X12+X5+1),然后取低16位数据,这种校验方式的检验数据长度为2个字节。判断使用哪种检验方式,是由接收方决定的;接收方在准备接收数据时,会每间隔3秒向发送方发送一个代表可以开始传输的标志字符;如果接收方发送的是大写字母C,则表示接收方要求发送方以CRC的校验方式对数据进行打包发送;如果接收方发送的是NAK(0x15),则表示接收方要求发送方以累加和的校验方式对数据进行打包发送;接收方在收到发送方数据后,同样也要使用与之相一致的校验方式进行计算校验。
Xmodem如何启动传输? 1、传输由接收方来启动,方法是接收方向发送方发送字符C或者NAK信号。 2、当接收方发送的是NAK信号启动传输时,表示接收方使用的是累加和方式进行校验的;当接收方发送的是字符C启动传输时,表示接收方使用的是CRC方式进行校验的;累加和检验占1个字节, CRC校验占2个字节。
Xmodem传输过程描述: 1、当接收方发送的第一个字符C或者NAK信号到达发送方后,发送方认为可以发送第一个数据包,传输已经启动。发送方接着应该将数据以每次128/1024字节的数据加上开始字符,数据包序列号,数据包序列号补码,末尾加上校验和,打包成帧格式进行传送。 2、发送方发送了第一帧数据后,就在等待接收方回复ACK确认字节,发送方收到接收方的ACK确认后,就认为数据包被接收方正确接收了,并且接收方要求发送方继续发送下一个帧数据。 3、如果发送方收到的是接收方传过来的NAK字节,则表示接收方请求重新发送刚才的数据帧;如果发送方收到的是接收方传过来的CAN字节,则表示接收方请求无条件停止传输。
Xmodem处理注意事项: 1、接收方首先应该确认数据包信号的完整性,通过对数据包序号取补码,然后和数据包序号的补码异或运算,结果为0表示正确,结果不为0,则发送NAK请求重传。 2、如果接收到的数据包的包序号和前一个包相同,那么接收方会忽略这个重复的包,向发送方发出ACK,并准备接收下一个数据包。 3、接收方确认了信息包序号的完整性和是否期望包序号后,只对128/1024字节的数据段进行累加和校验或者CRC校验,结果与帧中最后的数据校验比较,相同则发送ACK,不同则发送NAK。
实现功能 使用MobaXterm工具结合SHELL命令来实现选择Xmodem进行发送或是接收的测试;使用SecureCRT工具进行Xmodem数据的发送和接收;并通讯USB转串口打印出传输过程,查看传输结果。
实现代码 由于字数限制,下面的代码仅是核心实现部分,完整的代码可参考附件中的源代码工程,包含详尽的中文注释,易于阅读和理解。
Xmodem校验计算: uint16_t Xmodem_CalcCheckCRC(uint8_t *Buffer, uint16_t Length)
{
uint32_t Result = 0;
Length += 2;
while(Length--)
{
uint8_t Value = (Length < 2) ? 0 : *Buffer++;
for(uint8_t j = 0; j < 8; j++)
{
Result <<= 1;
if(Value & 0x00080) Result += 0x0001;
Value <<= 1;
if(Result & 0x10000) Result ^= 0x1021;
}
}
Result &= 0xFFFF;
printf("\r\n\r\nCheck CRC:0x%04x\r\n\r\n", Result);
return Result;
}
uint8_t Xmodem_CalcCheckSum(uint8_t *Buffer, uint16_t Length)
{
uint16_t Result = 0;
while(Length--)
{
Result += *Buffer++;
}
Result &= 0xFF;
printf("\r\n\r\nCheck Sum:0x%02x\r\n\r\n", Result);
return Result;
}
Xmodem接收处理: void Xmodem_RxHandler(void)
{
uint32_t StartTryTimtout = 0x0;
QUEUE_INIT(QUEUE_RS232_RX_IDX);
Xmodem_State = XMODEM_RX_STATE_SOH;
while(1)
{
if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
{
uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);
printf("0x%02x ", data);
switch(Xmodem_State)
{
case XMODEM_RX_STATE_SOH:
if((data == XMODEM_SOH) || (data == XMODEM_STX))
{
Xmodem_Length = (data == XMODEM_STX) ? 1024 : 128;
Xmodem_Index = 0;
Xmodem_State = XMODEM_RX_STATE_NUM;
}
else if(data == XMODEM_EOT)
{
RS232_SendData(XMODEM_ACK);
printf("\r\nXmodem Finish"); return;
}
else if(data == XMODEM_CAN)
{
printf("\r\nXmodem Cancle"); return;
}
else
{
}
break;
case XMODEM_RX_STATE_NUM:
Xmodem_Number[Xmodem_Index++] = data;
if(Xmodem_Index == 2)
{
Xmodem_Index = 0;
Xmodem_State = XMODEM_RX_STATE_DAT;
}
break;
case XMODEM_RX_STATE_DAT:
Xmodem_Buffer[Xmodem_Index++] = data;
if(Xmodem_Index == Xmodem_Length)
{
Xmodem_Index = 0;
Xmodem_State = XMODEM_RX_STATE_CHK;
}
break;
case XMODEM_RX_STATE_CHK:
Xmodem_CheckData[Xmodem_Index++] = data;
if((Xmodem_CheckType == 1) && (Xmodem_Index == 1))
{
Xmodem_Index = 0;
if(Xmodem_Number[0] == ((Xmodem_Number[1] ^ 0xFF) & 0xFF))
{
if(Xmodem_CheckData[0] == Xmodem_CalcCheckSum(Xmodem_Buffer, Xmodem_Length))
{
RS232_SendData(XMODEM_ACK);
}
else
{
RS232_SendData(XMODEM_NAK);
}
}
else
{
RS232_SendData(XMODEM_NAK);
}
Xmodem_State = XMODEM_RX_STATE_SOH;
}
else if((Xmodem_CheckType == 0) && (Xmodem_Index == 2))
{
Xmodem_Index = 0;
if(Xmodem_Number[0] == ((Xmodem_Number[1] ^ 0xFF) & 0xFF))
{
uint16_t Result = 0;
Result = Xmodem_CheckData[0];
Result <<= 8;
Result |= Xmodem_CheckData[1];
if(Result == Xmodem_CalcCheckCRC(Xmodem_Buffer, Xmodem_Length))
{
RS232_SendData(XMODEM_ACK);
}
else
{
RS232_SendData(XMODEM_NAK);
}
}
else
{
RS232_SendData(XMODEM_NAK);
}
Xmodem_State = XMODEM_RX_STATE_SOH;
}
else
{
}
break;
default:
break;
}
}
else
{
if(StartTryTimtout == 0)
{
if(Xmodem_CheckType)
{
RS232_SendData(XMODEM_NAK);
}
else
{
RS232_SendData('C');
}
}
StartTryTimtout = (StartTryTimtout + 1) % XMODEM_TRY_TIMEOUT;
}
}
}
Xmodem发送处理: void Xmodem_TxHandler(void)
{
for(uint16_t i = 0; i < sizeof(Xmodem_FileBuffer); i++)
{
Xmodem_FileBuffer[i] = 0x30 + (i % 10);
}
QUEUE_INIT(QUEUE_RS232_RX_IDX);
Xmodem_State = XMODEM_TX_STATE_WAIT_SOH;
Xmodem_SentLength = 0;
Xmodem_Length = 128;
while(1)
{
switch(Xmodem_State)
{
case XMODEM_TX_STATE_WAIT_SOH:
if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
{
uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);
printf("\r\nSOH: 0x%02x", data);
if((data == 'C') || (data == XMODEM_NAK))
{
Xmodem_CheckType = (data == 'C') ? 0 : 1;
Xmodem_Number[0] = 0x00;
Xmodem_Number[1] = (Xmodem_Number[0] ^ 0xFF) & 0xFF;
Xmodem_State = XMODEM_TX_STATE_SEND_DAT;
}
else if(data == XMODEM_CAN)
{
return;
}
else
{
}
}
break;
case XMODEM_TX_STATE_SEND_DAT:
memset(Xmodem_Buffer, 0, sizeof(Xmodem_Buffer));
QUEUE_INIT(QUEUE_RS232_RX_IDX);
Xmodem_Index = 0;
Xmodem_Number[0]++;
Xmodem_Number[1] = (Xmodem_Number[0] ^ 0xFF) & 0xFF;
while(Xmodem_SentLength < Xmodem_FileLength)
{
if(Xmodem_Index < Xmodem_Length)
{
Xmodem_Buffer[Xmodem_Index++] = Xmodem_FileBuffer[Xmodem_SentLength++];
}
else
{
break;
}
}
while(Xmodem_Index < Xmodem_Length)
{
Xmodem_Buffer[Xmodem_Index++] = XMODEM_CTRLZ;
}
if(Xmodem_Length == 128)
{
RS232_SendData(XMODEM_SOH);
}
else
{
RS232_SendData(XMODEM_STX);
}
RS232_SendData(Xmodem_Number[0]);
RS232_SendData(Xmodem_Number[1]);
for(uint16_t i = 0; i < Xmodem_Length; i++)
{
RS232_SendData(Xmodem_Buffer[i]);
}
if(Xmodem_CheckType)
{
RS232_SendData(Xmodem_CalcCheckSum(Xmodem_Buffer, Xmodem_Length));
}
else
{
uint16_t Result = Xmodem_CalcCheckCRC(Xmodem_Buffer, Xmodem_Length);
RS232_SendData((Result >> 0x08) & 0xFF);
RS232_SendData((Result >> 0x00) & 0xFF);
}
Xmodem_State = XMODEM_TX_STATE_WAIT_ACK;
break;
case XMODEM_TX_STATE_WAIT_ACK:
if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
{
uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);
printf("\r\nACK: 0x%02x", data);
if(data == XMODEM_ACK)
{
if(Xmodem_SentLength == Xmodem_FileLength)
{
QUEUE_INIT(QUEUE_RS232_RX_IDX);
RS232_SendData(XMODEM_EOT);
Xmodem_State = XMODEM_TX_STATE_WAIT_EOT;
}
else
{
Xmodem_State = XMODEM_TX_STATE_SEND_DAT;
}
}
else if(data == XMODEM_NAK)
{
Xmodem_Number[0] -= 1;
Xmodem_SentLength -= Xmodem_Length;
Xmodem_State = XMODEM_TX_STATE_SEND_DAT;
}
}
break;
case XMODEM_TX_STATE_WAIT_EOT:
if(QUEUE_EMPTY(QUEUE_RS232_RX_IDX) == 0)
{
uint8_t data = QUEUE_READ(QUEUE_RS232_RX_IDX);
printf("\r\nEOT: 0x%02x", data);
if(data == XMODEM_ACK)
{
printf("\r\nXmodem Finish"); return;
}
else if(data == XMODEM_NAK)
{
RS232_SendData(XMODEM_EOT);
}
}
break;
default:
break;
}
}
}
运行测试
程序启动后,我们可以通过MobaXterm终端看到打印信息,当输入1命令的时候,MCU作为接收方接收来自电脑通过SecureCRT传输过来的数据内容,当输入2命令的时候,MCU作为发送方将数据内容发送给电脑上的SecureCRT,SecureCRT会将接收到的数据存储在路径下的文件中。
我们将分别测试标准Xmodem和Xmodem-1k在不同校验方式下的接收数据和发送数据,在SecureCRT软件中Options->Session Options->X/Y/Zmodem中来选择发送数据包的长度,是标准的128字节还是1024字节。
1、使用CRC的校验方式进行测试:在MobaXterm终端输入XMODEM 1命令进入接收Xmodem数据状态,此时SecureCRT软件窗口会每间隔一段时间会显示一个大写字母C;通过点击Transfer->Send Xmodem...在弹出的窗口中,选择需要发送的文件1.TXT,点击Send后即刻发送数据。
1.1、标准Xmodem协议
1.2、Xmodem-1k协议
2、使用累加和的校验方式进行测试:在MobaXterm终端输入XMODEM 1命令进入接收Xmodem数据状态,此时SecureCRT软件窗口不会显示任何提示信息,因为此时接收方发送的是NAK,这是一个不可见的字符;通过点击Transfer->Send Xmodem...在弹出的窗口中,选择需要发送的文件1.TXT,点击Send后即刻发送数据。
2.1、标准Xmodem协议
2.2、Xmodem-1k协议 我们可以看到在当SecureCRT软件中将数据包设置成1024之后,在实际的发送过程中最后一个数据包的大小是根据文件剩余数据量大小来定的,当小于128字节时,使用的是标准的Xmodem数据包大小。
3、使用标准Xmodem协议接收来自MCU的文件数据:在MobaXterm终端输入XMODEM 2命令进入准备Xmodem数据状态,等待SecureCRT软件发送可以开始传输的起始字符;通过程序打印消息得知,SecureCRT软件在接收Xmodem时,使用的是CRC的校验方式。在SecureCRT软件中通过点击Transfer->Receive Xmodem...在弹出的窗口中,选择需要保存的文件路径和文件名2.TXT,点击Receive后即刻接收数据。
附件
|