一
、什么
是
IAP,为什么要IAP
IAP
即为
In Application Programming
(在
应用中编程
),一般情况下
,
以
STM32F10x
系列芯片为
主控制器的设备在出厂时就已经使用
J-Link
仿真器将应用代码烧录了,
如果在
设备使用过程中需要进行
应用
代码
的
更换、升级等
操作
的话
,则
可能需要将设备
返回
原厂
并
拆解出来再使用J-Link重新烧录代码,
这就
增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给用户一个代码文件即可。
而
IAP却能很好的解决掉这个难题,
一片
STM32芯片
的Code(代码)区内
一般只有一个用户程序
。
而IAP
方案
则是将代码
区
划分为
两部分,
两部分
区域各
存放一个程序,
一个
叫bootloader(
引导
加载程序)
,
另一个
较
user application(
用户
应用程序)
。
bootload
er
在
出厂时
就
固定下来了,在需要变更user application时只需要通过
触发
bootloader对
user
application
的
擦除和重新
写入
即可完成
用户
应用的更换。
如
图
1
-1
所示
在
程序执行初始进入
bootloader
,在bootloader里面
检测条件
是否被触发
(可
通过按键
是否被
按下、串口
是否
接收到特定的数据、U盘
是否插入
等等
),
如果有则进行对user application进行擦除和重新写入操作,
如果
没有则直接跳转到user application执行应用
;如果
有则进行擦除用户代码并重新写入新的用户代码。
二
、STM32F103ZET6硬件条件
STM32F103ZET6的启动方式有三种:内置FLASH
启动、
内置
SRAM
启动、
系统
存储器ROM启动
,通过
BOOT0和BOOT1引脚
的
设置可以选择从哪中方式启动
,
这里
选择
内置的FLASH启动。
其
FLASH
的
地址为
0
x08000000—0x0807ffff
,
共
512
KB
,
这些都能从芯片数据手册中直接得到。
而
这里
首要
的一个问题是
中断
的问题。
正常
情况下发生中断的过程为:
发生
中断
(中断
请求
)
à
到
中断向量表查找中断函数入口地址
à
跳转
到中断函数
à
执行
中断函数
à
中断
返回
。
也就
是说
在STM32
的
内置的Flash中
有
一个中断向量表
来
存放各个中断服务函数的入口地址,内置Flash的分配情况大致如下
图2
-1
。
在
只有一个程序的情况下,程序执行的走向应该如图
2
-2
所示(借用网友
的原图
)
。
STM
32F10x
有一个
中断向量表,
这个
中断向量表存放在
代码
开始部分的后4个字节处
(即0
x08000004
)
,代码开始的4个字节存放的是堆栈栈顶的地址,
当
发生中断
后程序
通过查找该表
得到
相应的中断服务程序入口地址,
然后
再跳到相应的中断服务程序中执行。
上电后从0x08000004处
取出
复位
中断向量的地址,然后跳转到复位中断程序的入口
(标号
①
所示)
,
执行
结束后跳转到main函数
中(标号
②所示
)。在
执行main函数的过程中
发生
中断
,
则
STM
32
强制
将
PC
指针
指回
中断
向量
表
处
(
标号
③
所示
)
,
从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数
(标号
④
所示),
执行完中断函数后
再
返回到main
函数
中来
(标号
⑤
所示)。
若
在STM32F103x中使用
IAP
方案,则内置的Flash
分配
情况大致如下
图2
-3
。
在内置的Flash里面添加一个BootLoader程序,
BootLoader
程序和
user
application
各有
一个中断向量
表
,
假设
BootLoader程序占用的空间为N+M字节,则程序的走向应该如图
2
-2
所示(借用
网友的原图
并
做改动
,
其中虚线部分为
原图步骤
④⑤
的
走向
,本人
改为
指向
灰色
部分)。
上电
初始程序依然从
0
x08000004
处
取出复位中断向量地址
,
执行复位中断函数后跳转到
IAP
的main
(标号
①
所示
)
,在
IAP的main函数
执行
完成后强制跳转到
0
x08000004+N+M处
(标号
②
所示),
最后跳转到
新的
main函数中来
(标号
③
所示)
,
当
发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,
再
跳转到新的中断服务函数中执行
(标号
④⑤
所示),执行
完中断函数后再返回到main函数中来
(标号
⑥
所示)。
对于步骤
④⑤
,
网友
认为
是:
“在
main执行的过程中
,
如果CPU
得到
一个中断请求,
PC
指针
仍
强制
跳转
到地址
0
x08000004
中断
向量表
处,
而不是新的中断向量表,
如
图标号
④
所示
,
程序
再根据
我们
设置的中断
向量表偏移量,
跳转
到
对应中断
源新
的中断服务
程序
中
,
如图标号
⑤
所示”。我对此
的理解是
:“当
发生中断后,程序从
0
x08000004(
旧
)
处的
中断向量表中
得到相应
的中断
服务
函数入口地址,继而跳转
到
相应的中断服务程序
”
。
但是
旧的中断向量列表里边存放的是
IAP
程序
中断
函数的入口地址,它是如何得到user程序中断函数的入口地址
呢
?所以
我
觉得
此种
说法是错误的。
“
当发生中断时PC指针强制会跳转
到0
x08000004
处”这种
说法并没有错,只是忽略了
后续
的一些
知识
要点
而
导致
这个
说法出现矛盾。
对于
步骤
④⑤
我认为
的是
,
在main函数的执行过程中,如果CPU得到一个中断请求,
PC
指针本
来应该
跳转到
0
x08000004
处
的中断向量表,
由于
我们设置
了
中断向量表偏移量为
N+M,因此PC
指针
被
强制
跳转
到
0
x08000004+N+M
处
的中断向量表中得到相应的中断
函数
地址
(待求证)
,再跳转到相应新的中断服务函数,执行结束后返回到main函数中来。
三
、
实现过程
STM32F103ZET6的Flash地址为
0x08000000
—
0x
0807ffff
共512
KB
,把
这512KB的空间
分为两块
,第一块大小为
32
KB
存放
BootLoader程序,
剩余
的空间存放用户程序
(根据
实际情况分配
这
两块空间的大小,BootLoader程序占用的空间越小越好
,
则BootLoader
地址
为
0x08000000
—
0x
08007fff
,
用户程序地址
为0x08008000
—
0x
0807ffff
。BootLoader
流程图
大致
应该如下:
1、初始化
时钟
。
2、
初始化中断向量表地址
。
3
、
初始化按键
。
(
使用
按键触发方式,
上电
时如果按键被按下则
进行
用户程序更新操作)
4、
初始化串口
。
5、
检测按键是否被按下
,
是则执行
步骤6,
否则执行步骤
10。
6
、
擦除用户程序
(擦除0x08008000
—
0x
0807ffff
地址
空间Flash
)。
7
、从
串口读取新的用户代码数据
,
把
代码
写入用户程序空间
。
8、
检测串口数据接收完毕?是
则
执行步骤
9,
否则
跳回
步骤
7。
9、用户
程序更新完毕,等待重新上电
或硬件
复位。
10
、
跳转到用户程序
(强制
将PC指针
跳转
到
0
x08008000+4
处)。
到这里首先
要解决的问题就有:
1
、如何
进行对STM32
的
Flash
进行
擦除和写入操作。
2
、
中断向量表偏移
如何
设置。
3
、如何改变
代码
存放
的地址空间
(因为BootLoader
要存放在
0x08000000处
,
用户
程序要存放在
0x08008000处,而
默认
的
代码存放的地址空间为
0x08000000)。
4
、
怎么进行PC指针的强制跳转,
跳转
时需要做些什么
。
5
、
串口接收
的
用户
代码
数据
是
什么样的
代码数据
,是一种什么样的文件。
问题
的
解决
:
1
、使用
STM32的固件库函数,只需调用几个库函数即可轻松解决
,
使用的
固件库
为stm32f10x_flash.c
文件,
对Flash的操作过程简要为:Flash解锁
à
Flash擦除
à
Flash写入
à
Flash
上锁。(对
Flash
编程
的
更
详细操作参考
S
TM32F10xxx
闪存编程手册
)
①
解锁
:
FLASH_Unlock(); //解锁Flash
FLASH_SetLatency(FLASH_Latency_2); //
因为
系统时钟为
72
M所以要设置两个时钟周期的延时
②
擦除
:
for(i=0;i<240;i++)
{
if(FLASH_ErasePage(FLASH_ADDR+i*2048) != FLASH_COMPLETE) //
一定
要判断是否擦除成功
return ERROR;
}
说明
:FLASH_ErasePage(uint32_t Page_Address)
即
为Flash擦除操作,按
页
擦除,每页
2
KB,Page_Address
为
页的起始地址,
如0
x08000000
是
第一页起始地址,
0
x08000800
为
第二页起始地址,这里
的
操作
擦除
了
0x08008000
—
0x
0807ffff
地址
空间的Flash。
③
写入
:
u
nsigned
char buf[1024]; //
假设待
写入的代码数据
unsigned short temp; //临时
数据
for(i=0;i<512;i++)
{
temp = (buf[2*i+1]<<8) | buf[2*i];
//2个
字节整合为1个半
字
if
(
FLASH_ProgramHalfWord(ADDR,temp) != FLASH_COMPLETE)
//判断
是否写入成功
{
Return ERROR;
}
ADDR +=2
; //地址
要加
2,
因为每次写入的是2个字节
(
1
个
半字
)
}
说明:因为
STM32的Flash写入为双字节
(
1
个
半字
)写入
,FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
函数
即为对
地址
为Address
写入
1
个
半字
的
Data,每次写入完成后地址要加
2。
④
上锁
:
FLASH_Lock(); //Flash
上锁
,
一个固件库
函数即可实现。
2、关于
中断向量表的
偏移
设置,对于BootLoader程序
只需
设置中断向量表
的
指向在
0x
08000000
处
,
对于
用户程序需要设置中断向量表的指向在
0x08008000处
即可
。
①
在
BootLoader程序的中断向量表指向设置
中
应有这么一句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //
设置
中断向量表指向
其中
NVIC_VectTab_FLASH
是
个宏定义,的值为
0x08000000。
②
在用户
程序
的
中断向量表指向设置用应有这么一句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); //
设置
中断向量表指向
3、
确认代码
存放
的地址空间,
在
IAR和在Keil中的设置是不同的,网上有在Keil中设置的方法,设立介绍在IAR
软件
环境下的设置方法。
①
在固件库
目录\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template\EWARM
下
找到一个stm32f10x_flash.icf
文件
,将其复制到工程目录中来
,
在
打开
IAR工程,
将配置
文件添加到工程中,如下图
3
-2所示
②
在
工程中打开stm32f10x_flash.icf该文件,修改两个参数即可改变代码存放的地址空间
,
图下图
3
-2
所示
。
4、
关于PC指针的强制跳转,想在BootLoader程序中
将
PC指针跳转到用户代码处
,
可选择下面的操作
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
#define ApplicationAddress 0x08008000
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
//
--------
①
{
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
//
--------
②
Jump_To_Application = (pFunction) JumpAddress;
//--------
③
__set_MSP(*(__IO uint32_t*) ApplicationAddress); //--------
④
Jump_To_Application();
//--------
⑤
}
①因为用户
程序开始
位置(
0x08008000
处)的
前
4个
字节存放的是堆栈的地址,堆栈地址必定是指向RAM空间的
,
而STM32
的
RAM空间
起始
地址为
0x20000000,
所以要
进行
判断。
②程序
跳转地址的确认,前面已经说过
0x08008004处
的
4个
字节存放的是复位函数的入口地址,该句
的
意思为获得
(ApplicationAddress + 4)
地址
处的数据,
即为获得
新的
复位函数入口地址。
③令
Jump_To_Application
这个
函数
指针
指向复位函数入口地址
。
④堆栈
的初始化,
重新
设定栈顶代
地址
,
把栈顶
地址设置为用户代码
指向
的栈顶地址
。
⑤跳转
到
新的
复位函数。
5、通过
串口来接收
代码
数据,
就是
PC机通过串口将代码数据发送到STM32中去。这里
就涉及
到两个问题:
①
数据
怎么得来
。
②
数据
传输的过程
需要
遵循的协议,什么时候开始,什么时候
结束
。
解决
①
:
一般我们就将*.hex
文件
使用JFlash-ARM打开再通过Jlink仿真器烧录到STM32
芯片
中,
但是*
.hex文件里边包含的数据不纯粹是代码数据还有一些别的东西,
而
*.
bin
文件数据就全部是代码数据。
在
IAR软件环境中打开一个用户工程,先设置好中断向量表偏移和代码存放的地址空间后
(前面
已介绍过
这
两种方法
)。设置工程
如下图
3
-3所示
,
确认后重新编译工程,在工程的\Debug\Exe
目录
下会相应生成一个xxx.bin文件
,
这就是所需要的代码文件。
②
数据
通过串口来传输文件常用的协议有XModem、YModem、ZModem这三种协议
,在
PC端使用这些协议传输文件只需要PC的超级终端或者终端工具SecureCRT
即可,但是在
STM32
这边
的编程会增加一些困难
(因为
要先去读懂、解析这些协议,在通过编程来实现
)。也可选择
自己定义一套
简单
的传输协议,
但
同样会有一些困难(
因为
要在PC
端
进行文件和串口编程)
。总之
不管通过什么办法都行,只要能将xxx.bin文件
数据通过
串口全部发送到STM32并且STM32
能
够全部接收到这些数据并写入Flash即可
(我
选择后者,自定义
传输
协议并用VC进行
文件
和串口编程)
。
四
、结束语
总的
来说
STM32的
IAP方案实现需要在
进行
用户程序
之前
加一段Bootloader程序,
BootLoader程序
的作用就是:
①
什么
都不做,直接跳转到用户程序。
②删除
原有的用户程序,
读取
*.bin
文件
数据
并
将数据重新写入新的用户程序。
对于
用户
程序
相比
普通
的编程只需要做
三步改动
即可
①改变
中断向量表。
②改变
代码存放的地址空间
③修改
生成*.bin
文件
使用
通过UART的IAP方案并不是很好的选择,这只是IAP方案的一个机制,因为能使用
PC
机通过串口升级程序,同样能通过Jlink烧写程序
,并且自定义
的串口通讯协议
在
没有校
CRC
校验的情况下
不能
及时发现数据传输
过程
发生的错误。
这里
推荐
使用
SD卡
(
或U盘
)
进行
用户
程序更新,
将
*.bin文件复制到SD卡
(
或U盘
)
中,
STM32
再通过读取SD卡
(
或U盘
)
的
*
.bin
文件
进行
用户
程序更新,这也避免了STM32
与
PC笨重的通讯,只需插一个SD卡(或U盘)更显得人性化一些,但需要去弄懂STM32
如何
与SD卡
(
或U盘
)
的通讯。