STM32的IAP

来源:互联网 发布:视频片头软件 编辑:程序博客网 时间:2024/05/16 12:56


首先两个区是肯定的
我是这样做的,一个引导区,一个应用程序区,外扩一片flash(本来就需要外扩,只是在里面存放几个变量)
每次启动就去读flash特定的那几个变量(怕误操作,我定义了5个变量,当5个变量相加 == 5 的话就是需要更新程序)

如果需要更新的话直接跳转到App,App里接收文件保存在外部flash里(我连CRC校验一起保存),当全部接收完,校验通过后就置标志,然后Reset,boot区读标志,读程序,然后更新,最后关门放狗

另外,这种方式可以很容易做到断点续传...

不过我还做了些额外的工作,多次从boot区跳app区都没有成功运行的话,这个时候就表明应用程序区已经损坏,且没有心的数据更新可用,就进入Xmodem升级方式不停的从串口发送C,最后一根稻草,可以通过windows超级终端升级...

首先,每次启动都先进boot区.
其次,只有当数据接收完成后才置标志,复位自己.
再者,如果多次app区都没有成功运行的话才认为该进入Xmodem升级(同样需要一个变量记录,每次在正常引导后也就是App区将boot区的一个多次引导的变量清0)

最后,可以在外部flash里放个最简单的bin文件,当所有的手段都失效后直接恢复这个程序到app提示用户该更新程序...







几乎所有的同类书籍都介绍综合性的应用示例如“万年历 + 温度显示 + 闹钟响铃 + 计时表”这样的一个实时时钟范例或“STM32 + 音频解码 + 大容量存储方案”这样的MP3播放器范例。这些综合性实例的目的在于引领读者进行综合性实验,达到把单片机的基础模块整合运用的目的。这些实例普遍存在一种共同点,即“练手”意义要大于“实用”的意义。本文将讲述一个STM32的综合性应用示例,该示例将涉及到STM32微控制器的时钟系统、GPIO、定时器、中断系统、异步串口以及内置可编程flash等设备的应用,作为一个综合性实验的同时还具有很强的“实用”意义。这个示例就是STM32的IAP方案。
         IAP,全称是“In-Application
Programming”,中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口(如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道)对正在运行程序的微控制器进行内部程序的更新的技术(注意这完全有别于ICP或者ISP技术)。ICP(In-Circuit Programming)技术即通过在线仿真器对单片机进行程序烧写,而ISP技术则是通过单片机内置的bootloader程序引导的烧写技术。无论是ICP技术还是ISP技术,都需要有机械性的操作如连接下载线,设置跳线帽等。若产品的电路板已经层层密封在外壳中,要对其进行程序更新无疑困难重重,若产品安装于狭窄空间等难以触及的地方,更是一场灾难。但若进引入了IAP技术,则完全可以避免上述尴尬情况,而且若使用远距离或无线的数据传输方案,甚至可以实现远程编程和无线编程。这绝对是ICP或ISP技术无法做到的。某种微控制器支持IAP技术的首要前提是其必须是基于可重复编程闪存的微控制器。STM32微控制器带有可编程的内置闪存,同时STM32拥有在数量上和种类上都非常丰富的外设通信接口,因此在STM32上实现IAP技术是完全可行的。
         实现IAP技术的核心是一段预先烧写在单片机内部的IAP程序。这段程序主要负责与外部的上位机软件进行握手同步,然后将通过外设通信接口将来自于上位机软件的程序数据接收后写入单片机内部指定的闪存区域,然后再跳转执行新写入的程序,最终就达到了程序更新的目的。
在STM32微控制器上实现IAP程序之前首先要回顾一下STM32的内部闪存组织架构和其启动过程。STM32的内部闪存地址起始于0x8000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动。而这张“中断向量表”的起始地址是0x8000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。最后还需要知道关键的一点,通过修改STM32工程的链接脚本可以修改程序文件写入闪存的起始地址。
在STM32微控制器上实现IAP方案,除了常规的串口接收数据以及闪存数据写入等常规操作外,还需注意STM32的启动过程和中断响应方式。图1显示了STM32常规的运行流程。

(原文件名:1.jpg)  
图1
对图1解读如下:
1、        STM32复位后,会从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,如图1中标号○1所示。
2、        复位中断服务程序执行的最终结果是跳转至C程序的main函数,如图1中标号○2所示,而main函数应该是一个死循环,是一个永不返回的函数。
3、        在main函数执行的过程中,发生了一个中断请求,此时STM32的硬件机制会将PC指针强制指回中断向量表处,如图1中标号○3所示。
4、        根据中断源进入相应的中断服务程序,如图1中标号○5所示。
5、        中断服务程序执行完毕后,程序再度返回至main函数中执行,如图1中标号○6所示。
若在STM32中加入了IAP程序,则情况会如图2所示。

