STM32OTA固件升级(五)

代码和软件下载

通过网盘分享的文件:OTA-5
链接: https://pan.baidu.com/s/1tfV9Jt6yVl6IB-FvffL5hA?pwd=b5d6 提取码: b5d6

通过网盘分享的文件:OTA-4
链接: https://pan.baidu.com/s/1Vpc3LrU6OVvcJy0AW8nEAw?pwd=grfm 提取码: grfm

通过网盘分享的文件:OTA-3
链接: https://pan.baidu.com/s/1Y0N6EQf7rPQdYg1GZ_ZOyA?pwd=p487 提取码: p487

通过网盘分享的文件:OTA-2
链接: https://pan.baidu.com/s/1EkqPfl7prhHy4tmIWCrnRw?pwd=ruey 提取码: ruey

通过网盘分享的文件:OTA-1
链接: https://pan.baidu.com/s/1KDnYpbBGgatckS_u99J5jg?pwd=b3vb 提取码: b3vb

通过网盘分享的文件:SecureCRT安装+破解
链接: https://pan.baidu.com/s/1Xpiqpk17I-xudDEcgNQW0w?pwd=yucd 提取码: yucd

这些代码,是从OTA1-5逐步实现下述功能的,CRT软件可以完成Xmode协议的传输。

1.1:将内部FLASH分成AB区

        B区在前,A区在后,B区主要实现的功能是引导系统区执行A分区代码。A分区主要是存放我们需要执行的代码。

        我们已知,我们每次烧录的程序都是烧录到内部FLASH中,从0X08000000开始执行我们烧录的代码。

        那我们这个工作就是将FLASH分为AB两块,B区首先判断我们是否需要进行代码升级,如果不进行代码升级,就去执行A区代码,否则进行代码升级。

1.2:对FLASH分区

STM32C8T6每个扇区大小是1024即1KB,共64个扇区,

        那么我们设置B区:20页,即B区大小是20*1024。起始地址:0X08000000

        A区:34页,A区大小 34*1024。 起始地址:0x08000000 + 20*1024   = 0x08005000

#define STM32_Flash_Saddr   0x08000000                   //FLASH起始地址
#define STM32_Page_Size     1024                                //FLASH扇区大小
#define STM32_Page_Num            64                            //FLASH扇区个数

#define STM32_B_Page_Num       20                            //FLASH  B扇区个数

#define STM32_A_Page_Num    STM32_Page_Num - STM32_B_Page_Num

                                                                                          //FLASH  A扇区个数
#define STM32_A_Start_Page  STM32_B_Page_Num     /FLASH  A扇区起始页码
#define STM32_A_Saddr           STM32_Flash_Saddr + STM32_B_Page_Num*STM32_Page_Size
                                                                 //FLASH  A扇区起始地址

1.3:OTA标志位

定义一个OTA标志位,通过B区判断标志位来确定时候更新A区代码

OTA_Flag ,用来判断是否要进行OTA固件升级,为一,则说明A区代码需要进行更新,反之正常执行A区代码。

 FireLen[],分别对应每一块要更新的代码的大小,
                比如我要更新的代码大小为1Kb,那么 FireLen[0]=1024,以此类推

注:这里为什么选择 11个,因为我们要将OTA标志位存放到24C02中,而24C02可以按页来存储数据,一页大小为8,正好OTA标志位为(11+1)*4,正好四页大小,OTA设置为32位,所以这里需要乘以四。

#define OTA_SET_Flag 0xAABB1122
typedef struct
{
    uint32_t         OTA_Flag;          //保存在24C02当中即保存在EEPROM中                             
    uint32_t         FireLen[ 11 ];    //0对应OTA的大小   
}OTA_InfoCB;
#define OTA_INFOCB_SIZE sizeof( OTA_InfoCB )
extern    OTA_InfoCB         OTA_Info;

1.4:编写读写OTA_INFO的函数

在IIC读写eeprom.c中,添加一个单独读写OTA_INFO的函数.

