STM32基于Ymodem协议IAP升级笔记

1.YMODEM 协议说明

  • YMODEM它分为YMODEM-1KYMODEM-g,平时说的YMODEM传输指是YMODEM-1K传输。
    • YMODEM-1K可以一次传输1024字节的信息块,同时支持传输多个文件。数据的发送会使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后才会继续传输下一个信息块,保证数据已经全部接收。
符号 数值 含义
SOH 0x01 128字节数据包
STX 0x02 1024字节数据包
EOT 0x04 结束传输
ACK 0x06 回应
NAK 0x15 不回应
CA 0x18 传输中止
C 0x43 请求数据包
1.1 起始帧
SOH 0x00 0xFF File_Name File_Size NUL CRC
0x01 帧序号 帧反序号 文件名(Hex) 文件大小 填充0x00 校验位
  • 起始帧帧长 = 3Bytes帧头 + 128Bytes数据域 + 2Bytes的CRC16校验 = 133Bytes

  • SOH = 0x01表示数据域为128Bytes,起始帧帧序号为0x000xFF0x00取反,通过判断这两个字节是否为取反关系,来确定数据是否出错。

  • File_Name + File_Size + NUL 属于数据域

  • FileName表示文件名,如main.bin,它在数据帧中存放的格式为16进制:6D 61 69 6E 2E 62 69 6E 00,需要在文件名最后添加00,表示文件名结束。

  • FileSize表示文件大小,如132KB,转为16进制为0x21000H,它在数据帧中的格式就是32 31 30 30 30 00,同样需要在最后添加0x00表示结束。

  • NUL是数据域128Bytes减去文件名文件大小后剩下的字节都用0x00填充。

  • CRC为校验部分,高8位在前,低8位在后,采用CRC16-CCITT

1.2 数据帧
STX 0x01 0xFE dat_pkt[1024] CRC
0x02 帧序号 帧反序号 数据包 校验位
  • STX = 0x02表示数据包为1024Bytes

  • 如果文件数据最后剩余的数据内容大小在128~1024之间,则还使用STX1024Bytes传输,但剩余的空间使用0x1A填充。

  • 如果文件大小<=128Bytes或者文件数据最后剩余数据内容大小 <128Bytes,则Ymodem会选择SOH数据帧用128Bytes来传输数据,如果数据不满128Bytes,剩余的数据用0x1A填充。

  • 文件大小小于128Bytes的结构为:SOH 01 FE data[ ] 1A ...1A CRCH CRCL

  • 文件最后剩余数据小于128Bytes的结构为:SOH [NUM] [~NUM] data[ ] 1A...1A CRCH CRCL

1.3 结束帧
SOH 0x00 0xFF NUL[128] CRC
0x01 帧序号 帧反序号 填充0x00 校验位

2.升级代码分析

  • 代码是用ST官方代码修改的,此处只讲升级API部分,调用的就是Ymodem_Receive函数(只需要ymodem.c & common.c两个文件),上传API实际中用的少,就忽略了。

#define APP_FLASH_START_ADDR (0x08002800U)   //APP程序跳转运行地址
#define IMAGE_UPDATA_FLASH_SIZE (MCU_FLASH_APP_MAIN_AREA_SIZE) //
unsigned char recv_buff[PACKET_1K_SIZE] ={0};//开辟数据包缓存区
unsigned char FileName[] = {0}; //文件名缓存区
/*
**app跳转引导函数
*/
void iap_load_app(const unsigned int app_addr)
{
   //app_addr为新程序的起始地址,检查栈顶地址是否合法,即栈顶地址是否为0x2000xxxx(内置SRAM)
    if (0x20000000 == ((*(volatile unsigned int*)app_addr) & 0x2FFE0000)) 
    {
        //__set_PRIMASK(1);//有中断,需要先关闭所有中断
        const unsigned int jump_addr = *(volatile unsigned int*)(app_addr + 4); 
        __set_MSP(*(volatile unsigned int*)app_addr);
        ((void (*)(void))jump_addr)();// 设置PC指针为新程序复位中断函数的地址
    }
}

