1.编译过程简介
- 编译:MDK软件使用的编译器是armcc和armasm, 它们根据每个c/c++和汇编源文件编译成对应的以“.o”为后缀名的对象文件(Object Code,也称目标文件), 其内容主要是从源文件编译得到的机器码,包含了代码、数据以及调试使用的信息。
- 链接: 链接器armlink把各个.o文件及库文件链接成一个映像文件“.axf”或“.elf”。
- 格式转换:一般来说Windows或Linux系统使用链接器直接生成可执行映像文件elf后,内核根据该文件的信息加载后, 就可以运行程序了,但在单片机平台上,需要把该文件的内容加载到芯片上, 所以还需要对链接器生成的elf映像文件利用格式转换器fromelf转换成“.bin”或“.hex”文件,交给下载器下载到芯片的FLASH或ROM中。
2.编译工具链
2.1环境变量
我们下载arm编译工具后,需要将目录添加到环境变了中。
- armcc用于把c/c++文件编译成ARM指令代码,编译后会输出ELF格式的O文件(对象、目标文件)。
- armasm是汇编器,它把汇编文件编译成O文件。
- armlink是链接器,它把各个O文件链接组合在一起生成ELF格式的AXF文件,AXF文件是可执行的,下载器把该文件中的指令代码下载到芯片后, 该芯片就能运行程序了;利用armlink还可以控制程序存储到指定的ROM或RAM地址。
- armar工具用于把工程打包成库文件。
- fromelf可根据axf文件生成hex、bin文件,hex和bin文件是大多数下载器支持的下载文件格式。
2.2使用fromelf工具生成bin文件
- HEX文件:这是一种ASCII编码的文件格式,它包含了以特定ASCII字符表示的十六进制数据。HEX文件通常较大,因为它们使用两个ASCII字符来表示一个字节的数据,并且包含了文件头、记录类型、地址字段等额外信息。
- BIN文件:这是一种二进制文件格式,只包含实际的二进制数据,没有额外的文本信息。BIN文件通常比HEX文件小,因为它们不包含任何ASCII编码的开销。
fromelf --bin --output ..\Output\F407_Demo.bin ..\Output\F407_Demo.axf
Build started: Project: F407_Demo
*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'D:\Keil_v5\ARM\ARM_Compiler_5.06u7\Bin'
Build target 'F407_Demo'
After Build - User command #1: fromelf --bin --output ..\Output\F407_Demo.bin ..\Output\F407_Demo.axf
"..\Output\F407_Demo.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:01
3.程序的组成、存储与运行
3.1CODE、RO、RW、ZI Data域及堆栈空间
在工程的编译提示输出信息中有一个语句“Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”, 它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候, 不同的域会呈现不同的状态,这些域的意义如下:
- Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到ROM区
- RO-data:Read Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在ROM区,因而程序不能修改其内容。 例如C语言中const关键字定义的变量就是典型的RO-data。
- RW-data:Read Write data,即可读写数据域,它指初始化为“非0值”的可读写数据,程序刚运行时,这些数据具有非0的初始值, 且运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容。例如C语言中使用定义的全局变量,且定义时赋予“非0值”给该变量进行初始化。
- ZI-data:Zero Initialie data,即0初始化数据,它指初始化为“0值”的可读写数据域, 它与RW-data的区别是程序刚运行时这些数据初始值全都为0, 而后续运行过程与RW-data的性质一样,它们也常驻在RAM区,因而应用程序可以更改其内容。例如C语言中使用定义的全局变量, 且定义时赋予“0值”给该变量进行初始化(若定义该变量时没有赋予初始值,编译器会把它当ZI-data来对待,初始化为0);
- ZI-data的栈空间(Stack)及堆空间(Heap):在C语言中,函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量, 退出时释放局部变量,归还内存空间。而使用malloc动态分配的变量属于堆空间。在程序中的栈空间和堆空间都是属于ZI-data区域的, 这些空间都会被初始值化为0值。编译器给出的ZI-data占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没有使用malloc动态申请堆空间, 编译器会优化,不把堆空间计算在内)。
3.2程序的存储与运行
RW-data和ZI-data它们仅仅是初始值不一样而已,为什么编译器非要把它们区分开?这就涉及到程序的存储状态了,应用程序具有静止状态和运行状态。 静止态的程序被存储在非易失存储器中,如STM32的内部FLASH,因而系统掉电后也能正常保存。但是当程序在运行状态的时候,程序常常需要修改一些暂存数据, 由于运行速度的要求,这些数据往往存放在内存中(RAM),掉电后这些数据会丢失。因此,程序在静止与运行的时候它在存储器中的表现是不一样的。
图中的左侧是应用程序的存储状态,右侧是运行状态,而上方是RAM存储器区域,下方是ROM存储器区域。
- 程序在存储状态时,RO节(RO section)及RW节都被保存在ROM区。
- 当程序开始运行时,内核直接从ROM中读取代码,并且在执行主体代码前, 会先执行一段加载代码,它把RW节数据从ROM复制到RAM, 并且在RAM加入ZI节,ZI节的数据都被初始化为0。加载完后RAM区准备完毕,正式开始执行主体程序。
编译生成的RW-data的数据属于图中的RW节,ZI-data的数据属于图中的ZI节。是否需要掉电保存,这就是把RW-data与ZI-data区别开来的原因, 因为在RAM创建数据的时候,默认值为0,但如果有的数据要求初值非0,那就需要使用ROM记录该初始值,运行时再复制到RAM。
STM32的RO区域不需要加载到SRAM,内核直接从FLASH读取指令运行。
- 当程序存储到STM32芯片的内部FLASH时(即ROM区),它占用的空间是Code、RO-data及RW-data的总和,所以如果这些内容比STM32芯片的FLASH空间大, 程序就无法被正常保存了。
- 当程序在执行的时候,需要占用内部SRAM空间(即RAM区),占用的空间包括RW-data和ZI-data。
在MDK中,我们建立的工程一般会选择芯片型号,选择后就有确定的FLASH及SRAM大小,若代码超出了芯片的存储器的极限, 编译器会提示错误,这时就需要裁剪程序了,裁剪时可针对超出的区域来优化。
3.3配置RAM和ROM大小
STM32F407ZGT6微控制器的SRAM和Flash大小如下:
- Flash:STM32F407ZGT6拥有最大1MB(1024KB)的Flash存储空间,用于存储程序代码和只读数据。Flash的地址范围从0x8000000开始。
- SRAM:该微控制器具备总共192KB的SRAM,其中包括:
- 112KB的主SRAM(SRAM1),地址从0x20000000开始,大小为0x20000即128KB。
- 64KB的CCM(核心耦合存储器)数据RAM,地址从0x10000000开始,这部分内存仅CPU可以访问,不适用于DMA操作。
- 备份SRAM,大小为4KB。
此外,STM32F407系列的芯片可以通过FSMC(灵活的静态存储控制器)扩展外部SRAM,例如使用型号为IS62WV51216的SRAM芯片,其容量为1MB(1024KB)。这为需要更多运行时数据存储空间的应用程序提供了扩展选项。
所以,
- 程序存储状态时占用的ROM区=Code+RO_data+RW_data不能够超过1024KB。
- 程序执行时占用RAM区=RW_data+ZI_data不能够超过128KB。
其中ZI_data包括初始值为0的全局变量、局部变量和动态分配的空间,局部变量属于栈空间,动态分配的空间属于堆空间。这两块空间也是可以单独配置的,在启动文件startup_stm32f40xx.s中。
4.map文件说明
在Listing目录下包含了*.map及*.lst文件,它们都是文本格式的。map文件是由链接器生成的,它主要包含交叉链接信息,查看该文件可以了解工程中各种符号之间的引用以及整个工程的Code、RO-data、 RW-data以及ZI-data的详细及汇总信息。它的内容中主要包含了“节区的跨文件引用”、“删除无用节区”、“符号映像表”、 “存储器映像索引”以及“映像组件大小”。
map文件的最后一部分是包含映像组件大小的信息(Image component sizes),这也是最常查询的内容。
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
140 56 128 0 128 1362 bsp_dma.o
60 26 0 0 0 531 bsp_exti.o
88 16 0 0 0 1389 bsp_led.o
12 4 0 0 0 465 bsp_pwm2.o
276 70 0 16 0 962 bsp_timer.o
350 32 0 10 0 4498 bsp_usart.o
16 0 0 0 0 506 bsp_wwdg.o
64 28 0 0 0 531 main.o
124 16 0 0 0 1887 misc.o
36 8 392 0 1024 952 startup_stm32f40xx.o
142 28 0 0 0 3647 stm32f4xx_dma.o
32 10 0 0 0 1260 stm32f4xx_exti.o
168 0 0 0 0 3286 stm32f4xx_gpio.o
20 0 0 0 0 4082 stm32f4xx_it.o
216 24 0 16 0 4626 stm32f4xx_rcc.o
38 0 0 0 0 3354 stm32f4xx_tim.o
308 8 0 0 0 5440 stm32f4xx_usart.o
28 10 0 0 0 1107 stm32f4xx_wwdg.o
276 34 0 0 0 306193 system_stm32f4xx.o
----------------------------------------------------------------------
2410 370 552 44 1152 346078 Object Totals
0 0 32 0 0 0 (incl. Generated)
16 0 0 2 0 0 (incl. Padding)
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Member Name
0 0 0 0 0 0 entry.o
0 0 0 0 0 0 entry10a.o
0 0 0 0 0 0 entry11a.o
4 0 0 0 0 0 entry12b.o
8 4 0 0 0 0 entry2.o
4 0 0 0 0 0 entry5.o
0 0 0 0 0 0 entry7b.o
0 0 0 0 0 0 entry8b.o
8 4 0 0 0 0 entry9a.o
30 0 0 0 0 0 handlers.o
36 8 0 0 0 68 init.o
0 0 0 0 0 0 iusefp.o
30 0 0 0 0 68 llshl.o
36 0 0 0 0 68 llsshr.o
32 0 0 0 0 68 llushr.o
26 0 0 0 0 80 memcmp.o
2218 90 0 0 0 464 printfa.o
0 0 0 4 0 0 stdout.o
44 0 0 0 0 80 uidiv.o
98 0 0 0 0 92 uldiv.o
48 0 0 0 0 68 cdrcmple.o
56 0 0 0 0 88 d2f.o
334 0 0 0 0 148 dadd.o
222 0 0 0 0 100 ddiv.o
186 0 0 0 0 176 depilogue.o
48 0 0 0 0 68 dfixul.o
26 0 0 0 0 76 dfltui.o
228 0 0 0 0 96 dmul.o
38 0 0 0 0 68 f2d.o
110 0 0 0 0 168 fepilogue.o
----------------------------------------------------------------------
3874 106 0 4 0 2044 Library Totals
4 0 0 0 0 0 (incl. Padding)
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Name
2574 106 0 4 0 988 mc_w.l
1296 0 0 0 0 1056 mf_w.l
----------------------------------------------------------------------
3874 106 0 4 0 2044 Library Totals
----------------------------------------------------------------------
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
6284 476 552 48 1152 342938 Grand Totals
6284 476 552 48 1152 342938 ELF Image Totals
6284 476 552 48 0 0 ROM Totals
==============================================================================
Total RO Size (Code + RO Data) 6836 ( 6.68kB)
Total RW Size (RW Data + ZI Data) 1200 ( 1.17kB)
Total ROM Size (Code + RO Data + RW Data) 6884 ( 6.72kB)
==============================================================================
5.sct分散加载文件
当工程按默认配置构建时,MDK会根据我们选择的芯片型号,获知芯片的内部FLASH及内部SRAM存储器概况, 生成一个以工程名命名的后缀为*.sct的分散加载文件(Linker Control File,scatter loading), 链接器根据该文件的配置分配各个节区地址, 生成分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置。
文件内容如下:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x10000000 0x00010000 {
.ANY (+RW +ZI)
}
}
我们的sct示例文件配置如下:程序的加载域为内部FLASH的0x08000000,最大空间为0x00100000;程序的执行基地址与加载基地址相同, 其中RESET节区定义的向量表要存储在内部FLASH的首地址,且所有o文件及lib文件的RO属性内容都存储在内部FLASH中; 程序执行时RW及ZI区域都存储在以0x20000000为基地址,大小为0x00020000的空间(128KB),这部分正好是STM32内部主SRAM的大小。
链接器根据sct文件链接,链接后各个节区、符号的具体地址信息可以在map文件中查看。
了解sct文件的格式后,可以手动编辑该文件控制整个工程的分散加载配置,但sct文件格式比较复杂,所以MDK提供了相应的配置选项可以方便地修改该文件, 这些选项配置能满足基本的使用需求,其实就是我们在上文中配置的RAM和ROM大小。
5.1控制文件分配到指定的存储空间
在弹出的对话框中有一个“Memory Assignment”区域(存储器分配),在该区域中可以针对文件的各种属性内容进行分配, 如Code/Const内容(RO)、Zero Initialized Data内容(ZI-data)以及Other Data内容(RW-data), 点击下拉菜单可以找到在前面Target页面配置的IROM1、IRAM1、IRAM2等存储器。 例如图中我们把这个bsp_led.c文件的OtherData属性的内容分配到了IRAM2存储器(在Target标签页中我们勾选了IRAM1及IRAM2), 当在bsp_led.c文件定义了一些RW-data内容时(如初值非0的全局变量),该变量将会被分配到IRAM2空间,配置完成后点击OK,然后编译工程, 查看到的sct文件。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x10000000 0x00010000 {
bsp_led.o (+RW)
.ANY (+RW +ZI)
}
}
虽然MDK的这些存储器配置选项很方便,但有很多高级的配置还是需要手动编写sct文件实现的, 例如MDK选项中的内部ROM选项最多只可以填充两个选项位置,若想把内部ROM分成多片地址管理就无法实现了; 另外MDK配置可控的最小粒度为文件,若想控制特定的节区也需要直接编辑sct文件。