(原文件名:2.jpg)   
图2
对图2的解读如下:
1、        STM32复位后,从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,随后跳转至IAP程序的main函数,如图2中标号○1、○2所示。这个过程和图1相应部分是一致的。
2、        执行完IAP过程后(STM32内部多出了新写入的程序,图2中以灰色底纹方格表示,地址始于0x8000004+N+M)跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,其过程如图2的标号○3所示。新程序的main函数应该也具有永不返回的特性。同时应该注意在STM32的内部存储空间在不同的位置上出现了2个中断向量表。
3、        在新程序main函数执行的过程中,一个中断请求来临,PC指针仍会回转至地址为0x8000004中断向量表处,而并不是新程序的中断向量表,如图2中标号○5所示。注意到这是由STM32的硬件机制决定的。
4、        根据中断源跳转至对应的中断服务,如图2中标号○6所示。注意此时是跳转至了新程序的中断服务程序中。
5、        中断服务执行完毕后,返回main函数。如图2中标号○8所示。
从上述两个过程的分析可以得知,对将使用IAP过程写入的程序要满足2个要求:
1、新程序必须从IAP程序之后的某个偏移量为x的地址开始;
2、必须将新程序的中断向量表相应的移动,移动的偏移量为x;
而设置程序起始位置的方法是(keil uvision4集成开发环境)在工程的“Option for Target….”界面中的“Target”页里将“IROM”的“Start”列改为欲使程序起始的地方,如图3中将程序起始位置设为0x8002000。

(原文件名:3.jpg)  
图3
将中断向量表移动的方法是在程序中加入函数:
void NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset);
其中参数NVIC_VectTab为中断向量表起始位置,而参数Offset则为地址偏移量,如将中断向量表移至0x8002000处,则应调用该函数如下:
void NVIC_SetVectorTable(0x8000000, 0x2000);
同时有必要提醒读者注意的是,此函数只会修改STM32程序中用于存储中断向量的结构体变量,而不会实质地改变中断向量表在闪存中的物理位置,详情请研究该程序原型。
有了以上准备后就可以着手设计一个IAP方案了,如下:
1、STM32复位后,利用一个按键的状态进行同步,当按键按下时表示将要进行IAP过程;
2、IAP过程中,通过上位机软件向STM32的USART1设备发送所要更新的程序文件,STM32接收到数据后转而从0x8002000地址开始写入收到的数据;
3、STM32借助定时器来判断数据是否完全接收,完全接收后IAP过程结束;
4、再次复位后,跳转0x8002004地址开始运行新写入的程序;
最后提出几点注意事项:
1、具体实现的工程见附件;
2、利用IAP写入的程序文件最好是.bin格式的文件,但不能是.hex格式的文件;
3、向STM32发送程序文件时尽量慢一些,因为STM32对FLASH的写入速度往往跟不上通讯外设接口的速度;
4、建议在STM32和上位机之间设计一套握手机制和出错管理机制,这样可以大幅提高IAP的成功率;
5、附件中的IAP工程具体运行现象为,按着连接于GPIOA.0引脚上的按键后对STM32进行复位操作,若连接于GPIOA.4引脚上的LED被点亮则表示进入了IAP程序,等待从USART1接口传入欲更新的程序文件。程序文件更新完毕后,LED被熄灭。此时再度对STM32进行复位,就开始运行新写入的程序了。





