一、栈的作用
1、保存临时变量
函数的非静态局部变量以及编译器自动生成的其他临时变量均保存在栈中。
2、保存现场
CPU 运行的时候,用到了一些寄存器,比如 r0,r1 等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈 push),等调用函数执行完毕返回后(出栈 pop),再恢复现场。这样 CPU就可以正确的继续执行了。保存寄存器的值,一般用的是 push 指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用 pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。 其中保存的寄存器中,也包括 lr 的值(因为用 bl 指令进行跳转话,那么之前的 PC 的值是存在 lr 中的),然后在子程序执行完毕的时候,再把栈中的 lr 的值 pop 出来,赋值给 PC,这样就实现了子函数的正确的返回。
3、传递参数
C 语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些 C 语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不于 4 个就可以通过寄存器 r0~r3 传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于 4 个时,寄存器不够用,就得用栈了。
二、关于程序
设置栈,其实就是设置 sp 寄存器,让其指向一块可用的内存,IROM 里的固定代码设置的 sp 就等于 0xD003_7D80,所以我们设置栈一般就指向0xD003_7D80
0xD003_7D80对应的就是SVC模式下的栈(就是官方指定的)。
1、start.s
/*
* 代码:设置栈并调用C函数点亮LED
* 日期:2020.7.11
* 作者:glass love
*
*/
.globl _start
_start:
/**********************一、关看门狗*******************************/
//通过查阅数据手册知道控制看门狗开关的寄存器是:
//Watchdog Timer Control Register (WTCON, R/W, Address =0xE2700000 )
//WTCON寄存器的bit[0]位是启用或禁用复位信号的看门狗定时器输出位
//1为启用,0为禁止
//因此只需要往WTCON中写入0x0即可
ldr r0, =0x00000000
ldr r1, =0xE2700000
str r0, [r1]
/***********************二、设置栈****************************/
//IROM 里的固定代码设置的 sp 就等于 0xD003_7D80,
//所以我们设置栈一般就指向0xD003_7D80,以调用c函数
ldr sp, =0xD0037D80
/*******************三、调用c函数点亮LED************************/
bl led_blink
//死循环
b .
2、Makefile
led_c_sp.bin: start.o led.o
arm-linux-ld -Ttext 0x0 -o led_c_sp.elf $^
arm-linux-objcopy -O binary led_c_sp.elf led_c_sp.bin
arm-linux-objdump -D led_c_sp.elf > led_c_sp_elf.dis
gcc mkv210_image.c -o mk210
./mk210 led_c_sp.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clear:
rm *.o *.elf *.bin *.dis mkmini210 -f
3、led.c
#define GPJ2CON 0xE0200280
#define GPJ2DAT 0xE0200284
#define rGPJ2CON (*(volatile unsigned long*)0xE0200280)
#define rGPJ2DAT (*(volatile unsigned long*)0xE0200284)
//点亮led的函数
void led_blink()
{
//初始化引脚,使对应的GPIO引脚为输出模式
rGPJ2CON = 0x00001111;
//让LED闪烁起来
while(1)
{
//led亮
rGPJ2DAT = 0x0;
//延时
delay(0x100000);
//led灭
rGPJ2DAT = 0xf;
//延时
delay(0x100000);
}
}
void delay(int r0)
{
volatile int count = r0;
while(count--);
}
4、mkv210_image.c
5、write2sd
注:4和5没变