完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
概念
上图是keil在工程编译时会提示的信息:“Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,意义如下:
可以查看内存分布详情和程序大小,如下图,程序大小可以用来参考芯片选型。 接下来我们仔细查看 .map文件,先观察内存(RAM)分布。 上图是内存RAM的分布,正如文章开始所诉,MDK-ARM编译器将RAM分成4个区域,分别为data区、bass区、堆区、栈区,每次上电启动的时候,STM32将FLASH中的RW-data复制到RAM中的data区,而bss区则初始化为0。看图便知,data区的内容是从ROM地址区复制过来的。RAM的起始地址为0X20000000,ROM的起始地址为0X08000000,这个可以查阅STM32中文参考手册存储器章节。当然这个起始地址可以修改,在制作Bootloader和应用APP程序时可以通过MDK-RAM中option->target->IROM和option->target->IRAM选项来修改,后面制作Bootloader程序再说。话归正传,FLASH ROM的分布如下,从起始地址先存放中断向量表,后代码、常量、RW-data。 STM32启动流程 我们先来看看 STM32 正常的程序运行流程,如下图所示: STM32 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 STM32 是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,STM32 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。 在上图中,STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的 main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断) ,此时 STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。 接下来我们打开启动文件 startup_stm32f10x_hd.s,如下图,文件中最上面定义了堆和栈的大小,栈为0X00000400(1K字节),堆为0X00000200(512字节),如果需要,你可以把栈改大点。 如下图,启动文件中罗列了中断向量表。注意中断向量表中的地址指的是偏移地址,如果程序从0X08000000存放,则0X08000000中存放的是栈顶地址,0X08000004中存放的是复位中断函数入口地址。如果程序从0X08001000存放,则0X08001000中存放的是栈顶地址,0X08001004中存放的是复位中断函数入口地址。另外启动文件中还申明了中断服务函数名,我们在编写自己的中断服务函数名时,必须跟这个名字一样。 重点!!!,一起来看芯片是如何启动运转起来的。下图是启动文件中的复位中断函数,当你按下复位键的那一刻,程序就从这里开始执行,SystemInit 函数用于设置系统时钟、滴答定时器等等M3内核的东西,不多介绍;随后是跳转到 __main 函数,这个函数是我们自己编写的 main 函数吗?答案不是的!!!在运行用户编写的 main 函数之前,芯片还有很多事要做,但在工程里找不到这个函数的身影。 还是回到那个 .map文件,搜索 __main 就会发现,__main 与 main 之间还有好多函数,这些scatterload大概意思就是将ROM中存放的RW-data数据复制到RAM中的data区,并且初始化RAM中的bass区,为程序运行做准备工作。RW-data(初值不为0的全局变量和静态变量)是尾随代码一起下载到FLASH ROM中的。 实践:栈区(stack),由编译器自动分配和释放,存放函数的参数、局部变量等。 前面知道了,我们的编写的代码存放在FLASH ROM中,初值不为0的全局变量和静态变量最开始从ROM中加载到RAM中的data区,初值为0的全局变量和静态变量被安置在RAM中的bss区。不管data区还是bss区,其内容本身就是我们定义的各种各样的全局变量或静态变量,其生存周期为永远,直到芯片掉电关机。我还想一探究竟局部变量是怎样存放的?下面做个实验: 编译工程,打开debug选项。如下图所示,准备调用 LCD_ShowNum() 函数,但先得调用 my_mem_perused() 函数,通过汇编窗口可以看到 my_mem_perused() 函数的入口地址为0X080076C 我们进入到my_mem_perused() 函数,如下图所示。MDK编译器把C语言翻译成汇编,第一步进行了PUSH入栈保护操作,这就是编译器自动操作栈空间。在函数内部,定义了两个局部变量,一个 used ,一个 i ,但编译器用的通用寄存器来保存这两个局部变量,起原因有二:第一、两个局部变量通用寄存器够用;第二、用通用寄存器更快,因为通用寄存器在CM3的内核中,CPU访问起来很方便。CM3 拥有12个通用寄存器,有关汇编指令和CM3寄存器组的知识可以查阅Cortex-M3权威指南。所以这里还体现不出局部变量存放在栈空间当中。 我在my_mem_perused() 函数又多定义了18个局部变量,这下通用寄存器就不够用了吧,看你编译器怎么安排,vu8是告诉编译器忽略优化的字节类型。见下图,现实入栈操作,然后栈顶指针SP自减48H(十进制72),用来保存局部变量。由于我没有对局部变量初始化,所以汇编指令并没有对相应的栈空间赋值,SUB sp,sp,#0x48 这句指令实际上就是在为局部变量开辟栈空间,看上去比较隐晦。后面的汇编操做看图都懂了,当然在最后有释放栈空间和POP出栈操作,太长截屏不下。 于是乎,栈区(stack)确实由编译器自动分配和释放,存放函数的参数、局部变量等,这加深了我对内存分配和STM32怎样运作的印象。 还说说堆区,在电脑上写C,用malloc函数就能申请堆空间,那是因为有操作系统和内存管理的加持。而在STM32的裸奔程序上,我还没有用到过堆空间。正点原子的例程有内存管理实验,那实际上是定义了一个超级大的数组(未初始化),也能实现内存申请和释放,但定义的大数组实际上存在于前面所诉的bss区,并没有使用到堆区。 制作Bootloader 当加入 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; 下面演示设计一个FLASH的APP程序。 1、程序起始地址的设置方法: 打开一个实例工程,点击 Options for Target→Target 选项卡,如下图所示: 默认的条件下, 图中 IROM1 的起始地址 (Start) 一般为 0X08000000, 大小 (Size) 为 0X80000,即从 0X08000000 开始的 512K空间为我们的程序存储 (因为我们的 STM32F103ZET6 的FLASH大小是 512K) 。而图中,我们设置起始地址(Start)为 0X08010000,即偏移量为 0X10000(64K字节) ,因而,留给 APP 用的 FLASH 空间(Size)只有 0X80000-0X10000=0X70000(448K 字节)大小了。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。这里的 64K 字节, 需要大家根据 Bootloader 程序大小进行选择, 比如我的 Bootloader程序为35K左右,如下图。理论上我们只需要确保APP起始地址在Bootloader之后, 并且偏移量为0X200的倍数即可。 在系统启动的时候,会首先调用 systemInit 函数初始化时钟系统,同时systemInit 还完成了中断向量表的设置,我们可以打开 systemInit 函数,看看函数体的结尾处有这样一句指令SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;,意思是设置中断向量表的偏移地址。注释写到,偏移地址VECT_TAB_OFFSET的值需是0X200的倍数,如下图所示。 中断向量表的偏移设置方法: 从 代 码 可 以 理 解 , VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址 。 默 认 的 情 况VECT_TAB_SRAM 是没有定义,所以执行 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 对于 FLASH APP,我们设置为 FLASH_BASE+偏移量 0x10000,所以我们可以在 FLASH APP 的main 函数最开头处添加如下代码实现中断向量表的起始地址的重设: SCB->VTOR = FLASH_BASE | 0x10000; 如下图: 另外还剩bin文件的制作,参考原子哥的实验教程。 软件设计 我们需要 2个程序:1,Bootloader;2,FLASH APP;其中,Bootloader (起始地址为0X08000000),大小为34.83kB ;FLASH APP 程序(起始地址为0X08010000,偏移地址64kB,前面64kB空间足够存放Bootloader程序) 。 iap.c, 代码如下: #include "sys.h" #include "delay.h" #include "usart.h" #include "stmflash.h" #include "iap.h" // //本程序只供学习使用,未经作者许可,不得用于其它任何用途 //ALIENTEK战舰STM32开发板 //IAP 代码 //正点原子@ALIENTEK //技术论坛:www.openedv.com //修改日期:2012/9/24 //版本:V1.0 //版权所有,盗版必究。 //Copyright(C) 广州市星翼电子科技有限公司 2009-2019 //All rights reserved // typedef void (*iapfun)(void); //取别名,iapfun 就代表定义函数指针的关键字 iapfun jump2app; //定义一个函数指针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[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)&0xFFFF0000)==0x20000000)//检查栈顶地址是否合法。for STM32F103ZET6 RAM地址范围0x20000000-0x2000ffff //(vu32*)appxaddr是将地址强制转换为32位类型的指针, //*(vu32*)appxaddr是对指针进行解引用,就是取值,实际上取到的值就是用户代码的栈顶地址, //前面内存分布描述过,用户代码第一字(4字节)存放的就是栈顶地址, //栈顶地址是由编译器赋值的,它取决于你的代码占用RAM的大小,即data区大小+bss区大小+堆大小+栈大小 { jump2app=(iapfun)*(vu32*)(appxaddr+4);//用户代码区第二个字(四字节)存放的复位中断向量,它指向复位中断函数入口地址 //(vu32*)(appxaddr+4)是将地址强制转换为32位类型的指针, //*(vu32*)(appxaddr+4)是对指针进行解引用,就是取值,实际上取到的值就是中断函数入口地址, //(iapfun)*(vu32*)(appxaddr+4)是将取到的中断函数入口地址强制转换为一个(无入口参数,无返回类型)函数指针 MSR_MSP(*(vu32*)appxaddr);//初始化APP堆栈指针。*(vu32*)appxaddr为栈顶指针 jump2app();//函数指针调用方法:函数指针名+(),即跳转到FLASH APP(新的用户代码) } } 跳转代码看的有点蒙的可以结合下面这张图来理解 可能你还对这句代码有所不解MSR_MSP(*(vu32*)appxaddr); 下面是这个函数的定义,它设计到的是汇编指令了,如下图。为什么要重新设置栈顶指针?仔细想想,你的Bootloader是烧录在FLASH里开头的一段代码(起始地址为0x08000000),开机启动后进入复位中断函数,先调用SystemInit()函数,再调用__main,在__main函数里会对RAM进行排兵布阵,比如从ROM中加载RW-data到RAM的data区,还有初始化bss空间,并赋值成0。显然Bootloader占用RAM的大小取决于你的Bootloader程序里定义了多少全局变量和静态变量,以及堆空间和栈空间定义多大。 假想现在我要跳转至FLASH APP(起始地址为0x08010000)运行我的APP程序,首先是进入复位中断函数,操作流程和上面一样,在APP的__main函数里肯定会重新排兵布阵我的RAM空间,如果不把栈顶地址修改为APP的栈顶地址,那不就乱套了吗,所以说这里要修改栈顶地址。怎样理解下面这两行汇编代码,往下看。 //设置栈顶地址 //addr:栈顶地址 __asm void MSR_MSP(u32 addr) { MSR MSP, r0 //set Main Stack value //写入R0值到主堆栈中 BX r14 } 预备知识:MSR指令、MSP主堆栈指针、R14连接寄存器、BX指令 看完预备知识就有点明白了,MSR MSP, r0 是将R0寄存的内容写入主堆栈寄存器MSP中,BX r14 是返回调用 MSR_MSP(u32 addr) 函数的地方。 那么不禁好奇R0中的内容是什么?addr 这个形参好像没有使用到啊,难道说R0寄存器默认存放了形参?我们来验证一下吧。若函数只有一个形参,MDK编译器通常将通用寄存器R0来存放形参,例子看下面:delay_ms(10);这句C代码被翻译成了两条汇编指令,R0来存放形参10 ,真是拍案叫绝!恍然大悟! 再检验一下!看下面 真是妙啊!到这里相信你对STM32的启动流程非常明了。 实际上,如果你的Bootloader占用RAM空间,比APP程序所需的RAM空间大,即使你不修改栈顶指针MSP,那APP的程序也能正常运行,但这样显得不规范。就如下面这样 bootloader原先占用RAM的空间比APP程序所需的RAM空间大,我们注释掉修改栈顶指针这一条代码,测试APP程序依然可以正常运行,反之则崩溃。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1801 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1629 浏览 1 评论
1096 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
735 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1684 浏览 2 评论
1944浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
746浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
580浏览 3评论
602浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
565浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-27 22:28 , Processed in 0.858450 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号