通过串口USART实现STM32F10x的IAP在线升级


    STM32F10x的IAP在线升级功能非常实用——不必拆开设备的外壳,然后通过JLINK或是其它工具烧录程序。而是仅通过设备上的串口、USB或是CAN,即可完成程序的更新,实现固件升级。

    整个过程用到两个程序,一个IAP程序,它用来实现通过串口下载用户程序。另一个就是用户应用程序了。

    通过串口实现在线升级的原理如下:
    1、首先通过JLINK或其它工具将IAP程序下载至STM32的FLASH中,其占据的地址空间为0x800 0000到0x800 2fff,这段空间用来存放IAP程序,IAP代码大小不能超过(0x800 2fff - 0x800 0000 = 12K)。
    2、打开“超级终端”,按照IAP程序里设置的参数(波特率、数据长度、校验位等),对“超级终端”作相应设置。
    3、将设备断电,按住设备上的某一开关或是按键(这个可以在IAP程序里自已定义,下文有叙述)。然后给设备上电,这时“超级终端”上会显示操作界面,让用户选择:1、给STM32下载新的用户程序;2、将STM32内现存的用户程序上传至电脑;3、运行新下载的用户程序。出现这个界面后,就可以松开开关或是按键了。
    4、通过电脑键盘上的“1”、“2”、“3”这几个数字键选择你想执行的操作。若是希望下载,按下数字键“1”.接下来电脑屏幕上会弹出对话框,让你选择希望下载的二进制文件,以及传输协议,这个得选“Ymodem”协议。完成后按“确定”就开始下载了。
    
    
    再来看一下两个源程序里,用户根据自己的需要可能得改动的地方。    
    an2557.zip里面就是IAP程序
    这个程序里面,用户可能要改动的就是IAP的触发方式,即如何进入IAP来进行新的用户程序的下载。该源程序里定义的是一个连接至PB9的按键,当设备刚上电的时候,如果该按键按下,则进入IAP模式,否则跳至0x800 3000运行用户程序。如何触发IAP,用户可以根据自己的实际情况,自行定义。另外,这里面初始化了一个串口,即用来和PC通信的串口,波特率、数据长度、校验位等,也是这里设置的。
    再来看用用户应用程序里需要改动两个的地方。
    1、将中断向量表映射至0x800 3000处。在main函数里,待系统初始化后,可以通过这条语句即可实现:
       NVIC_SetVectorTable(0x08000000,0x00003000);
    2、将用户应用程序的起始地址设置为0x800 3000,在keil4中如下图所示:
      通过串口USART实现STM32F10x的IAP在线升级

    另外请注意,在线升级时只能选择BIN格式的二进制文件,可以通过H-JTAG里面的Convert工具,将Keil编译生成的HEX文件转换成BIN文件。

    以上就是通过串口实现STM32IAP在线升级的原理及过程。
    若有不清楚的地方请看an2557:STM32F10x in-application programming using the USART 这个PDF文档,里面有详细的说明。
    
an2557 Application Note.pdf下载地址:

http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/APPLICATION_NOTE/
CD00161640.pdf

an2557.zip下载地址:(IAP源代码)
http://www.st.com/internet/com/SOFTWARE_RESOURCES/SW_COMPONENT/FIRMWARE/an2557.zip


第四十八章串口IAP实验

IAP,即在应用编程。很多单片机都支持这个功能,STM32也不例外。在之前的FLASH模拟EEPROM实验里面,我们学习了STM32FLASH自编程,本章我们将结合FLASH自编程的知识,通过STM32的串口实现一个简单的IAP功能。

48.1IAP简介   

IAPIn Application Programming)即在应用编程,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,
第一个项目程序不执行正常的功能操作,而只是通过某种通信方式
(USBUSART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:  

1)检查是否需要对第二部分代码进行更新  

2)如果不需要更新则转到4  

3)执行更新操作  

4)跳转到第二部分代码执行  

第一部分代码必须通过其它手段,如JTAGISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新是再通过第一部分IAP代码更新。 

我们将第一个项目代码称之为Bootloader程序,第二个项目代码称之为APP程序,他们存放在STM32 FLASH的不同地址范围,一般从最低地址区开始存放Bootloader
紧跟其后的就是
APP程序(注意,如果FLASH容量足够,是可以设计很多APP程序的,本章我们只讨论一个APP程序的情况)。这样我们就是要实现2个程序:BootloaderAPP。 

STM32APP程序不仅可以放到FLASH里面运行,也可以放到SRAM里面运行,本章,我们将制作两个APP,一个用于FLASH运行,一个用于SRAM运行。

我们先来看看STM32正常的程序运行流程,如图48.1.1所示:



48.1.1 STM32正常运行流程图

      STM32的内部闪存(FLASH)地址起始于0x08000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”
来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是
0x08000004,当中断来临,STM32的内部硬
件机制亦会自动将
PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。

      在图48.1.1中,STM32在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们
main函数,如图标号②所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号⑤所示。

      当加入IAP程序之后,程序运行流程如图48.1.2所示:

      在图48.1.2所示流程中,STM32复位后,还是从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAPmain函数,
如图标号①所示,此部分同图
48.1.1一样;在执行完IAP以后(即将新的APP代码写入STM32FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至
新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的
main函数,如图标号②和③所示,同样main函数为一个
死循环,并且注意到此时
STM32FLASH,在不同位置上,共有两个中断向量表。

      main函数执行过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0X08000004中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置
的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回
main函数继续运行,如图标号⑥所示。

      通过以上两个过程的分析,我们知道IAP程序必须满足两个要求:

1) 新程序必须在IAP程序之后的某个偏移量为x的地址开始;

2) 必须将新程序的中断向量表相应的移动,移动的偏移量为x

