> 技术文档 > STM32OTA固件升级(五)_stm32 ota

STM32OTA固件升级(五)_stm32 ota


代码和软件下载

通过网盘分享的文件: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 0xAABB1122typedef 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和EEPROMprintf(\"没有检测到串行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一页大小时,将数据写入内部flashif( ( 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;i1) && (((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 );}}elseSTM32_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;}elseprintf(\"编号错误\\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:00BootStaFlag &=~ SET_VERSION_FLAG;BootLoad_Info();}elseprintf(\"版本号格式错误\\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;}elseprintf(\"编号错误\\r\\n\");}else printf(\"数据长度错误\\r\\n\");}

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

小结

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