| 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
 
 
 |