在实现了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响应即算完成通信。