1.IAP介绍
IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对 Flash 部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。实现 IAP 功能时,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART )接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
- 1.检查是否需要对第二部分代码进行更新
- 2.如果不需要更新则转到4
- 3.执行更新操作
- 4.跳转到第二部分代码执行
我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果FLASH 容量足够,是可以设计很多APP 程序的,本章我们只讨论一个APP 程序的情况)
Note:
- 代码第一个字( 4bytes )为栈顶地址指向 RAM,RAM起始地址为 0x20000000
- 代码第二个字( 4bytes )为程序开始地址
代码内多处使用以上两个地址进行正确与否的判断,故希望读者可以先了解一下,代码中具有详细的讲解
篇幅有限,只贴出相对较为重要的代码,文章末尾有完整代码的链接可供下载
2.Bootloader程序
2.1. main.c
/*
**************************************************************
**
** 功能介绍: 上电启动3 秒内未收到APP 更新数据
** 自动跳转至APP 运行,如果3 秒内收到
** APP 更新数据,接收完成后将APP 代码
** 复制到指定地址并开始执行新的APP
**
** CPU : STM32F103ZET6
** FLASH : 512K 0x08000000起
** SRAM : 64K 0x20000000起
**
**************************************************************
*/
int main( void )
{
u16 oldcount = 0;
u16 applenth = 0;
u16 time = 0;
Stm32_Clock_Init(9);
delay_init(72);
uart_init(72,256000);
LED_Init();
LCD_Init();
KEY_Init();
POINT_COLOR=RED;
LCD_ShowString(50,50,200,16,16,"BootLoader program");
LCD_ShowString(50,80,200,16,16,"Update app program? ");
LCD_ShowString(50,100,200,16,16,"Yes->Updata");
LCD_ShowString(50,120,200,16,16,"N o->APP");
POINT_COLOR=BLUE;
while(1)
{
if(USART_RX_CNT)
{
time = 0;
if( oldcount==USART_RX_CNT )
{
applenth=USART_RX_CNT;
oldcount=0;
USART_RX_CNT=0;
printf("\r\nAPP 接收完成\r\n");
printf("APP SIZE = %dBytes\r\n",applenth);
printf("更新 APP...\r\n");
/*
** 1.判断新程序地址是否在FLASH 地址
** 2.USART_RX_BUF定义时也可以不指定地址,判断时使用k 与0xFF000000 与运算
** 3.k = (((u32)USART_RX_BUF[7])<<24)|(((u32)USART_RX_BUF[6])<<16)|(((u32)USART_RX_BUF[5])<<8)|(((u32)USART_RX_BUF[4]));
*/
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
{
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);
printf("更新完成...\r\n");
}
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
{
printf("APP 运行中...\r\n");
iap_load_app(FLASH_APP1_ADDR);
}
}
else
oldcount=USART_RX_CNT;
}else/* 3s 内未收到APP 更新程序自动跳转*/
{
time++;
if( time>300 )
{
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
{
printf("APP 运行中...\r\n");
iap_load_app(FLASH_APP1_ADDR);
}else
{
printf("无APP..\r\n");
}
}
}
delay_ms( 10 );
}
}
2.2. iap.h
#ifndef __IAP_H__
#define __IAP_H__
#include "sys.h"
typedef void (*iapfun)(void); /* 定义函数指针*/
#define FLASH_APP1_ADDR 0x08005000 /* 定义APP 程序在FLASH 中的存放地址*/
void iap_load_app(u32 appxaddr);
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen);
#endif
2.3. iap.c
iapfun jump2app;
u16 iapbuf[1024];
/*
***********************************************************
** appxaddr : 应用程序起始地址
** appbuf : 应用程序代码
** 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;
iapbuf[i++]=temp;
if( i==1024 ){
i=0;
STMFLASH_Write( fwaddr,iapbuf,1024 );
fwaddr+=2048;
}
}
if( i )
STMFLASH_Write( fwaddr,iapbuf,i );
}
/*
***************************************************
** appxaddr : 跳转到appxaddr 地址的代码运行
**
** note : appxaddr 存放的是用户程序Flash 的首
** 地址, (*(volatile u32*)appxaddr)的意
** 思是取用户程序首地址里面的数据
** ,这个数据就是用户代码的栈顶地址
** 栈顶地址指向RAM,而RAM的起始地址
** 是0x20000000,因此上面的判断语句执行
** 判断用户代码的栈顶地址是否落在
** 0x20000000~0x2001FFFF 区间中,这个区间的
** 大小为128K,一般情况下,我们使用的芯
** 片较多的落在<128K RAM 的区间
** 注意: 例程中判断栈顶位置是否合法使用的是
** 与0x2FFE0000 进行与运算, 笔者认为使用
** 0xFFFE0000 运算会更加严谨一些, 故修改
**
***************************************************
*/
void iap_load_app(u32 appxaddr) {
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000){ /* 检查栈顶位置是否合法*/
jump2app=(iapfun)*(vu32*)(appxaddr+4); /* 用户代码区第二个字为程序开始地址(复位地址) */
MSR_MSP(*(vu32*)appxaddr); /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
jump2app(); /* 跳转到APP. */
}
}
2.4. usart.c
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));
u16 USART_RX_CNT=0;/* 接收的字节数*/
void USART1_IRQHandler(void)
{
u8 res;
#ifdef OS_CRITICAL_METHOD
OSIntEnter();
#endif
if(USART1->SR&(1<<5))
{
res=USART1->DR;
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=res;
USART_RX_CNT++;
}
}
#ifdef OS_CRITICAL_METHOD
OSIntExit();
#endif
}
3. APP程序
3.1. main.c
/*
*********************************************************
** 功能介绍: 上电启动后,正常运行,如果需要
** 对此部分代码进行更新,需要按下
** KEY0 跳转到Bootloader 程序执行
*********************************************************
*/
int main(void)
{
u8 keyVal =0;
Stm32_Clock_Init(9);
delay_init(72);
uart_init(72,256000);
LED_Init();
LCD_Init();
KEY_Init();
POINT_COLOR=RED;
LCD_ShowString(40,50,200,16,16,"app program...");
LCD_ShowString(40,80,200,16,16,"Update app program? ");
LCD_ShowString(40,100,200,16,16,"Yes->Please pressed KEY0");
LCD_ShowString(40,120,200,16,16,"N o->None");
POINT_COLOR=BLUE;
while(1)
{
keyVal = KEY_Scan( 0 );
if( keyVal==KEY_RIGHT )
{
printf("请在 3 秒内发送新 APP...");
iap_load_app(BOOTLOADER_ADDR);
}
delay_ms(10);
}
}
3.2. iap.h
#ifndef __IAP_H__
#define __IAP_H__
#include "sys.h"
typedef void (*iapfun)(void); /* 定义函数指针 */
#define BOOTLOADER_ADDR 0x08000000 /* BootLoader 程序地址 */
void iap_load_app(u32 appxaddr);
#endif
3.3. iap.c
iapfun jump2app;
void iap_load_app(u32 appxaddr) {
jump2app=(iapfun)*(vu32*)(appxaddr+4);
MSR_MSP(*(vu32*)appxaddr);
jump2app();
}
4. 设置一哈,就完成啦
4.1. Bootloader程序
无需设置什么,但是读者需要知道自己的 Bootloader 所占空间大小,以设置 APP 的起始地址
笔者 Bootloader 所占大小为 Size = Code + RO-data + RW-data = 17612 bytes
4.2. APP 程序
上文计算 Bootloader 程序大小约为 17K 左右,故此处设置 APP 起始地址为 0x08005000,留给 Bootloader 20K空间
代码更新发送的是 BIN 文件,所以 APP 程序需要编译为 BIN 文件,设置如下,读者需要根据自己的安装路径进行修改
完整代码链接:
https://download.csdn.net/download/u010650845/10514286
读者可以通过串口助手运行上面的代码,以便更好的理解!!!