本章,我们有2APP程序,一个为FLASHAPP,程序在FLASH中运行,另外一个位SRAMAPP,程序运行在SRAM中,图48.1.2虽然是针对FLASH APP来说的,但是在

SRAM里面运行的过程和FLASH基本一致,只是需要设置向量表的地址为SRAM的地址。

1.APP程序起始地址设置方法

随便打开一个之前的实例工程,点击Options for TargetàTarget选项卡,如图48.1.3所示:

48.1.3 FLASH APP Target选项卡设置

      默认的条件下,图中IROM1的起始地址(Start)一般为0X08000000,大小(Size)为0X80000,即从0X08000000开始的512K空间为我们的程序存储(因为我们的STM32F103ZET6
FLASH
大小是512K)。而图中,我们设置起始地址(Start)为0X08010000,即偏移量为0X1000064K字节),因而,留给APP用的FLASH空间(Size)只有0X80000-0X10000=0X70000
448K字节)大小了。设置好StartSzie,就完成APP程序的起始地址设置。

      这里的64K字节,需要大家根据Bootloader程序大小进行选择,比如我们本章的Bootloader程序为22K左右,理论上我们只需要确保APP起始地址在Bootloader之后,
并且偏移量为
0X200的倍数即可(相关知识,请参考:http://www.openedv.com/posts/list/392.htm)。这里我们选择64K0X10000)字节,留了一些余量,方便Bootloader以后的升级修改。

      这是针对FLASH APP的起始地址设置,如果是SRAM APP,那么起始地址设置如图48.1.4所示:


48.1.4 SRAM APP Target选项卡设置

      这里我们将IROM1的起始地址(Start)定义为:0X20001000,大小为0XA00040K字节),即从地址0X20000000偏移0X1000开始,存放APP代码。因为整个STM32F103ZET6
SRAM大小为64K字节,所以IRAM1SRAM)的起始地址变为0X2000B0000x20001000+0xA000=0X2000B000),大小只有0X500020K字节)。这样,整个STM32F103ZET6SRAM
分配情况为:最开始的
4KBootloader程序使用,随后的40K存放APP程序,最后20K,用作APP程序的内存。这个分配关系大家可以根据自己的实际情况修改,不一定和我们这里的设
置一模一样,不过也需要注意,保证偏移量为
0X200的倍数(我们这里为0X1000)。

      2.中断向量表的偏移量设置方法

      之前我们讲解过,在系统启动的时候,会首先调用systemInit函数初始化时钟系统,同时systemInit还完成了中断向量表的设置,我们可以打开systemInit函数,看看函数体的结尾处有这样几行代码:

#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

从代码可以理解,VTOR寄存器存放的是中断向量表的起始地址。默认的情况VECT_TAB_SRAM是没有定义,所以执行SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;  

对于FLASH APP,我们设置为FLASH_BASE+偏移量0x10000,所以我们可以在FLASH APPmain函数最开头处添加如下代码实现中断向量表的起始地址的重设:

SCB->VTOR = FLASH_BASE | 0x10000;

      以上是FLASH APP的情况,当使用SRAM APP的时候,我们设置起始地址为:SRAM_bASE+0x1000,同样的方法,我们在SRAM APPmain函数最开始处,添加下面代码:

SCB->VTOR = SRAM_BASE | 0x1000;

      这样,我们就完成了中断向量表偏移量的设置。

通过以上两个步骤的设置,我们就可以生成APP程序了,只要APP程序的FLASHSRAM大小不超过我们的设置即可。不过MDK默认生成的文件是.hex文件,并不方便我
们用作
IAP更新,我们希望生成的文件是.bin文件,这样可以方便进行IAP升级(至于为什么,请大家自行百度HEXBIN文件的区别!)。这里我们通过MDK自带的格式转换
工具
fromelf.exe,来实现.axf文件到.bin文件的转换。该工具在MDK的安装目录\ARM\BIN40文件夹里面。

fromelf.exe转换工具的语法格式为:fromelf [options] input_file。其中options有很多选项可以设置,详细使用请参考光盘《mdk如何生成bin文件.pdf.

本章,我们通过在MDK点击Options for TargetàUser选项卡,在Run User Programs After Build/Rebuild 栏,勾选Run#1DOS16,并写入:D:\Keil3.80a\ARM\BIN40\fromelf.exe --bin -o  ..\OBJ\TEST.bin ..\OBJ\TEST.axf ,如图48.1.6所示:


      通过这一步设置,我们就可以在MDK编译成功之后,调用fromelf.exe(注意,我的MDK是安装在D:\Keil3.80A文件夹下,如果你是安装在其他目录,请根据你
自己的目录修改
fromelf.exe的路径),根据当前工程的TEST.axf(如果是其他的名字,请记住修改,这个文件存放在OBJ目录下面,格式为xxx.axf),生成一个

