STM32F103代码远程升级(三)基于YModem协议串口升级程序的实现

文章目录

        • 一、YModem协议简介
        • 二、YModem的数据格式
          • 1、起始帧的数据格式
          • 2、数据帧的数据格式
        • 三、基于Ymodem协议串口升级程序的实现过程
          • 1、串口工具的使用
          • 2、具体代码的实现
        • 下载链接
        • 参考链接

在实现了简单的串口更新代码之后,便开始考虑到了传输的数据的完整性、正确性和安全性,因此想到了在数据传输中添加通信协议,最常用的通信传输协议有:XModem、YModem、ZModem等,此次选用的协议是YModem协议。当然我们也可以自定义协议,只是自定义协议也需要我们自定义发送端。


一、YModem协议简介

YModem协议是XModem的改进协议,它最常用于调制解调器之间的文件传输的协议,具有快速,稳定传输的优点。它的传输速度比XModem快,这是由于它可以一次传输1024字节的信息块,同时它还支持传输多个文件,也就是常说的批文件传输。
YModem分成YModem-1K与YModem-g。
YModem-1K用1024字节信息块传输取代标准的128字节传输,数据使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后,才会继续传输下一个信息块,保证数据已经全部接收。
YModem-g传输形式与YModem-1K差不多,只是它去掉了数据的CRC校验码,同时在发送完一个数据块信息后,它不会等待接收端的ACK信号,而直接传输下一个数据块。正是它没有涉及错误校验与等待响应,才使得它的传输速度比YModem-1K来得快。
此次我用的就是YModem-1K传输。
YModem协议简介
此篇文章对YModem协议介绍地很详细。


二、YModem的数据格式

1、起始帧的数据格式

YModem的起始帧用于传输文件名与文件的大小,注意该数据包号为0,帧长=3字节的数据首部+128字节数据+2个字节CRC16校验码 = 133字节。数据结构为:

SOH 00 FF filename[ ] filezise[ ] NUL[ ] CRCH CRCL

2、数据帧的数据格式

YModem的数据帧从第二包数据开始,注意该数据包号为1。帧长 = 3字节的数据首部+1024字节数据+2字节的CRC16校验码 = 1029字节。数据结构为:

STX [num] [~num] data[ ] 1A …1A CRCH CRCL

其中的第二个字节为传输的数据包包号,第三个字节为数据包号取反组成。若文件数据的最后一包数据在128~1024之间,则数据部分剩余空间全部用0x1A填充。
注意,存在一种特殊情况:如果文件的大小小于或等于128字节或者文件数据最后剩余的数据小于128字节,则YModem会选择使用SOH数据帧,即用128字节来传输数据,如果数据不满128字节,剩余的数据用0x1A填充。这时数据帧的结构就变成了:
文件大小小于128字节:

SOH 01 FE data[ ] 1A …1A CRCH CRCL

文件最后剩余数据小于128字节:

SOH [num] [~~num] data[ ] 1A…1A CRCH CRCL
#####3、结束帧的数据格式
当传输结束时,YModem还会再传一包结束数据,只是数据内容为空。帧长=3字节首部+128字节的数据+2字节CRC16校验码 = 133字节,其数据帧结构为:
SOH 00 FF NUL[128] CRCH CRCL
#####4、文件传输过程
此处借用上面提到博客的图。以示说明。
STM32F103代码远程升级(三)基于YModem协议串口升级程序的实现_第1张图片
特别注意的是,在文件传输结束时发送端发送了结束标识EOT之后待收到接收端的回复后,还会再发送一包空数据包以表示传输真正结束。

三、基于Ymodem协议串口升级程序的实现过程

1、串口工具的使用

此次我使用的串口工具为 ttermpro.exe ,该串口工具支持Kermit、Xmodem、Ymodem、ZModem等通信协议,其中XModem和YModem协议采用的是CRC16_XModem校验法则。
运行该程序后先选择串口,如图:
STM32F103代码远程升级(三)基于YModem协议串口升级程序的实现_第2张图片
然后再配置通信波特率,一般默认为9600,我们可以根据自己需要修改成相应波特率,如图:
STM32F103代码远程升级(三)基于YModem协议串口升级程序的实现_第3张图片
紧接着就可以选择相应的通信协议进行文件传输了,如图:
STM32F103代码远程升级(三)基于YModem协议串口升级程序的实现_第4张图片

