【开源】串口YMODEM实现IAP程序升级(附工程源码)

1、什么是IAP?

IAP是In Application Programming的缩写,即在应用编程,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。
可以通过串口、USB、网络、无线等方式进行升级数据的传输。

2、IAP要点

(1)程序分两部分,BOOT和APP分别编写;
(2)flash空间划分(flash空间足够大的情况下,可以分成APP1和APP2进行备份升级)
在这里插入图片描述
本文Boot使用0x08000000-0x08003000空间,共12k存放Boot代码(可以根据Boot程序的大小,调整空间),剩下空间存放App代码。
(3)flash可编程(读、写、擦除),本文使用GD32f303,使用以下三个函数完成flash的编程操作。

void FlashWrite(uint16_t len,uint8_t  *data,uint32_t addr_start)
{	
	uint16_t i;
	uint16_t temp;
	
	fmc_state_enum  fmc_state=FMC_READY;
	
	fmc_unlock();
	
	fmc_flag_clear(FMC_FLAG_BANK0_END);
	fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
	fmc_flag_clear(FMC_FLAG_BANK0_PGERR);

	for(i=0;i<len/2;i++) 
	{
		fmc_state=fmc_halfword_program(addr_start, *(uint16_t*)data);
		
		if(fmc_state!=FMC_READY)
		{
			return;
		}
		
		fmc_flag_clear(FMC_FLAG_BANK0_END);
		fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
		fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
		
		data += 2;
		addr_start += 2;
	}
	if(len % 2)
	{
		temp = *data | 0xff00;
		fmc_state=fmc_halfword_program(addr_start,temp);
	}

	fmc_lock();
}

void FlashRead(uint16_t len,uint8_t *outdata,uint32_t addr_start)
{	
	uint32_t addr;
	uint16_t i;
	
	addr = addr_start;
	
	for(i=0;i<len;i++) 
	{
		*outdata = *(uint8_t*) addr;
		addr = addr + 1;
		outdata++;
	}
}

void FlashErase(uint32_t start, uint32_t end)
{
	uint32_t EraseCounter;
	fmc_state_enum fmc_state=FMC_READY;

	/* unlock the flash program/erase controller */
    fmc_unlock();
    /* clear all pending flags */
    fmc_flag_clear(FMC_FLAG_BANK0_END);
    fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
    fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
    
    /* erase the flash pages */
    while(start < end)
	{
		EraseCounter = start/FLASH_PAGE_SIZE;
        fmc_state=fmc_page_erase(EraseCounter*FLASH_PAGE_SIZE);
		if(fmc_state!=FMC_READY)
		{
			return;
		}
        fmc_flag_clear(FMC_FLAG_BANK0_END);
        fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
        fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
			 
		start += FLASH_PAGE_SIZE;
    }
    /* lock the main FMC after the erase operation */
    fmc_lock();
}

(4)从BOOT跳转到APP的时候需要,关闭中断,判断栈顶地址、设置栈指针。

	if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FF30000 ) == 0x20000000) 
	/*判断栈顶地址是否在合法范围内,*/
	{
		JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
		/* Jump to user application */
		Jump_To_Application = (pFunction) JumpAddress;
		  /* Initialize user application's Stack Pointer */
	    __set_MSP(*(__IO uint32_t*) ApplicationAddress);//设置栈指针
		Jump_To_Application();
	}

ApplicationAddress为跳转后App的flash首地址,前面4字节存放的正是栈顶地址。一般arm cortex m内核的cpu,ram起始地址为0x20000000,本文使用的GD32F303VC,ram为48k,范围为0x20000000-0x2000C000,所以要跳转到app之前先检查,确保栈顶地址在0x20000000-0x2000C000范围内。
(5)APP程序编译设置
在这里插入图片描述
在编译器里面设置,App程序的flash起始地址,flash空间大小。
(6)App程序设置(中断开和中断向量位置设置)

nvic_vector_table_set(NVIC_VECTTAB_FLASH,0x8003000); //设置向量表起始位置 

本文在Boot中没有关闭全局中断,所以不需要开全局中断。

3、YMODEM协议

3.1、YMODEM 帧格式

Ymodem 有两种帧格式,主要区别是信息块长度不一样。

帧头包序号包序号 取反信息块校验
SOH/STXPNXPNDATACRC
1byte1byte1byte128/1024byte2byte
3.1.1、帧头

帧头表示两种数据帧长度,主要是信息块长度不同。
当帧头为SOH(0x01)时,信息块为128字节;
当帧头为STX(0x02)时,信息块为1024字节。

3.1.2、包序号