这两段代码的作用分别为,读取24C02中的OTA,和对24C02中的OTA进行修改。

void M24C02_ReadOTAInfo(void)
{
	memset( &OTA_Info, 0, OTA_INFOCB_SIZE );
	ee_ReadBytes( (uint8_t * )&OTA_Info ,0,OTA_INFOCB_SIZE );
}

void  M24C02_WriteOTAInfo(void)
{
	uint8_t i;
	uint8_t * wptr;
	wptr = (uint8_t * )&OTA_Info;
	
	//for(i=0;i<OTA_INFOCB_SIZE/16;i++)
	ee_WritePageBytes(  wptr,0,OTA_INFOCB_SIZE );
	
}

*    功能说明: 从串行EEPROM指定地址处开始读取若干数据
*    形    参:_usAddress : 起始地址
*             _usSize : 数据长度,单位为字节
*             _pReadBuf : 存放读到的数据的缓冲区指针
*    返 回 值: 0 表示失败,1表示成功
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)

*********************************************************************************************************
*    函 数 名: ee_WriteBytes
*    功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
*    形    参:_usAddress : 起始地址
*             _usSize : 数据长度,单位为字节
*             _pWriteBuf : 存放读到的数据的缓冲区指针
*    返 回 值: 0 表示失败,1表示成功
uint8_t ee_WritePageBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)

2:构建boot.c

2.1:OTA标志位判断函数

首先我们可以定义一个OTA_SET_Flag 为 0XAABB1122,

        在A区代码正常执行时,如果有新的代码需要进行更新,那么A区函数会自动将EEPROM中的OTA_Flag设置为OTA_SET_Flag,

        当我们下一次启动板子时,程序会先从B区开始执行,第一时间会先 判断eeprom中的OTA_Flag是否等于OTA_SET_Flag,

