CH32V303基于Ymodem协议的IAP升级

本文详细介绍了Ymodem协议的帧格式、包序号、校验机制,以及在MCU通信中的应用,包括命令编码、通信流程和接收代码示例,展示了如何在CH32V303RCT6上实现文件传输和分区跳转。
摘要由CSDN通过智能技术生成

一、Ymodem协议简介

1、 帧格式

帧头包号包号反码信息块校验码
SOH /STX111024/128CRC(2)
0x01/0x02(1byte)0~0xFF(1byte)1byte1024byte/128byte2byte

信息帧包含三种形式:起始帧, 数据帧,结束帧。
起始帧

帧头包号包号反码文件名称文件大小填充区域校验高位校验低位
SOH0X000XFF文件名(例:CH_APP_A.bin)size(例:7184)NULL(0X00)CRC_HCRC_L

数据帧

帧头包号包号反码数据区校验高位校验低位
SOH/STX0X000XFFdata(128/1024)CRC_HCRC_L

对于数据帧,中间过程一般是128字节的信息,或者1024,但需要对最后一帧数据做一些处理,例如小于128字节的有效数据或者小于1024,大于128字节的有效数据时的情况处理

data长度< 128 byte128<data<1024 byte
133byte1029
不够128字节的补0x1A不够1024字节的补0X1A

结束帧

帧头包号包号反码数据区校验高位校验低位
SOH0X000XFF0x000x000x00

1.1、帧头

根据这一帧数据的第一个字节,判断下发的这一帧数据的信息块是128还是1024字节

SOHSTX
0x010x02
128byte1024byte

1.2、包序号

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

1.3、帧长度

SOHSTX
0x010x02
133byte1029byte

1.4、校验位

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

二、Ymodem 命令

cmd命令码注释
SOH0x01133byte 帧长度
STX0x021029byte 帧长度
EOT0x04结束命令
ACK0x06应答命令
NAK0x15重传当前数据包请求命令
CAN0x18取消传输命令,连续发送5个该命令
C0x43握手命令

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

三、通信过程

3.1、通信简介

首先,由接收机(如下位机MCU),发送0x43,表示已经准备好了接收来自上位机的数据。
此时可以通过超级终端或者有Ymodem协议的上位机界面查看到字符‘C’,然后,上位机选中要发送的BIN文件,点击发送,此时可以在上位机上看到发送的进度条。如果发送完成显示100%.

3.2、具体分析

3.2.1、操作流程

请添加图片描述
请添加图片描述
请添加图片描述
到这一步基本就上位机的配置完成。为了回显我们在上位机上输出的字符,点击文件,然后属性,点击ASCII设置回显。如下:
请添加图片描述

3.2.2、时序分析

通过逻辑分析仪,将其连接在RX和TX上,可以观察到上位机和下位机之间交互的数据信息
我使用的是Pluse View,具体设置如下所示:
在这里插入图片描述
由于这个时序较长,只能截取部分看
第一帧数据(起始帧):
请添加图片描述
请添加图片描述

在这里插入图片描述

由图可看其结构

帧头包号包号反码文件名称文件大小填充区域校验高位校验低位
SOH0X000XFFCH_APP_A.bin7104NULL(0X00)CRC_HCRC_L

数据帧:
在这里插入图片描述
我们可以在VSCODE中查看到ASCII的值和逻辑分析仪中的值是一致的
在这里插入图片描述
在最后一帧数据
在这里插入图片描述
由于帧头是02开头,所以当数据发送完不足1024的,补0x1A,如下所示:
在这里插入图片描述
接收完成后,上位机发送发送结束命令0x04在这里插入图片描述
下位机回应ACK命令0x06
然后上位机下发结束帧数据
在这里插入图片描述
在这里插入图片描述

可见,最后一帧数据校验是0。到此,数据传输完成。

四、代码分析

主要是接收部分代码:我也是抄的安富莱电子的代码:点击此处进入安富莱电子
在文章最后我会附上这IAP的安装包。

