ISP和IAP

ISP:在系统编程。通过芯片上已经固化好的bootloader,从串口将代码写入内部的flash。这块bootloader位于系统存储区,置位boot引脚来选择系统存储器为启动区域。

ios AFN post流程_stm32


ios AFN post流程_stm32_02


IAP:在应用编程。用户根据自己需要将flash分成两个区域,一块用作bootloader,一块是我们正常运行的程序(app),而bootloader的作用和上面ISP的一样,将app代码引导至flash中。

ios AFN post流程_stm32_03

IAP过程

stm32正常运行流程

ios AFN post流程_stm32_04


从图中可以看到,在0x08000000处存放着程序的中断向量表,它由栈顶地址和各中断的入口地址组成。在上电复位后,芯片会由硬件控制回到0x08000000处,先将0x08000000地址开始的4字节栈顶指针送给sp寄存器,然后将0x08000003地址开始存放的复位中断的入口地址给程序计数器pc,从此开始程序跳转到复位中断服务函数。

ios AFN post流程_ios AFN post流程_05


ios AFN post流程_ios AFN post流程_06


跳转到复位中断后,它做的只有两件事,一是系统时钟初始化,二是跳转到我们编写的main函数

ios AFN post流程_单片机_07

当我们进入到main函数时,程序会按照我们编写的逻辑运行。当有中断请求时,芯片会从中断向量表中获取相应的中断服务函数入口地址,跳转到相应的中断程序。并且中断向量表的位置是可以重定义的,它由SCB->VTOR寄存器控制。

有IAP的stm32运行流程

ios AFN post流程_stm32_08


从图中可以看到,与stm32正常运行流程相比,IAP相当于加入了一个bootloader,通过它将我们的app代码引导至flash中。首先,和上面的流程一样,复位后,芯片配置堆栈指针和pc指针,初始化系统时钟,然后进入我们的main函数,main函数里面是我们编写的bootlaoder程序,按照我们的需要引导app程序至flash或者跳转到app程序。

int main(void)
{		
	uart_init(115200);	//串口初始化为115200
	delay_init();	   	 	//延时初始化 
 	LED_Init();		  		//初始化与LED连接的硬件接口
	KEY_Init();					//初始化按键
	while(1)
	{
		if(KEY_Scan(KEY_CONT) == DOWNLOAD_PRES)				//引导app至flash指定位置
		{
			iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,USART_RX_CNT);
			LED1 = 0;
		}
		else if(KEY_Scan(KEY_CONT) == EXECUTE_PRES)		//跳转到app
		{
			LED1 = 1;
			iap_load_app(FLASH_APP1_ADDR);
		}
	}
}

(上面的代码是一个简单的bootloader的测试。通过按键控制,从串口接收app程序,并跳转到app程序。我们可以根据自己需求编写,功能都大同小异)
自此,bootloader部分暂时结束。程序会跳转到我们的app。从图中可以看到,它其实是又重复了一遍正常的运行流程,只不过相对于以前的0x08000000的起始地址有了一个偏移。值得注意的是,进入我们app的main函数之后,如果有中断请求,我们需要它进入的是app的中断,而不是bootloader的中断,这时就需要对SCB->VTOR寄存器进行重设。

IAP代码

这里主要是对正点原子提供的bootloader程序的部分代码分析。

bootloader

bootloader主要包括两个功能:引导app和跳转到app。

引导app到flash这个功能的实现很简单,利用的是flash的擦除和读写,在此不做赘述。

跳转到app

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
}

(appxaddr是我们定义的app存放在flash中的地址,注意它不能和bootloader程序占用的空间冲突)

如前面分析所示,要跳转到app程序执行,就是把app程序起始地址给程序计数器pc,同时初始化app的栈顶指针。变化是以前是硬件完成,现在由软件完成。

首先是pc指针赋值工作。我们无法对程序计数器pc直接操作,所以我们使用空函数跳转的办法。我们获取appxaddr+4的内容,得到复位中断的入口地址,然后跳转到它即可。这里的代码先是取出了32位的函数地址,然后将其强制转化成函数指针的类型赋给jump2app,最后执行jump2app这一空函数实现跳转。

以下代码是iapfun和jump2app的定义,涉及函数指针和typedef别名定义,可以参考这两篇文章,很有学习价值:

typedef  void (*iapfun)(void);		//定义一个函数指针
iapfun jump2app;

typedef用法总结函数指针和指针函数

接下来是初始化栈顶指针。

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr) 
{
    MSR MSP, r0 
    BX r14
}

这里使用的是两条汇编指令。函数的参数会自动传给寄存器r0,利用指令MSR将r0的值存储到特殊寄存器sp中,实现栈顶指针的重设。之后执行BX指令,r14会保存执行函数前的pc值,这样就跳转到原始位置执行jump2app。

app

通过前面的分析,可以知道app程序要满足两个要求:

  • 起始地址偏移
  • 中断向量表偏移

地址偏移的修改如下图所示,根据bootloader的大小决定起始地址偏移量。比如我之前使用的bootloader是6k字节,那么我就可以把0x08001800后的空间留给app使用(在这里使用0x08010000为起始地址),之后再根据flash容量确定大小即可。

ios AFN post流程_初始化_09


对比偏移后生成的二进制文件,就能发现其实是中断向量表存放的中断向量发生了一个整体偏移,对运行的代码没有影响。

ios AFN post流程_初始化_10


起始地址偏移修改完后,在没有中断发生的情况下,程序是可以正常跑起来的;在有中断请求时,芯片会根据中断向量表起始地址(VTOR寄存器存储)和中断类型号找到对应中断程序,因为中断向量表起始地址还是默认的0x08000000,所以它进入的中断还属于bootloader。在这里修改中断向量表起始地址,让它定位到app。具体操作如下:

SCB->VTOR = FLASH_BASE|0X10000

到这里,app的部分就配置好了,接下来就该生成对应的二进制文件让bootloader引导进flash。具体操作如下图所示。

ios AFN post流程_单片机_11


找到我们的MDK目录下的fromelf程序,使用以下命令,让它生成二进制文件。

D:\MDK\ARM\ARMCC\bin\fromelf.exe   --bin -o  xxx.bin xxx.axf

生成的二进制文件就是我们编译完成后的最纯粹的数据代码,cpu按照这个代码顺序执行就能实现相应功能。至于为什么不使用十六进制文件,是因为他还有很多附加信息,比如数据存储的起始地址,cpu执行过程中并不需要它们。具体可参考以下文章。