5.实现汇编和C语言的相互调用以及栈的设置

实现汇编和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要求使用满减栈,查看数据手册可知:

SVC的栈地址是从0xd0037780到0xd0037d80,我们使用的栈类型为满减栈,因此进栈时是先移动指针再存,并且指针向下移动。因此我们需要从高位地址写起,即0xd0037d80。

 1 #define WTCON        0xE2700000        //这里是开关看门狗的地址
 2 
 3 #define SVC_STACK    0xd0037d80        //从SVC栈的高位开始写
 4 
 5 .global _start                    // 把_start链接属性改为外部,这样其他文件就可以看见_start了
 6 _start:
 7     // 第1步:关看门狗(向WTCON的bit5写入0即可)
 8     ldr r0, =WTCON
 9     ldr r1, =0x0
10     str r1, [r0]
11     
12     // 第2步:设置SVC栈
13     ldr sp, =SVC_STACK
14 
15     ……从这里之后就可以开始调用C程序了

 

 

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

(1)汇编程序.s:

 1 #define WTCON        0xE2700000
 2 #define SVC_STACK    0xd0037d80
 3 
 4 .global _start                    // 把_start链接属性改为外部,这样其他文件就可以看见_start了
 5 _start:
 6     // 第1步:关看门狗(向WTCON的bit5写入0即可)
 7     ldr r0, =WTCON
 8     ldr r1, =0x0
 9     str r1, [r0]
10     
11     // 第2步:设置SVC栈
12     ldr sp, =SVC_STACK
13 
14     // 从这里之后就可以开始调用C程序了
15     bl led_blink                    // led_blink是C语言实现的一个函数
16     
17 // 汇编最后的这个死循环不能丢
18     b .

 

 

 

在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语言访问寄存器的方法为:

 1 #define GPJ0CON        0xE0200240
 2 #define GPJ0DAT        0xE0200244
 3 
 4 
 5 void delay(void);
 6 
 7 // 该函数要实现led闪烁效果
 8 void led_blink(void)
 9 {
10     // led初始化,也就是把GPJ0CON中设置为输出模式
11     unsigned int *p = (unsigned int *)GPJ0CON;
12     unsigned int *p1 = (unsigned int *)GPJ0DAT;
13     *p = 0x11111111;
14     
15     while (1)
16     {
17         // led亮
18         *p1 = ((0<<3) | (0<<4) | (0<<5));
19         // 延时
20         delay();
21         // led灭
22         *p1 = ((1<<3) | (1<<4) | (1<<5));
23         // 延时
24         delay();
25     }
26 }
27 
28 void delay(void)
29 {
30     volatile unsigned int i = 900000;        // volatile 让编译器不要优化,这样才能真正的减
31     while (i--);                            // 才能消耗时间,实现delay
32 }

 

1 #define GPJ0CON        0xE0200240
2 unsigned int *p = (unsigned int *)GPJ0CON;    //第一步,先定义一个指针
3 *p = 0x11111111;//第二步,通过该指针来访问对应的地址,并将数据写入到地址中
4 //把0xE0200240地址空间理解成一个指针,指针指向了一个内存空间,该内存空间就用p指针来指向,用*p的方式往指针指向的地址空间里写东西。
5 上述两步也可以用一步来完成:
6 *((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文件都体现出来

 1 led.bin: start.o led.o        //这里需要汇编生成的.o文件和C生成的.o文件都体现出来
 2     arm-linux-ld -Ttext 0x0 -o led.elf $^
 3     arm-linux-objcopy -O binary led.elf led.bin
 4     arm-linux-objdump -D led.elf > led_elf.dis
 5     gcc mkv210_image.c -o mkx210
 6     ./mkx210 led.bin 210.bin
 7     
 8 %.o : %.S
 9     arm-linux-gcc -o $@ $< -c -nostdlib    //nostdlib就是不使用标准函数库。
10 
11 %.o : %.c
12     arm-linux-gcc -o $@ $< -c -nostdlib
13 
14 clean:
15     rm *.o *.elf *.bin *.dis mkx210 -f

 

————————————————
版权声明:本文为CSDN博主「赵小琛在路上」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42826337/article/details/104457131

posted @ 2020-06-19 23:26  xielyzz  阅读(218)  评论(0)    收藏  举报