1、第五章基于ARM的嵌入式程序设计,5.1 ARM汇编语言的伪操作、宏指令与伪指令 5.2 ARM汇编语言程序设计 5.3 嵌入式C语言程序设计基础 5.4 嵌入式C语言程序设计实例 5.5 嵌入式C语言程序设计技巧 5.6 C与汇编语言混合编程 5.7 基于Embest IDE for ARM 环境的软件开发实例,5.1 ARM汇编语言的伪操作、宏指令与伪指令,5.1.1 两种常见的ARM编译开发环境 5.1.2 ADS编译环境下的伪操作和宏指令 5.1.3 GNU编译环境下的伪操作和宏指令 5.1.4 ARM汇编语言的伪指令,5.1.1两种常见的ARM编译开发环境,ADS/SDT IDE开发
2、环境:它由ARM公司开发,使用了CodeWarrior公司的编译器; 集成了GNU开发工具的IDE开发环境:它由GNU的汇编器as、交叉编译器gcc、和链接器ld等组成。,5.1.2ADS编译环境下的伪操作和宏指令,ADS编译环境下的伪操作可分为以下几类: 符号定义(Symbol Definition)伪操作 数据定义(Data Definition)伪操作 汇编控制(Assembly Control)伪操作 信息报告(Reporting)伪操作 其他(Miscellaneous)伪操作,符号定义伪操作,数据定义伪操作,汇编控制伪操作,信息报告伪操作,其他伪操作,5.1.3 GNU编译环境下的
3、伪操作和宏指令,GNU编译环境下的伪操作可分为以下几类:常量编译控制伪操作 汇编程序代码控制伪操作 宏及条件编译控制伪操作 其他伪操作,常量编译控制伪操作,字符编译控制,汇编程序代码控制伪操作,宏及条件编译控制伪操作,其他伪操作,5.1.4ARM汇编语言的伪指令,5.2 ARM汇编语言程序设计,5.2.1 ARM汇编中的文件格式 5.2.2 ARM汇编语言语句格式 5.2.3 ARM汇编语言编程的重点 5.2.4 ARM汇编程序实例,5.2.1ARM汇编中的文件格式,ARM源程序文件(可简称为源文件)可以由任意一种文本编辑器来编写程序代码,它一般为文本格式。在ARM程序设计中,常用的源文件可简
4、单分为以下几种:,5.2.2ARM汇编语言语句格式,ARM汇编语言语句格式如下所示: symbol instruction | directive | pseudo-instruction ;comment 其中: instruction为指令。 directive为伪操作。 pseudo-instruction为伪指令。 symbol为符号。 comment为语句的注释。,ARM汇编语言程序格式,ARM汇编语言是以段(section)为单位来组织源文件的。段是相对独立的、具有特定名称的、不可分割的指令或者数据序列。段又可以分为代码段和数据段,代码段存放执行代码,数据段存放代码运行时需要用到的
5、数据。一个ARM源程序至少需要一个代码段,大的程序可以包含多个代码段和数据段。,举例说明ARM汇编语言源程序的基本结构,.equ x, 45 /* x=45 */ .equ y, 64 /* y=64 */ .equ stack_top, 0x1000 /* define the top address for stacks */ .global _start .text _start: /* code start */mov sp, #stack_topmov r0, #x /* put x value into R0 */str r0, sp /* save the value of R0
6、 into stacks */mov r0, #y /* put y value into R0 */ldr r1, sp /* read the data from stack,and put it into R1 */ADD r0, r0, r1STR r0, sp stop:b stop /* end the code ,cycling */ .end,5.2.3ARM汇编语言编程的重点,ARM数据处理操作 设置条件码 汇编语言子程序调用及返回 跳转表思想 ARM与Thumb之间的状态转换及函数的相调用,ARM数据处理操作,ARM中数据的处理有以下三种形式: 简单的寄存器操作 立即数操作
7、 寄存器移位操作,其中32位立即数在32位指令中的编码以及ARM特有的寄存器移位操作是数据处理方面的难点。,设置条件码,ARM的任何数据处理指令都能通过增加“S”操作码来设置条件码(N,Z,C和V)。,条件执行 ARM指令集不同寻常的特征是每条指令(除了某些v5T指令)都可以是条件执行的。 条件转移 在程序中可以通过条件码的使用让微处理器决定是否进行转移,还可用来控制循环的退出。,汇编语言子程序调用及返回,子程序的调用 在ARM汇编语言中,子程序调用是通过BL指令来完成的。BL指令的语法格式如下: BL subname 其中,subname是被调用的子程序的名称。子程序的返回 在返回调用子程序
8、时,转移链接指令保存到LR寄存器(r14)中的值需要拷贝回程序寄存器PC(r15)。,跳转表思想,在程序设计中,有时为使程序完成一定的功能,需要调用一系列子程序中的一个,而决定究竟调用哪一个由程序的计算值确定。跳转表是解决该问题的有效方案。跳转表是利用程序计数器PC在通用寄存器文件中的可见性来实现的,如下例所示:,ARM与Thumb间的状态转换及函数的相调用,状态切换的实现 ARM/Thumb之间的状态切换是通过一条专用的转移交换指令BX来实现的。BX利用Rn寄存器中目的地址值的最后一位来判断跳转后的状态。当最后一位为0时,表示转移到ARM状态;当最后一位为1时,表示转移到Thumb状态,如下
9、图所示。,ARM与Thumb间的状态转换及函数的相调用,ARM/Thumb之间的函数调用 在同一状态下的子程序调用,通常只需要一条指令实现调用: BL function 实现返回也只需要从LR恢复PC即可: MOV PC,LR 在不同状态下的子程序调用中,就需要进行状态之间的切换,需要考虑到以下几点:,需要由BX来切换状态,因为BL不能完成状态切换。 需要在BX之前先保存好LR,BX不能自动保存返回地址到LR。 需要 用“BX LR”来返回,不能使用“MOV PC,LR”,返回时要仔细考虑保存在LR中最低位的内容是否正确。,5.2.4ARM汇编程序实例,简单的ARM指令程序 数据块复制 利用跳
10、转表实现程序跳转 ADS编译环境下的汇编代码与GNU编译环境下有较多不同点,主要是符号及伪操作的不同。,5.3 嵌入式C语言程序设计基础,5.3.1 C语言“预处理伪指令”在嵌入式程序 设计中的应用 5.3.2 嵌入式程序设计中的函数及函数库 5.3.3 嵌入式程序设计中常用的C语言语句 5.3.4 嵌入式程序设计中C语言的变量、数 组、结构、联合,5.3.1C语言“预处理伪指令”在嵌入式程序设计中的应用,“预处理命令”可以改进程序设计的环境,提高编程效率,一般以#号打头 ,可分为以下三种 :,文件包含宏定义 条件编译,文件包含,文件包含伪指令可将头文件包含到程序中,头文件中定义的内容包括符号
11、常量、复合变量原型、用户定义的变量类型原型和函数的原型说明等。编译器编译预处理时用文件包含的正文内容替换到实际程序中。 文件包含伪指令的格式 #include ;标准头文件 #include“头文件名.h” ;自定义头文件 #include 宏标识符,文件包含举例,#define MYINCLUDE “d:EmbestIDEdef.h” #include “44blib.h” #include “44b.h” #include MYINCLUDE #include “/LCD_Test/bmp.h”,宏定义,宏定义伪指令分为:简单宏、参数宏、条件宏、预定义宏及宏释放。,简单宏: # defin
12、e宏标识符 宏体 参数宏:# define宏标识符(形式参数表) 宏体 条件宏定义:#ifdef 宏标识符 #ifndef 宏标识符 #undef 宏标识符 #define 宏标识符 宏体 #define 宏标识符 宏体 #else #else #undef 宏标识符 #define 宏标识符 宏体 #define 宏标识符 宏体 #endif #endif,宏定义举例,简单宏 #define rSYSCFG (*(volatile unsigned *)0x1c00000) 参数宏 #define SQR(x,y) sqrt(x)*(y)+(y)*(y) #define min(x1,x2)
13、 (x1x2)?x1:x2) 条件宏定义 #ifndef BLOCK-SIZE#define BLOCK-SIZE 128 #else#undef BLOCK-SIZE#define BLOCK-SIZE 128 #endif,条件编译,条件编译伪指令是写给编译器的,指示编译器在满足某一条件时仅编译源文件中与之相应的部分。其格式如右框中所示:,#if(条件表达式1) #elif(条件表达式2) #elif(条件表达式n) #else #endif,5.3.2嵌入式程序设计中的函数及函数库,函数是C语言程序设计的核心。一个较大的C语言程序一般是由一个主函数和若干个子函数组成,每个函数完成一个特定
14、的功能。函数之间也可以相互调用。 函数的格式:,定义性说明格式 : 存储类说明符 类型说明符 修饰符 标识符 (参数表) 函数体 原型说明格式 : extern 类型说明符修饰符 标识符(参数表)函数体,存储类说明符:static、extern类型说明符:char、unsigned charint、unsignedlong、unsigned longfloat、double、long doublestruct、union、void修饰符:interrupt、near、far、huge标识符:函数名、*函数名等,嵌入式程序设计中的函数及函数库,函数库是为了减少编程工作量,将一些常用的功能的函数放
15、在函数库中供公共使用. 它包括C的标准库函数,也包括一些用户自己编写非标准库。 例如, 44blib.h 是根据基于S3C44B0X处理器的开发板及其功能模块编写的一个C语言函数库。它不属于C语言的标准库。,44blib.c结构如下: 用户定义头文件: 44blib.h:库函数原型定义 44b.h:44B0X片上各模块寄存器宏定义 def.h:数据类型重新宏定义,常用常量宏定义 Option.h: 44B0X片上可选项宏定义 标准头文件: Stdarg.h:定义读函数参数表宏 String.h:串操作和内存操作函数 Stdio.h:标准I/O预定义函数 Ctype.h:字符分类及转换信息,vo
16、id delay(int time); / 延时函数 void port_init(void); /I/O端口初始化函数 void cache_flush(void); /清空cache void uart_init(int nMainClk, int nBaud); void uart_select(int nChannel); void uart_txempty(int nChannel); char uart_getch(void); char uart_getkey(void); void uart_sendbyte(int nData); void uart_sendstring(c
17、har *pString); void uart_printf(char *fmt,.); void timer_start(int nDivider); / Watchdog Timer is used. int timer_stop(void); / Watchdog Timer is used. void sys_init(); / Interrupt,Port and UART int get_uartID();,5.3.3嵌入式程序设计中常用的C语言语句,C语言语句格式为: 标号: 语句; C语言语句很多,常用到的有以下几种: 条件语句swith语句循环语句,5.3.4嵌入式程序设计
18、中C语言的变量、数组、结构、联合,变量 存储类型 类型说明符 修饰符 标识符 初值 ,标识符初值; 存储类说明符:auto、register、extern、static类型说明符:char、unsigned charint、unsignedlong、unsigned longfloat、double、long doublestruct、union、void修饰符:const、volatileconst int a; volatile unsigned char *a; 标识符:变量名、*变量名等,数组一维数组: 类型说明符 标识符 常量表达式初值,初值,; char 标识符 =“字符串”;二维
19、数组: 类型说明符 标识符mn 初值表,初值表;指针数组和数组指针 类型说明符 *标志符 常量表达式 =地址,地址,; 类型说明符 (*标志符) =数组标识符;,嵌入式程序设计中C语言的变量、数组、结构、联合,结构说明存储类说明符 struct 结构原型名 类型说明标识符,标识符; 类型说明标识符,标识符; 标识符=初值表 ,标识符=初值表;,嵌入式程序设计中C语言的变量、数组、结构、联合,联合说明 存储类说明符 union联合原型名 类型说明符 标识符,标识符; 类型说明符 标识符,标识符; 标识符 =初值表,标识符初值表;,5.4 嵌入式C语言程序设计实例,5.4.1 S3VCE40开发板
20、测试程序实例 5.4.2 嵌入式C语言程序编写的简单构架 5.4.3 Flash测试代码介绍,5.4.1 S3VCE40开发板的测试程序实例,我们以S3VCE40开发板上的各个功能模块的整个测试程序为例,介绍如何运用C语言进行基于ARM的嵌入式程序设计。该程序完成的功能如下所示: 实验板加电时数码管八段全亮;LED1、LED2轮流闪烁(频率近1Hz);使用PC键盘操作;串口终端输出信息如图:然后使用开发板上的PC键盘选择各部分功能测试操作,如下图:,程序源代码介绍,整个测试程序主文件main.c的代码构成图如下图所示,由BootLoader启动程序进入C语言主函数main()入口。,5.4.2
21、嵌入式C语言程序编写的简单构架,#include预编译指令 个C语言代码,一般要用#include编译指令将所需要的头文件加到该程序中,这是很有必要的,尤其是对编写较大的程序代码时。随后是定义一些外部变量,并对程序中的函数进行声明。主函数main()的编写; 在每一个C语言代码中,一定要有一个main()函数,在该函数中完成该程序文件所要完成的各个功能,一般是通过调用各个子函数来完成。当然,它也可以调用其他文件中的函数。完成相应功能的各个功能函数的编写。 各个函数之间可以相互调用。,5.4.3Flash测试代码介绍,下面给出功能测试程序中Flash测试程序的代码结构图:,5.5 嵌入式C语言程
22、序设计技巧,5.5.1 变量定义 5.5.2 参数传递 5.5.3 循环条件,5.5.1变量定义,在变量声明的时候,最好把所有相同类型的变量放在一起定义,这样可以优化存储器布局。由下例可以看出:对于局部变量类型的定义,使用short或char来定义变量并不是总能节省存储空间。有时使用32位int或unsinged int局部变量更有效率一些,如下图所示:变量定义中,为了精简程序,程序员总是竭力避免使用冗余变量。但有时使用冗余变量可以减少存储器访问的次数这可以提高系统性能。,errs为全局变量,void test1(void) errs+=f();errs+=g(); ,Void test2(v
23、oid) int local=errs;local+=f();local+=g();errs=local; ,5.5.2参数传递,为了使单独编译的C语言程序和汇编程序能够互相调用,定义了统一的函数过程调用标准ATPCS。ATPCS定义了寄存器组中的R0R3作为参数传递和结果返回寄存器,如果参数数目超过四个,则使用堆栈进行传递。内部寄存器的访问速度是远远大于存储器的,所以要尽量使参数传递在寄存器里面进行,即应尽量把函数的参数控制在四个以下。,5.5.3循环条件,计数循环是程序中十分常用的流程控制结构,一般有以下两种形式: for (loop=1;loop=limit;loop+) for (lo
24、op=limit;loop!=0;loop-) 这两种循环形式在逻辑上并没有效率差异,但是映射到具体的体系结构中时,就产生了很大的不同,如下图所示。,5.6 C与汇编语言混合编程,5.6.1 ATPCS介绍 5.6.2 内嵌汇编 5.6.3 C和ARM汇编程序间相互调用,5.6.1ATPCS介绍,ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。,寄存器的使用规则,数据栈的使
25、用规则,根据堆栈指针指向位置的不同 和增长方向的不同可以分为以下4种数据栈 :FD (Full Descending) 满递减 ED (Empty Descending)空递减 FA (Full Ascending) 满递增EA (Empty Ascending) 空递增 ATPCS规定数据栈为FD(满递减)类型,并且对数据栈的操作是8字节对齐的。,参数的传递规则,参数个数固定的子程序参数传递规则:第一个整数参数,通过寄存器R0R3来传递。其他参数通过数据栈传递。 参数个数可变的子程序参数传递规则:当参数不超过4个时,可以使用寄存器R0R3来传递参数;当参数超过4个时,还可以使用数据栈来传递参
26、数 子程序结果返回规则 结果为一个32位的整数时,可以通过寄存器R0返回;结果为一个64位整数时,可以通过寄存器R0和R1返回,依次类推。,5.6.2内嵌汇编,在C程序中嵌入汇编程序可以实现一些高级语言没有的功能,并可以提高执行效率。armcc和armcpp内嵌汇编器支持完整的ARM指令集;tcc和tcpp用于Thumb指集。内嵌的汇编指令包括大部分的ARM指令和Thumb指令,但是不能直接引用C的变量定义,数据交换必须通过ATPCS进行。嵌入式汇编在形式上表现为独立定义的函数体。,内嵌汇编指令的语法格式,_asm(“指令;指令”); ARM C汇编器使用关键字“_asm“。如果有多条汇编指令
27、需要嵌入,可以用“”将它们归为一条语句。如: _asm 指令;指令 指令 需要特别注意的是_asm是两个下划线。,内嵌的汇编指令的特点,操作数可以是寄存器、常量或C表达式。它们可以是char、short或者int类型,而且是作为无符号数进行操作 。 内嵌的汇编指令中使用物理寄存器有一些限制。 常量前的符号“#”可以省略 只有指令B可以使用C程序中的标号,指令BL不能使用C程序中的标号。 不支持汇编语言中用于内存分配的伪操作。 指令中如果包含常量操作数,该指令可能会被汇编器展开成几条指令。,内嵌汇编器与armasm汇编器的区别,内嵌汇编器不支持通过“”指示符或PC获取当前指令地址; 不支持LDR
28、 Rn,= expression伪指令,而使用MOV Rn, expression指令向寄存器赋值; 不支持标号表达式; 不支持ADR和ADRL伪指令; 不支持BX和BLX指令; 不可以向PC赋值; 使用0x前缀替代“”表示十六进制数。,内嵌汇编注意事项,必须小心使用物理寄存器,如R0R3,LR和PC。 不要使用寄存器寻址变量。 使用内嵌汇编时,编译器自己会保存和恢复它可能用到的寄存器,用户无须保存和恢复寄存器。 LDM和STM指令的寄存器列表只允许物理寄存器。 汇编语言用“,”作为操作数分隔符,5.6.3C和ARM汇编程序间相互调用,在C和ARM汇编程序之间相互调用必须遵守ATPCS(ARM
29、-Thumb Procedure Call Standard)规则。 C和汇编之间的相互调用可以从以下这三方面来介绍:,汇编程序对C全局变量的访问 在C语言程序中调用汇编程序 在汇编程序中调用C语言程序,汇编程序访问全局C变量,汇编程序可以通过地址间接访问在C语言程序中声明的全局变量。通过使用.import关键词引人全局变量,并利用LDR和STR指令根据全局变量的地址可以访问它们。对于不同类型的变量,需要采用不同选项的LDR和STR指令,如下所示:,unsigned char LDRB/STRB unsigned short LDRH/STRH unsigned int LDR/STR cha
30、r LDRSB/STRSB short LDRSH/STRSH,在C语言程序中调用汇编程序,为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在汇编程序中需要使用EXPORT伪操作来声明,使得本程序可以被其它程序调用。同时,在C程序调用该汇编程序之前需要在C语言程序中使用extern关键词来声明该汇编程序。,在汇编程序中调用C语言程序,为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在C程序中不需要使用任何关键字来声明将被汇编语言调用的C程序,但是在汇编程序调用该C程序之前需要在汇编语言程序中使用IMPORT伪操作来声明该C程序。在汇编程序中通过BL指令来调用
31、子程序。,5.7 基于Embest IDE for ARM 环境的软件开发实例,5.7.1 嵌入式软件开发流程 5.7.2 开发实例介绍 5.7.3 源文件解释 5.7.4 工程建立和配置 5.7.5 在RAM中调试软件 5.7.6 软件的固化 5.7.7 程序在Flash中调试,5.7.1嵌入式软件开发流程,编写代码仅是嵌入式软件开发的第一步,还要学会在具体的编程环境下完成工程创建、源文件的编写、编译、链接、调试、固化等一系列工作。这就涉及到程序编译、链接、调试的具体配置以及开发板上硬件的初始化、程序下载等问题。 下面通过一个具体的在Embest IDE for ARM开发环境下的实际开发例
32、程,使读者熟悉嵌入式软件开发的整个过程,如下图所示:,5.7.2开发实例介绍,例程是一个基于S3C4510的完整的程序,可以在RAM中进行调试,固化在ROM中可以正常运行。正常运行时将间隔点亮一个发光二级管,按下按钮将点亮另外一个发光二级管。 整个软件只包含两个程序源文件:启动汇编文件 init.s 和C源程序文件 led_int.c 。 例程完整的演示了S3C4510处理器的启动过程,包括存储区配置、栈设置、中断向量设置,启动完成后演示了I/O端口的控制、中断的函数处理。,5.7.3源文件解释,启动汇编文件 启动汇编文件init.s,依次完成如下工作:中断向量的设置,系统配置寄存器的设置,存
33、储区的配置,程序中使用到的数据段向RAM区的拷贝,栈空间的初始化,进入C语言程序入口。 C主程序文件 C主程序文件 led_int.c完成I/O端口和中断的初始化、中断函数实现,本程序中未完成快速中断的处理,但作为链接的需要保留了一个快速中断处理的空函数。,5.7.4工程的建立和配置,建立工程 选择菜单项File New Workspace,系统弹出工程创建对话框 。在Project name编辑框中输入新建工程名led_int,Location编辑框中输入保存该工程的目录路径C:EmbestIDEEV4510led_int。 选择OK按钮,创建新工程led_int,集成环境将创建与工程同名的
34、workplace和project。在工作区窗中选择右键菜单创建源文件夹并添加相关源文件 。,工程的建立和配置,工程配置 工程创建完成后,需要对工程进行配置,Embest IDE才能正确的编译、链接和调试等。选择Project Settings 菜单项,弹出工程配置对话框,如下图所示 。 工程配置包括处理器的选择、仿真器配置、调试配置、目录配置、编译配置、汇编配置、链接配置,工程配置是整个软件开发过程中非常关键的一步。,5.7.5在RAM中调试软件,软件的调试既可以在ROM区也可以在RAM区完成,由于RAM区可以很方便地读写,访问速度高,因此软件开发过程中的调试只要硬件条件许可,都应该在RAM
35、区完成。 软件调试前需要完成以下几步: 编译链接工程 连接仿真器、评估板 程序下载,5.7.6软件的固化,在RAM中调试通过的程序与最终固化到电路板的Flash中的程序有所区别,需要做以下改动: 在汇编器的预定义选项中设置ROM=1,或者直接在init.s文件中增加 “.equ ROM ” 。 在链接器的链接文件中选择ldscript.flash 。 重新编译程序。然后使用Elf to Bin工具将led_int.elf文件转换成二进制指令格式文件led_int.bin。 最后使用Embest Flash Programmer将led_int.bin下载到电路板的Flash中,如图所示。,5.7.7程序在Flash中调试,程序在Flash中与在RAM中调试工程配置不同:调试选项中不需要执行脚本文件,该工作在启动文件中完成,需要将连接后行为(Action after connected)选项改为无(None); 调试过程也有所不同:连接仿真器后,无需再执行下载(Download)程序操作;如果要从启动程序的入口开始调试程序,先必须执行复位(reset)命令,此时程序将停在零地址处;程序在Flash中调试时最多可以设置两个硬件断点。,