void BootLoader_Brance(void)
{
    if(OTA_Info.OTA_Flag == OTA_SET_Flag )
    {
            u1_printf("OTA更新!\r\n");
            BootStaFlag  |=  UpData_A_Flag;
            UpDataA.W25Q64_BlockNum = 0;
    }
    else
    {
            u1_printf("跳转A分区!\r\n");
            LOAD_A(STM32_A_Saddr);
    }

2.2:分区判断重点:SP和PC

原理可以看 本文链接:STM32的BootLoader代码跳转原理-CSDN博客

        设置SP:即将A区起始位置赋予SP;

                即:将向量表的初始值进行重新赋值,让他从A区起始地址开始。意思就是我们要让系统认为我们的A区起始地址0X08005000是原来正常烧录函数的0X08000000。

MSR MSP, r0 意思是将r0寄存器中的值加载到MSP(主栈寄存器,复位时默认使用)寄存器中,r0中保存的是参数值,即addr的值

__asm void MSR_SP( uint32_t addr )
{
    MSR MSP,R0
    BX  R14
}

        设置PC:

void LOAD_A(uint32_t addr)
{
    //判断addr,即SP的初始值,这个值不可能位于FLASH中。
    //堆栈在 内存中,那么这个值就处于RAM空间范围内。起始值0x20000000 
    if( (*(uint32_t *)addr >= 0x20000000)  && ( *(uint32_t *)addr <= 0x20004fff )  )
    {
        MSR_SP( *(uint32_t *)addr );  //SP初始化 
        load_A =  ( load_a )*(uint32_t *)(addr+4);
        
        load_A();
        
    }
}

注:在程序运行A区程序之前,需要先将B区初始化的值进行一下复位

void BootLoader_Clear(void)
{
    GPIO_DeInit( GPIOA );
    GPIO_DeInit( GPIOB );
    GPIO_DeInit( GPIOC );
    
    USART_DeInit( USART1 );
    
}

此时,我们已经实现了AB分区,并且在B区判断OTAflag,并且可以执行A区代码

3:测试

        找一个串口或者电灯程序,我们将其烧录到A区(0X08005000)处。此时你会发现代码并不会执行,因为单片机默认是从0X08000000开始执行。

          3.1:将代码烧录到0X0800 5000处

      点击魔术棒,在IROM1中,将0x0800 0000改为 0X0800 5000

        将此处的值改为0X5000,因为这里是偏移量:所以不是0X0800 5000

注:我们这里烧录的是你的电灯或者串口程序,你会发现把他烧录到0X08005000没有任何现象产生

3.2:正常烧录我们的跳转程序

我们的跳转程序不需要进行上诉操作,只需要正常烧录即可。

你会发现刚才未执行的电灯程序开始执行了。这是因为我们的跳转程序,从B区判断为不更新代码,那么程序就会跳转到A分区执行

通过网盘分享的文件:OTA-1
链接: https://pan.baidu.com/s/11dW35dFIbu4eIUONqgJPKA?pwd=m2dp 提取码: m2dp

4:定义一个更新A区升级的结构体

每次往A区写入1024个字节的数据

在这里,我们通过定义一个结构体,来存储更新程序所用的标志位,

因为我们理想的情况下,W25Q64每一块的大小为64KB,那我们便一块区域存储一个代码。即代码0存储到块0,代码1存储到块1....这就是结构体中的W25Q64_BlockNum的作用

UpDataBuff[STM32_Page_Size];每个STM32_Page_Size都为STM32Flash的页大小1024,我们将块中的代码显存入UpDataBuff【】,然后再放入A区。

typedef struct
{
    uint8_t     UpDataBuff[STM32_Page_Size];
    uint32_t   W25Q64_BlockNum;                                            
}UpDataA_CB;

定义一个uint32_t   BootStaFlag;他的每一位发生变化,都会导致相应事件的发生。

#define UpData_A_Flag       0x00000001

下面即为将代码写入到STM32Flash的操作。

首先,判断更新A区代码的标志位是否置位

        首先判断长度是否为4的倍数。(二进制代码必定为4的倍数)

                首先先将1K的数据先写入Flash中,再考虑不足1K的数据(Flash页大小为1K)

                 循环写入

                        首先读取W25Q64的值 

                                SPI_FLASH_BufferRead( UpDataA.UpDataBuff, i*STM32_Page_Size + UpDataA.W25Q64_BlockNum*64*1024 ,STM32_Page_Size );

                        

        else

                长度错误,并将更新A区标志位置0

伪代码为下文代码的说明注释。

首先,判断更新A区代码的标志位是否置位

        首先判断长度是否为4的倍数。(二进制代码必定为4的倍数)

                首先先将1K的数据先写入Flash中,再考虑不足1K的数据(Flash页大小为1K)

                循环写入1k

                        首先读取W25Q64的值 
                        SPI_FLASH_BufferRead( UpDataA.UpDataBuff, i*STM32_Page_Size + UpDataA.W25Q64_BlockNum*64*1024 ,STM32_Page_Size );
              //代码分析  UpDataA.UpDataBuff,要存入的内存,
              //i*STM32_Page_Size + UpDataA.W25Q64_BlockNum*64*1024   块大小64K*1024字节
              //计算 我们要读取的W25Q64中的地址,i*1024,计算已经存入数据 + 块号*64*1024
                        往内部Flash写入数据
                写入不足1k的数据,原理如上

        else

                长度错误,并将更新A区标志位置0
		if( BootStaFlag & UpData_A_Flag )
		{
				//更新A区
				u1_printf("长度%d字节\r\n", OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ]);
				if( OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ] %4 ==0 )
				{
						//在往内部flash写入数据之前,需要先将A区FLASH擦除
						STM32_EraseFlash( STM32_A_Start_Page, STM32_A_Page_Num );
						//先1K的数据取走,最后处理不足1k的数据
						for(i=0;i<= (OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ]/STM32_Page_Size  ); i++  )
						{
								//读取W25Q64的值
								SPI_FLASH_BufferRead( UpDataA.UpDataBuff, i*STM32_Page_Size + UpDataA.W25Q64_BlockNum*64*1024 ,STM32_Page_Size );
									//往内部Flash写入数据
								STM32_WriteFlash( STM32_A_Saddr + i*STM32_Page_Size  , (uint32_t *)UpDataA.UpDataBuff  , STM32_Page_Size );
						}
						if( OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ]%1024 !=0 )
						{
								//读取W25Q64的值
								j = OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ]%1024;
								SPI_FLASH_BufferRead( UpDataA.UpDataBuff, i*STM32_Page_Size + UpDataA.W25Q64_BlockNum*64*1024 ,j );
									//往内部Flash写入数据
								STM32_WriteFlash( STM32_A_Saddr + i*STM32_Page_Size  , (uint32_t *)UpDataA.UpDataBuff  , OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ]%1024 );

						}
						
						if( UpDataA.W25Q64_BlockNum ==0 )  //UpDataA.W25Q64_BlockNum =0 是OTA使用的
						{
								//将OTAFlag 的值更新到M24C02中
								OTA_Info.OTA_Flag = 0;
								M24C02_WriteOTAInfo();
						}
						
						
						NVIC_SystemReset();
				
				}
				else 
				{
						u1_printf("长度错误\r\n");
						BootStaFlag &= ~ UpData_A_Flag;
				}
		}

