文章目录

  • 一、XModem简介
  • 二、XModem数据格式
  • 三、XModem文件传输过程
  • 四、具体代码实现
  • 参考链接



在实现了YModem通信协议下的串口升级程序后,我便开始尝试在原有基础上添加XModem协议。因为之前的YModem协议的主要代码是移植的官网代码,始终不是自己敲出来的代码,所以自己在YModem的基础上实现XModem,这样对协议本身的原理理解得更加透彻。


一、XModem简介

XModem是最早的通信传输协议之一,是几乎所有的通讯程序支持的文件传输协议,它每包数据均传输128字节信息块。

二、XModem数据格式

XModem协议最早由Ward Christensen在20世纪70年代提出并实现的,传输过程中的数据包均长,帧长 = 头部3个字节+128字节数据+2个字节CRC16校验 = 133字节,数据结构为:

SOH [num] [~num] [128Bdata] [CRCH] [CRCL]

同YModem一样,若是有效数据不足128B,则剩余部分用0X1A填充。其中数据校验分为CRC16校验和累加和校验两种,这个主要看选用的串口工具中支持什么校验方式,一般在支持CRC16校验的情况下,我们都选用CRC校验。此次我采用的串口工具使用的是CRC16_XModem校验算法(使用的串口工具已在上一篇文中提到)。


三、XModem文件传输过程

Sender

------------------

Reciever

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

通过对比XModem和YMdodem的通信过程,有几点需要注意:
1、XModem传输不会将文件大小以及文件名传给接收端,因此在擦除Flash作为写入准备时,我们需要注意,要么就直接擦除足够大的区域;要么就边接收边擦除Flash,此时就需要通过具体单片机页的大小进行预判擦除。
2、XModem传输数据包从包号1开始,没有包号为0的数据包,且每一包都是传输的有效数据。
3、XModem结束通信时,发送了EOT结束标识后,当收到ACK响应时就表示通信真正结束。