/*
** Ymodem OTA升级执行函数
*/
void ymodem_ota_task(void)
{
  //在执行的过程中,随时都可以按下 `A`按键主动结束升级动作。
  printf(">> IAP already,you can pressed `A` key to end ota....\r\n");

 //返回值携带升级信息
  const int file_sz = Ymodem_Receive(&recv_buff[0],APP_FLASH_START_ADDR);//API改造了一下,增加APP跳转地址形参,便于全局维护。

    if (0 < file_sz)
    {
    printf("\r\n--------- updata sucess --------\r\n");
    printf("file name: %s\r\n", FileName);
    printf("file size: %d bytes\r\n",file_sz);
    printf("--------------------------------\r\n");
        iap_load_app(APP_FLASH_START_ADDR);
    }
    else
    {
        printf("ota failed ...\r\n\r\n");
        if (-1 == file_sz)//升级文件大于分配的FLASH大小
        {
                printf("\r\n alloc flash size is small ...\r\n");
        }
        else if (-2 == file_sz)//升级文件校验出错
        {
                printf("\r\n verif failed ...\r\n");
        }
        else if  (-3 == file_sz)//用户主动结束升级(即按下了`A`按键)
        {
                printf("\r\n aborted by user ...\r\n");
        }
        else//升级超时或者其它
        {
                printf("\r\n receive failed ....\r\n");
        }	
       //NVIC_SystemReset();
     }
}

/*
**
*/
int Ymodem_Receive(unsigned char *buf, unsigned int app_addr)
{
    unsigned char packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD] , 
    file_size[FILE_SIZE_LENGTH] ,
    *file_ptr, //
    *buf_ptr;
    int i, 
    packet_length, 
    session_done, 
    file_done, 
    packets_received, 
    errors, 
    session_begin, 
    image_size = 0;

    volatile unsigned int flash_dst_addr, bin_data;
    flash_dst_addr = app_addr;
 
