Keil编译后的Code,RO,RW,ZI分别表示什么
- Code:代码的大小
- RO:常量所占空间
- RW:程序中已经初始化的变量所占空间
- ZI:未初始化的static变量和全局变量以及堆栈所占的空间
上述参数和芯片Flash以及SRAM的对应关系: - Flash占用大小=Code+RO+RW
- SRAM占用大小=RW+ZI
ZI变量是未初始化的变量,上电之后芯片会将这些变量初始化为0,因此这些变量在flash中无需占用空间。
RW变量为代码中初始化的变量,因此在flash中需要记录相应的值,占用flash空间;同时RW变量在运行过程中会被修改,需要占用RAM空间。
uint8_t a;//ZI变量
uint8_t a = 0x55;//RW变量
const uint8_t a = 0x55;//RO变量
查看程序占用的ram、flash空间
程序编译成功后,生成map文件,详细记录了各个函数和变量的位置和大小。最后还会给出ram占用情况,flash占用情况。通过勾选listing选项卡下的linker listing选项,即可打开map生成功能。
![](https://img.haomeiwen.com/i8609636/dca7a7572b356d1b.jpg)
其中Memory Map of the image部分,可以看到程序中不同的C文件生成的代码在ram和flash中是如何分布的。
==============================================================================
Memory Map of the image
Image Entry point : 0x00008001
Load Region BOOT_IROM (Base: 0x00000000, Size: 0x00007424, Max: 0x00008000, ABSOLUTE, COMPRESSED[0x0000738c])
Execution Region BOOT_IROM (Exec base: 0x00000000, Load base: 0x00000000, Size: 0x00007294, Max: 0x00008000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x00000000 0x00000000 0x000000e0 Data RO 1272 RESET startup_efm32lg.o
0x000000e0 0x000000e0 0x00000096 Code RO 2223 .emb_text port.o
0x00000176 0x00000176 0x00000002 PAD
0x00000178 0x00000178 0x00000a84 Code RO 299 .text em_cmu.o
0x00000bfc 0x00000bfc 0x00000260 Code RO 338 .text em_core.o
0x00000e5c 0x00000e5c 0x0000042e Code RO 493 .text em_dma.o
0x0000128a 0x0000128a 0x00000002 PAD
0x0000128c 0x0000128c 0x00000308 Code RO 549 .text em_emu.o
0x00001594 0x00001594 0x00000148 Code RO 616 .text em_gpio.o
0x000016dc 0x000016dc 0x00000374 Code RO 643 .text em_i2c.o
0x00001a50 0x00001a50 0x000001c0 Code RO 814 .text em_leuart.o
0x00001c10 0x00001c10 0x000000ac Code RO 842 .text em_msc.o
0x00001cbc 0x00001cbc 0x0000007c Code RO 1020 .text em_rtc.o
0x00001d38 0x00001d38 0x00000064 Code RO 1087 .text em_system.o
0x00001d9c 0x00001d9c 0x000001fc Code RO 1111 .text em_timer.o
0x00001f98 0x00001f98 0x00000330 Code RO 1138 .text em_usart.o
0x000022c8 0x000022c8 0x000000a2 Code RO 1215 .text em_wdog.o
Map文件的末尾,则给出了ram和flash的占用情况:
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
58976 3888 3528 1672 30280 789511 Grand Totals
58976 3888 3528 832 30280 789511 ELF Image Totals (compressed)
58976 3888 3528 832 0 0 ROM Totals
==============================================================================
Total RO Size (Code + RO Data) 62504 ( 61.04kB)
Total RW Size (RW Data + ZI Data) 31952 ( 31.20kB)
Total ROM Size (Code + RO Data + RW Data) 63336 ( 61.85kB)
==============================================================================
分割flash存储区
利用__attribute__((section("name")))
可以将变量或者函数定义到name区域,
如__attribute__((section(".appmain.rom"))) int main(void)
,main函数将被分配到.appmain.rom区域。
利用__attribute__((at(addr)))
可以将变量定义到绝对位置addr,addr可以是flash,也可以是ram,
如uint8_t buf[5] __attribute__((at(0X20001000)));//起始地址为0X20001000
也可以用#pragma arm section code=" name "
的形式将整个大段代码定义到name段。除了code,还可以用rodata、rwdata、zidata将变量定义到不同的段
#pragma arm section code,rodata,rwdata,zidata
不带name的形式意味着后续代码和变量定义到默认的段中。
默认代码段.text:
0x00004308 0x00004308 0x00000364 Code RO 2157 .text segger_rtt_printf.o
0x0000466c 0x0000466c 0x00000230 Code RO 2183 .text event_groups.o
0x0000489c 0x0000489c 0x00000088 Code RO 2211 .text list.o
0x00004924 0x00004924 0x00000134 Code RO 2224 .text port.o
0x00004a58 0x00004a58 0x00000624 Code RO 2254 .text queue.o
0x0000507c 0x0000507c 0x00000ce4 Code RO 2270 .text tasks.o
0x00005d60 0x00005d60 0x00000364 Code RO 2302 .text timers.o
0x000060c4 0x000060c4 0x00000062 Code RO 2320 .text mc_w.l(uldiv.o)
自定义代码段.appmain.rom、.app.rom:
0x0000a768 0x0000a768 0x00000f04 Code RO 1405 .app.rom bsp.o
0x0000b66c 0x0000b66c 0x00000240 Code RO 1493 .app.rom common.o
0x0000b8ac 0x0000b8ac 0x000005e8 Code RO 1521 .app.rom cpu_card.o
0x0000be94 0x0000be94 0x000007d0 Code RO 1565 .app.rom fingertask.o
0x0000c664 0x0000c664 0x000022e8 Code RO 1644 .app.rom locktask.o
0x0000e94c 0x0000e94c 0x00000658 Code RO 1762 .app.rom mfrc522.o
0x0000efa4 0x0000efa4 0x000003fc Code RO 1797 .app.rom os.o
0x0000f3a0 0x0000f3a0 0x00000258 Code RO 1828 .app.rom rfidtask.o
0x0000f5f8 0x0000f5f8 0x00000128 Code RO 1858 .app.rom sleeptask.o
0x0000f720 0x0000f720 0x00000698 Code RO 1888 .app.rom touchpadtask.o
0x0000fdb8 0x0000fdb8 0x00000578 Code RO 1921 .app.rom usermanage.o
0x00010330 0x00010330 0x000007c4 Code RO 1951 .app.rom videotask.o
0x00010af4 0x00010af4 0x000003cc Code RO 1984 .app.rom voicetask.o
0x00010ec0 0x00010ec0 0x0000018c Code RO 2026 .app.rom wt588h.o
0x0001104c 0x0001104c 0x00000085 Data RO 1412 .app.rom bsp.o
0x000110d1 0x000110d1 0x0000000e Data RO 1527 .app.rom cpu_card.o
分段链接固件
C代码编译过程包含预处理、编译、汇编、链接四个过程,其中链接过程决定了代码在固件中的分布情况。
![](https://img.haomeiwen.com/i8609636/01e3cbd0472ecad1.jpg)
Keil mdk根据链接脚本来控制代码的分布,默认的链接脚本如下所示:
LR_IROM1 0x00000000 0x0001E000 { ; load region size_region
ER_IROM1 0x00000000 0x0001E000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00007FFF { ; RW data
.ANY (+RW +ZI)
}
}
链接脚本由如下形式构成:
load_region_name base_address max_size
{
execution_region_description
{
}
}
load_region_name
为加载时域的名字,长度不超过31个字节;
base_address
为加载时域的起始地址,即从该地址开始加载相关代码;
max_size
为加载时域的最大范围,若实际大小超过该大小,链接器将会报错;
execution_region_description
是对执行时域的描述.
由于单片机代码在flash上运行,因此加载域和运行域在同一个地址,而变量涉及到平方修改,因此变量的运行域在ram上。
*.o (RESET, +First)
表示将reset段放在flash最开始的地方,因为单边机上电后就从这里开始执行代码。Reset段代码定义在start文件中,一般用汇编编写:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY, ALIGN=8
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
*(InRoot$$Sections)段用于加载代码,将变量从flash复制到ram。
.ANY (+RO)、.ANY (+XO)表示剩余的代码,.ANY (+RW +ZI)表示剩余的变量.
自定义链接脚本
为了减少升级时固件大小,可以将代码分成固定部分和可升级部分。启动代码、外设库、加解密代码等程序一般是不会改变的,而应用代码则可能根据需求进行更改,因此两部分分开存放,升级时仅需覆盖应用代码即可。由于程序更改时会导致分区大小的变化,因此在更新应用的同时,需要更新分区表。将分区表分配在单独的区域,置于boot之后,单独更新。
去掉use memory layout from target dialog的选项,即可选择自定义的链接脚本。
![](https://img.haomeiwen.com/i8609636/f9d1eb4563f1e912.jpg)
自定义链接文件如下:
#! armcc -E
#define RomBase 0x00000000
#define RomTotal 0x20000
#define RamBase 0x20000100
#define RamTotal 0x7F00
#define BootRomSize 0x8000
#define TableRomSize 0x1000
#define CodeRomSize (RomTotal - BootRomSize - TableRomSize)
#define BootRomAddr RomBase
#define TableRomAddr (RomBase + BootRomSize)
#define CodeRomAddr (TableRomAddr + TableRomSize)
#define BootRamSize 0x2c00
#define CodeRamSize (RamTotal - BootRamSize)
#define BootRamAddr RamBase
#define CodeRamAddr (RamBase + BootRamSize)
BOOT_IROM BootRomAddr BootRomSize {
BOOT_IROM BootRomAddr BootRomSize {
*.o (RESET, +First)
.ANY (+RO)
}
BOOT_IRAM BootRamAddr BootRamSize {
.ANY (+RW +ZI)
}
}
TABLE_IROM TableRomAddr TableRomSize {
TABLE_IROM TableRomAddr TableRomSize {
*(InRoot$$Sections)
}
}
CODE_IROM CodeRomAddr CodeRomSize {
CODE_IROM CodeRomAddr CodeRomSize {
*(.appmain.rom, +First)
*(.app.rom)
}
CODE_IRAM CodeRamAddr CodeRamSize {
*(.app.ram)
}
}
#! armcc -E
用来处理后续的#define,可以定义段地址、段长度等变量。
CODE_IROM用来存储可升级代码和变量,同样ram也分成两段。
TABLE_IROM用来存储分区表,升级后单片机重新读取分区表,才能够正确地加载代码。
可升级部分分成了.appmain.rom段和.app.rom,目的是为了从固定段运行到可升级段时,能从固定的地址开始。在上述例子中,可升级部分的第一个段.appmain.rom肯是在地址0xa200,而且.appmain.rom段中只有一个函数,因此这个函数一定是在0xa200。固件升级后,不管内容如何修改,固定段仍然可以通过跳转执行0xa200的函数,从而执行可升级段的程序。
考虑到flash一般扇区为4k,因此每段均4k对齐,分区表那段直接设为4k。
重映射中断向量表
单片机的中断跳转是由硬件决定的,代码分段存储后,中断依然会跳刀地址0去读取中断服务函数。由于应用更新后,中断服务函数的地址可能变化,因此需要将中断向量表映射到ram中,并且在可升级段代码的开头重映射,确保中断函数地址正确。
一般单片机都有重映射中断向量表的功能,如efm32系列单片机,可通过SCB->VTOR寄存器定义向量表的地址,因此在ram中定义中断服务函数数组,并将数组地址赋值到SCB->VTOR即可。
针对stm32f0系列单片机,由于没有提供SCB->VTOR寄存器,不能通过上述方式重映射。而是在ram开头0x20000000地址处定义中断服务函数数组,然后将地址0x00000000映射到0x20000000处实现中断向量表重映射。另外需要将加载脚本中ram地址空出一部分用于存放数组,防止其他变量占用这段地址空间。
批处理合并升级文件
keil在编译固件时,会根据链接文件的每一个加载地址生成多个固件文件。为了方便升级,编译完成后调用批处理文件,对固件进行合并。
set varpath="%~dp0obj\bin\TABLE_IROM.bin"
set codepath="%~dp0obj\bin\CODE_IROM.bin"
del /q temp.bin
del /q ..\update.bin
del /q ..\complete.hex
del /q .\obj\bin
fromelf.exe --bin --output .\obj\bin .\obj\EFM32LG.axf
copy .\obj\EFM32LG.hex ..\complete.hex
cd obj\bin
ren TABLE_IROM TABLE_IROM.bin
ren CODE_IROM CODE_IROM.bin
cd ..\..
for %%a in (%varpath%) do set /a size=4*1024-%%~za
fsutil file createnew temp.bin %size%
copy /b %varpath% + temp.bin + %codepath% ..\update.bin
del /q temp.bin
部分升级测试
利用jlink可以实现部分程序的更新,用以测试分散加载效果。选择芯片型号、升级文件名、烧写地址,就可实现用jlink下载固件。
@echo off
set chip=EFM32LG230F128
set filename=update.bin
set addr=0x8000
set software="F:/SEGGER/JLink/JLink.exe"
echo device %chip%> __download__script.jlink
echo if swd>> __download__script.jlink
echo speed 4000>> __download__script.jlink
echo r>> __download__script.jlink
echo loadfile %filename%,%addr%>> __download__script.jlink
echo r>> __download__script.jlink
echo exit>> __download__script.jlink
%software% __download__script.jlink
del __download__script.jlink
exit
网友评论