STM32学习笔记一一串口 IAP

发布者:温暖的拥抱最新更新时间:2019-01-09 来源: eefocus关键字:STM32  串口  IAP 手机看文章 扫描二维码
随时随地手机看文章

1. 简述


IAP(In-Application-Programming):应用编程,是应用在Flash程序存储器的一种编程模式,它可以在应用程序正常运行的情况下,通过调用特定的 IAP 程序对另外一段程序 Flash(User Flash) 空间进行读/写操作,甚至可以控制对某段、某页甚至某个字节的读/写操作。主要用于数据存储和固件升级。对于 IAP 应用,通常会有两个程序,第一个程序 Bootloader 程序不执行正常功能,只是通过某种方式(串口,usb,SD卡)接收第二个程序,并进行更新。第二个程序APP程序是执行的主体,用于实现应用功能。两部分项目代码都同时烧录在 User Flash 中。


执行流程如下:


在这里插入图片描述


第一部分代码(Bootloader 程序)必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码(APP 程序)可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序。


2 .STM32程序流程


2.1 STM32 正常的程序运行流程


下图为 STM32 正常的程序运行流程:


在这里插入图片描述


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


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


2.2 STM32 IAP程序运行流程


下图为 STM32 加入 IAP后的程序运行流程:


在这里插入图片描述


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


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


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


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

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


3 .IAP 更新固件地址设置方法


3.1 SRAM 中运行程序地址设置


如下图所示,我们可以查到 STM32 相关起始地址的设置:


在这里插入图片描述


此处将 IROM1 的起始地址(Start)定义为: 0X20001000,大小为 0XA000(40K 字节),即从地址 0X20000000 偏移 0X1000 开始, 之后的 40K 字节,用于存放 APP 代码。因为整个STM32F103RCT6 的 SRAM 大小为 48K 字节, 且偏移了 4K(0X1000), 所以 IRAM1(SRAM)的起始地址变为 0X2000B000(0X1000+0XA000),大小只有 0X1000(4K 字节)。这样,整个 STM32F103RCT6 的 SRAM 分配情况为:最开始的 4K 给 Bootloader 程序使用,随后的 40K 存放 APP 程序,最后 4K,用作 APP 程序的内存。


在这里插入图片描述


这里仅是示例,只要满足如下条件,可自行修改设置:

(1)保证偏移量为 0X200 的倍数(这里为 0X1000)。

(2) IROM1 的容量最大为 41KB(因为 IAP 代码里面接收数组最大是 41K 字节)。

(3) IROM1 的地址区域和 IRAM1 的地址区域不能重叠。

(4) IROM1 大小+IRAM1 大小,不要超过 44KB(48K-4K)。


3.2 FLASH 中运行程序地址设置

默认的条件下, IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X40000,即从 0X08000000 开始的 256K 空间为我们的程序存储区。如下图,设置起始地址(Start)为0X08010000,即偏移量为 0X10000(64K 字节),因而,留给 APP 用的 FLASH 空间(Size)只有0X80000-0X10000=0X30000(192K 字节)大小了。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。


在这里插入图片描述

这里的 64K 字节不是固定的,可以根据 Bootloader 程序大小进行不同设置, 理论上只需要确保 APP 起始地址在 Bootloader 之后,并且偏移量为 0X200 的倍数即可。 比如本章的 Bootloader 程序为 30K 左右,设置为 64K,还留有 20K 左右的余量供后续在 IAP 里面新增其他功能之用。


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

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


void SystemInit (void)

{

  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */

  /* Set HSION bit */

  RCC->CR |= (uint32_t)0x00000001;


  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */

#ifndef STM32F10X_CL

  RCC->CFGR &= (uint32_t)0xF8FF0000;

#else

  RCC->CFGR &= (uint32_t)0xF0FF0000;

#endif /* STM32F10X_CL */   

  

  /* Reset HSEON, CSSON and PLLON bits */

  RCC->CR &= (uint32_t)0xFEF6FFFF;


  /* Reset HSEBYP bit */

  RCC->CR &= (uint32_t)0xFFFBFFFF;


  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */

  RCC->CFGR &= (uint32_t)0xFF80FFFF;


#ifdef STM32F10X_CL

  /* Reset PLL2ON and PLL3ON bits */

  RCC->CR &= (uint32_t)0xEBFFFFFF;


  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x00FF0000;


  /* Reset CFGR2 register */

  RCC->CFGR2 = 0x00000000;

#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)

  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x009F0000;


  /* Reset CFGR2 register */

  RCC->CFGR2 = 0x00000000;      

#else

  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x009F0000;

#endif /* STM32F10X_CL */

    

#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)

  #ifdef DATA_IN_ExtSRAM

    SystemInit_ExtMemCtl(); 

  #endif /* DATA_IN_ExtSRAM */

#endif 


  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */

  /* Configure the Flash Latency cycles and enable prefetch buffer */

  SetSysClock();


#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 APP 的main 函数最开头处添加如下代码实现中断向量表的起始地址的重设:


SCB->VTOR = FLASH_BASE | 0x10000;

1

以上是 FLASH APP 的情况。


当使用 SRAM APP 的时候,设置起始地址为:


SCB->VTOR = SRAM_BASE | 0x1000;

1

这样, 就完成了中断向量表偏移量的设置。只要固件的程序大小不超过定义的大小,就可以通过 IAP 顺利更新代码。


注:


(1)IAP 更新程序烧写的是 bin 格式的文件,而不是 hex文件,区别请参见STM32学习笔记一一HEX文件和BIN文件格式


(2)bin文件生成:通过MDK自带的格式转换工具fromelf.exe,来实现.axf文件到.bin文件的转换。该工具在MDK的安装目录\ARM\ARMCC\bin(与KEIL的版本有关)文件夹里面。fromelf.exe转换工具的语法格式为:fromelf [options] input_file。


在这里插入图片描述


至此,我们就成功配置了 IAP 烧写程序的步骤了。


4 .软件实现

软件分为两部分:Bootloader + app程序,Bootloader 主要是配置串口接收数据,设置程序开始更新的地址,即 IAP 跳转地址处。app 程序就是具体的固件功能实现。


4.1 串口IAP 配置

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

#define FLASH_APP1_ADDR 0x08010000  //第一个应用程序起始地址(存放在FLASH)

//保留0X08000000~0X0800FFFF的空间为Bootloader使用(64KB)    

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

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

#endif


/////////////////////////////////////////////////////////////////////////////////////

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

{     

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.

}

}  