总结,我们已经实现了通过标志位来判断是否进行A区代码升级,若不升级则跳转至A区运行程序

5:设计一个串口交互式命令行

在这里,我们可以设计一个交互命令行,来对接下来的功能进行一个划分。

在单片机启动2S内,输入一个“w",则进入命令行。否则判断OTA_Flag,执行A区代码或者更新代码

void BootLoader_Brance(void)
{
	if( BootLoader_Enter(20)==0  )
	{
			if(OTA_Info.OTA_Flag == OTA_SET_Flag )
			{
					u1_printf("OTA更新!\r\n");
					BootStaFlag |= UpData_A_Flag;
					UpDataA.W25Q64_BlockNum = 0;
			}
			else
			{
					u1_printf("跳转A分区!\r\n");

					LOAD_A(STM32_A_Saddr);
			}
		}
	else
	{
			u1_printf("进入BootLoader命令行\r\n");
			BootLoad_Info();
	}
	

}
uint8_t BootLoader_Enter( uint8_t timeout)
{
    uint8_t i=10;
    printf("%d 毫秒内输入小写字母w ,进入BootLoader命令行\r\n", timeout*100);
    while(timeout--)
    {
        while(i)
        {
            delay_ms(20);
            if( U1_RxBuff[0] == 'w' )
            {
                    return 1 ; //进入命令行
            }
            else        
            i--;
        }
        
    }
    return 0;  //不进入命令行
}


void BootLoad_Info(void)
{     
    printf("【1】 擦除A区\r\n");
    printf("【2】串口IAP下载A区程序 \r\n");
    printf("【3】设置OTA版本号 \r\n");
    printf("【4】查询OTA版本号\r\n");
    printf("【5】向外部Flash下载程序\r\n");
    printf("【6】使用外部Flash内程序\r\n");
    printf("【7】重启\r\n");
}

再主函数中,我们首先进行24c02的检测,并读取OTA标志位,用于BootLoader_Brance判断。若输入W则进入命令行,进行功能选择,反之则判断A区是否更新代码

之后进入wile循环,判断串口是否接收到命令行数据。

注:我们串口并没有设置接受中断,我们使用的是DMA传输,所以这里不进入接受中断也会接收到数据,在我们串口的空闲中断中,我们已经将接收到的数据放入了内存中。只需要判断OUT和IN指针是否相同就可以判断是否有数据接收到。

if (ee_CheckOk() == 0){  //检查IIC和EEPROM
		printf("没有检测到串行EEPROM!\r\n");			
	}
	else{		
		M24C02_ReadOTAInfo();
	}
	BootLoader_Brance();
	
	while(1)
		delay_ms(10);
		//当串口收到数据
		if(   U1CB.URxDataOUT != U1CB.URxDataIN   )
		{	
				BootEvent(	U1CB.URxDataOUT->satrt , U1CB.URxDataOUT->end - U1CB.URxDataOUT->satrt +1 );
			
				U1CB.URxDataOUT++;
				
				if( U1CB.URxDataOUT == U1CB.URxDataEND )
				{
						U1CB.URxDataOUT = & U1CB.URxDataPtr[0];
				}
		}