TEST.bin的文件。并存放在axf文件相同的目录下,即工程的OBJ文件夹里面。在得到.bin文件之后,我们只需要将这个bin文件传送给单片机,即可执行IAP升级。

      最后再来APP程序的生成步骤:

1)设置APP程序的起始地址和存储空间大小

对于在FLASH里面运行的APP程序,我们可以按照图48.1.3的设置。对于SRAM里面运行的APP程序,我们可以参考图48.1.4的设置。

2)设置中断向量表偏移量

这一步按照上面讲解,重新设置SCB->VTOR的值即可。

3)设置编译后运行fromelf.exe,生成.bin文件.

通过在User选项卡,设置编译后调用fromelf.exe,根据.axf文件生成.bin文件,用于IAP更新。 

以上3个步骤,我们就可以得到一个.binAPP程序,通过Bootlader程序即可实现更新。

大家可以打开我们光盘的两个APP工程,熟悉这些设置。

48.2硬件设计

本章实验(Bootloader部分)功能简介:开机的时候先显示提示信息,然后等待串口输入接收APP程序(无校验,一次性接收),在串口接收到APP程序
之后,即可执行
IAP。如果是SRAM APP,通过按下KEY0即可执行这个收到的SRAM APP程序。如果是FLASH APP,则需要先按下WK_UP按键,将串口接
收到的
APP程序存放到STM32FLASH,之后再按KEY2既可以执行这个FLASH APP程序。通过KEY1按键,可以手动清除串口接收到的APP程序。DS0用于指示程序运行状态。

本实验用到的资源如下:

1) 指示灯DS0

2) 四个按键(KEY0/KEY1/KEY2/WK_UP

3) 串口

4) TFTLCD模块

这些用到的硬件,我们在之前都已经介绍过,这里就不再介绍了。

48.3软件设计

本章,我们总共需要3个程序:1Bootloader2FLASH APP3SRAM APP;其中,我们选择之前做过的RTC实验(在第二十章介绍)来做为FLASH APP程序
(起始地址为
0X08010000),选择触摸屏实验(在第三十一章介绍)来做SRAM APP程序(起始地址为0X20001000)。Bootloader则是通过TFTLCD显示实验(在第
十八章介绍)修改得来。本章,关于
SRAM APPFLASH APP的生成比较简单,我们就不细说,请大家结合光盘源码,以及48.1节的介绍,自行理解。本章软件设计仅针对Bootloader程序。

打开本实验工程,可以看到我们增加了IAP组,在组下面添加了iap.c文件以及其头文件isp.h。打开iap.c代码如下:

#include "sys.h"

#include "delay.h"

#include "usart.h"

#include "stmflash.h"

#include "iap.h"     

iapfun jump2app;

u16 iapbuf[1024];

//appxaddr:应用程序的起始地址

//appbuf:应用程序CODE.

//appsize:应用程序大小(字节).

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)

{

      u16 t;

      u16 i=0;

      u16 temp;

      u32 fwaddr=appxaddr;//当前写入的地址

      u8 *dfu=appbuf;

      for(t=0;t<appsize;t+=2)

      {                                           

             temp=(u16)dfu[1]<<8;

             temp+=(u16)dfu[0];       

             dfu+=2;//偏移2个字节

             iapbuf[i++]=temp;     

             if(i==1024)

             {

                    i=0;

                    STMFLASH_Write(fwaddr,iapbuf,1024);   

                    fwaddr+=2048;//偏移2048 16=2*8.所以要乘以2.

             }

      }

      if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去. 

}

//跳转到应用程序段

//appxaddr:用户代码起始地址.

void iap_load_app(u32 appxaddr)

{

      if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)      //检查栈顶地址是否合法.

      {

             jump2app=(iapfun)*(vu32*)(appxaddr+4);        

//用户代码区第二个字为程序开始地址(复位地址)         

             MSR_MSP(*(vu32*)appxaddr);                               

//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)

             jump2app();    //跳转到APP.

      }

}

      该文件总共只有2个函数,其中,iap_write_appbin函数用于将存放在串口接收buf里面的APP程序写入到FLASHiap_load_app函数,则用于跳转到APP程序运行,
其参数
appxaddrAPP程序的起始地址,程序先判断栈顶地址是否合法,在得到合法的栈顶地址后,通过MSR_MSP函数(该函数在sys.c文件)设置栈顶地址,
最后通过一个虚拟的函数(
jump2app)跳转到APP程序执行代码,实现IAPàAPP的跳转。

      打开iap.h代码如下:

