2.解析C语言的内部运行机制

目录

1.解析C语言的内部机制

2.了解ARM-THUMB 子程序调用规则 ATPCS

3.分析C语言的反汇编代码

4.传递参数(调用者传参数给背调用者)

5.看门狗定时器(WATCHDOG  TIMER)

6.优化程序



1.解析C语言的内部机制

1.把上一节编译第10节的C语言控制代码在Linux系统反汇编文件(它的功能是点亮一颗裸板的LED灯),led.dis文件传windows系统查看,然后分析这个程序时如何运行的,具体看上一节内容:点我查看

c语言代码:

int main()
{
	unsigned int *pGPFCON=(unsigned int *)0x56000050;
	unsigned int *pGPFDAT=(unsigned int *)0x56000054;
	/*配置GPF4的引脚为输出引脚*/
 
	*pGPFCON=0x100;
 
	/*配置GPF4的引脚输出低电平(点亮LED)*/
	*pGPFDAT=0;
 
	return ;
	
}

汇编代码 .S(启动文件):

.text
.global _start
 
_start:
 
/*c语言中局部变量保存在栈中,栈对应的是一块内存*/
/*设置内存 :sp 栈*/
	ldr sp ,=4096; //Nand Flash 启动
 
/*对于2440来说,当设置为Nand启动,从0开始的4k空间
  对应的是片内内存,把栈设置在内存的顶部*/
 
   // ldr sp ,=0x40000000+4096; //Nand Flash 启动
/*对于2440来说,当设置为Nor启动,片内4k内存的地址是0x40000000
  对应的是片内内存,把栈设置在内存的顶部*/
	
/*调用main函数,跳转到main函数*/
	bl main
halt:
	b halt

led.dis的文件的反汇编代码内容如下:

目的:分析c语言的运行机制,熟悉栈和内存分配的一些概念。


led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:	e3a0da01 	mov	sp, #4096	; 0x1000
   4:	eb000000 	bl	c <main>

00000008 <halt>:
   8:	eafffffe 	b	8 <halt>