串口接收配置:


#define USART_REC_LEN  41*1024 //定义最大接收字节数 41K

1

//u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.

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; //接收的字节数  

  


#if EN_USART1_RX   //如果使能了接收

void USART1_IRQHandler(void)                //串口1中断服务程序

{

u8 Res;

#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

OSIntEnter();    

#endif

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)

{

Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据

if(USART_RX_CNT

{

USART_RX_BUF[USART_RX_CNT]=Res;

USART_RX_CNT++;      

}  

     } 

#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

OSIntExit();   

#endif


这样串口就可以接收数据了。我们在设计一个程序把 APP 程序的数据通过串口接收进行更新,就可以实现 IAP 的功能了。


这里举一个flash & sram运行的程序更新:


int main(void)

u8 t;

u8 key;

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

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

u8 clearflag=0; 

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2

delay_init();     //延时函数初始化   

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

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

  KEY_Init(); //按键初始化

   

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==200)

{

LED0=!LED0;

t=0;

}    

key=KEY_Scan(0);

if(key==WKUP_PRES) //WK_UP按键按下

{

if(applenth)

{

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

printf("Copying APP2FLASH...\r\n");

  if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.

{  

iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   

printf("Copy APP Successed!!");

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

}else 

{

printf("Illegal FLASH APP!  \r\n");    

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

}

  }else 

{

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

printf("No APP!\r\n");

}

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

if(key==KEY1_PRES)

{

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");

printf("Illegal FLASH APP!\r\n");    

}   

}

if(key==KEY0_PRES)

{

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

if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判断是否为0X20XXXXXX.

{  

iap_load_app(0X20001000);//SRAM地址

}else 

{

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

printf("Illegal SRAM APP!\r\n");    

}  

}   

 

}       

}


注:FLASH需写入数据,SRAM是直接运行的。对于在 STM32 的片上 FLASH 写入数据,请参看 :STM32学习笔记一一FLASH 模拟 EEPROM


对于app程序,我们只需在原先的程序上在main函数的入口处加上地址跳转和 IROM1、IRAM1 的起始地址和大小,就可以作为一个可以通过 IAP 更新的程序:

eg:flash上运行的APP。


在这里插入图片描述


至此,编译通过之后,就可以得到 APP程序的 BIN 文件,通过串口助手打开 即可下载。


在这里插入图片描述

关键字:STM32  串口  IAP 引用地址:STM32学习笔记一一串口 IAP

上一篇:STM32学习笔记一一待机唤醒
下一篇:STM32学习笔记一一HEX文件和BIN文件格式

推荐阅读最新更新时间:2024-03-16 16:22

