1、开发环境
LPC-2478STK+IAR+JINK
2、特性
- 在系统编程:在系统编程(ISP)是使用 boot 装载程序软件和 UART0 串口对片内Flash 存储器进行编程和再编程的一种方法。
- 在应用编程: 在应用编程(IAP)是按照最终用户的应用代码指示,对片内 Flash 存储器执行擦除和写操作的一种方法。
3、IAP
下面是正点原子的STM32的IAP介绍
IAP(In Application Programming)即在应用编程,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。
通常实现IAP功能时,即用户程序运行中作自身的更新操作,需要在涉及固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信(如USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码,这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
- 检查是否需要对第二部分代码进行更新
- 如果不需要更新则转到4
- 执行更新操作
- 跳转到第二部分代码运行
第一部分代码必须通过其它手段,如JTAG或ISP烧入
第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分IAP代码更新
我们将第一个项目代码成为Bootloader程序,第二个项目代码称之为APP程序,他们就放在FLASH的不同地址范围,一般从最低地址区开始存放Bootloader,紧跟其后的就是APP程序(注意,如果FLASH容量足够,可以设计很多个APP程序)
STM32 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行
3.1、正常的程序运行流程
STM32的内部闪存FLASH地址起始于0x08000000,一般情况下,程序文件就是从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址0x08000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
- 在STM32复位后,先从0x08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如标号1所示
- 在复位中断服务程序执行完之后,会跳转到我们的main函数,如标号2所示
- 而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处,如图标号3所示
- 然后,根据中断源进入相应的中断服务程序,如标号4所示
- 执行完中断服务函数以后,程序再次返回main函数执行,如标号5所示
3.2、加入IAP程序之后
- STM32复位后,还是从0x08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数,如标号1所示
- 执行完IAP以后,(即将新的APP代码写入STM32的FLASH,灰底部分。新程序的复位中断向量起始地址为0x08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位向量表地址,并跳转执行新程序的复位中断服务程序,随后跳转到新程序的main函数,如标号2、3所示,同样main函数为一个死循环,并且注意到此时STM32的Flash,在不同位置上,共有两个中断向量表
- 在main函数执行过程,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0x08000004中断向量表处,而不是新程序的中断向量表,如图标号4所示
- 程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号5所示
- 在执行完中断服务程序后,程序返回main函数继续运行,如图标号6所示
通过以上两个过程的分析,我们知道IAP程序必须满足两个要求:
- 新程序必须在IAP程序之后的某个偏移量x的地址开始
- 必须将新程序的中断向量表相应的移动,移动的偏移量为x
3.3、IAP命令
对于在应用编程来说, IAP程序应当通过寄存器r0 中的字指针指向包含命令代码和参数的存储器(RAM)来调用IAP程序。 IAP命令的结果返回到寄存器r1 所指向的结果表(result table)。用户可通过传递寄存器r0 和r1 中的相同指针来重新使用命令表以获取结果。参数表应当大到足够存放所有的结果以防结果的数目大于参数的数目。参数传递的过程如图所示。参数和结果的数目根据IAP命令而有所不同。参数的最大数目为 5, 被传递给“将
RAM内容复制到Flash”命令。结果的最大数目为 2,由“扇区查空”命令返回。命令处理程序在接收到一个未定义的命令时发送状态代INVALID_COMMAND。 IAP程序是thumb代码,位于地址 0x7FFF FFF0 处。
定义 IAP 程序的入口地址。由于 IAP 地址的第 0 位是 1,因此,当程序计数器转移到该地址时会引起 Thumb 指令集的变化。
#define IAP_LOCATION 0x7ffffff1
定义数据结构或指针,将 IAP 命令表和结果表传递给 IAP 函数:
unsigned long command[5];
unsigned long result[2];
或
unsigned long * command;
unsigned long * result;
command = (unsigned long *) 0x….
result = (unsigned long *) 0x….
定义函数类型指针,函数包含 2 个参数,无返回值。注意: IAP 将函数结果和 R1 中的表格基址一同返回。
typedef void (*IAP) (unsigned int [ ] , unsigned int [ ]);
IAP iap_entry;
//设置函数指针
iap_entry=(IAP) IAP_LOCATION;
//调用
iap_entry (command , result);
3.3.1、准备写操作扇区
该命令使 Flash 写/擦除操作分两步执行。
3.3.2、将RAM内容复制到Flash
3.3.3、擦除扇区
3.3.4、扇区查空
3.3.5、读器件ID
3.3.6、读boot代码版本
3.3.7、比较
3.3.8、重新调用ISP
3.4、IAP存储功能代码实现
#include <nxp/iolpc2478.h>
#include <intrinsics.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u64;
typedef unsigned long u32;
#define DestAddr 0x00078000
#define Length 512
/* 定义IAP命令字 */
// 命令 参数
#define IAP_SELECTOR 50 // 选择扇区 【起始扇区号、结束扇区号】
#define IAP_RAMTOFLASH 51 // 拷贝数据 【FLASH目标地址、RAM源地址、写入字节数、系统时钟频率】
#define IAP_ERASESECTOR 52 // 擦除扇区 【起始扇区号、结束扇区号、系统时钟频率】
#define IAP_BLANKCHK 53 // 查空扇区 【起始扇区号、结束扇区号】
#define IAP_READPARTID 54 // 读器件ID 【无】
#define IAP_BOOTCODEID 55 // 读Boot版本号 【无】
#define IAP_COMPARE 56 // 比较命令 【FLASH起始地址、RAM起始地址、需要比较的字节数】
#define IAP_REINVOKE_ISP 57 // 重新调用ISP 【无】
/* 定义IAP返回状态字 */
#define CMD_SUCCESS 0
#define INVALID_COMMAND 1
#define SRC_ADDR_ERROR 2
#define DST_ADDR_ERROR 3
#define SRC_ADDR_NOT_MAPPED 4
#define DST_ADDR_NOT_MAPPED 5
#define COUNT_ERROR 7
#define SECTOR_NOT_BLANK 8
#define SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION 9
#define COMMPARE_ERROR 10
#define BUSY 11
#define IAP_FCCLK 48000
#define IAP_ENTER_ADR 0x7FFFFFF1 // IAP入口地址定义
u32 paramin[8]; // IAP入口参数缓冲区
u32 paramout[8]; // IAP出口参数缓冲区
/* IAP操作缓冲区选择,代码为50 */
u32 SelSector(u8 sec1,u8 sec2)
{
paramin[0] = IAP_SELECTOR; // 设置命令字
paramin[1] = sec1; // 设置参数
paramin[2] = sec2;
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
return(paramout[0]); // 返回状态码
}
/* 复制RAM的数据到FLASH,命令代码51 */
u32 RamToFlash(u32 dst, u32 src, u32 no)
{
paramin[0] = IAP_RAMTOFLASH; // 设置命令字
paramin[1] = dst; // 设置参数
paramin[2] = src;
paramin[3] = no;
paramin[4] = IAP_FCCLK;
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
return(paramout[0]); // 返回状态码
}
/* 函数功能:擦除扇区,命令代码52 */
u32 EraseSector(u32 sec1, u32 sec2)
{
paramin[0] = IAP_ERASESECTOR; // 设置命令字
paramin[1] = sec1; // 设置参数
paramin[2] = sec2;
paramin[3] = IAP_FCCLK;
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
return(paramout[0]); // 返回状态码
}
/* 查空扇区,命令代码53 */
u32 BlankCHK(u32 sec1, u32 sec2)
{
paramin[0] = IAP_BLANKCHK; // 设置命令字
paramin[1] = sec1; // 设置参数
paramin[2] = sec2;
paramin[3] = IAP_FCCLK;
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
return(paramout[0]); // 返回状态码
}
/* 读器件ID,命令代码54 */
u32 ReadParID(u32 *Device_ID)
{
paramin[0] = IAP_READPARTID; // 设置命令字
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
*Device_ID = paramout[1];
return(paramout[0]); // 返回状态码
}
/* 读器件ID,命令代码54 */
u32 BootCodeID(u32 *Boot_ID)
{
paramin[0] = IAP_BOOTCODEID; // 设置命令字
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
*Boot_ID = paramout[1];
return(paramout[0]); // 返回状态码
}
/* 校验数据,命令代码56 */
u32 Compare(u32 dst, u32 src, u32 no)
{
paramin[0] = IAP_COMPARE; // 设置命令字
paramin[1] = dst; // 设置参数
paramin[2] = src;
paramin[3] = no;
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 调用IAP服务程序
return(paramout[0]); // 返回状态码
}
/* 重新调用ISP,命令代码57 */
void Reinvoke_ISP(void)
{
paramin[0] = IAP_REINVOKE_ISP; // 设置命令字
(*(void(*)())IAP_ENTER_ADR)(paramin,paramout); // 重新调用ISP
}
/* 向FLASH中写入数据 */
u8 WriteFlash(u32 dst,u32 src,u32 no)
{
SelSector((u8)(dst/0x1000),(u8)((dst+no)/0x1000)); // 选择扇区
EraseSector((dst/0x1000),(dst+no)/0x1000); // 擦除扇区
BlankCHK((dst/0x1000),(dst+no)/0x1000); // 查空扇区
SelSector((u8)(dst/0x1000),(u8)((dst+no)/0x1000)); // 选择扇区
RamToFlash(dst,src,no); // 写数据到FLASH
return((u8)(Compare(dst,src,no))); // 比较数据
}
int main(void)
{
u32 uiValue,i;
u8 SendData[512]; /* 定义变量区 */
for (i = 0; i < 512; i++){ /* 初始化变量区数据 */
SendData[i] = (u8)i;
}
SelSector(22, 22); /* 选择扇区 */
EraseSector(22, 22); /* 擦除扇区 */
SelSector(22, 22); /* 选择扇区 */
RamToFlash(DestAddr, (u32)SendData, Length); /* 写数据到FLASH */
uiValue = Compare(DestAddr, (u32)SendData, Length); /* 数据比较 */
if (uiValue != 0){ /* 比较结果错误 */
while (1);
}
while (1);
}
3.5、IAP升级
- 程序开始,初始化外设
- 检查是否短接升级接口进入升级模式
- 是进入升级模式等待串口烧写完成并烧写到程序入口地址
- 否直接跳转到程序入口地址
4、ISP
用写入器将code烧入,不过,芯片可以在目标板上,不用取出来,在设计目标板的时候就将接口设计在上面,所以叫"在系统编程",即不用脱离系统;
ISP 程序升级需要到现场解决,不过好一点的是不必拆机器了;
IAP 如果有网管系统的话,用网管下载一切搞定,人不用跑来跑去,
在线编程目前有两种实现方法:在系统编程(ISP)和在应用编程(IAP)。ISP一般是通过单片机专用的串行编程接口对单片机内部的Flash存储器进行编程,而IAP技术是从结构上将Flash存储器映射为两个存储体,当运行一个存储体上的用户程序时,可对另一个存储体重新编程,之后将控制从一个存储体转向另一个。ISP的实现一般需要很少的外部电路辅助实现,而IAP的实现更加灵活,通常可利用单片机的串行口接到计算机的RS232口,通过专门设计的固件程序来编程内部存储器。 ISP和IAP很相似,都是不需要把芯片从板子上拔出来,就达到了用PC-MCU的编程接口(JTAG、串口、双绞线、SPI等)搞定新版本的升级的目的。MCU内部都是首先执行一段独立的Boot代码(这段Boot代码一般是出厂预置,或使用编程器烧录的,通常只有1k或4k,SST通常是占用一块独立的Block,Philips通常是让BootROM地址与其他Flash重叠,以达到隐藏的效果),Boot负责控制擦除程序存储器及给程序存储器编程的代码(或是处理器外部提供的执行代码),然后通过某种与PC计算机的通信方式(如,ether网口),将用户指定的某个在PC上编译完成的MCU可运行的二进制代码文件编程入MCU内的程序存储器。
4.1、ISP命令
4.2、ISP应用
isp应用主要都是通过uart,单片机只是被动接收处理,此内容芯片内部已经完成
5、追加1
扇区号地址如图所示: