整个工作过程
-
编译:
编译器是armcc(C文件代码)和armasm(汇编文件代码),它们根据每个c/c++和汇编源文件编译成对应的以".o"为后缀名的对象文件(Object Code,也称目标文件),其内容主要是从源文件编译得到的机器码,包含了代码、数据以及调试使用的信息。但是其中不包含 地址信息 -
链接:
链接器armlink把各个.o文件及库文件根据你在MDK中的芯片选型 地址信息设置 链接成一个映像文件".axf"或".elf"。这个".axf"文件是包含地址信息的。其中还会提示程序存储空间分配,这个具体下面再讲
Program Size: Code=14636 RO-data=576 RW-data=204 ZI-data=2732
- 格式转换:
一般来说Windows或Linux系统使用链接器直接生成可执行映像文件elf后,内核根据该文件的信息加载后,就可以运行程序了。但在单片机平台上,需要把该文件的内容加载到芯片上,所以还需要对链接器生成的elf映像文件利用格式转换器fromelf转换成“.bin”或“.hex”文件,交给下载器下载到芯片的FLASH或ROM中。
程序存储空间分配
应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候,不同的域存储、读写属性各不相同。主要分为几大类CODE、 RO、 RW、 ZI Data。在《程序员的自我修养》这本书里面讲了很多这种编译、链接类的知识。下面仅仅是针对于MCU的架构,指的是nor flash+sram的结构
-
CODE:代码段,这个是纯粹的代码,MCU就是执行这些二进制指令。在存储时代码段存放在nor flash中,在运行时代码段仍然在nor flash中,这跟PC等架构不一样。MCU内核直接从nor flash取指执行,不需要将CODE加载到RAM中
-
RO:只读数据区,指程序中用到的只读数据,这些数据被存储在ROM区,因而程序不能修改其内容。在存储时只读数据区在flash中,在运行时仍然在flash中,并不会被加载到sram中。比如
char *p="abc";
,其实abc字符串是存放在rom区的。一般情况下const定义的变量也是属于只读的存放在rom flash中。 -
RW:可读可写数据段,它指初始化为“非0值”的可读写数据,程序刚运行时,这些数据具有非0的初始值,且运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容。RW段在存储时放在flash中,在运行时被加载到sram中。如定义的全局变量(不初始化为0的)。
-
ZI Data:一般又称为BSS段。即0初始化数据(Zero Initialied Data),它指初始化为“0值”的可读写数据域,它与RW-data的区别是程序刚运行时这些数据初始值全都为0,而后续运行过程与RW-data的性质一样,它们也常驻在RAM区,因而应用程序可以更改其内容。在存储的时候并不占用flash中的资源,因为反正都是0,只需要记录有多少个0即可,在运行的时候会在SRAM中分配相应的大小的内存。
-
堆栈段:在存储的时候是不占用空间的(或者说就没有存储),在运行的时候有着自己的一套规则。
程序状态与区域 | 组成 |
---|---|
程序执行时的只读区域(RO) | Code + RO data |
程序执行时的可读写区域(RW) | RW data + ZI data |
程序存储时占用的ROM区 | Code + RO data + RW data |
MDK工具链
MDK就是将一系列工具如armcc、armlink、armar、 fromelf集成并做了友好的GUI界面,实际干活的还是这些软件工具。
下面介绍这些软件跟MDK设置界面的关系
首先添加环境变量,方便后续操作:如果是按MDK默认安装路径来的话,需要将fromelf的路径加入到环境变量,即C:\Keil_v5\ARM\ARMCC\bin
加入环境变量。
-
armcc :就是实际干活的编译器
在cmd窗口中输入armcc会提示一串用法,在MDK的Options For Targets->C/C+±>Compiler control string 窗口的一串字符串其实就是armcc编译时的选项,只不过这些选项是由MDK内置脚本自动生成的而已。
输入armcc --cpu list
可以看到当前版本armcc支持哪些内核的CPU -
armlink :就是实际干活的编译器
在cmd窗口中输入armlink会提示一串用法,在MDK的Options For Targets->Linker>Linker control string窗口的一串字符串其实就是armlink链接时的选项,只不过这些选项是由MDK内置脚本自动生成的而已。
链接器根据芯片的地址信息 和 armcc生成的.o文件来生成elf格式的axf可执行文件。
那么这个芯片的地址信息是哪里来的?原来在我们给芯片选型之后,MDK自动生成了一个.sct文件,这个也叫加载文件,链接器也就是根据.sct文件来确定链接地址的,不同芯片选型会生成不同的.sct文件。还有一篇文章将分散加载的博客,其中主要就是讲.sct文件的。 -
armar:是用于将工程文件打包成库文件的一个工具
在MDK中有Options for Targets->Output->Create Library选项,就是利用armar来生成库文件的。在你不想给对方源码,只想提供API接口的时候可以使用这种方法。 -
fromelf:可以根据elf格式的axf文件生成HEX、BIN文件
但是仅仅集成了hex选项,可以在Options for Targets->Output中看到。如果你想生成BIN文件怎么操作呢?当然第一种方法你可以在cmd命令行中根据已经生成的.axf文件利用fromelf工具来生成BIN文件;第二种方法是MDK中有Options for Targets->User窗口,里面就是让用户根据编译过程来添加自己的脚本命令的,其中分为了三个阶段,编译前、链接前、链接后,如果我们想利用.axf文件仅仅需要在连接后的里面添加脚本fromelf.exe --bin -o ..\OBJ\LED.bin ..\OBJ\LED.axf
即可
MDK工程文件类型详解
后缀 | 文件类型 |
---|---|
*.lib | 库文件 |
*.dep | 整个工程的依赖文件 |
*.d | 描述了对应.o的依赖的文件 |
*.crf | 交叉引用文件, 包含了浏览信息(定义、 引用及标识符) |
*.o | 可重定位的对象文件(目标文件) |
*.bin | 二进制格式的映像文件, 是纯粹的FLASH映像, 不含任何额外信息 |
*.hex | Intel Hex格式的映像文件, 可理解为带存储地址描述格式的bin文件 |
*.elf | 由GCC编译生成的文件, 功能跟axf文件一样, 该文件不可重定位 |
*.axf | 由ARMCC编译生成的可执行对象文件, 可用于调试, 该文件不可重定位 |
*.sct | 链接器控制文件(分散加载) |
*.scr | 链接器产生的分散加载文件 |
*.lnp | MDK生成的链接输入文件, 用于调用链接器时的命令输入 |
*.htm | 链接器生成的静态调用图文件 |
*.build_log.htm | 构建工程的日志记录文件 |
*.lst | C及汇编编译器产生的列表文件 |
*.map | 链接器生成的列表文件, 包含存储器映像分布 |
*.ini | 仿真、 下载器的脚本文件 |
*.uvprojx | 记录了整个工程的结构,如芯片类型、工程包含了哪些源文件等内容 |
*.uvoptx | 记录了工程的配置选项,如下载器的类型、变量跟踪配置、断点位置以及当前已打开的文件 |
*.uvguix | 记录了MDK软件的GUI布局,如代码编辑区窗口的大小、编译输出提示窗口的位置 |
HEX与BIN区别和联系
HEX文件是包括地址信息的,HEX文件都是由记录(RECORD)组成的。在HEX文件里面,每一行代表一个记录,而BIN文件格式只包括了数据本身,纯粹的二进制数据连大小端都没有,任何辅助信息都没有。HEX文件是用ASCII来表示二进制的数值。例如一般8-BIT的二进制数值0x5E,用ASCII来表示就需要分别表示字符’5’和字符’E’,每个字符需要一个BYTE,所以HEX文件需要 > 2倍的空间。对一个BIN文件而言,你查看文件的大小就可以知道文件包括的数据的实际大小。而对HEX文件而言,你看到的文件 大小并不是实际的数据的大小。一是因为HEX文件是用ASCII来表示数据,二是因为HEX文件本身还包括别的附加信息,如地址信息。
在烧写或下载HEX文件的时候,一般都不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。而烧写BIN文件的时候,用户是一定需要指定地址信息的(MDK下载的就是HEX文件,ESP8266自己编译固件产生bin文件下载的时候需要指定flash地址)。看图
MDK截图
Eclipse 和 ESP8266下载工具截图
HEX文件详解
下面是一个hex文件前几行
:020000040800F2
:10000000780B0020E1080008C9020008AB150008C1
:10001000CB020008CF020008D30200080000000055
:10002000000000000000000000000000D7020008EF
:10003000D90200080000000033150008AB360008A4
:10004000FB080008FB080008FB080008FB08000884
:10005000FB080008FB080008FB080008FB08000874
:10006000FB080008FB080008FB080008FB08000864
每行是以:开始,代表一条记录,一条记录的基本格式是:llaaaatt[dd…]cc
- : :冒号是一条记录开始
- ll:以16进制数表示这条记录的主体数据区的长度
- aaaa:表示这条记录中的内容应存放到FLASH中的起始地址
- tt:表示这条记录的类型,它包含中的各种类型
tt | 数据类型 |
---|---|
00 | 数据记录 |
01 | 本文件结束记录 |
02 | 扩展地址记录 |
04 | 扩展线性地址记录(表示后面的记录按个这地址递增) |
05 | 表示一个线性地址记录的起始(只适用于ARM) |
- dd:表示一个字节的数据,一条记录中可以有多个字节数据
- cc:表示本条记录的校验和,它是前面所有16进制数据 (除冒号外,两个为一组)的和对256取模运算的结果的补码