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 sizesCode (inc. data) RO Data RW Data ZI Data Debug Object Name140 56 128 0 128 1362 bsp_dma.o60 26 0 0 0 531 bsp_exti.o88 16 0 0 0 1389 bsp_led.o12 4 0 0 0 465 bsp_pwm2.o276 70 0 16 0 962 bsp_timer.o350 32 0 10 0 4498 bsp_usart.o16 0 0 0 0 506 bsp_wwdg.o64 28 0 0 0 531 main.o124 16 0 0 0 1887 misc.o36 8 392 0 1024 952 startup_stm32f40xx.o142 28 0 0 0 3647 stm32f4xx_dma.o32 10 0 0 0 1260 stm32f4xx_exti.o168 0 0 0 0 3286 stm32f4xx_gpio.o20 0 0 0 0 4082 stm32f4xx_it.o216 24 0 16 0 4626 stm32f4xx_rcc.o38 0 0 0 0 3354 stm32f4xx_tim.o308 8 0 0 0 5440 stm32f4xx_usart.o28 10 0 0 0 1107 stm32f4xx_wwdg.o276 34 0 0 0 306193 system_stm32f4xx.o----------------------------------------------------------------------2410 370 552 44 1152 346078 Object Totals0 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 Name0 0 0 0 0 0 entry.o0 0 0 0 0 0 entry10a.o0 0 0 0 0 0 entry11a.o4 0 0 0 0 0 entry12b.o8 4 0 0 0 0 entry2.o4 0 0 0 0 0 entry5.o0 0 0 0 0 0 entry7b.o0 0 0 0 0 0 entry8b.o8 4 0 0 0 0 entry9a.o30 0 0 0 0 0 handlers.o36 8 0 0 0 68 init.o0 0 0 0 0 0 iusefp.o30 0 0 0 0 68 llshl.o36 0 0 0 0 68 llsshr.o32 0 0 0 0 68 llushr.o26 0 0 0 0 80 memcmp.o2218 90 0 0 0 464 printfa.o0 0 0 4 0 0 stdout.o44 0 0 0 0 80 uidiv.o98 0 0 0 0 92 uldiv.o48 0 0 0 0 68 cdrcmple.o56 0 0 0 0 88 d2f.o334 0 0 0 0 148 dadd.o222 0 0 0 0 100 ddiv.o186 0 0 0 0 176 depilogue.o48 0 0 0 0 68 dfixul.o26 0 0 0 0 76 dfltui.o228 0 0 0 0 96 dmul.o38 0 0 0 0 68 f2d.o110 0 0 0 0 168 fepilogue.o----------------------------------------------------------------------3874 106 0 4 0 2044 Library Totals4 0 0 0 0 0 (incl. Padding)----------------------------------------------------------------------Code (inc. data) RO Data RW Data ZI Data Debug Library Name2574 106 0 4 0 988 mc_w.l1296 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 Totals6284 476 552 48 1152 342938 ELF Image Totals6284 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_regionER_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_regionER_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文件。