关于单片机固件的研究
MCU固件类型
MCU的固件格式有很多种,最常见的就是以bin作为后缀的文件来存储固件数据,其它还有hex、elf、axf等,bin文件是纯粹的二进制文件,这种格式的固件占用文件大小最小,下面介绍了几种常用文件格式的特点。
-
bin
bin文件是纯粹的二进制文件,内部无地址标记,是直接的内存映象表示,可直接在裸机运行,如果下载运行,则需要下载到对应链接文件中的起始地址。 -
hex
可理解为带存储地址描述格式的 bin 文件,烧写或下载 hex 文件时,一般都不需要用户指定地址,因为 hex 文件内部信息已经包括了编译时的地址,烧录工具会解析文件并将数据写入对应的地址。同时hex文件是用ASCII码来表示十六进制的数值,因此hex文件大小相比bin文件大很多。hex也可以通过工具直接转换为bin文件,但是bin要转化为hex文件必须要给定一个基地址。
-
elf
ELF(executable and linking format)文件是Linux系统下的一种常用目标文件(objectfile)格式,可由gcc 编译器生成,包含了符号表、汇编、调试信息,可在 Linux 中直接运行,无法直接在裸机上运行,有三种主要类型:
(1)适于链接的可重定位文件(relocatablefile),可与其它目标文件一起创建可执行文件和共享目标文件。
(2)适于执行的可执行文件(executable file),用于提供程序的进程映像,加载到内存执行。
(3)共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。elf文件可转化为hex和bin两种文件,而hex和bin不能转化为elf文件,因为elf的信息量要大。
-
axf
axf文件由ARM编译器产生,除了包含bin的内容之外,还附加其他调试信息,这些调试信息加在可执行的二进制数据之前。调试时这些调试信息不会下载到RAM中,真正下载到RAM中的信息仅仅是可执行代码。因此,如果ram的大小小于axf文件的大小,程序是完全有可能在ram中调试的,只要axf除去调试信息后文件大小小于ram的大小即可。axf文件可以转化为bin文件,Keil下可用以下命令
fromelf -nodebug xx.axf -bin -output=xx.bin
即可。
BIN固件组成
在Keil IDE中编译的固件划分了四种类型,分别为Code、RO-data、RW-data以及 ZI-data,Code是被编译器编译好的代码, RO-data是你在代码中定义的只读数据,也就是cosnt修饰过的变量,包括局部的const变量,RW-data就是可读写且初始化的全局变量和静态变量,ZI-data包括定义且未初始化的全局变量,这种类型比较特殊,因为其未初始化所以不需要额外空间存储它的数据,在MCU初始化过程中会将这个段中数据清零,在运行时它实际存储再MCU的RAM中。
Keil IDE
在Keil IDE中我们在编译输出中可以看到以下信息
Build started: Project: Project
*** Using Compiler 'V5.06 update 6 (build 750)', folder: 'D:\Keil_v5\ARM\ARMCC\Bin'
Build target 'STM32F4xx-Nucleo'
compiling main.c...
linking...
Program Size: Code=4084 RO-data=596 RW-data=32 ZI-data=1088
我们只看Program Size这一行,刚刚说到固件占用的空间大小为:Code + RO Data + RW Data,那我看看实际是不是这样呢,按照公式计算如下:
4084B(Code) + 596B(ROdata) + 32B(RWdata) = 4712B(Flash)
实际编译好的固件应该占用4712字节,我们再看下编译输出目录中bin文件固件大小,确实与计算结果吻合。
这里再简单说明下程序运行时占用RAM的大小,RW-data 不仅占用Flash,也占用RAM,是因为数据是保存在Flash中,运行的时候再从Flash搬到RAM,并且两者占用的空间大小是相等的,同时ZI-data也是存储在RAM中,所以在不计算堆栈占用大小的情况下RAM=RW-data+ZI-data,在Keil IDE编译生成的map文件中我们可以看到以下信息:
==============================================================================
Total RO Size (Code + RO Data) 4692 ( 4.58kB)
Total RW Size (RW Data + ZI Data) 1128 ( 1.10kB)
Total ROM Size (Code + RO Data + RW Data) 4732 ( 4.62kB)
==============================================================================
Total ROM Size为占用Flash大小,Total RW Size为占用RAM大小。
STM32 CubeIDE
在一些其它IDE中这些数据类型的名字不太一样,但本质代表的东西都类似,在STM32 Cube IDE中编译输出信息如下:
arm-none-eabi-objdump -h -S STM32F4xx-Nucleo.elf > "STM32F4xx-Nucleo.list"
arm-none-eabi-objcopy -O binary STM32F4xx-Nucleo.elf "STM32F4xx-Nucleo.bin"
arm-none-eabi-size STM32F4xx-Nucleo.elf
text data bss dec hex filename
9424 132 1660 11216 2bd0 STM32F4xx-Nucleo.elf
Finished building: default.size.stdout
Finished building: STM32F4xx-Nucleo.bin
Finished building: STM32F4xx-Nucleo.list
bss段:BSS 是 Block Started by Symbol 的简称,通常是指用来存放程序中未初始化的全局变量的内存区域,属于静态内存分配,在可执行文件中,由系统从可执行文件中加载。 在初始化时 BSS 段部分将会清零,并且BSS段不给该段的数据分配空间,只是记录数据所需空间的大小,在Keil IDE中它对应的是ZI-data。
data段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域,在Keil IDE中它对应RW-data。
text段:代码段通常是指用来存放程序执行代码的一块内存区域,在代码段中,也包含一些只读的常数变量,例如字符串常量等,在Keil IDE中它对应Code+RO-data。
IAR IDE
在IAR IDE中数据被简单的分为三种,readonly code和readonly data使用的是ROM空间,readwrite data使用的是RAM空间,其编译生成BIN文件大小等于readonly code+readonly data,对于一些已初始化的可读写全局变量的初始值IAR将它们划分到了readonly data里。readwrite data memory包括所有读写变量,对应Keil IDE中就是RW-data + ZI-data,也就是程序占用RAM的大小(不包括堆栈)。IAR IDE编译输出信息如下:
IAR ELF Linker V8.40.1.212/W32 for ARM
Copyright 2007-2019 IAR Systems AB.
6'504 bytes of readonly code memory
198 bytes of readonly data memory
1'336 bytes of readwrite data memory
Errors: none
Warnings: none
Link time: 0.16 (CPU) 0.16 (elapsed)
其编译出来的BIN固件大小应该为 6504B + 198B = 6702B,与下图中实际文件大小吻合。