Chinaunix首页 | 论坛 | 博客
  • 博客访问: 147307
  • 博文数量: 49
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 595
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-25 23:08
文章分类
文章存档

2011年(1)

2009年(48)

我的朋友

分类: LINUX

2009-03-18 21:13:59

 

第五章基于ARM的嵌入式程序设计

5.1 ARM汇编语言的伪操作、宏指令与伪指令

5.1.1 两种常见的ARM编译开发环境

ADS/SDT IDE开发环境:它由ARM公司开发,使用了CodeWarrior公司的编译器;

集成了GNU开发工具的IDE开发环境::它由GNU的汇编器as、交叉编译器gcc、和链接器ld等组成。

5.1.2 ADS编译环境下的伪操作和宏指令

ADS编译环境下的伪操作可分为以下几类:

符号定义(Symbol Definition)伪操作

数据定义(Data Definition)伪操作

汇编控制(Assembly Control)伪操作

信息报告(Reporting)伪操作

其他(Miscellaneous)伪操作

5.1.3 GNU编译环境下的伪操作和宏指令

GNU编译环境下的伪操作可分为以下几类:

常量编译控制伪操作

汇编程序代码控制伪操作

宏及条件编译控制伪操作

其他伪操作

5.1.3 GNU编译环境下的伪操作和宏指令

5.1.4 ARM汇编语言的伪指令

5.2 ARM汇编语言程序设计

5.2.1 ARM汇编中的文件格式

ARM源程序文件(可简称为源文件)可以由任意一种文本编辑器来编写程序代码,它一般为文本格式。在ARM程序设计中,常用的源文件可简单分为以下几种:

汇编程序文件 *.S ARM汇编语言编写的ARM程序或Thumb程序。

C程序文件 *.C C语言编写的程序代码。

头文件 *.H 为了简化源程序,把程序中常用到的常量命名、宏定义、数据结构定义等等单独放在一个文件中,一般称为头文件。

5.2.2 ARM汇编语言语句格式

ARM汇编语言语句格式如下所示:

symbol}{instruction | directive | pseudo-instruction} {;comment

其中:

instruction为指令。

directive为伪操作。

pseudo-instruction为伪指令。

symbol为符号。

comment为语句的注释。

ARM汇编语言是以段(section)为单位来组织源文件的。段是相对独立的、具有特定名称的、不可分割的指令或者数据序列。段又可以分为代码段和数据段,代码段存放执行代码,数据段存放代码运行时需要用到的数据。一个ARM源程序至少需要一个代码段,大的程序可以包含多个代码段和数据段。

举例说明ARM汇编语言源程序的基本结构

AREA EXAMPLECODEREADONLY

ENTRY

start

MOV r0#10

MOV r1#3

ADD r0r0r1

END



本程序的程序体部分实现了一个简单的加法运算

5.2.3 ARM汇编语言编程的重点

ARM数据处理操作

设置条件码

汇编语言子程序调用及返回

跳转表思想

ARMThumb之间的状态转换及函数的相调用

ARM中数据的处理有以下三种形式:

简单的寄存器操作

立即数操作

寄存器移位操作

其中32位立即数在32位指令中的编码以及ARM特有的寄存器移位操作是数据处理方面的难点。

设置条件码

ARM的任何数据处理指令都能通过增加“S”操作码来设置条件码(NZCV)。



条件执行

ARM指令集不同寻常的特征是每条指令(除了某些v5T指令)都可以是条件执行的。

条件转移

在程序中可以通过条件码的使用让微处理器决定是否进行转移,还可用来控制循环的退出。

汇编语言子程序调用及返回

子程序的调用

ARM汇编语言中,子程序调用是通过BL指令来完成的。BL指令的语法格式如下:

BL subname

其中,subname是被调用的子程序的名称。

子程序的返回

在返回调用子程序时,转移链接指令保存到LR寄存器(r14)中的值需要拷贝回程序寄存器PCr15)。