数据包序号只有1字节,因此计算范围是0~255;对于数据包大于255的,序号归零重复计算。

3.1.3、帧长度

以SOH(0x01)开始的数据包,信息块是128字节,该类型帧总长度为133字节;
以STX(0x02)开始的数据包,信息块是1024字节,该类型帧总长度为1029字节。

3.1.4、校验

Ymodem采用的是CRC16校验算法,校验值为2字节,传输时CRC高八位在前,低八位在后;CRC计算数据为信息块数据,不包含帧头、包号、包号反码

3.2、YMODEM起始帧

Ymodem起始帧并不直接传输文件内容,而是先将文件名和文件大小置于数据帧中传输;起始帧是以SOH 133字节长度帧传输,格式如下。

帧头包序号包序号 取反文件名称文件大小填充区校验
SOH0x000xFFfilename+0x00filesize+0x00n字节0x00CRC16

其中包号为固定为0;
filename为文件名称,文件名称后必须加0x00作为结束;
filesize为文件大小值,文件大小值后必须加0x00作为结束;
余下未满128字节数据区域,则以0x00填充。
可以看出起始帧也是遵守3.1中Ymodem包格式的。

3.3、YMODEM数据帧

Ymodem数据帧传输,在信息块填充有效数据。
传输有效数据时主要考虑的是最后一包数据的是处理,SOH帧和STR帧有不同的处理。
(1)对于SOH帧,若余下数据小于128字节,则以0x1A填充,该帧长度仍为133字节。
(2)对于STX帧需考虑几种情况:

  • 余下数据等于1024字节,以1029长度帧发送;
  • 余下数据小于1024字节,但大于128字节,以1029字节帧长度发送,无效数据以0x1A填充;
  • 余下数据等于128字节,以133字节帧长度发送;
  • 余下数据小于128字节,以133字节帧长度发送,无效数据以0x1A填充。

3.4、YMODEM结束帧

Ymodem的结束帧采用SOH 133字节长度帧传输,该帧不携带数据(空包),即数据区、校验都以0x00填充。

帧头包序号包序号 取反信息块校验
0x010x000xff128个0x000x00 0x00
3.5、YMODEM握手信号

握手信号由接收方发起,在发送方开始传输文件前,接收方需发送YMODEM_C (字符C,ASII码为0x43)命令,发送方收到后,开始传输起始帧。

3.6、YMODEM命令
命令命令码说明
SOH0x01128字节数据包
STX0x021024字节的数据包
EOT0x04结束传输
ACK0x06回应
NAK0x15没回应,需要重传当前数据包
CA0x18取消传输
C0x43握手
3.7、一个YMODEM传输过程

在这里插入图片描述
可以看出YMODEM只有起始帧、数据帧、结束帧的帧长度是133或者1029长度的,除此以外都是一个字节,这也提高了YMODEM的传输效率。

4、YMODEM工具

有些工具是支持YMODEM传输的,比如SecureCRT等。
在这里插入图片描述

5、C语言实现YMODEM协议

篇幅有限,本文只列举了核心代码

5.1、YMODEM接收
int32_t Ymodem_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, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;

  /* Initialize FlashDestination variable */
  FlashDestination = ApplicationAddress;

  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))
      {
        case 0:
          errors = 0;
          switch (packet_length)
          {
            /* Abort by sender */
            case - 1:
              Send_Byte(ACK);
              return 0;
            /* End of transmission */
            case 0:
              Send_Byte(ACK);
              file_done = 1;
              break;
            /* Normal packet */
            default:
              if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
              {
                Send_Byte(NAK);
              }
              else
              {
                if (packets_received == 0) //第一个包
                {
                  /* Filename packet */
                  if (packet_data[PACKET_HEADER] != 0)
                  {
                    /* Filename packet has valid data */
                    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';
                    for (i = 0, file_ptr ++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                    {
                      file_size[i++] = *file_ptr++;
                    }
                    file_size[i++] = '\0';
                    Str2Int(file_size, &size);

                    /* Test the size of the image to be sent */
                    /* Image size is greater than Flash size */
                    if (size > (FLASH_SIZE - 1))  //文件大小,大于flash容量
                    {
                      /* End session */
                      Send_Byte(CA);
                      Send_Byte(CA);
                      return -1;
                    }
					FlashErase(FlashDestination,FlashDestination+size); 
				
                    Send_Byte(ACK);
                    Send_Byte(CRC16);
                  }
                  /* Filename packet is empty, end session */
                  else
                  {
                    Send_Byte(ACK);
                    file_done = 1;
                    session_done = 1;
                    break;
                  }
                }
                /* Data packet */
                else
                {
                  memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
                  RamSource = (uint32_t)buf;
					
				  {
					 FlashWrite(packet_length,buf_ptr,FlashDestination);
					 FlashDestination+=packet_length;
				  }
				
                  Send_Byte(ACK);
                }
                packets_received ++;
                session_begin = 1;
              }
          }
          break;
        case 1:
          Send_Byte(CA);
          Send_Byte(CA);
          return -3;
        default:
          if (session_begin > 0)
          {
            errors ++;
          }
          if (errors > MAX_ERRORS)
          {
            Send_Byte(CA);
            Send_Byte(CA);
            return 0;
          }
          Send_Byte(CRC16);
          break;
      }
      if (file_done != 0)
      {
        break;
      }
    }
    if (session_done != 0)
    {
      break;
    }
  }
  return (int32_t)size;
}