#ifndef __IAP_H__

#define __IAP_H__

#include "sys.h"   

typedef void (*iapfun)(void);     //定义一个函数类型的参数.  

#define FLASH_APP1_ADDR           0x080010000      

//第一个应用程序起始地址(存放在FLASH)

//保留0X08000000~0X0800FFFF的空间为Bootloader使用    

void iap_load_app(u32 appxaddr);                                                //跳转到APP程序执行

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen);  //在指定地址开始,写入bin

#endif

      这部分代码比较简单,。本章,我们是通过串口接收APP程序的,我们将usart.cusart.h做了稍微修改,在usart.h中,我们定义USART_REC_LEN55K字节,
也就是串口最大一次可以接收
55K字节的数据,这也是本Bootloader程序所能接收的最大APP程序大小。然后新增一个USART_RX_CNT的变量,用于记录接收到
的文件大小,而
USART_RX_STA不再使用。打开usart.c,可以看到我们修改USART1_IRQHandler部分代码如下:

//串口1中断服务程序

//注意,读取USARTx->SR能避免莫名其妙的错误      

u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));

//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.   

//接收状态

//bit15接收完成标志

//bit14接收到0x0d

//bit13~0    接收到的有效字节数目

u16 USART_RX_STA=0;             //接收状态标记     

u16 USART_RX_CNT=0;                  //接收的字节数     

void USART1_IRQHandler(void)

{

      u8 res;    

#ifdef OS_CRITICAL_METHOD

//如果OS_CRITICAL_METHOD定义了,说明使用ucosII.

      OSIntEnter();   

#endif

      if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收到数据

      {    

             res=USART_ReceiveData(USART1);

             if(USART_RX_CNT<USART_REC_LEN)

             {

                    USART_RX_BUF[USART_RX_CNT]=res;

                    USART_RX_CNT++;                                                                                   

             }

      }

#ifdef OS_CRITICAL_METHOD     

//如果OS_CRITICAL_METHOD定义了,说明使用ucosII.

      OSIntExit();                                                                               

#endif

}

      这里,我们指定USART_RX_BUF的地址是从0X20001000开始,该地址也就是SRAM APP程序的起始地址!然后在USART1_IRQHandler函数里面,将串口发
送过来的数据,全部接收到
USART_RX_BUF,并通过USART_RX_CNT计数。代码比较简单,我们就不多说了。

最后我们看看main函数如下:

int main(void)

{           

      u8 t,key;

      u16 oldcount=0;     //老的串口接收数据值

      u16 applenth=0;     //接收到的app代码长度

      u8 clearflag=0;

      uart_init(256000);      //串口初始化为256000

      delay_init();              //延时初始化

      LCD_Init();            //液晶初始化

      LED_Init();                //初始化与LED连接的硬件接口

     KEY_Init();                  //按键初始化      

POINT_COLOR=RED;//设置字体为红色

      LCD_ShowString(60,50,200,16,16,"Warship STM32");    

      LCD_ShowString(60,70,200,16,16,"IAP TEST");     

      LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");

      LCD_ShowString(60,110,200,16,16,"2012/9/24"); 

      LCD_ShowString(60,130,200,16,16,"WK_UP:Copy APP2FLASH");

      LCD_ShowString(60,150,200,16,16,"KEY1:Erase SRAM APP");

      LCD_ShowString(60,170,200,16,16,"KEY0:Run SRAM APP");

      LCD_ShowString(60,190,200,16,16,"KEY2:Run FLASH APP");

      POINT_COLOR=BLUE;

      //显示提示信息

      POINT_COLOR=BLUE;//设置字体为蓝色 

      while(1)

      {

            if(USART_RX_CNT)

             {

                    if(oldcount==USART_RX_CNT)

//新周期内,没有收到任何数据,认为本次数据接收完成.

                    {

                           applenth=USART_RX_CNT;

                           oldcount=0;

                           USART_RX_CNT=0;

                           printf("用户程序接收完成!\r\n");

                           printf("代码长度:%dBytes\r\n",applenth);

                    }else oldcount=USART_RX_CNT;                    

             }

             t++; delay_ms(10);

             if(t==30)

             {

                    LED0=!LED0; t=0;

                    if(clearflag)

                    {

                           clearflag--;

                           if(clearflag==0)LCD_Fill(60,210,240,210+16,WHITE);//清除显示

                    }

             }            

             key=KEY_Scan(0);

             if(key==KEY_UP)

             {

                    if(applenth)

                    {

                           printf("开始更新固件...\r\n");     

                           LCD_ShowString(60,210,200,16,16,"Copying APP2FLASH...");

                          if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)

//判断是否为0X08XXXXXX.

                           {           

iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,

applenth); //更新FLASH代码  

                                  LCD_ShowString(60,210,200,16,16,"Copy APP Successed!!");

                                  printf("固件更新完成!\r\n");      

                           }else

                           {

                                  LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP!  ");         

                                  printf("FLASH应用程序!\r\n");

                           }

                   }else

                    {

                           printf("没有可以更新的固件!\r\n");

                           LCD_ShowString(60,210,200,16,16,"No APP!");

                    }

                    clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示        

             }

             if(key==KEY_DOWN)

             {

                    if(applenth)

                    {                                                                                                             

                           printf("固件清除完成!\r\n");   

                           LCD_ShowString(60,210,200,16,16,"APP Erase Successed!");

                           applenth=0;

                    }else

                    {

                           printf("没有可以清除的固件!\r\n");

                           LCD_ShowString(60,210,200,16,16,"No APP!");

                    }

                    clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示                                                        

             }

             if(key==KEY_LEFT)

             {

                    printf("开始执行FLASH用户代码!!\r\n");

                    if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)