//双循环
    for (session_done = 0, errors = 0, session_begin = 0;;)//等价 while(1)
    {
        for (packets_received = 0, file_done = 0, buf_ptr = buf;;)//等价 while(1)
        {
                /* 0x00: 正常返回 | -1:时间溢出或数据包错误 | 0x01:用户终止*/
    //分类处理接收包数据
                switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT))
                {
                        case 0://收到完整协议包
                        errors = 0;//清除累计错误
                        switch (packet_length)//判断包长
                        {
                        case -1:/* 连续的两个CA信号终止传输 */
                                Send_Byte(ACK);
                                return 0;
                        case 0:/*正常返回*/
                                Send_Byte(ACK);
                                file_done = 1;
                                break;
                        default:/* 数据区长度 */
                                if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))//检验包序号
                                {
                                        Send_Byte(NAK);//包序号不一致
                                }
                                else//包序号一致
                                {
                                        if (packets_received == 0)//起始帧
                                        {
                                                if (packet_data[PACKET_HEADER] != 0)
                                                {
                        //注:file_ptr = packet_data + PACKET_HEADER 等价 file_ptr = &packet_data[PACKET_HEADER],即从第PACKET_HEADER字节开始取数据指针
                                                        for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                                                        {
                                                                FileName[i++] = *file_ptr++;//获取文件名
                                                        }
                                                        FileName[i++] = '\0';//补\0字符串结束符操作。
                                                        for (i = 0, file_ptr++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                                                        {
                                                                file_size[i++] = *file_ptr++;//获取文件大小
                                                        }
                                                        file_size[i++] = '\0';//补\0字符串结束符操作。

                                                        Str2Int(file_size, &image_size);//将字符串转为hex数据

                                                        if (IMAGE_UPDATA_FLASH_SIZE < image_size )//判断将要升级的文件是否超过预留的FALSH升级空间大小
                                                        {
                                                                /* End session */
                                                                Send_Byte(CA);//超过FALSH升级空间大小,发送传输中止应答符'CA',结束升级
                                                                Send_Byte(CA);
                                                                return -1; //返回错误码
                                                        }

                                                        /* Erase the needed pages where the user application will be loaded */
                                                        /* Define the number of page to be erased */
                                                        const unsigned int nbr_of_pg = mcu_flash_page_alloc(image_size);//计算当前文件大小要擦除的页数
                                                        /* Erase the FLASH pages */
                                                        const unsigned int erase_pgn = mcu_flash_page_num_calc(flash_dst_addr);//计算APP起始地址所在的页序号(因为后面擦除FALSH的API是按页序号来操作的)
                                                        //#define SIMLATE_DEBUG
                                                        #ifdef SIMLATE_DEBUG //仿真接收数据信息
                                                        printf("image size:%uBytes |erase page num :%d | erase page nbr:%d\r\n",image_size,erase_pgn,nbr_of_pg);
                                                        #else
                                                        mcu_flash_page_erase_cc_num(erase_pgn,nbr_of_pg);//按页序号来擦除FALSH的API
                                                        #endif
                                                        Send_Byte(ACK);
                                                        Send_Byte(CRC16);//发送正常应答符  + CRC校验
                                                }
                                                else //结束帧
                                                {
                                                        Send_Byte(ACK);
                                                        file_done = 1; //
                                                        session_done = 1;//传输完毕
                                                        break;
                                                }
                                        }
                                        else //数据帧
                                        {
                    //注同样:packet_data + PACKET_HEADER 等价 &packet_data[PACKET_HEADER]
                                                memcpy(buf_ptr,packet_data + PACKET_HEADER,packet_length);//取有效程序数据(去掉前三个帧头)
                                                bin_data = (volatile unsigned int )buf;	

                    //注:packet_length/4:表示下面写FLASH函数是按照4字节(字长度)
                    //若是按2字节写入,则用packet_length/2
                    //1字节则按packet_length
                                                for (unsigned int j = 0; (j < packet_length/4);j++)
                                                {
                                                        #ifdef SIMLATE_DEBUG //先仿真交互信息
                                                        printf("image dating addr :0x%08X | image data:%08X\r\n",flash_dst_addr,*(unsigned int *)bin_data);
                                                        #else
                                                        /* Program the data received into STM32F10x Flash */
                                                        mcu_flash_word_write(flash_dst_addr,*(volatile unsigned int *)bin_data);//每次按4字节写入(1包则按packet_length/4次写完)
                                                        if (*(volatile unsigned int *)flash_dst_addr != *(volatile unsigned int *)bin_data)//每次写入后再读取出来进行对比。
                                                        {
                                                                /* End session */
                                                                Send_Byte(CA);
                                                                Send_Byte(CA);
                                                                return -2;   //写入出错(校验失败)
                                                        }
                                                        #endif
                                                        flash_dst_addr += 4;//每次地址按写入的字节长度偏移,当前API是4字节。
                                                        bin_data +=4;//同样数据源(来自包数据)一起同步偏移。
                                                }										
                                                Send_Byte(ACK);//写完后一包发送ACK.
                                        }
                                        packets_received++;//升级包数目计数
                                        session_begin = 1;//接收一包标记
    //						printf("received packets :%d\r\n",packets_received);
                                }
                        }
                        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;
                }//end Receive_Packet...
                if (file_done != 0)
                {
                        break;
                }
        }//end 接收while(1);
            if (session_done != 0)
            {
                    break;
            }
    }

	return (int)image_size;
}

/*
**获取输入\接收单字节数据
*/
unsigned int SerialKeyPressed(unsigned char *key)
{
  //串口查询方式接收数据
  if (RESET!=USART_GetFlagStatus(USART1,USART_FLAG_RXNE))
  {
    *key = (uint8_t)USART_ReceiveData(USART1);
    return 1;
  }
  return 0;
}

/*
** 接收字节函数
*/
static int Receive_Byte(unsigned char *c, unsigned int timeout)
{
	volatile unsigned int count = timeout;

	while (count-- > 0)
	{
        //收到数据立马退出
		if (SerialKeyPressed(c) == 1)
		{
			return 0;
		}
	}
	return -1;
}

