一、引出(IAP的原理和stm8上实现IAP的问题)
具有IAP功能的单片机,程序可以分为两部分:IAP和APP。APP是用来实现真正功能的程序,而IAP是用来远程重新编程APP的程序。单片机上电时会先执行IAP程序,在IAP中判断APP是否正常,然后再跳转到APP中执行。
这样就会有一个问题,那就是中断向量表的问题。当发生中断时,单片机会去中断向量表中查询中断服务函数的地址,然后才能按照地址跳转到中断服务函数去执行。中断向量表一般都在程序的开头,当把编译好的IAP下载到单片机里,单片机上电执行IAP程序,在执行IAP时发生中断,单片机就会到IAP的开头去查询中断服务函数地址,然后当从IAP跳转到APP后,开始执行APP程序,这个时候发生中断,单片机仍然会到IAP的开头去查询中断服务函数地址,这里就出现问题了。单片机在执行APP时,应该到APP的开头去查询中断服务函数地址才对。
因此需要某种方法通知单片机,当我们执行APP时,要在APP的开头去查询中断服务函数地址。在stm32单片机中,只需要在执行APP程序前加一句:
SCB->VTOR = FLASH_BASE | 0x4000; //0x4000是APP的开始地址
因为stm32有地址偏移寄存器SCB->VTOR,当发生中断时,stm32会去SCB->VTOR读取中断向量表的地址。执行上面的语句后,发生中断时就会到FLASH_BASE | 0x4000地址去查询中断服务函数地址。
而stm8没有地址偏移寄存器,所以我们需要其他的办法来解决。中断向量表实际上就是:跳转指令+地址。当发生中断单片机会去执行中断向量表,也就是执行跳转指令,跳转到目标地址。明白了这个,那我们的方法也就出来了:改写IAP的中断向量表,把它的地址改为APP的中断向量表的地址。这样,当在APP中发生中断,单片机会跳转到IAP的开头去执行中断向量表,然后跳转到APP的中断向量表,最后才跳转到APP的中断服务函数。当然,这样一来IAP的中断就用不了了。
二、stm8的IAP
stm8的Flash是字节编程的,而且不需要先擦除再写,可以直接写。因此stm8的IAP处理流程为:进入IAP---解锁Flash---接收APP数据写Flash---改写IAP的中断向量表---Flash上锁----跳转到APP。
读函数、解锁、上锁、写函数为:
//读出一个32位数
uint32_t FLASH_Read(uint32_t Address)
{
return(*(PointerAttr uint32_t *) (uint16_t)Address);
}
//解锁Flash,在写Flash前,只需要在IAP中调用一次
void FLASH_Unlock(FLASH_MemType_TypeDef FLASH_MemType)
{
FLASH->PUKR = FLASH_RASS_KEY1;
FLASH->PUKR = FLASH_RASS_KEY2;
}
//Flash上锁,写完Flash后,调用一次
void FLASH_Lock(FLASH_MemType_TypeDef FLASH_MemType)
{
FLASH->IAPSR &= (uint8_t)FLASH_MemType;
}
//往Flash中写入一个8位数
void FLASH_ProgramByte(uint32_t Address, uint8_t Data)
{
*(PointerAttr uint8_t*) (uint16_t)Address = Data;
}
//往Flash中写入一个32位数
void FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
/* Enable Word Write Once */
FLASH->CR2 |= FLASH_CR2_WPRG;
FLASH->NCR2 &= (uint8_t)(~FLASH_NCR2_NWPRG);
/* Write one byte - from lowest address*/
*((PointerAttr uint8_t*)(uint16_t)Address) = 0x00;
*((PointerAttr uint8_t*)(uint16_t)Address) = *((uint8_t*)(&Data));
/* Write one byte*/
*(((PointerAttr uint8_t*)(uint16_t)Address) + 1) = 0x00;
*(((PointerAttr uint8_t*)(uint16_t)Address) + 1) = *((uint8_t*)(&Data)+1);
/* Write one byte*/
*(((PointerAttr uint8_t*)(uint16_t)Address) + 2) = 0x00;
*(((PointerAttr uint8_t*)(uint16_t)Address) + 2) = *((uint8_t*)(&Data)+2);
/* Write one byte - from higher address*/
*(((PointerAttr uint8_t*)(uint16_t)Address) + 3) = 0x00;
*(((PointerAttr uint8_t*)(uint16_t)Address) + 3) = *((uint8_t*)(&Data)+3);
}
改写IAP向量表的函数为:
//重新初始化STM8的中断向量表 把它重新定义到APP的中断向量中
void STM8_HanderIqr_Init(void)
{
uint8_t Index;
disableInterrupts(); //关闭中断
FLASH_Unlock(FLASH_MEMTYPE_PROG);
for(Index = 1; Index < 32;Index++)
{
FLASH_ProgramWord(0x8000+4*Index,0x82000000+APPLICATION_ADDRESS+Index*4);
}
FLASH_Lock(FLASH_MEMTYPE_PROG);
}
跳转到APP的函数为:
void JPMainProgram(void)
{
//跳转至APP
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $9000"); //0x9000是APP的地址,根据自己的情况来改
}
最后:
stm8的Flash很小,建议使用寄存器操作,不要用库函数,否则不好控制代码大小。
IAR的SWIM仿真对于Memory的支持不是很好,当调用上面的函数改写Flash后,从Memory窗口上看可能并未改变,但实际上已经被改写了。(本人被坑了半天)