技术文章—详解串口转换CAN模块“透明带标识转换”
UART转CAN的应用已广泛应用于各行各业,因此对于数据帧转换的形式要求也逐渐增多,目前主流的转换形式包括透明转换、透明带标识转换以及自定义转换。具体是如何实现?本文将为大家介绍其中的透明带标识转换。 1 . 适用场景 串口转CAN模块在什么时候需要用到呢?一是老产品面临升级,需要用到CAN总线通信,但硬件平台中的MCU没有集成CAN总线的控制器。二是选用的MCU已经包含CAN总线接口,但数量上不能满足项目需求。若出现类似以上两种情况且MCU有闲置串口,则可以选用串口转CAN模块解决。 图1 应用行业 2 . 使用方法 该类模块可以很方便地嵌入到具有UART接口的设备中,在不需改变原有硬件结构的前提下使设
[汽车电子]
技术文章—详解<font color='red'>串口</font>转换CAN模块“透明带标识转换”
STM32 禁用swd-jtag下载口后,重新下载程序的方法
由于工作需要,复用了PA15的时候,程序禁用swd-jtag功能,网上多方查找解决方法。 在重新上电的时候,保证BOOT0为高电平,BOOT1为低电平,即可禁止程序从烧写过的代码启动,这时,重新烧写程序即可。 STM32三种启动模式对应的存储介质均是芯片内置的,它们是: 1)用户闪存=芯片内置的Flash。 2)SRAM=芯片内置的RAM区,就是内存啦。 3)系统存储器=芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROROMM区。 在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位
[单片机]
FlyMcu串口ISP下载STM32程序教程
开始写之前也是不能用的,但是为了使用,决定边探索怎么使用边写一篇博客! 最开始用过正点原子的开发板进行ISP下载,感觉挺好用,按照说明就好。 但是正点原子的板子用的是如下的一块电路: 这个电路很简单使用,但是如果你不懂,也很纠结,这里感觉有篇帖子写的很详细,可供大家参考,就不自己解释了,链接如下: http://www.51hei.com/bbs/dpj-35947-1.html 但是,很多人会想用一个USB转串口的小模块下载程序,怎么弄呢? 首先有一个小串口模块,注意转换为的电平是3.3V,不是标准的232电平那种的,东西如下 之后呢,将RXD,TXD,GND,VCC缺一不可的连接到你的STM32板子
[单片机]
FlyMcu<font color='red'>串口</font>ISP下载<font color='red'>STM32</font>程序教程
stm32 启动过程
当前的嵌入式应用程序开发过程里,并且C语言成为了绝大部分场合的最佳选择。如此一来main函数似乎成为了理所当然的起点——因为C程序往往从main函数开始执行。但一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行main函数的呢?很显然微控制器无法从硬件上定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“Bootloader”。 无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都
[单片机]
分享一个STM32菜单框架
相信很多攻城狮都用过液晶屏,想写好一点的ui好像不太可能或且花费很多时间,直接写吧,感觉好像很零碎,coding都怕了。 下面介绍一个简单易用的菜单框架,你会发现它能做多层菜单而且结果清晰。 基本原理: 如上图液晶显示一屏我们定义为一个page,page中的项目定义为i te m;这样page就是item的容器了。当我们选中其中的一个item进去后是不是又是一个page呢,如下图。 这样的话每一个item的下面都对应一个page,这样是不是就构成一个多层的菜单了。 他们是什么关系呢? 一个page中有item,那么用结构体就可以实现啦;item下面又有page,那么在item中加一个page的指针指向item
[单片机]
分享一个<font color='red'>STM32</font>菜单框架
STM32_EXIT中断
今天讲解“STM32F103 EXIT中断”,关于EXIT中断里面有很多学问,以我的工作经验来看,有几点是特别容易犯错的,我会在文章最后重点讲解。 每次讲解的不仅仅是基础,而是重点,不起眼的重点,容易被人忽视的重点。关注微信公众号“EmbeddDeveloper”还有更多精彩等着你。 今天提供并讲解的软件工程,基于前面的软件工程“TIM延时”修改而来。若有疑问,请关注微信公众号获取更多信息。 本着免费分享的原则,将讲解的工程源代码分享给大家,还望看到的朋友分享、关注和推广一下微信公众号,增加一下人气。 每天提供下载的“软件工程”都是在硬件板子上进行多次测试、并保证没问题才上传至360云盘。 今天的软件工程下载地
[单片机]
STM32_EXIT中断
STM32 控制lcm液晶ILI9341驱动的液晶驱动程序
/* 选择BANK1-BORSRAM1 连接 TFT,地址范围为0X60000000~0X63FFFFFF * FSMC_A16 接LCD的DC(寄存器/数据选择)脚 * 16 bit = FSMC 对应HADDR * 寄存器基地址 = 0X60000000 * RAM基地址 = 0X60020000 = 0X60000000+2^16*2 = 0X60000000 + 0X20000 = 0X60020000 * 当选择不同的地址线时,地址要重新计算。 */ //#define Bank1_LCD_D ((u32)0x60020000) //Disp Data ADDR //#define Bank1_LCD_C ((u3
[单片机]
STM32学习笔记—DAC基础内容及常见问题
DAC,Digital-to-Analog Converter(数模转换器),DA转换和AD转换有着同样重要的作用,在许多场合都能看到DAC的应用。 今天是第8篇分享,《STM32学习笔记》之DAC基础内容及常见问题。 DA转换器是把数字量转变成模拟量的器件,按模拟量输出类型通常分为:电流和电压输出类型。常见的DAC是电压输出型,在STM32中集成的DAC转换模块为电压输出型数模转换器。 STM32 DAC 基础内容 STM32内部集成的DAC输出通道和功能与型号有关,一般有1到3个通道。 下面结合STM32F4描述一下DAC基本的参数信息: 1. DAC分辨率 分辨率决定了DAC的转换精度,目前STM32内部集成的DA
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved