本系列将从升级流程、boot代码编写、APP代码编写以及固件打包来介绍,硬件选用STM32F407ZGT6(手里只有),来完成这系列教程。
前言
开发STM32固件升级并编写Bootloader时,需要注意以下几个关键点:
-
熟悉硬件和数据手册:在开发过程中,确保充分理解STM32微控制器的特性和功能。阅读相关数据手册,了解其内存布局、外设接口以及其他重要信息。
-
选择合适的通信接口:根据项目需求选择合适的通信接口进行固件升级,如串口、I2C、SPI、USB等。确保所选接口可以与外部设备(如PC)正常通信。(后续会使用CAN UART)
-
定义固件升级协议:设计一个简单且可靠的通信协议,用于在Bootloader和外部设备之间传输数据。协议应包括命令、地址、数据长度、数据包校验等信息。
-
保留足够的Bootloader空间:为Bootloader预留足够的程序存储空间。Bootloader的大小可能会随着功能的增加而增大,因此预留一定的余量非常重要。
-
安全和鲁棒性:确保Bootloader代码具有良好的异常处理和错误检测能力。避免因意外情况导致的设备损坏或不可恢复状态。
-
可扩展性:在设计Bootloader时考虑到未来可能的功能扩展。保持代码结构清晰,易于维护和升级。
-
测试与验证:在实际硬件上对Bootloader进行充分测试,确保其下载、擦除、写入等操作的正确性和稳定性。
遵循以上关键点,在开发过程中保持耐心和细致,能有效编写出一个可靠、高效的STM32 Bootloader。
编写Bootloader程序
在编写前我需要确定几个地方:
-
bootloader
- 确定bootloader存放地址 0x08000000
- 配置bootloader中断向量表
- 实现串口或USB等通信接口 UART
- 编写flash擦除、编程函数
- 确定应用程序存放地址 0x08000000 + Boot_size + PARAM_SIZE
- 跳转到应用程序入口 跳转指令
-
注意事项
- 确认芯片型号和数据手册,了解芯片的Flash大小和布局
- 确定应用程序和bootloader的存放地址
- 确定bootloader的触发方式,如按键触发、超时触发等
- bootloader需要配置中断向量表,以便跳转到应用程序时正确执行
- bootloader需要实现串口或USB等通信接口,以便与上位机进行通信
- bootloader需要编写flash擦除、编程函数,以便将应用程序下载到Flash中
- bootloader需要检查应用程序的合法性,如校验和、签名等
- bootloader需要跳转到应用程序入口,启动应用程序
在MCU中,bootloader主要作用引导进入APP1程序,检测升级标注位是否需要将备份APP2覆盖到APP1中(升级新程序)或者接收升级包进行升级(可以是CAN、UART通信或者读取SD卡获取升级包)。bootloader程序尽量保持简洁,不需要用到资源统统去掉(很容易出现问题,往往初始化的外设资源,最好都去初始化(保留原来的状态)),保证小巧、稳定和扩展性。
需要实现的功能:flash擦除接口和通信接口,主要这两个,还需要添加状态和标注位。
Flash读写和擦除
Flash 功能接口
u32 STMFLASH_ReadWord(u32 faddr)
{
return *(vu32*)faddr;
}
uint16_t STMFLASH_GetFlashSector(u32 addr)
{
if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
return FLASH_Sector_11;
}
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
{
FLASH_Status status = FLASH_COMPLETE;
u32 addrx=0;
u32 endaddr=0;
if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
FLASH_Unlock(); //解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
addrx=WriteAddr; //写入的起始地址
endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
if(status==FLASH_COMPLETE)
{
while(WriteAddr<endaddr)//写数据
{
if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据
{
break; //写入异常
}
WriteAddr+=4;
pBuffer++;
}
}
FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
FLASH_Lock();//上锁
}
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
{
u32 i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
ReadAddr+=4;//偏移4个字节.
}
}
实现在SD卡寻找APP程序,进行升级。
FIL fnew; /* 文件对象 */
FRESULT res_sd = FR_OK; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[512]={0}; /* 读缓冲区 */
u8 checknum = 0;
int writeSum = 0;
while(SD_Init())
{
}
exfuns_init();
f_mount(fs[0],"0:",1);
Flash_Erase(APP1_ADDRESS);
while(1)
{
t++;
res_sd = f_open(&fnew, "0:APP2.bin", FA_OPEN_EXISTING | FA_READ);
if(res_sd == FR_OK)//有升级文件
{
printf("open bootloader\r\n");
fnum = 1;
do
{
res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(res_sd == FR_OK)
{
printf("##");
if(fnum!=0)
STMFLASH_Write(APP1_ADDRESS+writeSum,(u32*)ReadBuffer,fnum/4);
writeSum += fnum;
for(int i = 0 ; i < fnum;i++)
{
checknum ^= ReadBuffer[i];
printf("0x%2x ",ReadBuffer[i]);
}
printf("\r\n");
memset(ReadBuffer,0,sizeof(ReadBuffer));
}
else
{
printf("read file failth(%d)\n", res_sd);
}
}while(fnum > 0);
/* 不再读,关闭文件 */
f_close(&fnew);
u8 readBuf[4];
u8 readCheckCrc = 0;
for(int i = 0;i < writeSum ; i++)
{
STMFLASH_Read(APP1_ADDRESS+i*4,(u32*)readBuf,1);
for(int j = 0;j<4;j++)
readCheckCrc ^= readBuf[j];
}
printf("boot jump app\r\n");
if(readCheckCrc == checknum && readCheckCrc!=0)
{
printf("check sum success jump APP1\r\n");
//跳转
jump_to_app();
}
else
{
printf("check sum Failth\r\n");
}
while(1);
}
else
{
printf("not found bootloader \r\n");
}
LED0=!LED0; //状态灯 bootloader状态
}
上面还需要进行优化,需要添加一些标志位可以在一个扇区或者在备份寄存器标记,来做升级标记。
跳转指令 重点
#define APP1_ADDRESS 0x8020000
__asm void start_app(uint32_t r0_msp, uint32_t r1_pc)
{
MOV SP, R0 //R0的数值 其实就是参数r0_msp
BX R1 //R1 其实就是r1_pc
}
void jump_to_app(void)
{
start_app((*(uint32_t *)(APP1_ADDRESS)),(*(uint32_t *)(APP1_ADDRESS + 4)));
}
在实际操作过程中,遇到跳转,没能正常运行APP。
我进行分析,排除问题,把问题范围缩小,可能会出现的问题:1、Flash读写接口有问题 2、FAT32文件系统读取数据有问题。
看前面的代码,我都加了日志信息输出,把文件系统读到的数据输出,和真实的bin文件内容对比,对比问题是正确,说明问题出现在Flash擦除。
看下面的截图,左边是都是FF没做写入前做擦除,右边是擦除后再写,是写入成功的。