文章目录
概要
Bootloader根据不同总线发送到单片机(stm32f407)的Xmodem帧数据进行解析并校验,然后写入FLASH缓存区,接收完整个BIN文件后,将FLASH缓存区部分的代码copy到执行区。设置FALSH缓存区的意义在于防止在更新过程中意外掉电导致更新失败而造成设备变砖。
整体架构流程
1.加载各种驱动配置,包括UART、IIC、TIMER、CAN、GPIO、CRC等等
2.IIC读取更新标志位
3.根据更新标志位的不同,执行不同的阶段流程,共有三个阶段流程:
3.1 更新:意为需要更新,准备进入 准备接收带有Xmodem协议的BIN程序 的状态
3.2 缓存完成:意为此前已将更新的程序下载到FLASH缓存区,准备进入 将缓存区的数据copy到执行区域 的状态
3.3 正常启动: 意为无需更新或已更新完成,准备进入 正常跳转到执行区域 的状态
例如:当前BL检测到更新标志位为缓存完成,那么会将缓存区的数据copy到执行区域,然后修改更新标志位的值为正常启动,reset重启后检测到更新标志的值为正常启动,接着正常跳转到执行区域。
4.(以正常更新的完全流程描述)总线中断函数接收数据存入IAP接收缓冲区,进入上面3.1的状态后不断的读IAP缓冲区的数据直至读到Xmodem的报头,然后读取报头后的132个字节,加上第一个字节的报头为一帧。
5.将一帧数据解析,判断帧序号及取反值,计算128字节数据的CRC校验比对,无误后写入FLASH缓存区。
6.等待全部程序接收完毕后,最后一帧会接收到上位机发送来的整个程序的CRC32位校验值,然后将这个校验值写入eeprom中。
7.读取缓存区中的数据计算其整体的CRC32校验值并与eeprom中的校验值进行比较,正确则会将缓存区的数据copy到执行区域,否则更新失败跳转执行区域。
8.copy到执行区域的数据会再次进行一次CRC32完整性校验,并读取eeprom中的校验值与之比对,正确则表示更新成功,否则表示copy失败会再次copy一次。
主框架
main函数
//更新过程分为三个阶段:
//1.无需更新/已更新 -- NO_FLAG
//2.缓存完成 -- DOWNLOAD_FLAG
//3.需要更新 -- UPDATE_FLAG
int main()
{
uint8_t update_flag = 0;
//加载驱动配置
BSP_Config();
//读更新标志位
I2C_Read(I2C1,ADDR_24LC02,UPDATE_ADDR,&update_flag,1);
//读更新标志位--要进行更新
if(update_flag == UPDATE_FLAG)
{
//将标志位写“未更新/无需更新”等更新完毕后才会修改更新标志位,否则判定为更新失败
I2C_WriteOneByte(I2C1,ADDR_24LC02,UPDATE_ADDR,(uint8_t)NO_FLAG);
//开始更新程序,(选择接收方式为CAN总线,此时接收的程序文件下载到flash缓存区部分)
if( !IAP_UpdateProgram(CAN_Mode))
{
//CRC校验程序完整性,(接收程序文件时计算并写入eeprom,再读缓存区中的数据并计算CRC校验值与之比对)
if(!IAP_ProgramIntegrityCheck((uint32_t)Application_Buff_ADDR))
{
//CRC相同,缓存区下载完成,更新标志位为“缓存完成”
I2C_WriteOneByte(I2C1, ADDR_24LC02, UPDATE_ADDR, (uint8_t)DOWNLOAD_FLAG);
//将缓存区数据拷贝到APP区域,完成后设置标志位为“已更新”
LOOP: if(!IAP_FlashBuffToApplication((uint32_t)Application_ADDR, (uint32_t)Application_Buff_ADDR))
{
I2C_WriteOneByte(I2C1,ADDR_24LC02,UPDATE_ADDR,(uint8_t)NO_FLAG);
//加入一点延时防止IIC没有写入完成就重启
Delay_ms(100);
NVIC_SystemReset();
}
else
{
goto LOOP;
}
}
else //CRC不同,更新失败,跳转到APP
{
JumpToApp();
}
}
else //更新程序超时,(此时更新标志位为"未更新/无需更新")
{
//跳转到APP
JumpToApp();
}
}
else if(update_flag == DOWNLOAD_FLAG) //读更新标志位--已经缓存完成
{
//跳转到《将缓存区数据拷贝到APP区域》
goto LOOP;
}
else //否则跳转APP执行区域
{
JumpToApp();
}
while(1)
{
LED3_ON();
Delay_ms(200);
LED3_OFF();
Delay_ms(200);
}
}
main 函数 2025/1/3更新
自定义标志位读写方式,改成状态机设计更简洁明了
int main()
{
uint32_t update_flag = 0;
uint8_t mode = 0;
uint8_t step = 0;
//加载驱动配置
BSP_Config();
printf("Bootloader Version : V 1.0.0\r\n");
FLASH_Unlock();
//在此处读取标志位
/*
your code
*/
while(1)
{
switch(step)
{
case 0:
if(update_flag == UPDATE_FLAG)
step = 1;
else if(update_flag == DOWNLOAD_FLAG)
step = 3;
else
step = 0xff;
break;
case 1:
//在此处初始化标志位到 NO_FLAG
/*
your code
*/
if(IAP_ProgramBuffProcess(CAN_Mode))
step = 0xff;
else
step = 2;
break;
case 2:
if(IAP_ProgramIntegrityCheck((uint32_t)__Buffer_Addr))
step = 0xff;
else
{
//在此处初始化标志位到 DOWNLOAD_FLAG
/*
your code
*/
step = 3;
}
break;
case 3:
if(IAP_DataBufferToApp((uint32_t)__App_Addr, (uint32_t)__Buffer_Addr))
step = 3;
else
step = 4;
break;
case 4:
//在此处初始化标志位到 NO_FLAG
/*
your code
*/
FLASH_Lock();
Delay_ms(100);
NVIC_SystemReset();
break;
default:
FLASH_Lock();
Delay_ms(100);
JumpToApp();
break;
}
printf("step:%d\r\n",step);
}
}
API
JumpToApp
函数名称:static void JumpToApp(void)
功能:跳转到执行区域
参数:无
返回值:无
static void JumpToApp(void)
{
uint32_t i=0;
void (*SysMemBootJump)(void); /* 声明一个函数指针 */
__IO uint32_t AppAddr = Application_ADDR; /* STM32F4的系统BootLoader地址 */
/* 设置所有时钟到默认状态,使用HSI时钟 */
RCC_DeInit();
/* 关闭全局中断 */
DISABLE_INT();
/* 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
/* 使能全局中断 */
//ENABLE_INT(); //建议将此语句加入到APP中防止跳转被中断打断
/* 跳转到系统APP,首地址是MSP,地址+4是复位中断服务程序地址 */
SysMemBootJump = (void (*)(void)) (*((uint32_t *) (AppAddr + 4)));
/* 设置主堆栈指针 */
__set_MSP(*(uint32_t *)AppAddr);
// /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
// __set_CONTROL(0);
/* 跳转到系统BootLoader */
SysMemBootJump();
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
}
}
IAP_UpdateProgram
函数名称:uint8_t IAP_UpdateProgram(IAP_Mode_t mode)
功能:根据不同模式进行数据接收并将数据写入FLASH缓存区
参数:IAP_Mode_t mode -- 不同总线模式
返回值:0 -- 正常结束
1 -- 接收超时
uint8_t IAP_UpdateProgram(IAP_Mode_t mode)
{
uint8_t RtnACK = 0;
uint8_t data[128] = {0};
uint16_t pBuffer = 0;
uint8_t i = 0;
FLASH_Unlock();//flash解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
FLASH_EraseSector(FLASH_Sector_6,VoltageRange_3); //擦除0x0804 0000
FLASH_EraseSector(FLASH_Sector_7,VoltageRange_3); //擦除0x0806 0000
FLASH_DataCacheCmd(ENABLE);//FLASH擦除结束,开启数据缓存
TIM_Cmd(TIM2,ENABLE); //使能定时器2
Xmodem.Xmodem_PacketCMP = 1;
IAP_SendSignalMode(mode,(uint8_t *)START_FRAME);
Delay_ms(3000);
IAP_SendSignalMode(mode,(uint8_t *)START_FRAME);
while(1)
{
RtnACK = Xmodem_PacketAnalyze(data);
if(RtnACK == ACK)
{
for(i = 0;i < 64;i++)
{
pBuffer= ((uint16_t)(data[i*2])) | ((uint16_t)(data[i*2+1] << 8));
IAP_FlashProgramdata(pBuffer);
}
IAP_SendSignalMode(mode,&RtnACK); //发送应答信号
LED2_Toggle();
Xmodem.Flash_PacketCNT++; //写入FLASH的Xmodem帧数+1
memset(data,0,128);
}
else if(RtnACK == CRC32)
{
RtnACK = ACK;
IAP_SendSignalMode(mode,&RtnACK); //发送应答信号
}
else if(RtnACK == EOT)
{
RtnACK = ACK;
FLASH_Lock();//flash锁
TIM_Cmd(TIM2,DISABLE); //失能定时器2
IAP_SendSignalMode(mode,&RtnACK); //发送应答信号
// printf("User APP Receive Completed!!!\r\n");
// printf("Code Length:%dBytes\tXmodem_packcnt:%d\tFlash_PacketCNT:%d\r\n",\
// UART1_RX_CNT,Xmodem.Xmodem_PacketCNT,Xmodem.Flash_PacketCNT);
return 0;//结束函数
}
else if (RtnACK == NAK)
{
IAP_SendSignalMode(mode,&RtnACK); //发送应答信号
}
else
IAP_SendSignalMode(mode,&RtnACK); //发送应答信号
RtnACK = 0;
if(rcvTimeout > 10)
{
FLASH_Lock();//flash锁
TIM_Cmd(TIM2,DISABLE); //失能定时器2
return 1;
}
}
}
Xmodem_PacketAnalyze
函数名称:uint8_t Xmodem_PacketAnalyze(unsigned char *buff)
功能:Xmodem解析函数,读取缓存区中的数据,识别Xmodem报头,解析出128字节数据放入形参中
参数:unsigned char *buff -- 用来传递BIN文件的128字节数据
返回值: EOT 0x04 //发送结束标志
ACK 0x06 //应答标志
NAK 0x15 //非应答标志
CRC32 0x43 //使用CRC校验标志
uint8_t Xmodem_PacketAnalyze(unsigned char *buff)
{
uint8_t data = 0;
uint16_t crc_value = 0;
uint32_t f_size = 0;
uint8_t i = 0;
uint8_t crc_buff[4] = {0};
while(!IAP_BufferRead(&data) && (rcvTimeout < 10));
if(data == SOH)
{
Xmodem.Xmodem_Packet[0] = data;
Xmodem.Xmodem_PacketCNT++; //接收到的Xmodem帧数++
for(i = 1; i < 133; i++)
{
while(!IAP_BufferRead(&data));
Xmodem.Xmodem_Packet[i] = data;
}
if((Xmodem.Xmodem_Packet[1] == Xmodem.Xmodem_PacketCMP) && (Xmodem.Xmodem_Packet[2] == (uint8_t)~Xmodem.Xmodem_PacketCMP))
{
for(i = 0;i < 128;i++)
{
buff[i] = Xmodem.Xmodem_Packet[i+3];
}
crc_value = calculate(buff,128); //计算接收到的数据的CRC校验值
Xmodem.Xmodem_CRC16 = (((uint16_t)Xmodem.Xmodem_Packet[131]) << 8) | ((uint16_t)(Xmodem.Xmodem_Packet[132])); //获取帧尾CRC校验值
if(Xmodem.Xmodem_CRC16 == crc_value)
{
Xmodem.Xmodem_PacketCMP++; //Xmodem帧序列++
memset(Xmodem.Xmodem_Packet,0,133);
return ACK;
}
else //CRC校验错误
{
// printf("\r\n单片机计算的CRC:0x%04x\r\n",crc_value);
return NAK;
}
}
else if((Xmodem.Xmodem_Packet[1] == (Xmodem.Xmodem_PacketCMP-1)) && (Xmodem.Xmodem_Packet[2] == (uint8_t)~(Xmodem.Xmodem_PacketCMP-1) )) //上位机没有接收到ACK信号发送过来上一个的重复帧
{
return ACK;
}
else
{
// printf("\r\nXmodem_Packet[1][2]:0x%02x\t0x%02x\r\nXmodem.Xmodem_PacketCMP:0x%02x\t0x%02x\r\n",\
// Xmodem.Xmodem_Packet[1],Xmodem.Xmodem_Packet[2],Xmodem.Xmodem_PacketCMP,(uint8_t)~Xmodem.Xmodem_PacketCMP);
return NAK;
}
}
else if(data == EOT)
{
// printf("接收到传输完毕信号\r\n");
f_size = Xmodem.Flash_PacketCNT*128;
I2C_Write(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)f_size,4); //记录接收到多少个字节
// printf("共接收到%d个字节\r\n",f_size);
return EOT;
}
else if(data == CRC32)
{
for(i = 0; i < 4; i++)
{
while(!IAP_BufferRead(&data));
crc_buff[i] = data;
}
CRC_Compare.Program = (uint32_t)crc_buff[0] | (uint32_t)(crc_buff[1]<<8) | (uint32_t)(crc_buff[2]<<16) | (uint32_t)(crc_buff[3]<<24);
// printf("CRC From GET Program Vaule : 0x%08x\r\n",CRC_Compare.Program);
I2C_Write(I2C1,ADDR_24LC02,PROGRAMCRC_ADDR,(uint8_t *)CRC_Compare.Program,4); //记录整个程序CRC校验值
return CRC32;
}
return 0;
}
IAP_ProgramIntegrityCheck
函数名称:uint8_t IAP_ProgramIntegrityCheck(uint32_t addr)
功能:程序完整性校验,首先会获取eeprom中接收程序时计算的CRC值和字节数,接着读取缓存区中的数据并计算CRC,进行比较
参数:uint32_t addr -- 缓存区起始地址
返回值:0 -- CRC相同
1 -- CRC不同
uint8_t IAP_ProgramIntegrityCheck(uint32_t addr)
{
static uint32_t flash_buf[32];
uint32_t flash_addr = addr;
uint32_t flash_len = 0x80; //128
uint32_t Program_CRC = 0;
uint32_t packet_cnt = 0;
uint8_t i = 0;
CRC_ResetDR(); //每次使用CRC寄存器前应当重置一下寄存器,不然会与上次计算值重叠,读寄存器不会自动清除,且重置后会恢复缺省--大端模式
FLASH_Unlock();
I2C_Read(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)packet_cnt,4);
I2C_Read(I2C1,ADDR_24LC02,PROGRAMCRC_ADDR,(uint8_t *)Program_CRC,4); //这里注意大小端顺序
// printf("cnt : 0x%08x \r\n",packet_cnt);
packet_cnt /= 128;
while(packet_cnt--)
{
memcpy(flash_buf, (uint32_t *)flash_addr, flash_len); //这里试下能不能读出来
for(i = 0;i < 32;i++)
{
CRC_CalcCRC(flash_buf[i]);
}
flash_addr += flash_len;
}
CRC_Compare.Flash = CRC_GetCRC();
FLASH_Lock();
if(CRC_Compare.Flash == Program_CRC)
return 0;
else
// printf("********************0x%08x***************\r\n",Program_CRC);
return 1;
}
IAP_FlashBuffToApplication
函数名称:uint8_t IAP_FlashBuffToApplication(uint32_t addr1,uint32_t addr2)
功能:将FLASH缓存区中的数据copy到执行区域
参数:uint32_t addr1 -- 执行区起始地址
uint32_t addr2 -- 缓存区起始地址
返回值:0 -- CRC相同
1 -- CRC不同
uint8_t IAP_FlashBuffToApplication(uint32_t addr1,uint32_t addr2)
{
uint32_t __app_addr = addr1;
uint32_t __buf_addr = addr2;
uint32_t buff = 0;
uint32_t size = 0;
uint32_t i = 0;
I2C_Read(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)size,4);
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_2,VoltageRange_3); //擦除0x0804 0000
FLASH_EraseSector(FLASH_Sector_3,VoltageRange_3);
FLASH_EraseSector(FLASH_Sector_4,VoltageRange_3);
FLASH_EraseSector(FLASH_Sector_5,VoltageRange_3); //0x0803 FFFF
// printf("固件缓存烧录中...\r\n");
for(i = 0;i < size/4 ; i++)
{
memcpy(&buff,(uint32_t *)__buf_addr,0x4);
FLASH_ProgramWord(__app_addr,buff);
__app_addr += 4;
__buf_addr += 4;
}
FLASH_Lock();
// printf("程序完整性校验中...\r\n");
if(!IAP_ProgramIntegrityCheck((uint32_t)Application_Buff_ADDR))
return 0;
else
return 1;
}
IAP_SendSignalMode
函数名称:void IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)
功能:根据不同模式发送不同信号到上位机
参数:IAP_Mode_t mode -- 不同总线
uint8_t *signal -- 信号数据
返回值:无
这里只举了两个例子,其他总线按需求补充即可
void IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)
{
switch((uint8_t)mode)
{
case UART_Mode:
UART1_Send_Data(signal,2);
break;
case IIC_Mode:break;
case SPI_Mode:break;
case CAN_Mode:
CAN1_Send((uint32_t)signal,0);
}
}
IAP_BufferRead
函数名称:uint8_t IAP_BufferRead(uint8_t *data)
功能:读IAP接收缓冲区
参数:uint8_t *data -- 存放读到的数据
返回值:0 -- 无数据可读
1 -- 有数据可读
uint8_t IAP_BufferRead(uint8_t *data)
{
if(Update_Rptr == Update_Wptr)//无数据可读
{
return 0;
}
*data = ProgramBuffer[Update_Rptr++];//读取缓冲区数据
Update_Rptr = Update_Rptr % MAXBUFFER;//保证读位置值不溢出
return 1;
}
IAP_FlashProgramdata
函数名称:void IAP_FlashProgramdata(uint16_t data)
功能:向FLASH中写入数据
参数:uint16_t data -- 要写入FLASH的数据
返回值:无
void IAP_FlashProgramdata(uint16_t data)
{
FLASH_ProgramHalfWord(AppBuffer_Addr,data);
AppBuffer_Addr+=2;
}
IAP_BufferWrite
函数名称:void IAP_BufferWrite(uint8_t data)
功能:写IAP接收缓冲区
参数:uint8_t data -- 要写入缓冲区的数据
返回值:无
void IAP_BufferWrite(uint8_t data)
{
if(Update_Wptr == (Update_Rptr - 1))//缓冲区存满了 当读速度比较慢时,写不能和读同一个地方前一个数据还没读走不能写入。
{
return;//返回
}
ProgramBuffer[Update_Wptr++] = data;
Update_Wptr %= MAXBUFFER; //保证写位置值不溢出
rcvTimeout = 0;
}
全局变量、宏定义、结构体
//*************update.c**************************
#define MAXBUFFER 512
uint8_t ProgramBuffer[MAXBUFFER]; //CAN接收数据缓存区
uint16_t Update_Wptr = 0; //缓存区写指针
uint16_t Update_Rptr = 0; //缓存区读指针
uint32_t AppBuffer_Addr = Application_Buff_ADDR; //用于存放下载缓存区地址
struct Xmodem_t Xmodem;
struct CrcCheck_t CRC_Compare;
extern uint8_t rcvTimeout;
//***************update.h*******************************
#define Application_ADDR 0x08008000 //应用程序地址
#define Application_Buff_ADDR 0x080A0000 //程序缓存地址
#define SOH 0x01 //Xmodem 128字节头标志
#define EOT 0x04 //发送结束标志
#define ACK 0x06 //应答标志
#define NAK 0x15 //非应答标志
#define CRC32 0x43 //使用CRC校验标志
struct Xmodem_t
{
int Xmodem_PacketCNT; //接收到的Xmodem帧数量 -- 只要接收到帧起始信号就会自增1
int Flash_PacketCNT; //写入Flash中的Xmodem帧数 -- 每一帧校验完CRC才会写入自增1
uint8_t Xmodem_PacketCMP; //Xmodem帧序列号比较值 -- 用于比对记录帧是否重复接收,确保帧序正确
uint8_t Xmodem_Packet[133];
uint16_t Xmodem_CRC16;
};
struct CrcCheck_t
{
uint32_t Program; //接收到BIN文件程序的CRC校验值
uint32_t Flash; //写入FLASH后计算的CRC校验值
};
typedef enum Mode_t
{
UART_Mode,
IIC_Mode,
SPI_Mode,
CAN_Mode
}IAP_Mode_t;
//**********************main.h***************************
/* 开关全局中断的宏 */
#define ENABLE_INT() __set_PRIMASK(0) /* 使能全局中断 */
#define DISABLE_INT() __set_PRIMASK(1) /* 禁止全局中断 */
//更新指令帧ID
#define UPDATE_FRAME 0x0ACE
#define START_FRAME 0x000C
//更新指令标志位
#define UPDATE_FLAG 0xAA
#define NO_FLAG 0xFF
#define DOWNLOAD_FLAG 0xDD
//AT24C02地址 0 ~ 0x100
#define UPDATE_ADDR 0x50
#define PROGRAMSIZE_ADDR 0x60 //程序大小存放地址
#define PROGRAMCRC_ADDR 0x64 //程序CRC值存放地址
技术细节
如要使用此设计模式,可直接将IAP_BufferWrite(uint8_t data)函数增加到不同总线的中断接收函数中,并将IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)、IAP_UpdateProgram(IAP_Mode_t mode)中的形参mode设为对应的总线模式即可。
小结
水平尚浅,变量命名有些乱,凑活看吧,后续会继续研究兼容其他总线,精力应该主要放在USB总线上(真的好难呜哇o(╥﹏╥)o)