嵌入式知识-ARM裸机-学习笔记(3):实现汇编和C语言的相互调用以及栈的设置

嵌入式知识-ARM裸机-学习笔记(3):实现汇编和C语言的相互调用以及栈的设置

一、栈的设置

1. C语言运行时需要和栈的意义

C语言运行时(runtime) 需要一定的条件,这些条件由汇编来提供,在普通的单片机中这部分不需要我们来考虑。C语言运行时主要是需要栈。

C语言和栈的关系: C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个程序就会崩溃。

我们平时在编写单片机程序(譬如51单片机)或者编写应用程序时并没有去设置栈,但是C程序还是可以运行的。 原因是:在单片机中由硬件初始化时提供了一个默认可用的栈,在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编实现的代码,这个代码中就帮我们的C程序设置了栈及其他的运行时需要。

2. CPU模式和各种模式下的栈

在ARM中37个寄存器中,为什么每种模式下都有自己的独立的SP寄存器(堆栈指针寄存器r13)?
答:如果各种模式都使用同一个SP,那么就意味着整个程序(操作系统内核程序、用户自己编写的应用程序)都是用一个栈的。你的应用程序如果一旦出错(譬如栈溢出),就会连累操作系统的栈也损坏,整个操作系统的程序就会崩溃。这样的操作系统设计是非常脆弱的,不合理的。
因此在每种模式下,都设置一个自己独有的SP,即哎各种模式下都用不同的栈,瞻仰就会互不影响,即使应用程序中的栈出错了,仍不会影响到系统程序。

如何访问SVC模式下的SP呢(这里仅举一个SVC的例子)?
答:先把模式设置为SVC,再直接操作SP。但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。因此这里要查看SP的寄存器地址,才能对其进行操作。

3. 设置栈指针至合法位置

栈必须是当前一段可用的内存 ,可用的意思是这个地方必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用。
当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。因此我们只能在SRAM中找一段内存来作为SVC的栈。

栈的四种构成介绍:
满栈: 进栈:先移动指针再存; 出栈:先出数据再移动指针
空栈: 进栈:先存再移动指针; 出栈:先移动指针再出数据
减栈: 进栈:指针向下移动; 出栈:指针向上移动
增栈: 进栈:指针向上移动; 出栈:指针向下移动
通过这四种构成四种栈的类型,即满增栈、满减栈、空增栈、空减栈

在ARM中,ATPCS要求使用满减栈,查看数据手册可知:
嵌入式知识-ARM裸机-学习笔记(3):实现汇编和C语言的相互调用以及栈的设置_第1张图片
SVC的栈地址是从0xd0037780到0xd0037d80,我们使用的栈类型为满减栈,因此进栈时是先移动指针再存,并且指针向下移动。因此我们需要从高位地址写起,即0xd0037d80。

#define WTCON		0xE2700000		//这里是开关看门狗的地址

#define SVC_STACK	0xd0037d80		//从SVC栈的高位开始写

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:设置SVC栈
	ldr sp, =SVC_STACK

	……从这里之后就可以开始调用C程序了

二、汇编程序和C程序的相互调用

1. 如何实现两者之间的相互配合

在工程中新建并且添加一个C语言源文件(以.c结尾的文件,这里面是程序运行的代码)。在汇编启动代码中设置好栈后,使用bl xxx的方式来调用C中的函数xxx。注意要修改Makefile,使其包括编译C文件的部分。

(1)汇编程序.s:

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:设置SVC栈
	ldr sp, =SVC_STACK

	// 从这里之后就可以开始调用C程序了
	bl led_blink					// led_blink是C语言实现的一个函数
	
// 汇编最后的这个死循环不能丢
	b .

在.s文件中,将之前所有执行程序全部替换掉,改用bl指令进行函数调用,这里用bl指令调用led_blink函数,并用C程序编写led_blink函数内部执行内容。

(2)C语言程序.c:

#define GPJ0CON		0xE0200240
#define GPJ0DAT		0xE0200244


void delay(void);

// 该函数要实现led闪烁效果
void led_blink(void)
{
	// led初始化,也就是把GPJ0CON中设置为输出模式
	unsigned int *p = (unsigned int *)GPJ0CON;
	unsigned int *p1 = (unsigned int *)GPJ0DAT;
	*p = 0x11111111;
	
	while (1)
	{
		// led亮
		*p1 = ((0<<3) | (0<<4) | (0<<5));
		// 延时
		delay();
		// led灭
		*p1 = ((1<<3) | (1<<4) | (1<<5));
		// 延时
		delay();
	}
}

void delay(void)
{
	volatile unsigned int i = 900000;		// volatile 让编译器不要优化,这样才能真正的减
	while (i--);							// 才能消耗时间,实现delay
}

在C语言程序中,负责编写汇编程序中bl指令调用的led_blink函数,实现LED灯的闪烁。这里涉及到了如何用C语言访问寄存器的方法:

利用汇编语言访问寄存器的方法为:

#define GPJ0CON		0xE0200240
//向GPJ0CON寄存器中写入数据0x11111111
ldr r0, =0x11111111		//第一步:用伪指令将立即数存入寄存器r0中
ldr r1, =GPJ0CON		//第二步,用伪指令将地址0xE0200240村日寄存器r1中
str r0, [r1]			//第三步,通过寄存器间接寻址的方式将立即数存入到指定地址

寄存器的地址类似于内存地址(IO与内存统一编址的),所以这里的问题是用C语言读写寄存器,就是用C语言来读写内存地址。用C语言来访问内存,就要用到指针
因此对应的利用C语言访问寄存器的方法为:

#define GPJ0CON		0xE0200240
unsigned int *p = (unsigned int *)GPJ0CON;	//第一步,先定义一个指针
*p = 0x11111111;//第二步,通过该指针来访问对应的地址,并将数据写入到地址中
//把0xE0200240地址空间理解成一个指针,指针指向了一个内存空间,该内存空间就用p指针来指向,用*p的方式往指针指向的地址空间里写东西。
上述两步也可以用一步来完成:
*((unsigned int *)0xE0200240) = 0x11111111;

总结: 在汇编语言中,使用立即数前需要定义一个寄存器来暂时存放此数据,因此有ldr r0, =0x11111111;紧接着要将该数据存到寄存器GPJ0CON中,所以需要知道其内存地址为0xE0200240,对于汇编语言要定义寄存器用来存放该地址,既有ldr r1, =GPJ0CON,对于C语言要通过定义一个指针来对应该地址,既有unsigned int *p = (unsigned int *)GPJ0CON; 最后要将数据存入该地址时,对于汇编语言要通过寄存器间接寻址的方式来进行存入str r0, [r1],而对于C语言来说,要通过指针的方式来访问地址从而实现该地址内数据的写入 *((unsigned int *)0xE0200240) = 0x11111111;

(3)Makefile文件
与之前纯汇编语言的不同是多了一个C程序,因此需要在Makefile文件中实现两者的相互配合,共同编译。

led.bin: start.o led.o		//这里需要汇编生成的.o文件和C生成的.o文件都体现出来
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 led.bin 210.bin
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c -nostdlib	//nostdlib就是不使用标准函数库。

%.o : %.c
	arm-linux-gcc -o $@ $< -c -nostdlib

clean:
	rm *.o *.elf *.bin *.dis mkx210 -f

你可能感兴趣的:(Linux嵌入式)