嵌入式软件开发的新技术趋势和挑战

发表时间: 2024-06-18 16:03

嵌入式软件开发 华清元建嵌入式软件开发基于ARM的嵌入式系统设计问题调整C库以适应目标硬件映像文件内存映射调整ARM启动代码分析华清元建嵌入式软件开发流程华清元建开发流程ELF格式的目标文件ELF格式的映像文件华清元建示例int x=10;int func(void){ return x; }AREA 代码func LDR r1,[pc,#4] ;加载x的地址(r1 == 指向x的指针) LDR r0,[r1,#0] ;从指针加载x的值到x BX lr DCD 0;指向x的指针(编译时未知)AREA 数据 DCD 10; x生成2个elf段华清元建编译器优化级别使用的编译器优化级别可选择-O0关闭大部分优化。 最佳调试信息,最少优化 -O1大多数优化选项可实现满意的调试,良好的代码密度 -O2(默认)完全优化有限的调试信息,最佳代码密度针对代码大小或运行速度进行优化,可选择:-Ospace(默认)或-Otime。使用-g选项可包含源级调试信息华清元建编译器优化示例int dummy(){ int x=10, y=20; int z; z = x + y; return 0;}armcc -O1dummy MOV r0, #0 BX lr - O1或更高优先级将被“优化掉”华清元建使用“volatile”华清元建优化级别示例 - 优化管道例如:int f(int *p, int x) { return *p + x * 3; }无调度(-O0)有调度(-O1,-O2,-O3)ADD r1,r1,r1,LSL #1LDR r0,[r0,#0]LDR r0,[r0,#0]ADD r1,r1,r1,LSL #1ADD r0,r0,r1;interlock on ARM9ADD r0,r0,r1BX lrBX lr华清远建嵌入式开发-程序调用1开始直接使用寄存器R0-R3传递4个字大小的参数(快速高效)详细请参考ATPCS,如果需要传递更多的参数就会用到堆栈

(需要额外的指令和较慢的内存操作)所以参数个数通常限制在4个或更少,如果无法避免,把常用的参数放在前4个 华清远建嵌入式开发-过程调用2 传递的参数不足32位,还是用整个寄存器,如果参数多,可以用结构体“打包”数据,然后传递结构体的指针。 在C++语言中,第一个参数用来保存this指针 华清元鉴内容补充 ATPCS 为了使单独编译的C语言程序与汇编程序能够互相调用,对子程序调用规定了如下规则: ①寄存器使用规则 ②数据堆栈使用规则 ③参数传递规则 华清元鉴内容补充 ATPCS r0、r1、r2、r3:参数寄存器 r4~r8:通用变量寄存器 r9:生成位置无关代码时,为基址寄存器 r10:保存堆栈边界(使用堆栈检查) r11:结构指针 r12:通用临时变量寄存器,过程调用时销毁 r13:堆栈指针 华清元鉴参数传递示例——4个参数 参数传递(4个参数) int func1(int a, int b, int c, int d) {return a+b+c+d;} int caller1(void){return func1(1,2,3,4);}func10x000000 : ADDr0,r0,r1 0x000004 : ADDr0,r0,r2 0x000008 : ADDr0,r0,r3 0x00000c : BXlrcaller1 0x000014 : MOVr3,#4 0x000018 : MOVr2,#3 0x00001c : MOVr1,#2 0x000020 : MOVr0,#1 0x000024 : Bfunc1 华清远建参数传递示例——6个参数func2 0x000000 : STRlr, [sp,#-4]! 0x000004 : 0x000024 : MOVr3,#6 0x000028 : MOVr2,#5 0x00002C : STMIA sp,{r2,r3} 0x000030 : MOVr3,#4 0x000034 : MOVr2,#3 0x000038 : MOVr1,#2 0x00003C : MOVr0,#1 0x000040 : BLfunc2 0x000044 : LDMFD sp!,{r1-r3,pc}华清远见 循环终止条件 - 哪一个更有效? 计数int fact1(unsigned int limit){unsigned int i;int fact = 1;for (i = 1;i 0,avoid 0x7ffffffff华清元鉴嵌入式开发——除法ARM内核不包含除法硬件除法通常用运行时库函数实现,需要多次循环才能运行unsigned div(unsigned a, unsigned b){return (b / a);}divB __rt_udiv华清元鉴除法——组合除法与余数运算源程序如下。

int combined_div_mod (int a, int b){ return (a / b) + (a % b);}combined_div_modSTMDB sp!,{lr}MOV a3,a2MOV a2,a1MOV a1,a3BL __rt_sdivADD a1,a1,a2LDMIA sp!,{pc}华清元鉴除法-使用2的整数幂作为除数 源程序如下: typedef unsigned int uint;uint div16u (uint a){ return a / 16;}int div16s (int a){ return a / 16;} 编译结果: div16uMOV a1,a1,LSR #4MOV pc,lrdiv16sCMP a1,#0ADDLT a1,a1,#&fMOV a1,a1,ASR #4MOV pc,lr华清元鉴除法-余数操作 uint counter1 (uint count){ return (++count % 60);}uint counter2 (uint count){if (++count >= 60) count = 0; return (count);}华清元鉴除法-余数编译结果 counter1STMDB sp!,{lr}ADD a2,a1,#1MOV a1,#&3cBL __rt_udivMOV a1,a2LDMIA sp!,{pc}counter2ADD a1,a1,#1CMP a1,#&3cMOVCS a1,#0MOV pc,lr华清元鉴除法操作总结 尽量避免使用除法,环形缓冲区的处理可以不用除法,如果无法避免除法运算,那么可以考虑使用除法程序同时生成商和余数。 华清元鉴嵌入式开发-变量类型 全局和静态变量都保存在RAM中,访问外部存储器时需要Load/Store。 局部变量通常放在寄存器中,以便快速高效地处理。如果编译器的寄存器分配算法认为现有的寄存器数目超过数量,则变量将被压入堆栈。对于局部变量,使用字大小(int),而不要使用半字和字节:为保证不受其它情况影响,可以特别指定32位寄存器变量。华清远建变量类型 - 局部变量 int wordsize (int a) wordsize {0x000000 : MOVr0,r0,LSL #1 return (a*2); 0x000004 : MOVpc,lr} short halfsize (short b) halfsize {0x000008 : MOVr0,r0,LSL #17 return (b*2); 有符号 0x00000c : MOVr0,r0,ASR #16} 0x000010 : MOVpc,lr char bytesize(char c)bytesize{0x000014 : MOVr0,r0,LSL #25 返回(c*2); default unsigned 0x000018 : MOVr0,r0,LSR #24} 0x00001c : MOVpc,lr 华清远见 变量类型——全局变量 全局数据保存在内存中,而不是寄存器中 char a;short b;char c;int d;char a;char c;short b;int d;a�pad�b�c�pad�d�a�c�b�d�华清远见 变量类型总结 对于保存在寄存器中的局部变量,除8位或者16位算术运算外,尽量不要使用char或者short类型,而要用signed或者unsigned int类型。

在进行除法运算时,为了执行速度更快,使用无符号数。对于存储在主存中的数组和全局变量,在满足数据大小的前提下,尽可能使用小的数据类型。这样可以节省存储空间。 华清元鉴嵌入式开发-堆栈的使用 C/C++代码中会用到大量的堆栈。堆栈包括: 函数参数超过4个的函数返回地址 局部数组、结构体和C++类 华清元鉴堆栈使用注意事项 尽量限制函数参数不超过4个,这样函数调用效率会更高。也可以将几个相关的函数组织在一个结构体中,用结构体指针来传递,而不是多个参数。 把较小的被调用函数和调用函数放在同一个源文件中,在调用之前先定义好。编译可以优化函数调用,或者内联较小的函数。 华清元鉴嵌入式开发-结构体安排 结构体定义: struct{char a;int b;char c;short d; }a�pad�pad�pad�b[7,0]�b[15,8]�b[23,16]�b[31,24]�c�pad�d[7,0]�d[15,8]����华清元剑结构体排列 - packedarmcc 编译器支持关键字 _packed,表示去掉所有填充位: _packed struct{char a;int b;char c;short d;}a�b[7,0]�b[15,8]�b[23,16]�b[31,24]�c�d[7,0]�d[15,8]����华清元剑结构体排列 - 存在问题 节省存储空间,但是访问结构体的速度较慢,效率比较低:由于数据边界未对齐的问题,编译器只能通过多次边界对齐操作对结构体进行合并重组,以模拟边界未对齐的加载/存储操作。

华清远鉴结构排列小结 将所有8位元素排列在结构最前面;16位、32位、64位元素一次排列;所有数组和比较大的元素排列在结构最后;对于一条指令,如果结构太大无法访问所有元素,则将元素组织成子结构。编译器可以维护指向单独子结构的指针。 (特别是对于Thumb指令)华清远见extern int a;extern int b;int func(void){return (a+b);}baLDR r2, [pc,#16]LDR r0, [r2,#0]LDR r3, [pc,#12]LDR r1, [r3,#0]ADD r0,r1,r0BX lrDCD “a的地址”DCD “b的地址”注意,在-O0时没有这样做LDR r2, [pc,#12]LDR r0, [r2,#0]LDR r1, [r2,#4]ADD r0,r1,r0BX lrDCD “a和b的基地址”int a;int b; int func(void){return (a+b);}嵌入式开发——优化指针基址*华清元建嵌入式软件开发基于ARM C库的嵌入式系统设计问题调整使其适配到目标硬件映像文件内存映射调整ARM启动代码分析华清元建系统编译华清元建C库函数重定向C��������������ISO C�����/�����Semihosting�����ISO C�����/������������������������������华清元建重定向的实现——重定向 fput() 下面的例子将 fputc() 的输入字符参数重定向到一个连续的输出函数sendchar() extern void sendchar(char *ch);int fputc(int ch, FILE *f){ /* . 向UART写入一个字符*/char tempch = ch;sendchar(&tempch);return ch;}fputc()充当目标相关输出和C库标准输出函数之间的抽象层华清元建嵌入式软件开发基于ARM的嵌入式系统设计问题调整C库适应目标硬件映像文件内存映射调整ARM启动代码分析华清元建内存布局只读可读写初始化为0华清元建内存映射-示例ARM:scatter文件GUN:ld文件IAR:xcl文件(安装目录下ARM\config\)*实际做法华清元建嵌入式软件开发基于ARM的嵌入式系统设计问题调整C库适应目标硬件映像文件内存映射调整ARM启动代码分析华清元建应用程序启动C库用户代码__main复制代码和数据清零未初始化数据程序入口点*系统启动,实际演示华清元建两个文件:所谓启动代码就是处理器启动时执行的一段代码它启动了。其主要工作是初始化处理器模式、设置堆栈、初始化变量等。由于以上操作和处理器架构、系统配置息息相关,所以一般都是用汇编来写。具体对于一颗SoC芯片来说,启动代码分为两部分:1、与ARM核相关的部分,包括处理器各个异常向量的配置、各个处理器模式的堆栈设置(必要时将向量复制到RAM中,以便处理器在remap后能正确处理异常)、初始化数据(包括RW和ZI),最后跳转到Main。2、与处理器外部设备相关的部分,这个和厂商关系比较大,虽然都是用ARM核,但是不同厂商集成的片上外设不一样,需要的初始化也不同,比较重要的是初始化WDT、初始化各个子系统的时钟。这部分和一般控制器的初始化类似,只是多了寄存器的设置。**实际操作* 系统启动,实际演示