/*
** 接收包数据函数
*/
int Receive_Packet(unsigned char *data, int *length, unsigned int timeout)
{
	unsigned short int i, packet_size;
	unsigned char c;
	unsigned short int crc;

	*length = 0;

	/* 接收一个字符 */
	if (Receive_Byte(&c, timeout) != 0) //没有收到数据,退出
	{
		return -1;
	}

	switch (c)
	{
	/* SOH表示数据区有128字节 */
	case SOH:
		packet_size = PACKET_SIZE;
		break;
	/* STX表示数据区有1k字节 */
	case STX:
		packet_size = PACKET_1K_SIZE;
		break;
	/* 传输结束 end of transmission */
	case EOT:
		return 0;
	/* 连续的两个CA信号终止传输 */
	case CA:
		/* 收到两个连续的CA信号 */
		if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
		{
			*length = -1;
			return 0;
		}
		else/* 只收到一个CA信号 */
		{
			return -1;
		}
	/* 用户终止传输 */
	case ABORT1:
	case ABORT2:
		return 1;
	default:
		return -1;
	}

	*data = c;
	for (i = 1; i < (packet_size + PACKET_OVERHEAD); i++)
	{
        //data + i 等价 &data[i]
		if (Receive_Byte(data + i, timeout) != 0)//没有收到数据,退出
		{
			return -1;
		}
	}
	/* 第PACKET_SEQNO_COMP_INDEX(数字2)字节是PACKET_SEQNO_INDEX(数字1)字节的反码 */
	if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))//正反帧序号校验不通过
	{
		return -1;
	}

	/* 计算CRC */
	crc = data[packet_size + PACKET_HEADER] << 8;
	crc += data[packet_size + PACKET_HEADER + 1];

	if (Cal_CRC16(&data[PACKET_HEADER], packet_size) != crc)//数据包校验不通过
	{
		return -1;
	}

	/* 取数据区长度 */
	*length = packet_size;
	return 0;
}

//FLASH擦写函数API参考
/*
** 根据提供的文件大小,计算所需要的FLASH页数。
*/
unsigned int  mcu_flash_page_alloc(volatile unsigned int img_size)
{
  unsigned int pg_num = 0x0;
  unsigned int img_sz = img_size;

  if ((img_sz % MCU_FLASH_PAGE_SIZE) != 0)
  {
    pg_num = (img_sz / MCU_FLASH_PAGE_SIZE) + 1;
  }
  else
  {
    pg_num = img_sz / MCU_FLASH_PAGE_SIZE;
  }
	
  return pg_num;
}

/*
**页所在的FLASH地址计算当前页序号
*/
unsigned int mcu_flash_page_num_calc(const unsigned int flash_addr)
{
     /* calculate the number of page to be programmed/erased */
    return (flash_addr - MCU_FLASH_START_ADDR) / MCU_FLASH_PAGE_SIZE;
}

/*
**(按序号)连擦除FLASH函数
*/
void mcu_flash_page_erase_cc_num(const unsigned int erase_pgn,unsigned int erase_pg_nbr)
{
    unsigned int erase_pgaddr_ofs=mcu_flash_page_addr_calc(erase_pgn);/*计算当前页所在FLASH地址*/
    /* Unlocks the FLASH Program Erase Controller */
    FLASH_Unlock();
    /* clear all pending flags */
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR | FLASH_FLAG_PGERR);

    /* erase the flash pages */
    for(unsigned int i=0;i<erase_pg_nbr;i++)
    {
       while(FLASH_COMPLETE != FLASH_ErasePage(erase_pgaddr_ofs));//擦除当前FLASH地址所在页
	   FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR | FLASH_FLAG_PGERR);
	   erase_pgaddr_ofs+=MCU_FLASH_PAGE_SIZE;
    }
    /* Locks the FLASH Program Erase Controller */
    FLASH_Lock();
}

3.Ymodem升级工具

  • 1.网上大多推荐的是SecureCRT终端工具,我用的是Tera Term这款终端工具,里面有支持Xmodem,Ymodem,Zmodem等多协议文件发送功能,

  • 2.Ymodem升级文件使用: 打开终端

    • 2.1 首先设置串口:点击"设置"-> “串口”,选择相应串口号以及波特率。
    • 2.2 加载并升级文件:点击"文件"-> “传输” -> “YMODEM” -> “发送” ,选择app升级bin文件即可。
    • 注意:貌似调试发现,其发送的包大小是1KB的。
      STM32基于Ymodem协议IAP升级笔记_第1张图片
  • 3.后来在gitee上找到了作者biglu制作的专门用于Ymodem升级的上位机Ymodem_tool,其支持发送包大小是128B/1024B的,界面简洁好用。
    STM32基于Ymodem协议IAP升级笔记_第2张图片

  • 4.两款工具升级界面
    STM32基于Ymodem协议IAP升级笔记_第3张图片
    STM32基于Ymodem协议IAP升级笔记_第4张图片


  • 参考资料:https://mp.weixin.qq.com/s/Ub46OMKK15KdJitoxbTgYA

你可能感兴趣的:(嵌入式开发笔记,stm32,单片机,YMODEM,IAP,OTA)