//判断是否为0X08XXXXXX.

                    {    

                           iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码

                    }else

                    {

                           printf("FLASH应用程序,无法执行!\r\n");

                           LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP!");      

                    }                                                            

                    clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示 

             }

             if(key==KEY_RIGHT)

             {

                    printf("开始执行SRAM用户代码!!\r\n");

                    if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)

//判断是否为0X20XXXXXX.

                    {    

                           iap_load_app(0X20001000);//SRAM地址

                    }else

                    {

                           printf("SRAM应用程序,无法执行!\r\n");

                           LCD_ShowString(60,210,200,16,16,"Illegal SRAM APP!");       

                    }                                                            

                    clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示

             }                            

              

      }             

}

      该段代码,实现了串口数据处理,以及IAP更新和跳转等各项操作。Bootloader程序就设计完成了,但是一般要求bootloader程序越小越好(给APP省空间嘛),
所以,本章我们把一些不需要用到的
.c文件全部去掉,最后得到工程截图如图48.3.1所示:


48.3.1 Bootloader工程截图

      从上图可以看出,虽然去掉了一些不用的.c文件,但是Bootloader大小还是有22K左右,比较大,主要原因是液晶驱动和printf占用了比较多的flash,如果大家
想进一步删减,可以去掉
LCD显示和printf等,不过我们在本章为了演示效果,所以保留了这些代码。

     至此,本实验的软件设计部分结束。

      FLASH APPSRAM APP两部分代码,我们在实验目录下提供了两个实验供大家参考,不过要提醒大家,根据我们的设置,FLASH APP的起始地址必须是0X08010000,而SRAM APP的起始地址必须是0X20001000

48.4下载验证

在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,得到,如图48.4.1所示:


48.4.1 IAP程序界面

此时,我们可以通过串口,发送FLASH APP或者SRAM APP到战舰STM32开发板,如图48.4.2所示:

48.4.2串口发送APP程序界面

      先用串口调试助手的打开文件按钮(如图标号1所示),找到APP程序生成的.bin文件,然后设置波特率为256000(为了提高速度,Bootloader程序将波特率被设置为256000了),
最后点击发送文件(图中标号
3所示),将.bin文件发送给战舰STM32开发板。

      在收到APP程序之后,我们就可以通过KEY0/KEY2运行这个APP程序了(如果是FLASH APP,则先需要通过WK_UP将其存入对应FLASH区域)。  




附件1:实验48 串口IAP实验-战舰STM32开发板.zip (文件大小: 2 MB 下载次数:2462次)AttachmentIcon







 

STM32 IAP在线升级

4442人阅读 评论(13)收藏 举报

         STM32很强大的一个功能是支持IAP在线升级,IAP(In-Application Programming),即在应用程序中编程 ",  通俗的来将是程序自己可以往程序存储器里写数据或修改程序。有了IAP功能, 即使在产品发布之后也可以方便的通过预留的通信端口(如串口、USB、IIC等)对产品中的程序固件进行更新升级,而无需通过传统的JTAG方式做烧录更新。IAP功能的固件一般包含两个部分:Boot UserApp其中Boot部分必须通过JATGISP进行烧录,APP部分可以在烧录BOOT后通过IAP升级烧入或者与BOOT合并到一起后通过JATGISP进行烧录。

         MCU上电后,首先运行BOOTBOOT起来后,做如下操作:

         1.APP部分做校验,如果校验失败,认为APP出现异常,自动切换到升级流程(流程3),反之,跳转到APP执行(流程4)

         2.检查升级标志,看是否需要升级,如果需要升级,进入升级流程(流程3),反之,跳转到APP执行(流程4)    

         3.执行升级流程,升级完成后重置升级标志并软件复位;

         4.跳转到APP执行,APP在需要升级时,写入升级标志并软件复位。