跳转表思想

在程序设计中,有时为使程序完成一定的功能,需要调用一系列子程序中的一个,而决定究竟调用哪一个由程序的计算值确定。跳转表是解决该问题的有效方案。跳转表是利用程序计数器PC在通用寄存器文件中的可见性来实现的,如下例所示:

ARMThumb间的状态转换及函数的相调用

状态切换的实现

ARM/Thumb之间的状态切换是通过一条专用的转移交换指令BX来实现的。BX利用Rn寄存器中目的地址值的最后一位来判断跳转后的状态。当最后一位为0时,表示转移到ARM状态;当最后一位为1时,表示转移到Thumb状态,如下图所示

ARMThumb间的状态转换及函数的相调用

ARM/Thumb之间的函数调用

在同一状态下的子程序调用,通常只需要一条指令实现调用: BL function

实现返回也只需要从LR恢复PC即可:

MOV PCLR

在不同状态下的子程序调用中,就需要进行状态之间的切换,需要考虑到以下几点:

需要由BX来切换状态,因为BL不能完成状态切换。

需要在BX之前先保存好LRBX不能自动保存返回地址到LR

需要 用“BX LR”来返回,不能使用“MOV PCLR”,返回时要仔细考虑保存在LR中最低位的内容是否正确。

5.2.4 ARM汇编程序实例

数据块复制

简单的ARM指令程序

利用跳转表实现程序跳转

ADS编译环境下的汇编代码与GNU编译环境下有较多不同点,主要是符号及伪操作的不同。

5.3 嵌入式C语言程序设计基础

5.3.1 C语言“预处理伪指令”在嵌入式程序设计中的应用

“预处理命令”可以改进程序设计的环境,提高编程效率,一般以#号打头,可分为以下三种 :

文件包含

文件包含伪指令可将头文件包含到程序中,头文件中定义的内容包括符号常量、复合变量原型、用户定义的变量类型原型和函数的原型说明等。编译器编译预处理时用文件包含的正文内容替换到实际程序中。

文件包含伪指令的格式

#include<头文件名.h> ;标准头文件

#include“头文件名.h” ;自定义头文件

#include 宏标识符

宏定义

宏定义伪指令分为:简单宏、参数宏、条件宏、预定义宏及宏释放。

简单宏: # define宏标识符宏体

参数宏:# define宏标识符(形式参数表) 宏体

条件宏定义:

#ifdef 宏标识符 #ifndef 宏标识符

#undef 宏标识符 #define 宏标识符 宏体

#define 宏标识符 宏体 #else

#else #undef 宏标识符

#define 宏标识符 宏体 #define 宏标识符 宏体

#endif #endif

条件编译

条件编译伪指令是写给编译器的,指示编译器在满足某一条件时仅编译源文件中与之相应的部分。其格式如右框中所示:

#if(条件表达式1

#elif(条件表达式2

#elif(条件表达式n

#else

#endif

5.3.2 嵌入式程序设计中的函数及函数库

函数是C语言程序设计的核心。一个较大的C语言程序一般是由一个主函数和若干个子函数组成,每个函数完成一个特定的功能。函数之间也可以相互调用。

函数的格式:

定义性说明格式 :

[存储类说明符] 类型说明符 [修饰符] 标识符 (参数表) {函数体}

原型说明格式 :

extern 类型说明符[修饰符] 标识符(参数表){函数体}

函数库是为了减少编程工作量,将一些常用的功能的函数放在函数库中供公共使用.

它包括C的标准库函数,也包括一些用户自己编写非标准库。

例如, 44blib.h 是根据基于S3C44B0X处理器的开发板及其功能模块编写的一个C语言函数库。它不属于C语言的标准库.



5.3.3 嵌入式程序设计中常用的C语言语句

C语言语句格式为:

[标号:] 语句[]

