1、第三章 汇编语言与 C 语言 3.1 C 语言与汇编语言的比较 本课程全程使用 C 语言来开发 S3C2410A。目前很少有用汇编语言来开发 ARM 系统了。 相比较而言汇编语言的优势是执行效率高,但其劣势是代码效率较低,而 C 语言正好相反, 其代码效率较高,执行效率较低。 什么是执行效率?什么是代码效率?在嵌入式设计领域,一般执行效率就是程序的执 行时间可以精确控制,从而可以使程序高效率的运行。采用汇编语言编写 ARM 程序,每一 条指令的执行时间都是固定不变的(外部时钟确定) ,所以写出的程序每一步的执行时间都 是可以精确控制的。这是采用汇编语言的优势所在。但是,相信有过用汇编语言进行程
2、序 设计经验的读者在读别人用汇编语言写的程序时都会觉得十分吃力,尤其在碰到一些编程 习惯不好的 coding,整个程序注释寥寥或者干脆没有,那么对于一般的设计人员来讲,这 样的程序就是天书一卷了。这也就是所谓的程序可读性不高,不便于维护和移植重用。这 也可以说是代码效率底下。C 语言编写的程序相对来说可读性高,便于移植重用,结构灵 活。一个注释完全,结构完整的 C 程序很容易就读懂了,而且我们还可以把一些常用的代 码封装成函数,这样就可以根据需要来直接调用这些函数。C 语言有了这些性质,相对于 汇编语言其代码效率就较高了。 C 语言有其优点,必然在嵌入式领域内还有其不足的地方。采用 C 语言编
3、写的 ARM 驱 动代码需要经过编译器编译而生成相对应的汇编代码,最后生成可下载执行的二进制文件。 在这个过程中,所生成的汇编代码完全由编译器所决定,这样一来对于一条 C 语句来讲预 先不知道所生成的汇编代码有几多,所以也就无从精确判断程序执行的时间,这种特质也 就是所谓的执行效率相对较低。 在嵌入式控制领域,有一些设计对于程序的执行时间需要精确的把握,大多数设计对 于程序的时间要求没有那么精确。故此,一般情况下 C 语言完全能够胜任开发任务。如果 在设计中碰到了需要严格把握程序执行时间的地方,可以根据需要采取 C 语言和汇编语言 混合编程的方法来处理。 3.2 应用 C 语言开发 ARM 入
4、门 学过 51 单片机开发的读者都知道,控制单片机其实就是对其内部的一些寄存器进行配 置和操作。ARM 开发与单片机开发十分相似,只不过 ARM 的结构相对于单片机更为复杂, 内部寄存器更多而已。 1. 访问 S3C2410A 的功能寄存器 采用汇编程序可以对 ARM 的寄存器进行操作,而采用 C 语言开发驱动则需要定义寄存 器的头文件。对于达盛的试验系统来讲,S3C2410A 的寄存器定义头文件为 2410ADDR.H。 在这个文件中,所有的 2410 芯片开发所用到的寄存器全部都进行了定义。在用 C 语言开 发程序时,必须先包含这个头文件,然后在主程序中可以直接对定义好的寄存器进行操作。
5、/ I/O PORT #define rGPACON (*(volatile unsigned *)0x56000000) /Port A control #define rGPADAT (*(volatile unsigned *)0x56000004) /Port A data #define rGPBCON (*(volatile unsigned *)0x56000010) /Port B control #define rGPBDAT (*(volatile unsigned *)0x56000014) /Port B data #define rGPBUP (*(volatile
6、unsigned *)0x56000018) /Pull-up control B 上面几行程序就是从 2410ADDR.H 摘录出来的 I/O 口寄存器的部分定义。比如第一行 定义意思是说端口 A 的控制寄存器命名为 rGPACON,其在 S3C2410A 芯片中的访问地址是 0x56000000,在编写程序时,可以直接给 rGPACON 赋值,实际上也就是给地址 0x56000000 赋值。例如: rGPGCON = rGPGCON 上面这个操作其实是对寄存器 rGPGCON 进行了相应的配置。再比如第二行程序定义了端口 A 的数据寄存器并且命名为 rGPADAT,其在 S3C2410A
7、芯片中的访问地址是 0x56000004, 我们也可以在程序中对 rGPADAT 进行赋值,例如: rGPGDAT = rGPGDAT 2. 跳转操作 在 ARM 汇编指令中有相应的跳转指令可以使用,那么对应于 C 语言,跳转语句可以翻 译成 if else 模式或者 switch case 模式。 3. 循环操作 在 C 语言中循环操作最为简单,可以采用 for 语句或者 while 语句都可以实现循环操作。 4. C 语言变量的位数 ARM 为 32 位 CPU,在 ARM 开发中有时会需要定义 8 位、16 位或者 32 位变量,这些 变量实际上就对应着处理器中的通用寄存器。在一些特殊的
8、时候还需要对数据精确到 bit 来操作,这样就需要定义特殊的位段结构来实现(有兴趣的读者可以上网参考一些资料, 这里就不再赘述) 。所以我们应当清楚 C 语言中变量的范围和位数。下表列出了常用的 C 变量的位数。 数据类型 位数(byte) char,signed char,unsigned char 1 short, signed short,unsigned short 2 int,signed int,unsigned int 4 long,signed long,unsigned long 4 5. ARM C 语言程序的使用规则 在 ARM 程序的开发中,需要大量读写硬件寄存器,并且
9、尽量缩短程序的执行时间的代 码一般使用汇编语言来编写,比如 ARM 的启动代码,ARM 的操作系统的移植代码等,除此 之外,绝大多数代码可以使用 C 语言来完成。 C 语言使用的是标准的 C 语言, ARM 的开发环境实际上就是嵌入了一个 C 语言的集成 开发环境,只不过这个开发环境和 ARM 的硬件紧密相关。 在使用 C 语言时,要用到和汇编语言的混合编程。当汇编代码较为简洁,则可使用直 接内嵌汇编的方法,否则,使用将汇编文件以文件的形式加入项目当中,通过 ATPCS 的规 定与 C 程序相互调用与访问。 ATPCS,就是 ARM、Thumb 的过程调用标准(ARM/Thumb Proced
10、ure Call Standard) , 它规定了一些子程序间调用的基本规则。如寄存器的使用规则,堆栈的使用规则,参数的 传递规则等。 在 C 程序和 ARM 的汇编程序之间相互调用必须遵守 ATPCS。而使用 ADS 的 C 语言编译 器编译的 C 语言子程序满足用户指定的 ATPCS 的规则。但是,对于汇编语言来说,完全要 依赖用户保证各个子程序遵循 ATPCS 的规则。具体来说,汇编语言的子程序应满足下面 3 个条件: (1) 在子程序编写时,必须遵守相应的 ATPCS 规则; (2)堆栈的使用要遵守相应的 ATPCS 规则; (3)在汇编编译器中使用-atpcs 选项。 (4)汇编程序
11、调用 C 程序 汇编程序的设置要遵循 ATPCS 规则,保证程序调用时参数正确传递。 在汇编程序中使用 IMPORT 伪指令声明将要调用的 C 程序函数。 在调用 C 程序时,要正确设置入口参数,然后使用 BL 调用。 (5)C 程序调用汇编程序 汇编程序的设置要遵循 ATPCS 规则,保证程序调用时参数正确传递。 在汇编程序中使用 EXPORT 伪指令声明本子程序,使其他程序可以调用此子程序。 在 C 语言中使用 extern 关键字声明外部函数(声明要调用的汇编子程序) 。 在 C 语言的环境内开发应用程序,一般需要一个汇编的启动程序,从汇编的启动程序, 跳到 C 语言下的主程序,然后,执
12、行 C 程序,在 C 环境下读写硬件的寄存器,一般是通过 宏调用,在每个项目文件的 Startup2410/INC 目录下都有一个 2410addr.h 的头文件,那里 面定义了所有关于 2410 的硬件寄存器的宏,对宏的读写,就能操作 2410 的硬件。 具体的编程规则同标准 C 语言。 6. 下面是一个简单的小例子 IMPORT Main AREA Init ,CODE, READONLY; ENTRY LDR R0, =0x01d00000 LDR R1, =0x245 STR R1 , R0 ;把 0x245 放到地址 0X01D00000 BL Main ;跳转到 Main()函数处
13、的 C/C+程序 END ;标识汇编程序结束 以上是一个简单的程序,先寄存器初始化,然后跳转到 Main()函数标识的 C/C+代 码处,执行主要任务,此处的 Main 是声明的 C 语言中的 Main()函数。 对宏的预定义,在 2410addr.h 中已定义,如: #define rGPGCON (*(volatile unsigned *)0x56000060) /Port G control #define rGPGDAT (*(volatile unsigned *)0x56000064) /Port G data #define rGPGUP (*(volatile unsigne
14、d *)0x56000068) /Pull-up control G 在程序中实现, for(;) if(flag=0) for(i=0;i100000;i+); /延时 rGPGCON = rGPGCON rGPGDAT = rGPGDAT for(i=0;i100000;i+); /延时 flag = 1; else for(i=0;i100000;i+); /延时 rGPGCON = rGPGCON rGPGDAT = rGPGDAT for(i=0;i100000;i+); /延时 flag = 0; 完成对 GPIO 的 G 口的操作,该程序可以交替点亮 CPU 板左下角的两个 LE
15、D 灯。 7. 实例说明 #include #include “INCconfig.h“/头文件包含,config.h 中已经包含了 2410ADDR.H void Main(void) /主函数入口 int i,j; /定义了两个 32 位的变量 Target_Init(); /目标初始化函数调用 rGPGCON = rGPGCON /配置端口 G 的控制寄存器 rGPGDAT = rGPGDAT /向端口 G 的数据寄存器写数据 for(i=0;i4000000;i+);/延时功能 for(i=0;i4000000;i+);/延时功能 for(;)/死循环,等同于 while(1) rGP
16、GDAT = rGPGDAT for(i=0;i4000000;i+); /延时 for(i=0;i4000000;i+); /延时 rGPGDAT = rGPGDAT for(j=0;j4000000;j+); /延时 for(j=0;j4000000;j+); /延时 注:一个好的程序不仅结构要紧凑严谨,同时还要有大量的注释以便别人阅读和维护。 8. 实验 (1)实验内容 用 C 语言编写一个简单的应用程序 (2)实验设备 EL-ARM-830 教学实验箱,PentiumII 以上的 PC 机,仿真调试电缆,串口电缆。 PC 操作系统 WIN98 或 WIN2000 或 WINXP,ADS
17、1.2 集成开发环境,仿真调试驱动 程序 (3)实验步骤 本实验仅使用实验教学系统的 CPU 板,串口。在进行本实验时,LCD 电源开关、 音频的左右声道开关、AD 通道选择开关、触摸屏中断选择开关等均应处在关闭状态。 在 PC 机并口和实验箱的 CPU 板上的 JTAG 接口之间,连接 WIGGER 调试电缆,以 及串口间连接公/母接头串口线。 检查连接是否可靠,可靠后,接入电源线,系统上电,打开 H_JTAG 软件检测 CPU。 打开 ADS1.2 开发环境,从里面打开实验程序HARDWAREADS实验三C.mcp 项 目文件,进行编译。 编译通过后,进入 ADS1.2 调试界面,加载实验
18、程序HARDWAREADS实验一 C_DataDebug 中的映象文件程序映像 C.axf。 打开/实验软件/tools/目录下的串口调试助手工具,配置为波特率为 115200, 校验位无,数据位为 8,停止位为 1。不要选十六进制显示。之后,在 ADS 调试环境下全 速运行映象文件,应出现图 3.1 界面。本程序连续发送 55。 图 3.1 下边分析一下主程序的源码。 #include “incconfig.h“ /嵌入包括硬件的头文件 unsigned char data; /定义全局变量 void Main(void) Target_Init(); /目标板初始化,定义串口的硬件初始化在 /target.c 中定义 Delay(10); /延时 data = 0x55; /给全局变量赋值 while(1) Uart_Printf(“%x “,data); /串口 0 输出 Delay(10); 把 data = 0x55;语句中的 0x55,换成其他 8 位数,重新编译,下载,看看串口工 具上输出是什么内容。