需要注意的是:如果BOOT程序被破坏,产品就只能通过JATG或ISP进行烧录了,这一点是不能容忍的,解决的方法是我们可以对BOOT区域设置成写保护。以禁止对BOOT区域进行编程或擦除操作。 

    在实现IAP之前,先了解一下STM32的存储器架构和启动过程:

    STM32的内部闪存地址起始于0x8000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张中断向量表来响应中断,程序启动后,将首先从中断向量表取出复位中断向量执行复位中断程序完成启动。而这张中断向量表的起始地址是0x8000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到中断向量表处,并根据中断源取出对应的中断向量执行中断服务程序。

   IAP功能设计:

   带IAP的功能有两个程序需要编写,一个是IAP(及BOOT)工程,一个是APP工程。设计将IAP放在STM32内部FLASH的0x80000000--0x80002000区域,大小为8K,APP放在0x80002000以后的区域,内部FALSH具体的大小由具体的芯片决定。

IAP部分核心代码如下:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //**********************************************************************************************     
  2. //  STM32F10x IAP OnlineUpdate Test-IAP Part  
  3. //  compiler: Keil UV3     
  4. //  2012-08-09 , By friehood     
  5. //**********************************************************************************************     
  6.   
  7. #define APP_ADDR           (0x08002000)                   // APP地址  
  8. #define APP_CRC_ADDR       ((u32 *)(APP_ADDR+28))         // APP CRC校验码存放地址,存放在中断向量表中第七个  
  9. #define APP_CRC            ((u32)(*(u32 *)APP_CRC_ADDR))  // APP CRC校验码  
  10. #define APP_LEN            ((u32)(*(u32 *)(APP_ADDR+32))) // APP长度,存放在中断向量表中第八个  
  11.   
  12. int main(void)  
  13. {  
  14.     void (**AppEntry)(void) = (void(**)(void))0x08002004; // APP mian函数地址  
  15.     void (**BootEntry)(void) = (void(**)(void))0x08000004;// BOOT mian函数地址  
  16.     u32 crcCode = 0;  
  17.     // 硬件初始化  
  18.     HWInit();  
  19.     // 对APP部分代码做CRC校验  
  20.     crcCode = GetCRC32((u8*)APP_ADDR,APP_LEN);  
  21.     // 读取升级标志,该标志存放在备份寄存器中  
  22.     g_bUpdateFlag = BKP_ReadBackupRegister(BKP_DR9);  
  23.     if(g_bUpdateFlag)  
  24.     {  
  25.         // 清除升级标志  
  26.         BKP_WriteBackupRegister(BKP_DR9,0x00);  
  27.     }  
  28.     // 判断时候需要做升级处理  
  29.     if(crcCode != APP_CRC || g_bUpdateFlag)  
  30.     {  
  31.         // APP校验失败或检测到APP升级时直接进入升级流程  
  32.         printf("go to boot mode,begin to upgrade..\n");                                  
  33.         // 进入升级流程  
  34.         UpdateProc();  
  35.         // 升级完成后跳转到BOOT,做校验检查  
  36.         BootEntry[0]();                         
  37.     }  
  38.     else  
  39.     {  
  40.         // 跳转到APP执行  
  41.         printf("goto app mode..\n");  
  42.         AppEntry[0]();    
  43.     }  
  44. }  

 

APP部分核心代码如下:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //**********************************************************************************************     
  2. //  STM32F10x IAP OnlineUpdate Test-APP Part  
  3. //  compiler: Keil UV3     
  4. //  2012-08-09 , By friehood     
  5. //**********************************************************************************************   
  6. int main(void)  
  7. {  
  8.     void (**BootEntry)(void) = (void(**)(void))0x08000004;// BOOT mian函数地址  
  9.     // 硬件初始化  
  10.     HWInit();  
  11.     while(1)  
  12.     {  
  13.         // 是否收到升级请求  
  14.         if(g_bRevUpdateReq)  
  15.         {  
  16.             // 设置升级标志  
  17.             BKP_WriteBackupRegister(BKP_DR9,0x01);  
  18.             // 跳转到BOOT执行  
  19.             BootEntry[0]();   
  20.         }  
  21.         Task1();      
  22.         Task2();  
  23.         //...  
  24.     }  
  25. }         
0 0