再接受到命令行数据之后,那我们就需要设计命令行的功能了。

即BootEvent( uint8_t *data , uint16_t datalen )函数,data为接收到的数据,datalen为数据长度。

void BootEvent( uint8_t *data , uint16_t datalen )
{
	
	int temp,i;
	
	if( BootStaFlag == 0  )
	{
		if( (datalen ==1)&&( data[0]== '1') )
		{
				//将A区FLASH擦除
				printf("擦除A区\r\n");
				STM32_EraseFlash( STM32_A_Start_Page, STM32_A_Page_Num );
				delay_ms(20);
		
		}
		else if( (datalen ==1)&&( data[0]== '2') )
		{
			printf("通过Xmodem协议,串口IAP下载A区程序,请使用.bin文件格式\r\n");
			//将A区FLASH擦除
			printf("擦除A区\r\n");
			STM32_EraseFlash( STM32_A_Start_Page, STM32_A_Page_Num );
			BootStaFlag |= ( IAP_XMODEMC_FLAG |IAP_XMODEMD_FLAG );
			UpDataA.XmodemTimer = 0;
			UpDataA.XmodemNB = 0;
				
		}
		else if( (datalen ==1)&&( data[0]== '3') )
		{
			printf("设置版本号\r\n");
			BootStaFlag |=		SET_VERSION_FLAG;
		}
		else if( (datalen ==1)&&( data[0]== '4') )
		{
			printf("查询版本号\r\n");
			//M24C02_WriteOTAInfo();  
			printf( " 版本号:%s \r\n",	OTA_Info.OTA_ver  );
			BootLoad_Info();
			
		}
		else if( (datalen ==1)&&( data[0]== '5') )
		{
			BootStaFlag |= CMD_5_FLAG;
			printf("向外部Flash下载程序,输入需要使用的块编号(1-10):\r\n");
			
		}
		else if( (datalen ==1)&&( data[0]== '6') )
		{
			BootStaFlag |= CMD_6_FLAG;
			printf("使用外部Flash的程序,输入需要使用的块编号(1-10):\r\n");
			
		}

		else if( (datalen ==1)&&( data[0]== '7') )
		{
					printf("重启\r\n");
					
					NVIC_SystemReset();
		}
	}

6:设计标志位

通过设计标志位,来进行功能判断,

        通过或进行置为,通过与来进行清零。

举个例子:   IAP_XMODEMC_FLAG        0x00000002,那么再二进制中,这个2就是 0010.

        通过 BootStaFlag |= IAP_XMODEMC_FLAG       ,那么是不是将 BootStaFlag的倒数第二位变为1。 假设BootStaFlag为0X05,那么换算为二进制位 0000 0101 ,当他或上 0000 0010 时,BootStaFlag就变成了 0x07,即 0000 0111,

        也就是我们将BootStaFlag的倒数第二位,设置成了IAP_XMODEMC_FLAG 的标志位。

        uint32_t       BootStaFlag;
#define UpData_A_Flag       0x00000001
#define IAP_XMODEMC_FLAG        0x00000002
#define IAP_XMODEMD_FLAG        0x00000004
#define SET_VERSION_FLAG        0x00000008
#define CMD_5_FLAG                    0x00000010
#define CMD5_XMODEM_FLAG        0x00000020
#define CMD_6_FLAG                    0x00000040

7:设计程序二进制写入W25Q64,

6.1 Xmode协议

6.2 解包

再Xmode协议中,我们可以实现两个功能,功能一,通过串口直接将二进制文件写入到A区,也就是直接更新代码。

                                                                      功能二,就是通过串口将二进制文件写入到W25Q64中,也就是实现前述所说的再不同的块中,存入不同的代码

功能一:串口IAP下载程序

当通过窗口命令行选择功能【2】时,便将BootStaFlag中的IAP_XMODEMC_FLAG 和IAP_XMODEMD_FLAG位置一

if( (datalen ==1)&&( data[0]== '2') )
		{
				printf("通过Xmodem协议,串口IAP下载A区程序,请使用.bin文件格式\r\n");
			//将A区FLASH擦除
			printf("擦除A区\r\n");
			STM32_EraseFlash( STM32_A_Start_Page, STM32_A_Page_Num );
			BootStaFlag |= ( IAP_XMODEMC_FLAG |IAP_XMODEMD_FLAG );
			UpDataA.XmodemTimer = 0;
			UpDataA.XmodemNB = 0;
				
		}

标志位置一后,便需要将串口的输入数据写入到A区了。

首先判断标志位
    判断传入的数据是否位133位,且首位是否为xmode协议的帧头0x01
        标志位清零
        校验位计算
        计算的校验位与 Xmode协议中[131][132]的校验位进行判断
            将数据帧【3】-【130】匹配到 UpDataA.UpDataBuff【( (UpDataA.XmodemNB )%( STM32_Page_Size /128) )  *128】中
                (UpDataA.XmodemNB )%( STM32_Page_Size /128)*128如何理解:每次传输完一帧数据后XmodemNB会加一,一帧协议有128位数,
                STM32_Page_Size大小为1024,1024/128=8,(UpDataA.XmodemNB )%( STM32_Page_Size /128)*128的作用便是将每一帧数据放入他该放的位置
                比如,第0帧数据放入  0%8=0*128 ,放入UpDataA.UpDataBuff的【0】-【127】
                      第1帧数据放入  1%8=1*128 ,放入UpDataA.UpDataBuff的【128】-【..】
            判断( ( UpDataA.XmodemNB>5 ) &&  (UpDataA.XmodemNB+1 )%( STM32_Page_Size /128) ==0 )        
                //为何UpDataA.XmodemNB+1 当XmodemNB为7的时候,已经读入了最后一帧数据,所以我们需要+1,让他为八,否则会多读一帧数据,将第一帧数据覆盖。
                判断是否将数据写入W25Q64  注:这个标志位在下述就会说明
                else    写入内部Flash
           
            XmodemNB++;
            回返“0x06”
    如果数据传输结束,那么上位机会给单片机传回一个0x04,结束位,
       // 这个时候,就需要判断,上次接收完的数据有没有写入到内部Flash中,即判断XmodemNB是不是大于1,如果大于一,就说明新的几帧数据不足1024,还没有写入,这时候我们就需要将这几帧数据写入了
       if( (UpDataA.XmodemNB>1) && (((UpDataA.XmodemNB )%( STM32_Page_Size /128)) !=0 ))
            将数据写入内部Flash
        之后  如果写入的是W25Q64则标志位清零,并将OTA_Info的内容修改,以及写入24C02中,如下代码
        if( BootStaFlag & CMD5_XMODEM_FLAG )	
						{
							BootStaFlag &= ~ CMD5_XMODEM_FLAG ;
							OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ] = UpDataA.XmodemNB*128;
							M24C02_WriteOTAInfo();
							delay_ms(20); 
							BootLoad_Info();
							
						}
    
		else if(  BootStaFlag & IAP_XMODEMD_FLAG )
		{
				if( (datalen==133) && (data[0] == 0x01 ) )
				{
					BootStaFlag &= ~ IAP_XMODEMC_FLAG ;
					
					UpDataA.XmodemCRC = Xmodem_CRC16( &data[3] , 128 );
					
					if( UpDataA.XmodemCRC == ( data[131]*256+data[132] ) )
					{
						
						memcpy( &UpDataA.UpDataBuff[ ( (UpDataA.XmodemNB )%( STM32_Page_Size /128) )  *128 ], &data[3],128 );
							
						//当XmodemNB为8时,128*16 = 2048 即128*8 = 1024;即内部FLASH一页大小时,将数据写入内部flash
						if( ( UpDataA.XmodemNB>5 ) &&  (UpDataA.XmodemNB+1 )%( STM32_Page_Size /128) ==0 )  
						{
									//此处UpDataA.UpDataBuff中有2K即2048B个数据,但是W25Q64一页只能写256B数据,即一次需要i写8页
									if( BootStaFlag & CMD5_XMODEM_FLAG )	
									{
										for(i=0;i<8;i++)
										{
											SPI_FLASH_PageWrite( &UpDataA.UpDataBuff[i*256], ( (( (UpDataA.XmodemNB+1)/16 -1)*8+i)*256 ) + UpDataA.W25Q64_BlockNum*64*1024, 256 );
										}
									}
									else
										STM32_WriteFlash( STM32_A_Saddr + ( (UpDataA.XmodemNB /  (STM32_Page_Size /128)) )*STM32_Page_Size  , (uint32_t *)UpDataA.UpDataBuff  , STM32_Page_Size );
						}
						UpDataA.XmodemNB ++;
						printf("\x06");
					}
					else
					{
						printf("\x15");
					}
				}
					
				if( (datalen==1) && (data[0] == 0x04 ) )
				{
						printf("\x06");
						if( (UpDataA.XmodemNB>1) && (((UpDataA.XmodemNB )%( STM32_Page_Size /128)) !=0 ))
						{
							if( BootStaFlag & CMD5_XMODEM_FLAG )	
							{
								for(i=0;i<8;i++)
								{
									SPI_FLASH_PageWrite( &UpDataA.UpDataBuff[i*256], ( (((UpDataA.XmodemNB+1)/16 )*8+i)*256 ) + UpDataA.W25Q64_BlockNum*64*1024, 256 );
								}
							}
							else
								STM32_WriteFlash( STM32_A_Saddr + ( (UpDataA.XmodemNB /  (STM32_Page_Size /128)) )*STM32_Page_Size  , (uint32_t *)UpDataA.UpDataBuff  , (UpDataA.XmodemNB %( STM32_Page_Size /128))*128 );

						}
						BootStaFlag &= ~ IAP_XMODEMD_FLAG ;
						
						if( BootStaFlag & CMD5_XMODEM_FLAG )	
						{
							BootStaFlag &= ~ CMD5_XMODEM_FLAG ;
							OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ] = UpDataA.XmodemNB*128;
							M24C02_WriteOTAInfo();
							delay_ms(20); 
							BootLoad_Info();
							
						}
						else
						{
							printf("重启\r\n");	
							NVIC_SystemReset();
						}
						
						
				}
			
		}

   功能二,串口将二进制文件写入到W25Q64中,实现前述所说的在不同的块中,存入不同的代码

