1.程序运行过程
IAP程序运行过程
1、 STM32复位后,从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,随后跳转至IAP程序的main函数。
2、 执行完IAP过程后(STM32内部多出了新写入的程序,地址始于0x8000004+N)跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数。新程序的main函数应该也具有永不返回的特性。同时应该注意在STM32的内部存储空间在不同的位置上出现了2个中断向量表。
3、 在新程序main函数执行的过程中,一个中断请求来临,PC指针仍会回转至地址为0x8000004中断向量表处,而并不是新程序的中断向量表,注意到这是由STM32的硬件机制决定的。
4、 根据中断源跳转至对应的中断服务,注意此时是跳转至了新程序的中断服务程序中。
5、 中断服务执行完毕后,返回main函数。
关于启动过程的内存关系可看stm32启动过程解析startup启动文件
2.实现
程序分为两个部分,一个是平时运行的程序叫做APP,内容和普通程序一样,另一个程序是IAP程序,用于接收新程序并更新写入指定位置。
2.1 APP部分
APP程序内容和普通程序一样,仅仅在编译器中设置它存放的位置
ROM这里起始地址常规程序是0x0800000,芯片复位后就会从这个向量表地址使用Reset_Handler,最后调用main函数,但是这个地方需要用来放IAP程序向量表,所以将APP程序的向量表地址改为其它,IAP程序留个0x10000或者0x8000字节就够了,0x10000==1x256x256=64KB,根据芯片Flash内存是否有这么大决定。
平时使用ISP时通常需要的是elf或者hex文件,IAP中使用的是bin二进制文件格式,因此添加命令生成bin文件
//前面是keil安装路径下的fromelf.exe命令,OBJ是存放axf文件的目录
D:\promgram_exe\keil5\armmdk\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\RTC.bin ..\OBJ\RTC.axf
编译生成bin文件即可,不可烧录
2.2 IAP部分
1. 使用uart串口接收bin文件,并保存在数组中,这个数组定义为:
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.位于sram中
2. 主函数main.c如下
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "usmart.h"
#include "gdflash.h"
#include "iap.h"
/************************************************
WKS GD32F427ZGT6核心板 实验37
串口IAP实验Bootloader-HAL库函数版
************************************************/
int main(void)
{
u8 t;
u8 key;
u16 oldcount=0; //老的串口接收数据值
u32 applenth=0; //接收到的app代码长度
u8 clearflag=0;
HAL_Init(); //初始化HAL库
GD32_Clock_Init(336,8,2,7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
uart_init(256000); //初始化USART
LED_Init(); //初始化LED
KEY_Init(); //初始化KEY
LCD_Init(); //初始化LCD
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"GD32F427 Core Board");
LCD_ShowString(30,70,200,16,16,"IAP TEST");
LCD_ShowString(30,90,200,16,16,"2023/7/18");
LCD_ShowString(30,110,200,16,16,"KEY_UP:Copy APP2FLASH");
LCD_ShowString(30,150,200,16,16,"KEY0:Run FLASH APP");
//显示提示信息
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(30,170,240,190,WHITE);//清除显示
}
}
key=KEY_Scan(0);
if(key==WKUP_PRES) //WK_UP按键按下
{
if(applenth)
{
printf("开始更新固件...\r\n");
printf("flash addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000);
LCD_ShowString(30,170,200,16,16,"Copying APP2FLASH...");
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000 || ((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000 )//判断是否为flash或sram合法地址
{
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//将接收到的bin文件更新到指定FLASH地址中
LCD_ShowString(30,170,200,16,16,"Copy flash APP Successed!!");
printf("固件更新完成!\r\n");
}
else
{
LCD_ShowString(30,170,200,16,16,"Illegal FLASH APP! ");
printf("非FLASH应用程序!\r\n");
}
}else
{
printf("没有可以更新的固件!\r\n");
LCD_ShowString(30,170,200,16,16,"No APP!");
}
clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示
}
if(key==KEY0_PRES) //KEY0按下
{
printf("开始执行FLASH用户代码!!\r\n");
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000 || ((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x20000000)//判断是否为0X08XXXXXX.
{ printf("开始执行FLASH用户代码++!!\r\n");
iap_load_app(FLASH_APP1_ADDR);//从指定地址加载、执行FLASH APP代码
printf("执行FLASH用户代码结束!!\r\n");
}
else
{
printf("非FLASH应用程序,无法执行!\r\n");
LCD_ShowString(30,170,200,16,16,"Illegal FLASH APP!");
}
clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示
}
}
}
3. iap.h
#ifndef __IAP_H__
#define __IAP_H__
#include "sys.h"
typedef void (*iapfun)(void); //定义一个函数类型的参数.
#define FLASH_APP1_ADDR 0x08010000 //第一个应用程序起始地址(存放在FLASH)
//#define FLASH_APP1_ADDR 0x20001000 //第一个应用程序起始地址(存放在SRAM)
//保留0X08000000~0X0800FFFF的空间为Bootloader使用(共64KB)
void iap_load_app(u32 appxaddr); //跳转到APP程序执行
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen); //在指定地址开始,写入bin
#endif
4. iap.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "gdflash.h"
#include "iap.h"
iapfun jump2app;
u32 iapbuf[512]; //2K字节缓存
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{//在主程序中这样调用:iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth)
//即将保存在USART_RX_BUF数组中bin文件写入指定的FLASH或SRAM地址
u32 t;
u16 i=0;
u32 temp;
u32 fwaddr=appxaddr;//当前写入的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=4)
{
temp=(u32)dfu[3]<<24;
temp|=(u32)dfu[2]<<16;
temp|=(u32)dfu[1]<<8;
temp|=(u32)dfu[0];
dfu+=4;//偏移4个字节
iapbuf[i++]=temp;
if(i==512)
{
i=0;
GDFLASH_Write(fwaddr,iapbuf,512);
fwaddr+=2048;//偏移2048 512*4=2048
}
}
if(i)GDFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.
}
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000 || ((*(vu32*)appxaddr)&0xFF000000)==0x08000000) //检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
}
}
5. 串口中断程序
//串口1中断服务程序
void USART1_IRQHandler(void)
{
u8 Res;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))
{
HAL_UART_Receive(&UART1_Handler,&Res,1,1000);
//Res=USART1->DR;
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=Res;//接收到的bin文件存放在这个数组中
USART_RX_CNT++;
}
}
HAL_UART_IRQHandler(&UART1_Handler);
#if SYSTEM_SUPPORT_OS //使用OS
OSIntExit();
#endif
}
#endif
6. IAP程序的地址设置不变
2.3 串口发送bin文件
打开串口后点击发送文件,打开bin文件
发送文件延时设置这里需要改为连续发送,否则会分多次发送,则程序错误
注意需要一次性发完,如有中断则程序出错,重新发送,下图这样就是一次发送
下图就是被中断分两次发送,错误了,重新发送
2.4 运行
上电后IAP程序运行,这个过程中串口接收到bin数据保存到数组中,按下WK_UP按键开始将数组中的程序写到指定位置,按下KEY0按键后运行
1. 程序来源:惠勤志远GD32F427开发板实验37串口IAP实验
2.路径E:\document\gd32\GD32F427\3_GD32F427SourceCode\StandardCode-HAL\Task37-IAP_SerialPort
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/tao_sc/article/details/143865644
|
|