四、具体代码实现
/**************************************************************************************************
** 函数名称 : Receive_Packet
** 功能描述 : 从发送方基于串口查询方式接收一个数据包
** 入口参数 : <data>[in] 接收到的数据缓存区
              <length>[in] 接收的数据长度
              <timeout>[in] 最大等待接收时间
** 出口参数 : length
              0   序号和补码校验不成功
              -1  发送方中止传输
              >0  正常的数据包长度
** 返 回 值 : 0 正常返回
              -1 超时或者包错误
              1  用户中止传输
** 作 者 :
** 日 期 :
** 其他说明 : 无
***************************************************************************************************/  
 int32_t Receive_Packet (uint8_t *data, int32_t *length, uint32_t timeout)
{
  uint16_t i, packet_size;
  uint8_t c =0;
  *length = 0;
  if (Receive_Byte(&c, timeout) != 0)
  {     
    return -1; //超时返回-1
  }
  
  switch (c) //c表示接收到的数据的第一个字节
  {
    case SOH: //数据包开始
      packet_size = 128;
      break;
    case EOT: //数据包结束
      return -2;
    case CA:  //发送方中止传输
      if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
      { 
        *length = -1; 
        return 0;
      }
      else
      { 
        return -1; //中止传输返回-1
      }
    case ABORT1://A
    case ABORT2://a
    //用户中止传输
      return 1;
    default:
      return -1;
  }
	
  *data = c;
  for (i = 1; i < (packet_size + PACKET_OVERHEAD); i ++)
  {
    if (Receive_Byte(data + i, timeout) != 0) //获取剩下的数据(以字节为单位)
    { 
      return -1;//接收数据超时
    }
  }
  uint8_t temp1 =0;
  uint8_t temp2 =0;
  temp1 = data[PACKET_SEQNO_INDEX];
  temp2 = data[PACKET_SEQNO_COMP_INDEX];
  if (temp1 != ((temp2 ^ 0xff) & 0xff))//校验序号和补码
  {     
   // printf("temp1 != ((temp2 ^ 0xff) & 0xff)  \r\n");
    return -1;
  }
	//序号和补码校验不成功则 length = 0;
  *length = packet_size; //获取数据包长度
	
  return 0;
}
/**************************************************************************************************
** 函数名称 : Xmodem_Receive
** 功能描述 : xmodem协议接收接收文件
** 入口参数 : <checkType>[in] 接收文件的校验方式,'C':crc校验,NAK:累加和校验
** 出口参数 : 无
** 返 回 值 : 接收文件操作时的相关错误代码
** 作 者 :
** 日 期 :20180704
** 其他说明 : 无
***************************************************************************************************/
int32_t Xmodem_Receive(uint8_t checkType)
{
  uint8_t xbuff[XPACKET_SEZE];  
  uint8_t crc = 1;      //启用CRC校验
  uint16_t	packetno = 1; //数据包编号 Xmodem没有数据包00
  uint8_t session_stop = 0;
  uint8_t file_stop = 0;
	uint8_t errors = 0;
	uint8_t session_begin = 0;
	uint16_t i,j;
  int32_t packet_length;
	uint32_t RamSource;
	uint32_t Source;
  if(checkType==CHECK_CRC)      //0x01 CRC校验
  {
    crc = 1;
    g_CheckType='C';
  }
  else if(checkType==CHECK_SUM) //0x02 SUM校验
  {
    crc = 0;
    g_CheckType=NAK;
  }
  
  SerialPutString("Waiting for the file to be sent ... (press 'a' to abort)\n\r");
  FlashDestination = ApplicationAddress;
  memset(xbuff,0,XPACKET_SEZE); //初始化为0
	for(;;)
	{
		for(;;)
		{
			switch(Receive_Packet(xbuff,&packet_length,NAK_TIMEOUT))
			{
				case 0: //数据包接收正常
					errors = 0;
					switch(packet_length)
					{
						case -1://由发送方中止传输
							Send_Byte(ACK);
						  return 0;
						case 0://序号和补码校验错误
							Send_Byte(ACK);
							file_stop = 1;
							break;
						default:
							if((xbuff[PACKET_SEQNO_INDEX]&0XFF) != (packetno & 0xFF))
							{
								Send_Byte(NAK);//要求重新发送
							}
							else
						  {
								if((check(crc,&xbuff[PACKET_DATA_INDEX],128))) //进行校验
								{
										//擦除Flash
										if(FLASHStatus == FLASH_COMPLETE)
										{
											if((packetno-1)%16 == 0)
											{
												FLASHStatus = FLASH_ErasePage(ApplicationAddress+ (PageSize *EraseCounter));
												EraseCounter++;
											}	
										}
										packetno++;
										if(packetno >= 4096) //文件大小超出Flash可写大小
										{
											Send_Byte(CA); //中止通信
											Send_Byte(CA);
											return -1;
										}
										RamSource = (uint32_t)&xbuff[PACKET_DATA_INDEX];		
										for (i = 0;(i < packet_length) && (FlashDestination <  /*ApplicationAddress*/BackupAddress + (PageSize * EraseCounter));i += 4)
										{
											/*将收到的数据写到flash中*/
											FLASH_ProgramWord(FlashDestination, *(uint32_t*)RamSource); 

											if (*(uint32_t*)FlashDestination != *(uint32_t*)RamSource)//写入数据是否一致
											{
												return -2;
											}
											FlashDestination += 4;//目标地址后移
											RamSource += 4;
										}//for
										Send_Byte(ACK);	
									}
								else
								{
									return -4;
								}
								
             }//else
							
						session_begin = 1;
						
					}
					memset(xbuff,0,XPACKET_SEZE);//数据包清0
					break;
				case 1:
				  return -3;
				case -2: //通信结束
					Send_Byte(ACK);
					file_stop = 1;
					session_stop = 1;
					break;
					
				default: // 超时或者包错误
					if (session_begin > 0)
					{
						errors ++;
					}
					if (errors > MAX_ERRORS)
					{
						Send_Byte(NAK);
						 return 0;
					}					
					Send_Byte(g_CheckType);
					break;
			}
			if(file_stop != 0)
			{
				break;
			}
		}//内循环
	
		if(session_stop != 0)
		{		  
			break;
		}//if
	}//外循环
  return 1;
}

以上两个函数,就是XModem接收数据,然后处理数据的函数了。其大致结构是模仿YModem通信处理函数来的。
其中需要注意的就是对于接收到数据时,将数据写入Flash之前的擦写操作。我此处是采用的边接收边擦除的方式,因为调用的Flash操作函数一次性是擦除一页的大小,而我选用的stm32F103c8t6一页大小为2KB,每个数据包有效数据为128B,因此在数据包号每记录到16的倍数时,就应该提前擦除一页的Flash。(注意数据包号从1开始)
其次,就是对结束标识的处理,此时只需要向发送端发送ACK响应即算完成通信。


参考链接

XModem协议