/*
*********************************************************************************************************
*	函 数 名: Receive_Packet
*	功能说明: 按照ymodem协议接收数据       
*	形    参: buf 数据首地址
*	返 回 值: 文件大小
*********************************************************************************************************
*/
uint32_t TotalSize = 0;
int32_t Ymodem_Receive (uint8_t *buf, uint32_t appadr)
{
	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;
	uint32_t flashdestination, ramsource;
	uint8_t ucState;
	uint32_t SectorCount = 0;
	uint32_t SectorRemain = 0;

	/* 初始化flash编程首地址 */
	flashdestination = appadr;

	/* 接收数据并进行flash编程 */
	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表示接收成功 */
				case 0:
					errors = 0;
					switch (packet_length)
					{
						/* 发送端终止传输 */
						case - 1:
							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)
									{
										/* 读取文件名 */
										for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
										{
											FileName[i++] = *file_ptr++;
										}
										/* 文件名末尾加结束符 */
										FileName[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);

										
										/* 检测文件大小是否比flash空间大 */
										if (size > (1024*1024*2 + 1))
										{
											/* 终止传输 */
											Send_Byte(CA);
											Send_Byte(CA);
											return -1;
										}

										/* 擦除用户区flash */
										SectorCount = size/(128*1024);
										SectorRemain = size%(128*1024);	
										
										for(i = 0; i < SectorCount; i++)
										{
											bsp_EraseCpuFlash((uint32_t)(flashdestination + i*128*1024));
										}
										
										if(SectorRemain)
										{
											bsp_EraseCpuFlash((uint32_t)(flashdestination + i*128*1024));
										}
										Send_Byte(ACK);
										Send_Byte(CRC16);
									}
									/* 文件名数据包处理完,终止此部分,开始接收数据 */
									else
									{
										Send_Byte(ACK);
										file_done = 1;
										session_done = 1;
										break;
									}
								}
								
								/* 数据包 */
								else
								{
									/* 读取数据 */
									memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
									ramsource = (uint32_t)buf;
									
									/* 扇区编程 */
									ucState = bsp_WriteCpuFlash((uint32_t)(flashdestination + TotalSize),  (uint8_t *)ramsource, packet_length);
									TotalSize += packet_length;
									
									/* 如果返回非0,表示编程失败 */
									if(ucState != 0)
									{
										/* 终止传输 */
										Send_Byte(CA);
										Send_Byte(CA);
										return -2;
									}
									
									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;
}

对于不同的MCU,需要修改的地方很少:

	/* 擦除用户区flash */
										SectorCount = size/(128*1024);
										SectorRemain = size%(128*1024);	
										
										for(i = 0; i < SectorCount; i++)
										{
											bsp_EraseCpuFlash((uint32_t)(flashdestination + i*128*1024));
										}
										
										if(SectorRemain)
										{
											bsp_EraseCpuFlash((uint32_t)(flashdestination + i*128*1024));
										}
										Send_Byte(ACK);
										Send_Byte(CRC16);
									}
									/* 文件名数据包处理完,终止此部分,开始接收数据 */
									else
									{
										Send_Byte(ACK);
										file_done = 1;
										session_done = 1;
										break;
									}
								}
								
								/* 数据包 */
								else
								{
									/* 读取数据 */
									memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
									ramsource = (uint32_t)buf;
									
									/* 扇区编程 */
									ucState = bsp_WriteCpuFlash((uint32_t)(flashdestination + TotalSize),  (uint8_t *)ramsource, packet_length);
									TotalSize += packet_length;

就只有数据在FLASH中的擦除和写入换成对应MCU的就行。
我使用的是CH32V303RCT6,一页大小是256byte,同时也可以进行快速页编辑,对于1024的YModem_1k协议,刚好是*4,所以,先计算下发文件在FLASH中占据的页数,如size/256 = NbrOfPage,可以多加一页擦除,防止出现错误; 再通过快速页擦除,
最后写入即可。

FLASH_Status BSP_Flash_ErasePage(uint32_t addr, uint32_t cnt)
{
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t value = 0xE339E339;

    HAL_Flash_FastUnLock();
    for (int i = 0;  i < cnt; ++i)
    {
    	FLASH_ErasePage_Fast(addr);
        addr += 256;
     }
    HAL_Flash_FastLock();
    for (int j = 0;  j < cnt; ++ j)
    {
    for (int i = 0; i < 64; ++i)
    {
        if (value != *(uint32_t *)addr)
        {
            status = FLASH_ERROR_WRP;
            printf("flash erase fail addr = %#x, valeu = %#x\r\n", (uint32_t *)addr, *(uint32_t *)addr);
        }
        addr += 4;
    }
        }

    return status;
}

在CH32V303中,当下载完成后,关闭使用的外设,然后使用软件复位,具体参考官方例程:

void IAP_To_APP(void)
{
    Delay_Ms(50);
    GPIO_DeInit(GPIOA);
    GPIO_DeInit(GPIOB);
    USART_DeInit(USART3);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, DISABLE);
    Delay_Ms(10);
    NVIC_EnableIRQ(Software_IRQn);
    NVIC_SetPendingIRQ(Software_IRQn);
}

修改中断地址

void SW_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SW_Handler(void)
{
          __asm("li  a6, 0x5000");
          __asm("jr  a6");

          while(1);

}

将整个0x5000改为自己APP的地址即可,这里可以设置一个IF判断,来选择跳入那个APP,比如想跳入A区,则if(addr == app_a_addr),同理B区页一样。

五、实验现象

下载程序到APP_A分区
请添加图片描述

下载程序到APP_B分区
请添加图片描述
AB分区跳转
请添加图片描述
使用软件:超级终端
SecureRTC
参考代码:stm iap
这两个软件操作没啥区别。都可以实现。同时这个也可以用来验证自己写的Ymodem上位机或下位机程序,十分方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值