0000000c <main>:
   c:	e1a0c00d 	mov	ip, sp
  10:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  14:	e24cb004 	sub	fp, ip, #4	; 0x4
  18:	e24dd008 	sub	sp, sp, #8	; 0x8
  1c:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  20:	e2833050 	add	r3, r3, #80	; 0x50
  24:	e50b3010 	str	r3, [fp, #-16]
  28:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  2c:	e2833054 	add	r3, r3, #84	; 0x54
  30:	e50b3014 	str	r3, [fp, #-20]
  34:	e51b2010 	ldr	r2, [fp, #-16]
  38:	e3a03c01 	mov	r3, #256	; 0x100
  3c:	e5823000 	str	r3, [r2]
  40:	e51b2014 	ldr	r2, [fp, #-20]
  44:	e3a03000 	mov	r3, #0	; 0x0
  48:	e5823000 	str	r3, [r2]
  4c:	e3a03000 	mov	r3, #0	; 0x0
  50:	e1a00003 	mov	r0, r3
  54:	e24bd00c 	sub	sp, fp, #12	; 0xc
  58:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.

看起来,比直接使用汇编语言复杂很多,其实c语言最终也是要转换成汇编语言,最终转换成机器码(二进制),烧写到soc上运行的。

先捋一下思路:

1.在汇编文件中,设置栈内存的地址

2.使用bl命令调用main,且设置返回地址存入lr寄存器

3.在c程序中,有一个main函数,写寄存器的值

问题:

1.为何要设置栈?

答:因为c语言要设置局部变量,有变量就需要内存

2.如何使用栈?

答:局部变量是保存在栈中的,保存lr等寄存器

问题:

1.调用者如何向被调用者传递参数?

2.被调用者如何传返回值给调用者?

3.如何从栈中恢复寄存器?

想要了解这些问题需要对ARM-THUMB 子程序调用规则有所了解。


2.了解ARM-THUMB 子程序调用规则 ATPCS

为了使 C 语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则,在ARM 处理器中,这个规则被称为 ATPCS:ARM 程序和 Thumb 程序中子程序调用的规则。基本的ATPCS 规则包括寄存器使用规则、数据栈使用规则、参数传递规则。

2.1. 寄存器使用规则
ARM 处理器中有 r0~r15 共 16 个寄存器,它们的用途有一些约定的习惯,并依具这些用途定义了别名,如下表所示:

  • PCS 中各寄存器的使用规则及其名称

寄存器  别名                 使用规则
r15         pc                  程序计数器
r14          lr                   连接寄存器
r13         sp                  数据栈指针
r12         ip                  子程序内部调用的 scratch 寄存器
r11         v8                  ARM 状态局部变量寄存器 8
r10         v7、s1          ARM 状态局部变量寄存器 7、在支持数据栈检查的 ATPCS 中为数据栈限制指针
r9           v6、sb          ARM 状态局部变量寄存器 6、在支持 RWPI 的 ATPCS 中为静态基址寄存器
r8            v5                ARM 状态局部变量寄存器 5
r7            v4、wr        ARM 状态局部变量寄存器 4、Thumb 状态工作寄存器
r6             v3               ARM 状态局部变量寄存器 3
r5            v2                ARM 状态局部变量寄存器 2
r4             v1               ARM 状态局部变量寄存器 1
r3             a4              参数/结果/scratch 寄存器 4
r2             a3              参数/结果/scratch 寄存器 3
r1             a2              参数/结果/scratch 寄存器 2
r0             a1              参数/结果/scratch 寄存器 1


寄存器的使用规则总结如下:

  •   子程序间通过寄存器 r0~r3 来传递参数,这时可以使用它们的别名 a0~a3。被调用的子程序返回前无需恢复 r0~r3 的内容。
  •   在子程序中,使用 r4~r11 来保存局部变量,这时可以使用它们的别名 v1~v8。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器则不必进行这些操作。在 Thumb 程序中,通常只能使用寄存器 r4~r7 来保存局部变量。
  •   寄存器 r12 用作子程序间 scratch 寄存器,别名为 ip。
  •   寄存器r13用作数据栈指针,别名为sp。在子程序中寄存器r13不能用作其他用途。它的值在进入、退出子程序时必须相等。
  •   寄存器 r14 被称为连接寄存器,别名为 lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将 lr 值保存到数据栈中),r14 可以用作其他用途。
  •   寄存器 r15 是程序计数器,别名为 pc。它不能用作其他用途。

2.2.数据栈使用规则

数据栈有两个增长方向:向内存地址减小的方向增长时,称为 DESCENDING 栈;向内地址增加的方向增长时,称为 ASCENDING 栈。
所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为 FULL 栈;当栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元时,称为 EMPTY 栈。
综合这两个特点,数据栈可以分为以下 4 种:
① FD Full Descending,满递减
② ED Empty Descending,空递减
③ FA Full Ascending,满递增
④ EA Empty Ascending,空递增

注意:ATPCS 规定数据栈为 FD 类型,并且对数据栈的操作是 8 字节对齐的。使用 stmdb/ldmia批量内存访问指令来操作 FD 数据栈。使用 stmdb 命令往数据栈中保存内容时,“先递减 sp 指针,再保存数据”,使用 ldmia命令从数据栈中恢复数据时, “先获得数据,再递增 sp 指针”──sp 指针总是指向栈顶元素,这刚好是 FD 栈的定义。

2.3. 参数传递规则
一般来说,当参数个数不超过 4 个时,使用 r0~r3 这 4 个寄存器来传递参数;如果参数个数超过 4 个,剩余的参数通过数据栈来传递。对于一般的返回结果,通常使用 a0~a3 来传递。


 简图分析如下:


3.分析C语言的反汇编代码

3.1.知识基础

假设程序从Nand启动,对于Nand启动的程序,硬件上会把Nand Flash 前4K的内容完全复制到片内的4K内存上,那么上面的哪些机器码在程序运行就会保存在片内内存4k内存的前面。

机器码在片内RAM的存储结构简图如下所示:

3.2.从第一句开始分析代码:

开发版一上电,从0地址开始执行

接着开始执行main函数的内容了

注意:pc的值是当前的地址+8,所以pc=0x10+0x8=0x18; 

再执行下面的语句: 

得到的结果使用简图表示为:

 

接着分析下一条语句:

执行完如下图所示:

   

继续

因为r0-r3用来保存调用和被调用者的参数,因此,C语言中的ruturn 应该保存在这几个变量中的一个。

退出主函数时恢复栈

从栈中恢复寄存器采用的是  ldmia 指令

指令:ldm

含义:读内存读取数据,然后把读取的数据写入多个寄存器

命令解析:

例子:

ldmia    sp, {fp, sp, pc}

假设:sp=4080

例子:ia的含义是过后增加(Increment After),就是先读取后增加,而且的顺序的依据是:高编号的寄存器存储在高地址

fp,sp,  pc 这三个的寄存器编号分别如下所示(ARM编程手册查看)

pc->R15,sp->R13,fp->R11.所以存取的顺序是:fp-sp-pc(与指令顺序无关)

附录:

其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。

因此执行完这条指令之后:fp = [4080-4083]  地址的内容。sp =[4084-4087]地址的内容,pc=[4088-4092] 地址的内容。

再返回去看看,我们在程序开始运行之前这些地址保存的东西是什么?


4.传递参数(调用者传参数给背调用者)

例子:

在程序中,我们并不一定需要mian函数,可以在启动文件中修改我们启动的函数

4.1编写一个 .c文件,内容如下:

void delay(volatile int s)  //用于延时,需要传递一个参数 
{
	while(s--);

}
//volatile的作用是不需要系统优化处理,因为我们在delay中系统来看像是什么正事都没干,会把它优化掉


int led_on(int  a)  //用于选择点亮哪个led灯,需要一个参数
{
	if(a==4)
	{
		/*配置GPF4的引脚为输出引脚*/
		*pGPFCON=0x100;
	}
	else if(a==5)
	{
		/*配置GPF5的引脚为输出引脚*/
		*pGPFCON=0x400;
	}
	/*配置GPF4的引脚输出低电平(点亮LED)*/
	*pGPFDAT=0;
	return 0;
}

4.2编写启动汇编文件(.S):

.text
.global _start

_start:

	/*设置内存:sp       栈*/
	ldr sp ,=4096; //Nand Flash 的初始值为4096 
/*使用r0-r3 进行参数的传递*/
	//把r0=4的值传递给调用者,并跳转到led_on函数
	mov r0,#4 
	bl led_on
	//把r0=1000000传递给被调用者,并跳转到延时函数
	ldr r0, = 100000;
	bl delay
	//把r0=5传递给被调用函数
	mov r0,#5
	bl led_on
halt:
	b halt

4.3.编写一个Makefile文件进行,对.c和.S文件进行,编译->链接->生成bin文件和反汇编文件,具体如下

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 start.o led.o -o led.elf
	arm-linux-objcopy -O binary -S led.elf led.bin	
	arm-linux-objdump -D led.elf > led.dis

clean:
	rm *.bin *.o *.elf *.dis

4.3.上传到Linux系统进行编译

 

4.4.编译

4.5.把bin文件传回window系统,使用oflash软件和eop烧录器进行烧录:

实验效果和我们预设的一样,LED1先亮,接着系统进入延时,延时结束LED2亮。


4.6.看看是函数是如何向被调用者传递参数的?

分析汇编代码,也就是 .dis文件的内容

.dis的文件内容如下:


led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:


   0:	e3a0da01 	mov	sp, #4096	; 0x1000
   4:	e3a00004 	mov	r0, #4	; 0x4
   8:	eb000012 	bl	58 <led_on>
   c:	e59f000c 	ldr	r0, [pc, #12]	; 20 <.text+0x20>
  10:	eb000003 	bl	24 <delay>
  14:	e3a00005 	mov	r0, #5	; 0x5
  18:	eb00000e 	bl	58 <led_on>
0000001c <halt>:
  1c:	eafffffe 	b	1c <halt>
  20:	000186a0 	andeq	r8, r1, r0, lsr #13

00000024 <delay>:
  24:	e1a0c00d 	mov	ip, sp
  28:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  2c:	e24cb004 	sub	fp, ip, #4	; 0x4
  30:	e24dd004 	sub	sp, sp, #4	; 0x4
  34:	e50b0010 	str	r0, [fp, #-16]
  38:	e51b3010 	ldr	r3, [fp, #-16]
  3c:	e2433001 	sub	r3, r3, #1	; 0x1
  40:	e50b3010 	str	r3, [fp, #-16]
  44:	e51b3010 	ldr	r3, [fp, #-16]
  48:	e3730001 	cmn	r3, #1	; 0x1
  4c:	0a000000 	beq	54 <delay+0x30>
  50:	eafffff8 	b	38 <delay+0x14>
  54:	e89da808 	ldmia	sp, {r3, fp, sp, pc}

00000058 <led_on>:
  58:	e1a0c00d 	mov	ip, sp
  5c:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  60:	e24cb004 	sub	fp, ip, #4	; 0x4
  64:	e24dd00c 	sub	sp, sp, #12	; 0xc
  68:	e50b0010 	str	r0, [fp, #-16]
  6c:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  70:	e2833050 	add	r3, r3, #80	; 0x50
  74:	e50b3014 	str	r3, [fp, #-20]
  78:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  7c:	e2833054 	add	r3, r3, #84	; 0x54
  80:	e50b3018 	str	r3, [fp, #-24]
  84:	e51b3010 	ldr	r3, [fp, #-16]
  88:	e3530004 	cmp	r3, #4	; 0x4
  8c:	1a000003 	bne	a0 <led_on+0x48>
  90:	e51b2014 	ldr	r2, [fp, #-20]
  94:	e3a03c01 	mov	r3, #256	; 0x100
  98:	e5823000 	str	r3, [r2]
  9c:	ea000005 	b	b8 <led_on+0x60>
  a0:	e51b3010 	ldr	r3, [fp, #-16]
  a4:	e3530005 	cmp	r3, #5	; 0x5
  a8:	1a000002 	bne	b8 <led_on+0x60>
  ac:	e51b2014 	ldr	r2, [fp, #-20]
  b0:	e3a03b01 	mov	r3, #1024	; 0x400
  b4:	e5823000 	str	r3, [r2]
  b8:	e51b3018 	ldr	r3, [fp, #-24]
  bc:	e3a02000 	mov	r2, #0	; 0x0
  c0:	e5832000 	str	r2, [r3]
  c4:	e3a03000 	mov	r3, #0	; 0x0
  c8:	e1a00003 	mov	r0, r3
  cc:	e24bd00c 	sub	sp, fp, #12	; 0xc
  d0:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.

 解析:可以看到这个二进制文件,所占用RAM的内存是从地址0开始,到0xd0,转换成十进制,也就是208个字节,最后一部分从comment中是注释信息,不会写入RAM里边去。在RAM中具体体现入下:

1.设置栈sp的起始地址

2.跳转到led_on函数

指令:stm

含义:把多个寄存器的值写入内存

例子:

指令:stmdb    sp!, {fp, ip, lr, pc}

解析:假设sp=4096,db是预先减少(Decrement Before)的意思,就是先减少后存入sp开始的地址,而且存取的顺序的依据是:高编号的寄存器存储在高地址。fp, ip, lr, pc 这四个的寄存器编号分别如下所示(ARM编程手册查看)

pc->R15,lr->R14,ip->R12,fp->R11.所以存取的顺序是:pc-lr-ip-fp(与顺序无关)

存储的大体如下,先减后存入:

提示:sp!的含义代表的是,sp会等于执行完此条语句后的值,比如存入四个寄存器,那么得减掉4次,那么sp=4096-16=4080。

感叹后表示执行完语句后sp的值会被改变.

附:

其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。

3.读内存读取数据,然后把读取的数据写入多个寄存器(fp, sp, pc)

注意:此时fp、sp、pc的值已经更新,且sp=4096 (每次退出函数之前,sp都会恢复起初设定的值)

4.此时pc开始从0xc执行语句

5.跳转到 delay函数:

开始进行延时,其实就是把一个参数从 100000 减到 0 的操作而已

注意:在上边的语句中,把sp的值减去4,这是因为在地址0x54的时候,还需要把r3的值读取出来,体会一下。 

接着执行  ldmia ,也就是退出函数时,存储变量的值,比如pc的值,因为入栈时,pc的值保存了跳转之前的下一条语句的地址,需要回去重新执行,sp 堆栈指针,在进入和退出时的值应该是一样的。

6.点亮另一个LED灯 

执行完这个delay函数,pc保存了0x14的值,从0x14地址开始执行函数

其实后边的汇编程序和点亮第一个LED灯的程序思路是一样的,只有一处需要注意一下:

 


5.看门狗定时器(WATCHDOG  TIMER)

看门狗简介:看门狗顾名思义就是帮着你盯住这个系统的运行,防止系统跑飞或者卡死的情况出现,如果在看门狗是一个倒数的定时器,如果在倒数到0之前没有对计数器进行重装载值的重新赋值操作(喂狗),就会触发系统复位。

在第四节中,点亮两个LED灯,他的真正实验结果是,点亮LED1然后延时,延时结束,点亮LED2,我们本来是想让系统进入死循环的,但是长时间在死循环中,没有进行喂狗,系统自动复位,又重复了上面点灯的顺序和步骤。

那如何让系统不自动复位,达到我们想要的结果呢?     关闭开门狗

1.看一看看门狗寄存器的内容(SC32440芯片数据手册)

从手册看出,默认是开启看门狗的,我们只需要往寄存器WTCON中的第0位写0就可以关闭看门狗计时器

小技巧:在程序中如何自动分辨是nor启动还是nand启动

/*自动分辨是nand/nor启动方式*/
思路:因为nand是支持直接读取的,不需要什么格式,而nor可以任意写,但是读取需要特定的格式,若一开始往0地址写入一个值,然后读出来。如果读取的值和写入的值是一致的,说明是nand启动(因为nand可以任意读写)否则是nor启动。

2.这个实验目的是顺序点亮三盏灯

看一下对应的IO口是什么?

需要设置的是:GPF4/5/6 这三个IO口

2.1.在主函数中配置GPF4/5/6 为输出引脚

为了不破坏这个寄存器其他的位,我们可以这样做(假设已经设置:unsigned int *pGPFCON=(unsigned int *)0x56000050;):

①把这几个位清零:*pGPFCON &=~((3<<8)| (3<<10) | (3<<12));

②然后设置为输出:*pGPFCON  | =((1<<8) | (1<<10) | (1<<12));

2.2.往GPFDAT寄存器写值,设置IO口输出的电平 

3.汇编代码如下,设置栈 sp,自动判别是nand还是nor启动

.text
.global _start

_start:
    /*关闭看门狗*/
	ldr r0 ,=0x53000000
	ldr r1 ,=0
	str r1 ,[r0]

	/*设置内存:sp       栈*/
    /*自动分辨是nand/nor启动方式*/
	/*思路:因为nand是支持直接读取的,不需要什么
	  格式,而nor可以任意写,但是读取需要特定的
	  格式,若一开始往0地址写入一个值,然后读出来
	  如果读取的值和写入的值是一致的,说明是nand启动
	  否则是nor启动*/
	  mov r1 ,#0    //r1赋值为0
	  ldr r0 ,[r1]  //读取0地址的值,以便后面恢复
	  str r1 ,[r1]  //把0写入0地址里面
	  ldr r2 ,[r1]  //从0地址读取值到r2
	  cmp r1 ,r2    //判断r1和r2是否相等,相等为nand启动
	  ldr sp ,=0x40000000+4096  //假设为nor启动
	  moveq sp, #4096 //相当为nand启动,重新设置sp
	  streq r0,[r1]   //恢复nand中0地址的值


	  bl main
halt:
	b halt




4.C语言代码如下:

void delay(volatile int s)  //用于延时,需要传递一个参数 
{
	while(s--);

}



int main(void)  //用于选择点亮哪个led灯,需要一个参数
{
    volatile unsigned int *pGPFCON=(volatile unsigned int *)0x56000050;
	volatile unsigned int *pGPFDAT=(volatile unsigned int *)0x56000054;
	int val=4;
 	/*配置GPF4/5/6为输出引脚*/
	*pGPFCON &=~((3<<8)|(3<<10)|(3<<12));
	*pGPFCON |= ((1<<8)|(1<<10)|(1<<12));
	/*初始化先熄灭三盏灯*/
	*pGPFDAT |=(7<<4);
	/*循环点亮三盏led灯*/
	while(1)
	{
		*pGPFDAT &=~(1<<val);
		 delay(100000);
		 val++;
		 if(val==8)
		 {
		   val=4;
		   *pGPFDAT |=(7<<4);
		   delay(50000);
		 } 
		
	}
	
	return 0;
} 

volatile的功能:防止编译器优化

5.Makefile文件内容如下:

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 start.o led.o -o led.elf
	arm-linux-objcopy -O binary -S led.elf led.bin	
	arm-linux-objdump -D led.elf > led.dis

clean:
	rm *.bin *.o *.elf *.dis

 6.传回Linux系统,编译:

编译,使用make命令:

7.把led.bin文件传回window系统 ,使用oflash下载到开发版当中

8.运行程序

运行效果:

三盏LED灯一次点亮,然后熄灭,又依次点亮,重复运行。

提示:这个程序可以烧写到nor中,然后从nor启动,程序可以自动识别是nor启动还是nand启动。


6.优化程序

6.1.为了让程序看起来更人性化,所以可以把上面的程序作如下的修改,运行结果还是不变的:

void delay(volatile int s)  //用于延时,需要传递一个参数 
{
	while(s--);

}

/*
注意:此处的GPFCON相当于刚刚的(            *pGPFCON) 
      现在:GPFCON = 5;的效果就是相当于
      往0x56000050这个地址写数值5的意思。
      
*/
#define GPFCON (*((volatile unsigned int *)0x56000050))
#define GPFDAT (*((volatile unsigned int *)0x56000054))
int main(void)  //用于选择点亮哪个led灯,需要一个参数
{
    
	int val=4;
 	/*配置GPF4/5/6为输出引脚*/
	GPFCON &=~((3<<8)|(3<<10)|(3<<12));
	GPFCON |= ((1<<8)|(1<<10)|(1<<12));
	/*初始化先熄灭三盏灯*/
	GPFDAT |=(7<<4);
	/*循环点亮三盏led灯*/
	while(1)
	{
		GPFDAT &=~(1<<val);
		 delay(100000);
		 val++;
		 if(val==8)
		 {
		   val=4;
		   GPFDAT |=(7<<4);
		   delay(50000);
		 } 
		
	}
	
	return 0;
} 

6.2.还可以进一步优化,把这些有关于寄存器的宏定义单独放在一个文件里面

然后再使用到这些寄存器的程序,使用#include 就可以了

最终优化如下:

#include "s3c2440_soc.h"


void delay(volatile int s)  //用于延时,需要传递一个参数 
{
	while(s--);

}

/*
注意:此处的GPFCON相当于刚刚的(            *pGPFCON) 
      现在:GPFCON = 5;的效果就是相当于
      往0x56000050这个地址写数值5的意思。
      
*/
int main(void)  //用于选择点亮哪个led灯,需要一个参数
{
    
	int val=4;
 	/*配置GPF4/5/6为输出引脚*/
	GPFCON &=~((3<<8)|(3<<10)|(3<<12));
	GPFCON |= ((1<<8)|(1<<10)|(1<<12));
	/*初始化先熄灭三盏灯*/
	GPFDAT |=(7<<4);
	/*循环点亮三盏led灯*/
	while(1)
	{
		GPFDAT &=~(1<<val);
		 delay(100000);
		 val++;
		 if(val==8)
		 {
		   val=4;
		   GPFDAT |=(7<<4);
		   delay(50000);
		 } 
		
	}
	
	return 0;
} 


 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值