2、具体代码的实现

此次我不再使用上篇文章中的代码,而是从官网上下的stm32f4_iap_using_usart官方F4xx的例程,刚好该例程中也是使用了YModem通信协议。
我将该例程移植到我的stm32F103的工程下,并且把外部按键触发升级程序修改为了软件触发。具体实现是:使用stm32中的备份寄存器作为标识位,当该位被修改,则重启程序进入Bootloader升级程序,在Bootloader程序中也根据备份寄存器中的值进行相应的升级操作。
而要使用备份寄存器,首先得使能该寄存器,查看数据手册后得到如下操作:

/**************************************************************************************************
** 函数名称 : BKP_Configuration
** 功能描述 : 使能BKP寄存器
** 入口参数 : 无
** 出口参数 : 无
** 返 回 值 : 无
** 其他说明 : 无
***************************************************************************************************/  
void BKP_Configuration(void)
{
    /* 使能PWR和BKP时钟 */
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
    /* 使能对后备寄存器的访问 */ 
	  PWR->CR|=1<<8;//置PWR_CR寄存器的第八位DBP为1-->使能对后备寄存器和RTC的访问
}

在理解了YModem的数据结构和传输过程之后,再看例程中对YModem接收的处理就比较清晰了.

/**************************************************************************************************
** 函数名称 : Receive_Packet
** 功能描述 : 从发送方基于串口查询方式接收一个数据包
** 入口参数 : [in] 接收到的数据缓存区
              [in] 接收的数据长度
              [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;
  //printf("time_out = %x\r\n",timeout);
  if (Receive_Byte(&c, timeout) != 0)
  {     
    return -1; //超时返回-1
  }
  
  switch (c) //c表示接收到的数据的第一个字节
  {
    case SOH: //数据包开始
      packet_size = PACKET_SIZE;
      break;
    case STX://正文开始
      packet_size = PACKET_1K_SIZE;
      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
//  case CAN:
			//用户中止传输
      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;
}
/**************************************************************************************************
** 函数名称 : Receive
** 功能描述 : 基于Ymodem协议获取文件
** 入口参数 : [in] 文件首地址
** 出口参数 : 无
** 返 回 值 : 文件大小
** 作 者 :
** 日 期 :
** 其他说明 : 无
***************************************************************************************************/  
int32_t Receive (uint8_t *buf)
{
  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
  int32_t i, j, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
 
  FlashDestination = ApplicationAddress; //初始化闪存目标地址
  memset(packet_data,0,PACKET_1K_SIZE + PACKET_OVERHEAD); //初始化为0
  
  for (session_done = 0, errors = 0, session_begin = 0; ;)
  {
    for (packets_received = 0, file_done = 0, buf_ptr = buf; ;)
    {
      switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT))//0,-1,1
      {
//---------case 0  正常返回
        case 0: 
				case -2:  //收到结束标志,读取结束包 SOH 00 FF 00…00[128个00] CRCH CRCL
          errors = 0;
          switch (packet_length)
          {
            /* 由发送方终止传输*/ 
            case - 1:
              Send_Byte(ACK);   
              return 0;
            /*  数据包中序号和补码不匹配,终止数据发送*/ 
            case 0:            
              Send_Byte(ACK);
              file_done = 1;
              break;
            /* length>0 正常的数据包*/
            default:
              if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))//检查数据包中的序号和接收到的数据包序号是否一致
              {
                Send_Byte(NAK);//发送应答NAK,接收失败要求重发
              }
              else
              {
                if (packets_received == 0)//第一包,包含文件名,文件大小
                {
                  
                  if (packet_data[PACKET_HEADER] != 0)//去除3个字节的首部,读取128B的数据包
                  {
										//取出文件名--32B用于存储
                    for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                    {
                      file_name[i++] = *file_ptr++;
                    }
                    file_name[i++] = '\0';
										
										//取出文件大小--2B用于存储
                    for (i = 0, file_ptr ++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                    {
                      file_size[i++] = *file_ptr++;
                    }
                    file_size[i++] = '\0';
                    
                    Str2Int(file_size, &size); //字符转整型                   

                    //文件大小是否超出flash存储大小
                    if (size > (FLASH_SIZE - 1))
                    {
                      
                      Send_Byte(CA); //中止通信
                      Send_Byte(CA);
                      return -1;
                    }
                    //擦除用户应用程序将被加载的所需的页面
                    //定义需要被擦除的页面号
                    NbrOfPage = FLASH_PagesMask(size);

                    // 擦除指定的flash页面
                    for (EraseCounter = 0; (EraseCounter < NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)
                    {
                      FLASHStatus = FLASH_ErasePage(FlashDestination + (PageSize * EraseCounter));
                    }
										//如果文件大小小于20K,一个页面大小2K
										if(NbrOfPage < 10) 
										{
											delay2_ms(100);
										}
                    Send_Byte(ACK); //发送应答ACK
                    Send_Byte(CRC16); //发送“C”,等待接收下一包数据包
                  }
									//文件名首字节为空
                  else
                  {
                    Send_Byte(ACK);
                    file_done = 1;  //文件传输中止
                    session_done = 1; //传输中止
                    break;
                  }
                }// if (packets_received == 0)
								
                //packets_received > 0,1024B数据包开始传输
                else
                {
                
									//取出数据
									if(check(1,&packet_data[PACKET_DATA_INDEX],1024))//增加CRC校验验证
									{
											RamSource = (uint32_t)&packet_data[PACKET_DATA_INDEX];
											for (j = 0;(j < packet_length) && (FlashDestination <  ApplicationAddress + size);j += 4)
											{
//												BKP->DR6 = 2;//表示正在以Ymodem协议向Flash中写程序
												/*将收到的数据写到flash中*/
												FLASH_ProgramWord(FlashDestination, *(uint32_t*)RamSource); 

												if (*(uint32_t*)FlashDestination != *(uint32_t*)RamSource)//写入数据是否一致
												{
													/* 中止通信 */
													Send_Byte(CA);
													Send_Byte(CA);
													return -2;
												}
												FlashDestination += 4;//目标地址后移
												RamSource += 4;
											}//for
											Send_Byte(ACK);
									}
									else
									{   //CRC验证不通过
										  Send_Byte(NAK);
									}
                }//else
                packets_received ++; //接收到的数据包加1
                session_begin = 1;
              }//else--序号一致
          }//switch   packet_length
          memset(packet_data,0,PACKET_1K_SIZE + PACKET_OVERHEAD);//数据包清0
          break;
//------case 1 由用户输入A(a)中止传输
        case 1: 
          Send_Byte(CA);//发送字节CA
          Send_Byte(CA);
          return -3;
//------返回-1	超时或者包错误	
//				case -2:
//					Send_Byte(ACK);
//				  break;
        default:
          if (session_begin > 0)
          {
            errors ++;
          }
          if (errors > MAX_ERRORS)
          {
            Send_Byte(NAK);
            Send_Byte(CA);
            Send_Byte(CA);
            return 0;
          }
          Send_Byte(CRC16);//发送“C”
          break;
      }//switch   Receive_Packet
			
      if (file_done != 0) //文件传输中止
      {
        break;
      }
    }//for 2 内循环
    if (session_done != 0) //传输中止
    {
      break;
    }
  }//for 1 外循环
  return (int32_t)size;
}

以上两段代码为YModem协议接收数据及处理数据的过程,在此之前我们需先初始化系统以及结构,同时需要注意以下几点:
1、Bootloader中尽可能不使用中断,因此此处串口接收数据采用查询接收方式;
2、Bootloaderz中不要让程序卡死或者进入某个死循环,应在适当的地方进行软件复位;
3、在Keil环境下涉及内存拷贝时,尽量不用memcpy()
4、注意数组长度越界或者溢出错误;
5、注意YModem协议第一包数据的包号为00。


下载链接

因为大家都留言寻求 ttermpro.exe 的软件包,所以我特意更新了博客,将下载链接放在这里,需要的小伙伴自取哟
百度网盘链接
提取码:jysq


参考链接

YModem协议理解
YModem协议简介
Ymodem协议详解

你可能感兴趣的:(stm32单片机代码远程升级)