ISP IAP 详解与ISP IAP的实现
本帖的大致内容分为三部分
1 STM32 IAP方式与ISP方式选择的具体实现操作步骤
2 ISP方式与实现过程
3 IAP方式与实现过程
ISP:In System Programing 在系统编程(通过固化的ISP程序来实现,bootloader(启动引导程序)为ISP程序)
IAP:In applicating Programing 在应用编程(通过用户自己写的的IAP程序来实现,bootloader(启动引导程序)为IAP程序)
1 STM32 IAP方式与ISP方式选择的具体实现操作步骤
STM32的三种启动方式及引脚配置。
BOOT引脚是那个GPIO口,具体查看芯片手册。
1. FLASH启动
2. SRAM启动
3. 系统存储器启动
这三种启动顺序决定了上电后第一条指令的位置。如果你选择FLASH启动,则上电复位后PC指针指向第一条指令位置——0x08000000(运行IAP程序)如果你选择SRAM启动,则上电复位后PC指针指向第一条指令位置——0X20000000(用于在线调试)若你选择系统存储器启动,则上电复位后PC指针指向第一条指令位置——0X1FFF0000(运行ISP程序。)
我们来具体了解一下context-m3的存储器映射
Cortex-M3存储器映射
(存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。)
CM3的地址空间有4G,但它只对这4G空间作了预先的定义,把4G空间分成8个块,每块大小为512M,并指出各段该分给哪些设备。具体的实现由芯片厂商决定,厂商可以设计出具有自己特色的产品。下图是CM3的存储器映射图(来自CM3权威指南)。
其中代码区又分为
Flash 主闪存区(Flash main memory),地址范围(0x08000000-0X0807ffff),不同容量产品,地址范围不同,存放用户的代码,可按页擦除或全擦除(不会擦Flash信息区的内容),闪存编程一次可写入半字。
Flash信息区(Information block),该区域又可以分为选项字节区(Option Bytes)和系统存储区(System Memory)区域。
System Memory:STM32在出厂时,已经固化了一段程序在System memory(medium-density devices的地址为:0x1FFF_B000-0x1FFF_F7FF,大小为18KB)厂家出厂时烧写在存储器中,这段程序就是一个芯片出出厂时固定好的,并且没法修改的Boot Loader。是一个固定的协议,(下面详细讲解)其功能主要是从串口接收程序,并擦除原来的主闪存区,并拷入新程序。
Option Bytes:可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);该区域除了互联型所用型号地址都一样:(0x1fff_f000~0x1fff_f80f),正好16个字节。
(忽略不提闪存存储器接口寄存器部分,咱认为存在于代码区最后一部分的保留区中)
2 ISP 方式 与实现过程
ISP是指可以在板级上进行编程,而不用把芯片拆下来放到烧写器中,即不脱离系统,所以称作“在系统编程”,它是对整个程序的擦除和写入,通过单片机专用的串行编程接口对单片机内部的Flash存储器进行编程。即使芯片焊接在电路板上,只要留出和上位机接口的串行口就能进行烧写。
ISP的过程:
1.芯片复位
在给STM32复位之前,首先要确定BOOT0,BOOT1引脚的状态.通过各种方式,先让BOOT0处于高电平状态,BOOT1处于低电平状态,然后在RST脚上产生一个负脉冲,STM32就能进入ISP状态(或者说激活系统自举程序,或者说开始执行系统存储区的BootLoader程序).注意,复位之后,一定要延时一定的时间,让ISP程序稳定,才能发送指令和数据.
注释:系统自举程序,或者说系统BootLoader,是一段单片机出厂前,就由生产公司固化在系统存储区的一段代码,用于程序下载
自举程序的主要功能如下
①使用嵌入式串行接口按照预定义的通信协议下载代码
②可传送并更新 Flash 代码、数据和向量表部分
(前文已经叙述)。
2.自举程序(或者说BootLoader)进行的系统配置(STM32F10XX系列)
3.系统自举程序的的运行过程
通过bootloader具体运行过程,我们可以知道,在ISP下载方式下载程序时,对于(STM32F10XXX系列),我们一定要将PC与单片机串口1相连,下载波特率可设定为任何值。
注:本部分使用 STM32F10xxx 指代小容量、中容量及大容量 STM32F101xx 和 STM32F103xx 器 件,低容量和中容量 STM32F102xx 器件,低容量、中容量及大容量 STM32F100xx 器件以 及中容量和大容量超值型器件。
不同型号的单片机的自举程序并不完全相同,所支持的串行接口的数量和种类,也不完全相同,如果想进一步弄清自举程序,或者不同型号STM32具体型号对应的自举程序,请参考
https://wenku.baidu.com/view/e7df6a56b9f3f90f77c61b67.html
在说IAP之前不得不提一下程序HEX文件与程序BIN文件的区别。(ISP方式使用的是HEX文件IAP是Bin文件)
具体参考:https://blog.csdn.net/wangwenxue1989/article/details/42217353
好了前面基本都属于大致的科普,重头戏IAP来了。
3 IAP方式与实现过程
IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对User Flash(即主闪存区) 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 通常实现IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如USB、USART)接收程序或数据(即IAP程序,或者说BootLoader程序,与ISP不同,这段BootLoader程序是我们自己编写的一段代码),执行对第二部分代码的更新;第二个项目代码(即APP程序)才是真正的功能代码。这两部分项目代码都同时烧录在User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
(1)检查是否需要对第二部分代码进行更新 (是否接收更新新的APP)
(2)如果不需要更新则转到 (4)
(3)如果需要执行更新操作
(4)跳转到第二部分代码执行
首先研究一下STM32复位后,程序是如何运行的,为接下来理解IAP与理解中断向量表,提供基础理论知识支持。
正常情况下STM32运行程序的流程图
单片机复位后(我们的选择BOOT引脚,从主闪存中启动),Cortex-M3做的第一件事就是读取下列两个32位整数的数值。
从地址绝对地址0x0000,0000H(映射地址0X8000,0000H),中取出 MSP的初始值送入SP寄存器中(CONTEX-M3寄存器组通用目的寄存器R13), 从绝对地址0X0000,0004(映射地址0X8000,0004)中取出PC的初始值,送入PC寄存器中,(该地之内存放的是复位中断函数的入口地址,也是中断向量表的起始地址)。
中断向量表
当一个异常发生,被Contex-M3内核接受时,对应的异常处理函数就会执行.为了决定中断处理函数的入口地址,Contex-M3使用了“向量表查表机制”.向量表其实是一个WORD(32位整数数组,每个中断向量占4个字节),每个下标,对应一个中断,下标元素的值是异常中断处理函数的入口地址。向量表的存储位置是可以设置的,通过NVIC中的重定位寄存器来指出向量表的地址(VTOR寄存器)。复位后该寄存器的内容为0X8000,0000。
STM32启动后进入MAIN()前执行过程,具体分析见。
https://www.cnblogs.com/amanlikethis/p/3719529.html(启动分析)
https://blog.csdn.net/cgsz1992/article/details/7935717(启动代码分析)
摘录出复位中断处理函数
; Reset handler
Reset_Handler PROC ;复位中断服务程序,PROC…ENDP结构表示程序的开始和结束
EXPORT Reset_Handler [WEAK] ;声明复位中断向量Reset_Handler为全局属性,这样外部文件就可以调用此复位中断服务
IMPORT SystemInit ;声明SystemInit标号
IMPORT __main ;声明__main标号
LDR R0, =SystemInit ;跳转到SystemInit地址执行
BLX R0 ;
LDR R0, =__main ;跳转__main地址执行
BX R0
ENDP
初始化子程序__main程序的一个主要作用是初始化堆栈(对于程序清单一来说则是跳转 __user_initial_stackheap标号进行初始化堆栈的),
并初始化映像文件,最后跳转C程序中的main函数。这就解释了为何所有的C 程序必须有一个main函数作为程序的起点——因为这是由C/C++标准实时库所规定的.
可以总结一下STM32的启动文件和启动过程。首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转到C/C++标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的 main函数开始执行C程序。假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
其中执行SystemInit (void)。函数的最后几句C代码实现了VTOR寄存器的初始值0X8000,0000的赋值。
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
其中
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
加入IAP后程序执行过程。
现在的程序结构即为IAP+APP。我们设定0X8000000到0X8000000+M这段地址空间存放BOOTLOADER(引导程序,我们自己编写),这段程序所占FLash的大小为M字节,用J-Link事先烧写进入单片机。主要初始化串口,接收APPbin往FLASH中写入APPbin。
写入的起始地址,在BOOTLOADER中定义完成,由于SRAM的存储空间有限,程序代码往往比较大,难以一次性接收完成,因此往往要分批接收(分包接收),每包数据通过串口接收完成后,要把SRAM中的数据写入FLash中,腾出APPbin数据对SRAM的占用,但把数据从SRAM中写入到FLash中需要时间,往往比较慢,只有确定第一次SRAM中所有数据往FLash中写入完成后,才能再次发送下一个数据流,SRAM接收完成后,再次写入FLash中,第二次SRAM写入FLash的起始地址,是第一次数据从SRAM写入FLash的末尾地址。
就像要把一个装满水的水桶A里的水转移进入另一个水桶中B中,A就是我们的电脑中的内存,编译后生成的APPbin的存放位置,B就是单片机FLash,而单片机SRAM就充当了舀水的水瓢C的作用,我们无法举起那个装满水的水桶A一次把里面所有的水倒入空空的水桶B,但是我们可以通过水瓢C不紧不慢的,一点点将水从桶A中,转移进入水桶B。一次一瓢,这一瓢水水平线在B缸中的起点,就是上一瓢水,全部倒入B缸后,水平线的终点。
上述为大体的实现思路,具体的细节上,
1.在上位机上,通过C+ C#等等,编写一个类似串口助手的软件,将读出将HEX文件转换成的Bin文件,将Bin文件分割成n个程序数据包进行发送,每包的长度,根据单片机具体的SRAM大小,进行合理的设定,我们把发送的数据包分为①信息数据包,和②程序数据包。其中
①信息数据包中,包含的是进行程序升级的帧头验证,每个程序数据包的长度,程序数据包的个数。
②程序数据包,则包含的是具体的程序Bin文件。
2.下位机上,只需在正点原子的IAP例程bootloader上稍作修改,在bootloader串口接受程序中,记录接受每包接收到的字节数,,当在固定时间间隔内,未接收到数据,认为本次数据包发送完成,统计本次接受数据包接收到的字节总数,是否和上位机,每次发送的数据包所包含的字节数是否相同(即信息数据包中的约好定每个程序数据包的长度),如果不相同,则说明,传输过程中丢失数据,哪怕只是一个字节,这对无线升级程序来说,都是致命的,下位机需要对上位机回执,第m个数据包接受失败,上位机重新发送第m个数据包,下位机重新接受。若统计的接收到的字节数与上位机发送的数量一致,则进行写入FLASH操作,此操作的目的,就是为了腾出宝贵的SRAM空间,将接收到暂存于SRAM的Bin文件包,全部写入到SRAM后,给上位机,回执已经准备好下次接受,等待接受第m+1个数据包。
上述通信方式,只是在接收数据的数量上做了统计,信息传输过程中,即可能会出现丢失,又可能会出现传输内容错误,通过本人的大量实际实验,在用常规的蓝牙模块,WIFI模块,进行无线串口升级时,往往不会出现数据传输内容错误,玩玩会出现丢失数据,因此上面写的大致协议,只是做了是否丢失数据的判定,若进一步追求通信系统的可靠性,可以在此基础上,加入汉明码校验,等方式,来进行纠错处理。
至于如果只想在需要更新的情况下,单片机才执行bootloader,正常情况下,单片机一上电,直接执行APP程序,笔者有两个思路。
1.FLASH特定地址标志位法,就是当串口接收到特定更新指令后,执行按顺序执行两步操作①在FLASH靠近末尾部分某个地址,写入特定标志,该标志位置位成 ''执行更新'' (之所以选末尾的某个地址,是防止标志位被程序Bin文件覆盖掉),②执行软件复位。在BootLoader程序中,执行标志位的读取和判断,当判断标志位为 ''执行更新'' 的话,则初始化串口等,执行软件升级操作,若判断 ''不执行更新'',则直接执行PC指针强制跳转,执行用户APP,在用户APPmain()内,死循环前,将标志位重置为''不执行更新'',这样单片机下次上电后就不在执行更新,PC指针直接强制跳转,执行APP。
2.寄存器标志位法,其实ARM架构中,有一个专门的寄存器是记录复位方式的,RCC->CSR记录了复位的复位方式,软件复位,上电复位,看门狗复位等等,我们在APP中,接收到升级指令,只做1.中的②操作,即只执行软件复位,我们在Bootloader中执行读RCC->CSR寄存器操作,若判断系统复位方式为软件复位,则说明我们要执行升级,(因为只有接收到执行升级指令,才会有软件复位执行,软件复位标志位才会被置位)。当正常上电启动单片机时,软件复位标志位不会被置位,单片机不执行更新操作,直接进行PC指针强制跳转,执行APP.
废话不多说,上bootloader和APP的核心代码 ,原理其实很简单,读者可以根据自己的情况自行创新YY,定制自己的通信协议。
Bootloader部分
①读寄存器
②写FLASH中写APP
③更新完成后PC指针强制跳转至APP起始地址代码
APP部分
①修改中断向量表位置的代码
SCB->VTOR = FLASH_BASE | 0x4000; 在OX8000000的基础上偏移0X4000(即为bootloader程序所占有的空间),即APP的起始地址,0x8004000
为什么要进行中断向量表的偏移?
因为对于单片机而言,当特定中断发生后,单片机会去中断向量表中,查找该中断对应的入口地址,PC强制跳转到该中断的地址处,执行该中断。
如果我们不执行中断向量表的偏移,当APP执行过程中,有特定中断发生,单片机仍会在0x8000000(即Bootloader的起始地址处)的中断向量表中查找该中断对应的入口地址,然而该中断向量表是Bootloader程序的中断向量表,并不是APP程序的中断向量表,记录的并不是APP中断对应的入口地址,因此要进行中断向量表地址的一个修改,让单片机知道 ,中断向量表在0x8000000+M处(M为bootloader程序所占的空间)即为APP程序的起始地址处,而不是在默认的0X8000000处,应在0x8000000+M处的中断向量表中查找特定中断程序的入口地址。
0x8004000起第一个表项为APP程序的MSP地址,第二个表项为APP程序的复位中断地址,系统整个初始化过程,与正常程序初始过程完全相同,上文已做了详尽的解释。
②串口中加入的软件复位代码,串口接收到升级指令后,即执行以下两句代码。
__disable_fault_irq();
NVIC_SystemReset();