代码和软件下载
通过网盘分享的文件: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区。
小结
至此,我们的功能已经全部完结,我们一共实现了交互式命令行的全部功能。