1 / 49

嵌入式软件开发

嵌入式软件开发. 嵌入式开发过程. 独立的嵌入式应用. “ PC 软件”. 当程序员开始开发一个基于 ARM 应用的时候,你可以使用 ARM 的 ADS 编写类似于“ HELLO WORLD” 的程序,使用 ARMulator 或者在评估板上来调试,但当你把他移植到独立的嵌入式应用设备中时,下面这些问题就成为我们首要考虑的: 硬件环境中所使用的 C 库函数 目标板上的存储器资源 应用程序的初始化. 议程. PC 软件的构造 定制标准 C 库函数到目标板 定制 IMAGE 的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑

gerd
Télécharger la présentation

嵌入式软件开发

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 嵌入式软件开发

  2. 嵌入式开发过程 独立的嵌入式应用 “PC软件” 当程序员开始开发一个基于ARM应用的时候,你可以使用ARM的ADS编写类似于“HELLO WORLD”的程序,使用ARMulator或者在评估板上来调试,但当你把他移植到独立的嵌入式应用设备中时,下面这些问题就成为我们首要考虑的: • 硬件环境中所使用的C库函数 • 目标板上的存储器资源 • 应用程序的初始化

  3. 议程 • PC软件的构造 定制标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  4. ADS默认的标准C库 ANSI C 应用程序调用的C库函数 eg: fputc() C Library input/ output error handling stack & heap setup other 设备驱动层 使用semihosting SWI’s eg: _sys_write() Debug Agent Semihosting Support 调试工具环境 C库函数功能是支持PC软件的,而目标板上的可执行软件则依赖相关的硬件资源;在ARM体系中,我们可以采用semihosting通过相应的驱动来进行调试。

  5. ADS默认的存储器映射 • 在默认的情况下,我们链接、定位、运行在0x8000 • heap 被直接放置在数据区的上面 • 堆栈的基地址是通过调试环境从C库函数的Startup Code 里读取出来的。 • ARMulator => from configuration file (peripherals.ami) • default = 0x08000000 • Multi-ICE => from debugger internal variable $top_of_memory • default = 0x80000 由调试环境提供 Stack Heap(malloc,alloc) 链接时确定 ZI RW RO 0x8000

  6. 应用程序启动 __rt_entry set up application stack and heap initialize library functions call top-level constructors (C++) Exit from application main( ) causes the linker to pull in library initialization code __main • copy code and data • zero uninitialized data User Code C Library 程序入口点

  7. Agenda 一个PC软件的构造 定制标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  8. 重定向C库函数 (1) • 你可以使用适合你目标板运行的驱动来替换标准C库中的设备驱动。 Eg: printf( ) 可打印到LCD上,而不是打印控制台上 ANSI C ANSI C User Code C Library Retarget input/ output input/ output Semihosting Support Debug Agent Target Hardware

  9. 重定向C库函数(2) • 要重定向C库函数,简单的办法是使用你自己的可执行的semihosting SWIs来代替原来的C库函数,从而来满足你的系统要求 比如说, the printf()系列函数(sprintf()除外) 都会调用fputc(). 在默认情况下fputc()的执行使用了semihosting SWI. 用下面的语句来代替: extern void sendchar(char *ch);int fputc(int ch, FILE *f){ /* e.g. write a character to an LCD */ char tempch = ch; sendchar(&tempch); return ch;} • 可查看在ADS Embedded example目录下的retarget.c,可看到更多的重定向例子 • 你可以确定有不在连接时使用semihosting SWI 的吗?…...

  10. 消除C库函数中的semi hosting • 为了确保在连接时没有函数使用了semi hosting SWIs ,你可以在程序中加入下面的句子: #pragma import(__use_no_semihosting_swi) • 如果在程序中仍然使用了semihosting ,编译时将会报错: Error: Symbol __semihosting_swi_guard multiply defined • 修改: • 如果使用 (check -verbose linker output for occurrences of I use_ semihosting_ swi), 那么连接器将会把那些使用了smeihosting 的程序列出来,然后: • 提供你自己可运行的功能函数。 • 在ADS 1.2 编译器和库函数手册, 表4-2给出了所有使用了semihosting的C库函数。 • 注意: 连接器在用户自己的应用代码中不会出现任何有关 semihosting SWI使用的报告。

  11. Agenda 一个PC软件的构造 定制标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  12. 分散加载(Scatterloading) • 在一个实际应用当中,你可能并不想在0x8000处开始运行。 • 大多数嵌入式系统都有存储器设备,他们的地址空间是在整个存储器映射中交叉出现的。 • 分散加载提供了一种把你的代码和数据放在不同的存储器定位上的办法 • 分散加载定义了两种类型的存储器区域。 • Load 区: - 在reset/load时保留了应用程序的代码和数据 (典型应用为 ROM). • Execution 区 – 在程序执行的同时保留了程序的代码和数据。在应用程序启动 期间,每个load区都可创建一个或多个可执行区。 • 分散加载了的应用把详细的存储器映射保存在一个描述文件中,作为一个参数给armlink使用 eg: armlink program.o -scatter scatter.scf -o program.axf

  13. 分散加载(简单例子) Load View Execute View 0x18000 0x18000 Fill with zeros ZI RAM RAM RW 0x10000 0x10000 Copy 0x4000 0x4000 RW ROM ROM RO RO 0x0000 0x0000 只读代码和数据保存在ROM中 C库函数初始化代码 (在 __main) 将 : 从ROM拷贝RW数据到RAM 在RAM中的ZI 数据初始化

  14. Scatter 描述文件 Execute View Load View LOAD_ROM 0x0000 0x4000 { } 0x18000 0x18000 ZI RAM RAM RW 0x10000 0x10000 0x4000 0x4000 RAM 0x10000 0x8000 { * (+RW,+ZI) } RW ROM ROM RO RO 0x0000 0x0000 • 通配符(*)语法允许简单的对CODE 和DATA 进行分组 EXEC_ROM 0x0000 0x4000 { * (+RO) }

  15. 链接器放置规则 section A from file2.o ZI RO -DATA B RW section A from file1.o A A CODE RO -CODE RO RO • 在每个可执行区,链接器通过一些基本的规则来放置CODE 和DATA • 基本的排序方法是通过属性来安排的: • RO 领先于RW ,RW 领先于ZI • 有相同的属性时,CODE 在DATA之前放置。 • 更多的排序方法决定于: • 输入的组名按字母排序, • 在ARMLINK命令行中指定的顺序。 • eg: armlink file1.o file2.o …

  16. 在SCATTOR 文件中的对象排序 • 为了把特定的CODE 和DATA 放在指定的地址上,你可以不考虑标准的放置规则 • 使用+FIRST和 +LAST ,直接把第一个和最后一个对象放在可执行区。 • 图例:把VECTOR表放在区的开始。 LOAD_ROM 0x0000 0x4000 { EXEC_ROM 0x0000 0x4000 { vectors.o (Vectors, +FIRST) file1.o (+RO) file2.o (+RO) } : } 在可执行区内,scattor 文件中要排序的对象对输出image没有影响 链接器的标准放置规则仍然适用

  17. ROOT区 LOAD_ROM 0x0000 0x4000 ; start address and length { EXEC_ROM 0x0000 0x4000 ; root (load = exec address) { __main.o (+RO) ; copying code * (Region$$Table) ; RO/RW addresses to copy * (ZISection$$Table) ; ZI addresses to zero } RAM 0x10000 0x8000 { * (+RO) ; All other RO areas * (+RW,+ZI) ; program variables }} Must be in a root region outside root region 一个 root 区是一个可执行区,它的加载地址等于执行地址。

  18. Root区要点 • 一个 root 区是一个可执行区,它的加载地址等于执行地址. • 每个scatter描述文件必须最少包含一个root区,并且最少要包含下列内容: • __main.o – 含有拷贝code/data的代码 • Region$$Table 和 ZISection$$Table – 含有将要拷贝的code/data的地址,他是由链接器产生的,不是一个对象文件。(所以*必须用) Error: L6202E: Section Region$$Table cannot be assigned to a non-root region. Error: L6202E: Section ZISection$$Table cannot be assigned to a non-root region. • 注意: 如果* (+RO)被定位在 root 区,在此之前的将被自动放置 • Main应用程序的入口点必须放在root区。 Error: L6203E: Entry point (0x08000000) lies within non-root region EXE_FLASH.

  19. Run-time 存储器管理 如何设置stack和 heap来满足我们的目标存储器? • 我们已经通过执行__user_initial_stackheap()把C标准库的运行存储器模式修改到目标平台上。 ANSI C ANSI C C Library User Code Stack & Heap Setup Retarget Stack & Heap Setup Semihosting Support Debug Agent Target Hardware

  20. Stack 和 Heap 初始化 __user_initial_stackheap( ) set up application stack and heap __rt_entry initialize library functions call top-level constructors (C++) Exit from application main( ) cause linker to pull in library initialization code User Code C Library Image Entry Point __main copy code and data zero uninitialized data

  21. Run-time 存储器模式 • 你必须决定在放置stack和heap时所使用的区域是单一的区(one-region model)或是不同的两个区(two-region model) SB HL heap is checked against heap limit Stack Heap HB SB heap is checked against stack pointer Stack Heap (SL) HB Two region model One region model 单一存储器模式是默认方式 为了实现多区域模式,你可以使用 use_two_region_memory 在所有的模式下,软件堆栈检查要许可。 编译开关是: -apcs /swst 指定堆栈限制 (为 two-region 模式)

  22. __user_initial_stackheap( ) • 可以用C或汇编来写,他要返回: • Heap 基地址在R0 ,STACK 的基地址在R1. • Heap 的限制地址在R2,STACK的限制地址在R3 SB = 0x88000 EXPORT __user_initial_stackheap __user_initial_stackheap LDR r0, =0x80000 ;HB LDR r1, =0x88000 ;SB ; r2 not used (HL) ; r3 not used (SL) MOV pc, lr Stack Heap HB = 0x80000 Heap 的限制地址在单一模式是不被使用的。 Stack 的限制地址只在软件堆栈检查许可的情况下才有效。

  23. 警告! • 当使用分散加载时你必须执行 __user_initial_stackheap() • 在C库初始化代码内的__user_initial_stackheap() 的默认执行是在映像文件的RW/ZI数据段后放置HEAP。 • 使用 Image$$RW$$Base / Image$$ZI$$Base连接符号 • 这些符号对scatterloading是无效的。 • 在ADS 1.1和早期版本的软件中: • 符号被设置为0X0, heap被定位在这! • Heap的并发使用,无论是直接(e.g. with malloc())或间接(by use of argc/argv)的都可能破坏向量表或其他代码,典型的结果是不可预知的程序在运行时出错了。 • 在ADS 1.2: • 符号没有定义,应用程序不会联接: Error: L6218E: Undefined symbol Image$$ZI$$Limit (referred from sys_stackheap.o).

  24. Agenda 一个PC软件的构造 裁减标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  25. 中断向量表 The Vector Table AREA Vectors, CODE, READONLY IMPORT Reset_Handler; import other exception handlers ; … ENTRY B Reset_Handler B Undefined_Handler B SWI_Handler B Prefetch_Handler B Data_Handler NOP ; Reserved vector B IRQ_Handler ; FIQ_Handler will follow directly END 在使用scatterloading+FIRST时直接定位在0X0(或 0xFFFF0000) ENTRY直接告诉链接器这是一个入口点,防止某些段被删除

  26. 初始化步骤 reset handler initialize stack pointers configure MMU/MPU setup cache/enable TCM __user_initial_stackheap( ) set up stack & heap __rt_entry initialize library functions call top-level constructors (C++) Exit from application main( ) tells linker to link in library initialization code User Code C Library Image Entry Point __main copy code and data zero uninitialized data $Sub$$main( ) enable caches & interrupts

  27. ROM or RAM at 0x0? Reset Handler 0x4000 RAM Vectors Reset Handler Vectors 0x0000 • 需要一个有效的地址在 0x0 ROM at 0x0 ROM/RAM Remapping 0x18000 0x18000 RAM ROM 0x10000 0x10000 0x4000 0x4000 Aliased ROM Reset Handler ROM 0x0000 0x0000 这项功能可被编码在像RESET HANDLER 一样的模块中 在本章结束的时候,我们还会讲到。

  28. ROM/RAM的重定向 ROM/RAM Remapping 下面的例子可像Reset handler 一样在源码中编码。 ; --- Integrator CM control regCM_ctl_reg EQU 0x1000000C ; Address of CM Control RegisterRemap_bit EQU 0x04 ; Bit 2 is remap bit of CM_ctl ENTRY; On reset, an alias of ROM is at 0x0, so jump to 'real' ROM. LDR pc, =Instruct_2 Instruct_2 ; Remap by setting Remap bit of the CM_ctl register LDR r1, =CM_ctl_reg LDR r0, [r1] ORR r0, r0, #Remap_bit STR r0, [r1]; RAM is now at 0x0.; The exception vectors must be copied from ROM to RAM (in __main); Reset_Handler follows on from here 这个功能也可在有mmu时使用

  29. 初始化栈的指针 ; --- Amount of memory (in bytes) allocated for stacks Len_FIQ_Stack EQU 256 Len_IRQ_Stack EQU 256 … Offset_FIQ_Stack EQU 0 Offset_IRQ_Stack EQU Offset_FIQ_Stack + Len_FIQ_Stack … Reset_Handler LDR r0, stack_base ; located by scatter file ; Enter each mode in turn and set up the stack pointer MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit ; No interrupts SUB sp, r0, #Offset_FIQ_Stack MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit ; No interrupts SUB sp, r0, #Offset_IRQ_Stack … ; System mode stack is set up last MSR CPSR_c, #Mode_SYS:OR:I_Bit:OR:F_Bit ; No interrupts SUB sp, r0, #Offset_SYS_Stack ; Set up stack limit if needed LDR r10, stack_limit ; located by scatter file

  30. 局部存储器设置 • run-time的存储器必须在C库初始化前定义 • 如果你使用的ARM7芯片还有MMU/MPU,它必须设置; • ROM/RAM 的重新映射必须完成。 • TCM’s(Tightly coupled memory) 如果有TCM,典型的必须使能它。 • 请注意:在TCM使能之前,要屏蔽ROM • 在Cache打开之前要返回。 • 在c库初始化代码运行之后,如果cache被使能,可以避免与cache相关的问题;

  31. 扩展功能 • 系统初始化代码通常在进入主应用之前运行 • 当然,reset handler 不是一个适合使能中断和使能caches地方。 • 在reset handler最后应该放一个C运行库初始化代码 • EG。 IMPORT __main B __main • 我们可使用$Sub和$Super功能来包装符号 extern void $Super$$main(void); void $Sub$$main(void) { cache_enable(); // enables caches int_enable(); // enables interrupts sys_to_usr_mode(); // change mode - see next slide $Super$$main(); // calls original main() } • 相关描述可在ADS 1.2 Linker and Utilities Guide - 4.4章查阅到。

  32. 运行模式考虑 • 主应用程序运行在何种模式是要考虑的重要问题。 • 用户模式(User mode)是非特权模式(unprivileged mode)-保护你的系统 • 系统初始化代码只能运行在特权模式(privileged mode)。 • 需要执行特权操作 比如:使能中断。 • 如果你的应用要运行在管理模式,简单的在管理模式下退出你的reset handler 就可。 • 如果你想在用户模式下运行你的应用,你需在$Sub$$main( )改变为用户模式 • 当然, __user_initial_stackheap( ) 必须有权使用你的应用模式寄存器。 • 解决办法是在系统模式里退出reset handler • 所有C库初始化代码有权使用用户寄存器,但是仍然可以执行特权操作。

  33. Agenda 一个PC软件的构造 裁减标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  34. ROM_LOAD 0x0000 { ROM_EXEC 0x0000 { * (+RO) } RAM 0x80000000 { farfunc.o (+RO) * (+RW,+ZI) } } /* main.c */int main(void) { farfunc(); } /* farfunc.c */void farfunc(void); { :} 长跳转Veneers 代码段可被远距离分开放置 (比BL的跳转范围还远) 链接器可自动增加长跳转Veneers,远距离的函数可被成功调用。 0x00000000 bl Ven$AA$L$$farfunc : : Ven$AA$L$$farfunc ldr pc,[pc,#-4]dcd 0x80000000 : : 0x80000000: mov pc,lr

  35. 存储器映射寄存器 • 你可以使用scatterloading来放置外设寄存器的存储器映射 • 在文件中定义它 e.g. timer_reg.c struct { volatile unsigned reg1; /* timer control */ volatile unsigned reg2; /* timer value */ } timer_reg;在存储器映射的请求地址上增加另外的可执行区来放置他们: LOAD_FLASH 0x24000000 0x04000000 { : TIMER 0x40000000 UNINIT { timer_reg.o (+ZI) } : }UNINIT显示在 ZI 段没有被初始化为0。

  36. Stack 和 Heap 区(1) • 你也可以在SCATTER文件中放置stack和heap • 在汇编原文件里定义stack 和heap 区 比如. stackheap.s • 这个空间直接保留一个为0的存储器块 AREA stack, DATA, NOINIT SPACE 0x3000 ; Reserve stack space AREA heap, DATA, NOINIT SPACE 0x3000 ; Reserve heap space END

  37. Stack 和 Heap 区(2) • 增加一个可执行区来定位这个区域 LOAD_FLASH 0X24000000 0x04000000 { : STACK 0x1000 UNINIT ; length = 0x3000 { stackheap.o (stack) ; stack = 0x4000 to 0x1000 } HEAP 0x15000 UNINIT ; length = 0x3000 { stackheap.o (heap) ; heap = 0x15000 to 0x18000 } } • Heap的基地址起始为 0x15000. Stack的最大地址为 0x4000.

  38. Stack 和 Heap 区 (3) • 链接器将产生一个为每个可执行区的基地址和限制地址的符号指针 • 在你的代码中引入这些符号 IMPORT ||Image$$STACK$$ZI$$Base|| IMPORT ||Image$$STACK$$ZI$$Limit|| IMPORT ||Image$$HEAP$$ZI$$Base|| IMPORT ||Image$$HEAP$$ZI$$Limit|| stack_base DCD ||Image$$STACK$$ZI$$Limit|| stack_limit DCD ||Image$$STACK$$ZI$$Base|| heap_base DCD ||Image$$HEAP$$ZI$$Base|| heap_limit DCD ||Image$$HEAP$$ZI$$Limit|| • 使用DCD指令为这些段命名

  39. __user_initial_stackheap( ) • 在reset handler,这个stack指针 (r13)和stack 限制值(r10) 通常设置了,他们分别通过R1和R3作为参数传递给 __user_initial_stackheap IMPORT __use_two_region_memory EXPORT __user_initial_stackheap __user_initial_stackheap LDR r0, heap_base ; SB value setup in reset handler LDR r2, heap_limit ; SL value setup in reset handler MOV pc, lr Heap Stack 这个__user_initial_stackheap( )例子实现了两个存储器区域模式。 必须引用__use_two_region_memory,在这HEAP被检查,它是HEAP的限制值,而不是STACK指针

  40. 存储器映射例子 RO Reset Handler Heap RW & ZI 0x40000000 Peripherals 外设控制寄存器的地址映射 0x28000000 直接在FLASH运行的代码大小 Flash 0x24000000 0x18000 16 bit RAM 16位 RAM 被用来保存数据和HEAP区 0x10000 0x4000 Stack Fast 32 bit RAM 一些紧急的代码和数据可放在快速的RAM区 Exception Handlers Vector Table 0x0000

  41. SCATTER文件例子 FLASH 0x24000000 0x04000000 { FLASH 0x24000000 0x04000000 { init.o (Init, +First) * (+RO) } 32bitRAM 0x0000 { vectors.o (Vect, +First) handlers.o (+RO) } STACK 0x1000 UNINIT { stackheap.o (stack) } : : : 16bitRAM 0x10000 { * (+RW,+ZI) } HEAP 0x15000 UNINIT { stackheap.o (heap) } TIMER 0x40000000 UNINIT { timer_reg.o (+ZI) } } 这个scatter文件执行上页所显示的存储器映射。

  42. Agenda 一个PC软件的构造 裁减标准C库函数到目标板 定制IMAGE的存储器映射到目标板 复位和初始化 深层次的存储器器映象考虑 编译和调试IMAGE

  43. 不使用段的消除和程序的入口点 • 在默认的情况下,链接器将从最终的image文件中删除一些从不使用的代码段,或从未使用的数据段。 • 要查看哪些段被删除了,在链接时用:‘-info unused’. • 为了确保不删除重要的段(比如:中断向量表) : • 使用汇编指令entry标示所有的入口点(c库有一个入口点:__main()), • 使用‘-entry’选择其中一个入口点作为image的入口,否则,链接器将给警 告:Image does not have an entry point. (Not specified or not set due to multiple choices) • 在生成ROMmable image 使推荐使用下面的链接命令:armlink obj1.o obj2.o -scatter scatter.scf -info unused -entry 0x0 -o prog.axf

  44. 输出选项 • 链接器产生ELF/DWARF2 格式的映像文件, 选择适当的调试器下载调试 • 为把elf映像文件转为 ‘ROMmable’格式 使用fromelf, 例如.:fromelf image.axf -bin -o image.bin • 产生binary格式的文件可烧入到适当的 ROM, Flash或 EPROM-Emulator, 等. • 其他 ‘ROMmable’ 格式的文件也可由 fromelf产生, 例如.: • Motorola 32 bit Hex (-m32) • Intel 32 bit Hex (-i32) • Intellec Hex (-ihf).

  45. 调试ROM映像文件 • 编译时加调试表(-G)来进行源码级调试。 • 在ROM( EPROM 、 Flash 、 EPROM-Emulator )设备里烧入IMAGE文件,然后,把IMAGE文件加载到RAM里: • For AXD, select ‘File->Load Memory From File’ with load address 0x0 • On command line, use: loadbinary image.bin 0x0 • 从ELF格式的IMAGE文件里装载含调试信息的符号表 • For AXD, select ‘File->Load Debug Symbols’ • On command line, use: loadsymbols image.axf

  46. 附加信息 • 附加信息: • 例子代码在ADS\Examples\embedded目录 • ADS 1.2 Developer Guide • 第6章 : Writing Code for ROM • ADS 1.2 Compilers and Libraries Guide • 第 4章 : The C and C++ Libraries • ADS 1.2 Linker and Utilities Guide

  47. 测验 1. 默认情况下,应用程序的STACK和HEAP如何放置的? 2. 如何确认在C库里没有链接进semihosting SWI功能? 3. 在scatter描述文件里,如何确定中断向量表放在0x0? 4. 哪个函数被用来放置应用stack和heap? 5. 在c库初始化(—main)前,使能cache,可以避免cache相关的问题? 6. 在两个区域的stack 和 heap模式,什么符号必须引入执行?

  48. ROM/RAM 重定向(remmap) 0x4000 RAM Vectors 0x0000 1 2 3 0x18000 0x18000 ROM ROM Reset Handler Reset Handler Reset Handler 0x10000 0x10000 Branch to real ROM Remove alias 0x4000 Aliased ROM Reset Handler Reset Handler 0x0000 1. 复位时,ROM通常定位到0x0; 2. 跳转到实际的ROM地址:0x10000 3. 这时,把0x0的ROM替换为RAM,把中断向量表拷贝到0。

More Related