STM32 的内部闪存地址起始于0x08000000。一般情况下,程序从此地址开始写入。
由于STM32 内部是通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004。
中断代码响应过程简单表述如下:
1、当中断来临,STM32 的内部硬件机制亦会自动将PC 指针定到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序;
2、STM32 在复位后,先从0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序;
3、在复位中断服务程序执行完之后,会跳转到我们的main 函数;
4、在main 函数执行过程中,如果收到中断请求(发生重中断),此STM32 强制将PC 指针指回中断向量表处;
5、根据中断源进入相应的中断服务程序;在执行完中断服务程序以后,程序再次返回main 函数执行。
STM32管理升级的代码段习惯沿用arm的称呼,将其称为bootlader,或者IAP程序。这段代码是专门用于升级的(本身也可以把这段代码融入到正式程序中(APP程序),但是为了方便,本文将IAP和APP分开使用,防止代码交叉,升级失败处理困难)。
加入IAP程序后,执行过程如下:
1、STM32 复位后,还是从0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序;
2、在运行完复位中断服务程序之后跳转到bootlader 的main 函数;
3、在执行完bootlader以后,跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main 函数。
如此,远程升级程序必须满足两个要求:
1、 新程序必须在bootader 程序之后的某个偏移量为x 的地址开始。
2、 必须将新程序的中断向量表相应的移动,移动的偏移量为x。
IAP程序实现的功能(本质上该程序也是一段单片机可运行的代码,其中也有main()函数):
1、判断是否需要升级,如果升级,进入升级流程,如果不升级,跳转到APP执行;
2、使用大数组,接收升级文件数据,每一次接收都应该有数据校验;
3、将现在APP区间的代码数据备份到flash;
4、把大数组中的数据写到APP的地址中;
5、防止升级失败,应该设置APP无法正常运行的情况下,重新恢复原程序;
6、在APP代码中添加中断向量表偏移。
下面贴出代码:
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_PSP(uint32_t addr)
{
MSR PSP, r0 //set process Stack value
BX r14
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
CPSIE I
BX LR
}
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
CPSID I
BX LR
}
typedef void (*iapfun)(void);//定义一个函数类型的参数.
iapfun jump2app;
//跳转到应用程序段
//appxaddr:用户代码起始地址.
uint8_t iap_load_app(uint32_t appxaddr)
{
if(((*(__IO uint32_t*)appxaddr)&0x2FF00000)==0x20000000)//检查栈顶地址是否合法.
{
printf("jump to APP...\r\n");
jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);//用户代码区第二个字为程序开始地址(复位地址)
// MSR_PSP(*(__IO uint32_t*)appxaddr);
// __set_CONTROL(0);
MSR_MSP(*(__IO uint32_t*)appxaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
INTX_DISABLE();//关闭全部中断(但是不包括fault和NMI中断)
//__set_PRIMASK(1);
// __disable_irq();
__disable_fault_irq ();
jump2app();//跳转到APP
return 0;
}
else
{
printf("jump error...\r\n");
return 1;
}
}
uint8_t getAppNumber(uint64_t *num)//读取升级标志位,并且擦除该页
{
uint8_t state = 0;
uint64_t tmpbuf[4];
for(int i=0;i<4;i++)
{
tmpbuf[i] = *(__IO uint64_t*)(ADDR_UPDATA_START+i*8);
printf("tmpbuf[%d]:%lld\r\n",i,tmpbuf[i]);
}
if((tmpbuf[0] == (~0x0))&&(tmpbuf[1] == (~0x0)))
{
printf("升级标志位中未读取到数据,亦不升级!!!\r\n");
*num = 0;
state = 0;
// return 0;
}
else if(tmpbuf[0] == 1)//tmpbuf[0]是升级标志位,tmpbuf[1]是iap重启次数
{
state = 1;
}
else
state = 0;
printf("升级标志位:\t%d(0不升级,1升级)\r\n",state);
*num = tmpbuf[1] + 1;
tmpbuf[0] = 0;
tmpbuf[1] = *num;
printf("IAP跳转次数:\t%lld\r\n",*num);
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
eraseFlash(ADDR_UPDATA_START,ADDR_UPDATA_END);
HAL_FLASH_Unlock();
//tmpbuf[0]是升级标志位,tmpbuf[1]是iap重启次数
for(int i=0;i<2;i++)
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ADDR_UPDATA_START+8*i, (uint64_t)tmpbuf[i]) != HAL_OK)
{
HAL_FLASH_Lock();
return 2;
}
}
HAL_FLASH_Lock();
return state;
}
//获取当前APP代码的数据量,为备份该数据做准备
uint32_t getValidByte(uint32_t start_addr,uint32_t end_addr)//获取地址中的数据量,单位双字节
{
uint32_t addrNum = end_addr - start_addr + 1;
addrNum = addrNum / 8 * 8;
uint64_t data = 0;
do
{
addrNum -= 8;
data = *(__IO uint64_t*)(start_addr + addrNum);
}while((data==(~0x0))&&(addrNum>0));
return addrNum/8*8+8;
}
//擦除flash
uint8_t eraseUpdateFlash(uint32_t start_addr,uint32_t end_addr)
{
printf("开始擦除FLASH...");
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
eraseFlash(start_addr,end_addr);
return 0;
}
//获取已经写入大数组的数据条数,该数组是一个二维数组,返回值是当前存入的数据条数
uint16_t getLine(const uint8_t (*buf)[COLCOUNT],uint32_t length)
{
int i = 0;
for(i=0;i
main()函数中在最开始加入如下代码:
SCB->VTOR = FLASH_BASE | 0x4000;//0x4000是偏移地址
__enable_fault_irq();
INTX_ENABLE();
项目地址:https://download.csdn.net/download/sehanlingfeng/12625369