5.2、YMODEM发送
uint8_t Ymodem_Transmit (uint8_t *buf, const uint8_t* sendFileName, uint32_t sizeFile)
{
  
  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
  uint8_t FileName[FILE_NAME_LENGTH];
  uint8_t *buf_ptr, tempCheckSum ;
  uint16_t tempCRC, blkNumber;
  uint8_t receivedC[2], CRC16_F = 0, i;
  uint32_t errors, ackReceived, size = 0, pktSize;

  errors = 0;
  ackReceived = 0;
  for (i = 0; i < (FILE_NAME_LENGTH - 1); i++)
  {
    FileName[i] = sendFileName[i];
  }
  CRC16_F = 1;       
    
  /* Prepare first block */
  Ymodem_PrepareIntialPacket(&packet_data[0], FileName, &sizeFile);
  
  do 
  {
    /* Send Packet */
    Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);
    /* Send CRC or Check Sum based on CRC16_F */
    if (CRC16_F)
    {
       tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
       Send_Byte(tempCRC >> 8);
       Send_Byte(tempCRC & 0xFF);
    }
    else
    {
       tempCheckSum = CalChecksum (&packet_data[3], PACKET_SIZE);
       Send_Byte(tempCheckSum);
    }
  
    /* Wait for Ack and 'C' */
    if (Receive_Byte(&receivedC[0], 10000) == 0)  
    {
      if (receivedC[0] == ACK)
      { 
        /* Packet transfered correctly */
        ackReceived = 1;
      }
    }
    else
    {
        errors++;
    }
  }while (!ackReceived && (errors < 0x0A));
  
  if (errors >=  0x0A)
  {
    return errors;
  }
  buf_ptr = buf;
  size = sizeFile;
  blkNumber = 0x01;
  /* Here 1024 bytes package is used to send the packets */
  
  
  /* Resend packet if NAK  for a count of 10 else end of commuincation */
  while (size)
  {
    /* Prepare next packet */
    Ymodem_PreparePacket(buf_ptr, &packet_data[0], blkNumber, size);
    ackReceived = 0;
    receivedC[0]= 0;
    errors = 0;
    do
    {
      /* Send next packet */
      if (size >= PACKET_1K_SIZE)
      {
        pktSize = PACKET_1K_SIZE;
       
      }
      else
      {
        pktSize = PACKET_SIZE;
      }
      Ymodem_SendPacket(packet_data, pktSize + PACKET_HEADER);
      /* Send CRC or Check Sum based on CRC16_F */
      /* Send CRC or Check Sum based on CRC16_F */
      if (CRC16_F)
      {
         tempCRC = Cal_CRC16(&packet_data[3], pktSize);
         Send_Byte(tempCRC >> 8);
         Send_Byte(tempCRC & 0xFF);
      }
      else
      {
        tempCheckSum = CalChecksum (&packet_data[3], pktSize);
        Send_Byte(tempCheckSum);
      }
      
      /* Wait for Ack */
      if ((Receive_Byte(&receivedC[0], 0xffffff) == 0)  && (receivedC[0] == ACK))
      {
        ackReceived = 1;  
        if (size > pktSize)
        {
           buf_ptr += pktSize;  
           size -= pktSize;
           if (blkNumber == (FLASH_IMAGE_SIZE/1024))
           {
             return 0xFF; /*  error */
           }
           else
           {
              blkNumber++;
           }
        }
        else
        {
          buf_ptr += pktSize;
          size = 0;
        }
      }
      else
      {
        errors++;
      }
    }while(!ackReceived && (errors < 0x0A));
    /* Resend packet if NAK  for a count of 10 else end of commuincation */
    
    if (errors >=  0x0A)
    {
      return errors;
    }
    
  }
  ackReceived = 0;
  receivedC[0] = 0x00;
  errors = 0;
  do 
  {
    Send_Byte(EOT);
    /* Send (EOT); */
    /* Wait for Ack */
      if ((Receive_Byte(&receivedC[0], 0xffffff) == 0)  && receivedC[0] == ACK)
      {
        ackReceived = 1;  
      }
      else
      {
        errors++;
      }
  }while (!ackReceived && (errors < 0x0A));
    
  if (errors >=  0x0A)
  {
    return errors;
  }
  
  /* Last packet preparation */
  ackReceived = 0;
  receivedC[0] = 0x00;
  errors = 0;

  packet_data[0] = SOH;
  packet_data[1] = 0;
  packet_data [2] = 0xFF;

  for (i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
  {
     packet_data [i] = 0x00;
  }
  
  do 
  {
    /* Send Packet */
    Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);
    /* Send CRC or Check Sum based on CRC16_F */
    tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
    Send_Byte(tempCRC >> 8);
    Send_Byte(tempCRC & 0xFF);
  
    /* Wait for Ack and 'C' */
    if (Receive_Byte(&receivedC[0], 0xffffff) == 0)  
    {
      if (receivedC[0] == ACK)
      { 
        /* Packet transfered correctly */
        ackReceived = 1;
      }
    }
    else
    {
        errors++;
    }
 
  }while (!ackReceived && (errors < 0x0A));
  /* Resend packet if NAK  for a count of 10  else end of commuincation */
  if (errors >=  0x0A)
  {
    return errors;
  }  
  
  do 
  {
    Send_Byte(EOT);
    /* Send (EOT); */
    /* Wait for Ack */
      if ((Receive_Byte(&receivedC[0], 0xffffff) == 0)  && receivedC[0] == ACK)
      {
        ackReceived = 1;  
      }
      else
      {
        errors++;
      }
  }while (!ackReceived && (errors < 0x0A));
    
  if (errors >=  0x0A)
  {
    return errors;
  }
  return 0; /* file trasmitted successfully */
}