C语言语句很多,常用到的有以下几种:

条件语句

条件语句的格式

两重选择:

if(条件表达式)

语句1

else

语句2

多重选择 :

if(条件表达式1

语句2

else if(条件表达式2

语句3

else if(条件表达式n

语句n

swith语句

switch语句的格式

switch(开关表达式)

{ case常量表达式1[语句1]

case常量表达式2[语句2]

case常量表达式n[语句n]

default[语句n+1]

}

循环语句

循环语句的格式

for循环语句格式 :

for(表达式1;表达式2;表达式3

语句;

while循环语句格式:

while(条件表达式)

语句;

do while循环语句格式:

do

语句;

while(条件表达式);

5.3.4 嵌入式程序设计中C语言的变量、数 组、结构、联合

变量

[存储类型] 类型说明符 [修饰符] 标识符 [=初值] [,标识符[=初值]]…

数组

一维数组:

类型说明符 标识符 [常量表达式][{初值,初值,…}]

char 标识符[ ] =“字符串”;

二维数组:

类型说明符 标识符[m][n] [{{初值表},{初值表}…}]

指针数组和数组指针

类型说明符 *标志符 [常量表达式] [={地址,地址,…}]

类型说明符 (*标志符)[ ][=数组标识符]

结构说明

[存储类说明符] struct [结构原型名]

{ 类型说明标识符[,标识符…]

类型说明标识符[,标识符…]

}标识符[={初值表} [,标识符[={初值表}]…]

联合说明



[存储类说明符] union[联合原型名]

{类型说明符 标识符[,标识符…]

类型说明符 标识符[,标识符…]

}标识符 ={初值表}[,标识符[{初值表}]…]

5.4 嵌入式C语言程序设计实例

5.4.1 嵌入式C语言程序编写的简单构架

程序源代码介绍

整个测试程序主文件main.c的代码构成图如下图所示,由BootLoader启动程序进入C语言主函数main()入口。

#include预编译指令

C语言代码,一般要用#include编译指令将所需要的头文件加到该程序中,这是很有必要的,尤其是对编写较大的程序代码时。随后是定义一些外部变量,并对程序中的函数进行声明。

主函数main()的编写;

在每一个C语言代码中,一定要有一个main()函数,在该函数中完成该程序文件所要完成的各个功能,一般是通过调用各个子函数来完成。当然,它也可以调用其他文件中的函数。

完成相应功能的各个功能函数的编写。

各个函数之间可以相互调用。

5.4.2 Flash测试代码介绍

下面给出功能测试程序中Flash测试程序的代码结构图:

5.5 嵌入式C语言程序设计技巧

5.5.1 变量定义

在变量声明的时候,最好把所有相同类型的变量放在一起定义,这样可以优化存储器布局。由下例可以看出:

对于局部变量类型的定义,使用shortchar来定义变量并不是总能节省存储空间。有时使用32intunsinged int局部变量更有效率一些,如下图所示:

变量定义中,为了精简程序,程序员总是竭力避免使用冗余变量。但有时使用冗余变量可以减少存储器访问的次数这可以提高系统性能。

5.5.2 参数传递

为了使单独编译的C语言程序和汇编程序能够互相调用,定义了统一的函数过程调用标准ATPCSATPCS定义了寄存器组中的{R0~R3}作为参数传递和结果返回寄存器,如果参数数目超过四个,则使用堆栈进行传递。

内部寄存器的访问速度是远远大于存储器的,所以要尽量使参数传递在寄存器里面进行,即应尽量把函数的参数控制在四个以下。

5.5.3 循环条件

5.6 C与汇编语言混合编程

5.6.1 ATPCS介绍

ATPCSARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。

数据栈的使用规则

根据堆栈指针指向位置的不同 和增长方向的不同可以分为以下4种数据栈:

FD Full Descending) 满递减

ED Empty Descending)空递减

FA Full Ascending) 满递增

EA Empty Ascending) 空递增

ATPCS规定数据栈为FD(满递减)类型,并且对数据栈的操作是8字节对齐的。

参数的传递规则

参数个数固定的子程序参数传递规则:

第一个整数参数,通过寄存器R0R3来传递。其他参数通过数据栈传递。

参数个数可变的子程序参数传递规则:

当参数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数超过4个时,还可以使用数据栈来传递参数

子程序结果返回规则

结果为一个32位的整数时,可以通过寄存器R0返回;结果为一个64位整数时,可以通过寄存器R0R1返回,依次类推。

5.6.2 内嵌汇编

C程序中嵌入汇编程序可以实现一些高级语言没有的功能,并可以提高执行效率。armccarmcpp内嵌汇编器支持完整的ARM指令集;tcctcpp用于Thumb指集。

内嵌的汇编指令包括大部分的ARM指令和Thumb指令,但是不能直接引用C的变量定义,数据交换必须通过ATPCS进行。嵌入式汇编在形式上表现为独立定义的函数体。

内嵌汇编指令的语法格式

__asm(“指令[;指令]”);

ARM C汇编器使用关键字“__asm"。如果有多条汇编指令需要嵌入,可以用“{}”将它们归为一条语句。如:

__asm

指令[;指令]

[指令]

需要特别注意的是__asm是两个下划线。

内嵌的汇编指令的特点

操作数可以是寄存器、常量或C表达式。它们可以是charshort或者int类型,而且是作为无符号数进行操作。

内嵌的汇编指令中使用物理寄存器有一些限制。

常量前的符号“#”可以省略

只有指令B可以使用C程序中的标号,指令BL不能使用C程序中的标号。

不支持汇编语言中用于内存分配的伪操作。

指令中如果包含常量操作数,该指令可能会被汇编器展开成几条指令。

内嵌汇编器与armasm汇编器的区别

内嵌汇编器不支持通过“·”指示符或PC获取当前指令地址;

不支持LDR Rn= expression伪指令,而使用MOV Rnexpression指令向寄存器赋值;

不支持标号表达式;

不支持ADRADRL伪指令;

不支持BXBLX指令;

不可以向PC赋值;

使用0x前缀替代“&”表示十六进制数

内嵌汇编注意事项

必须小心使用物理寄存器,如R0~R3LRPC

不要使用寄存器寻址变量。

使用内嵌汇编时,编译器自己会保存和恢复它可能用到的寄存器,用户无须保存和恢复寄存器。

LDMSTM指令的寄存器列表只允许物理寄存器。

汇编语言用“,”作为操作数分隔符

5.6.3 CARM汇编程序间相互调用

CARM汇编程序之间相互调用必须遵守ATPCSARM-Thumb Procedure Call Standard)规则。

C和汇编之间的相互调用可以从以下这三方面来介绍:

汇编程序对C全局变量的访问

汇编程序可以通过地址间接访问在C语言程序中声明的全局变量。通过使用IMPORT关键词引人全局变量,并利用LDRSTR指令根据全局变量的地址可以访问它们。

对于不同类型的变量,需要采用不同选项的LDRSTR指令,如下所示: unsigned char LDRB/STRB

unsigned short LDRH/STRH

unsigned int LDR/STR

char LDRSB/STRSB

short LDRSH/STRSH

c语言程序中调用汇编程序

为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在汇编程序中需要使用EXPORT伪操作来声明,使得本程序可以被其它程序调用。同时,在C程序调用该汇编程序之前需要在C语言程序中使用extern关键词来声明该汇编程序。

在汇编程序中调用C语言程序

为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在C程序中不需要使用任何关键字来声明将被汇编语言调用的C程序,但是在汇编程序调用该C程序之前需要在汇编语言程序中使用IMPORT伪操作来声明该C程序。在汇编程序中通过BL指令来调用子程序。

阅读(1964) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~