在代码写入W25Q64时,首先我们传入的第一个数据是 块号 ,即1-64
    判断数据长度是否为1,即是否为块号,(仅为串口传输,并非Xmode协议) 且因为我们的FirLen长度为11,0号存储的是OTA的长度,所以在这里可用的FirLen仅为9块。
        将Xmode协议所用到的标志位置一
        BootStaFlag |= ( IAP_XMODEMC_FLAG | CMD5_XMODEM_FLAG | IAP_XMODEMD_FLAG );
            在上述IPA下载程序到A区是,有个判断是否写入W25Q64,在那时的标志位就是在这里置一的,
            也就是,写入W25Q64的代码其实是在上述我们已经写完,
            只不过这里是吧当时所需要的一个标志位给置一了,也就是打开了上述写入W25Q64的功能
    
    
if(  BootStaFlag & CMD_5_FLAG )
		{
				if( datalen == 1 )
				{
					if( ( data[0]>=0x31 )  && ( data[0]<=0x39) )   //因为发送的数据是ASCII码,而0为0x30,所以这里判断的是否大于0x31
					{
							UpDataA.W25Q64_BlockNum = data[0]-0x30;
							BootStaFlag |= ( IAP_XMODEMC_FLAG | CMD5_XMODEM_FLAG | IAP_XMODEMD_FLAG );
							UpDataA.XmodemTimer = 0;
							UpDataA.XmodemNB = 0;
						
							OTA_Info.FireLen[ UpDataA.W25Q64_BlockNum ] = 0 ;
							W25Q64_Erase64K( UpDataA.W25Q64_BlockNum );
						
							printf( "通过Xmodem协议,向外部Flash第%d个块写入程序,请使用.bin文件格式 ", UpDataA.W25Q64_BlockNum );
							BootStaFlag &= ~CMD_5_FLAG;
						
					}
					else
							printf("编号错误\r\n");
				}
				else 
					printf("数据长度错误\r\n");

		}