6、升级

6.1、上电复位等待输入操作

可以输入1,2,3选择操作:
1、下载app程序(升级)
2、上传app程序(从mcu读取)
3、执行app程序。
在这里插入图片描述

6.2、给mcu下载app程序(升级)并运行app

只需要输入1即可,进入下载(升级)过程,可以看到升级过程由接收方先发送字符“C”与发送发握手,升级成功后,输入3执行app。
在这里插入图片描述

6.3、mcu上传app程序(从mcu读取)并另存为文件

在这里插入图片描述
注:这里从mcu读取了app所有空间的flash,即244k;可以设置只读取app实际大小空间的flash。

7、说明

​YMODEM协议适用于传输文件,如果系统里面有参数设置或读取,YMODEM协议不太适合。

8、GD32F303 YMODEM Boot源码

代码已经在GD32F130、GD32F303上面验证通过,移植到其他mcu上也比较方便;分享GD32F303 YMODEM Boot完整工程源码

  • 15
    点赞
  • 132
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
串口Ymodem是一种用于通过串口进行程序升级的通信协议。它可以使设备与计算机之间通过串口传输二进制文件,实现设备固件的升级。 具体的实现步骤如下: 1. 首先,在设备固件中实现串口的初始化和配置。设置串口的波特率、数据位、校验位等参数。 2. 在设备端,创建一个接收缓冲区来存储将通过Ymodem协议传输的数据。 3. 在计算机端,使用串口通信工具,打开与设备连接的串口,并发送Ymodem传输指令。Ymodem传输指令的格式包括起始命令、文件名、文件大小等信息。 4. 设备接收到Ymodem传输指令后,从串口接收数据,并将数据存储到接收缓冲区中。同时,设备端还需进行数据完整性校验和校验值的计算。 5. 当一条数据块传输完毕后,设备端将校验结果发送给计算机端进行确认。确认信息由计算机端发送下一条数据块的指令。 6. 计算机端接收到确认信息后,将下一条数据块发送给设备端,直到所有数据块都传输完毕。 7. 设备端在接收完所有数据块后,还需进行总体数据校验和升级完成的确认。 这样,通过串口Ymodem协议,就可以实现设备固件的升级。其中,计算机端需要的是串口通信工具以及能够构建和发送Ymodem传输指令的软件;设备端需要实现串口的初始化和配置,以及接收、校验和存储通过Ymodem协议传输的数据。通过这种方式,可以方便、快速地进行设备固件的升级

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

freemote

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值