注:可能有疑惑,为什么上述功能判断的时候,我们判断的时候并不是 0X3xx,这样的格式,而这里却这样考虑了?

        if( (datalen ==1)&&( data[0]== '5') )     前文功能判断

        if( ( data[0]>=0x31 )  && ( data[0]<=0x39) )  本节块号判断

        可以看到二者的区别,前文是'5'这样的格式,它属于我们判断的是ASCII是否为5,也可以改为 data[0]==0x35),在前文我们看的时候不太方便,所以选用ASCII判断的方式'5'

        而这里,这个1-9,我们之后是要使用这个数字了,我们也可以使用 if( ( data[0]>=‘1’ )  && ( data[0]<=‘9’) ) ,但是这个1-9,它实际上是0X31-0X39,作为块号他是不合格的,需要减去0x30,之后的数字才是一个合格的 “块号”,为了防止遗忘,所以选用0X3xx,来进行判断

8:设置版本号

在这里,设置版本号格式为:VER-1.0.0-2023/02/20-12:00

使用sscanf()函数,这个函数的功能是匹配,意思是,我们输入的版本号格式,必须为VER-1.0.0-2023/02/20-12:00这种格式,如果格式不一样,则无法匹配,
使用判断语句的话        如果不是这种格式,那么就会显示错误

sscanf( (char *) data,"VER-%d.%d.%d-%d/%d/%d-%d:%d" , &temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp) == 8)

if( BootStaFlag & SET_VERSION_FLAG )
			{
				if( datalen == 26 )
				{
						if( sscanf( (char *) data,"VER-%d.%d.%d-%d/%d/%d-%d:%d" , &temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp) == 8)
						{
							memset( OTA_Info.OTA_ver,0,32 );
							memcpy( OTA_Info.OTA_ver,data,26 );
							//保存到24C02之中
							M24C02_WriteOTAInfo();
							printf("版本号正确\r\n");	//版本号格式 VER-1.0.0-2023/02/20-12:00
							
							BootStaFlag &=~ SET_VERSION_FLAG;
							BootLoad_Info();
							
						}
						else
								printf("版本号格式错误\r\n");
					
				}
				else
				{
					printf("版本号长度错误\r\n");
				}
		}

9:使用外部Flash的程序

		else if( (datalen ==1)&&( data[0]== '6') )
		{
			BootStaFlag |= CMD_6_FLAG;
			printf("使用外部Flash的程序,输入需要使用的块编号(1-10):\r\n");
			
		}

这段需要结合main.C中的函数来一起使用,在这里,我们选择了要使用的W25Q64中的块号,并将UpDataA.W25Q64_BlockNum设置为选择的块号,而且将UpData_A_Flag置位。

 if(  BootStaFlag & CMD_6_FLAG )
		{
				if( datalen == 1 )
				{
					if( ( data[0]>=0x31 )  && ( data[0]<=0x39) )
					{
						UpDataA.W25Q64_BlockNum = data[0]-0x30;
							BootStaFlag |= UpData_A_Flag;
							BootStaFlag &=~ CMD_6_FLAG;
					
					}
					else
							printf("编号错误\r\n");
				}
				else 
					printf("数据长度错误\r\n");
		}

在UpData_A_Flag置位后,我们将会调用章节4中的 更新A区。

小结

至此,我们的功能已经全部完结,我们一共实现了交互式命令行的全部功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值