设置栈和调用c语言
“C语言运行时(runtime)”需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈
寄存器

ARM 处理器有三十七个寄存器,其中一些是在一定条件下使用的,所以一次只能使用十六个...
寄存器 0 到寄存器 7 是通用寄存器并可以用做任何目的。不象 80x86 处理器那样要求特定寄存器被用做栈访问,或者象 6502 那样把数学计算的结果放置到一个累加器中,ARM 处理器在寄存器使用上是高度灵活的。
寄存器 8 到 12 是通用寄存器,但是在切换到 FIQ 模式的时候,使用它们的影子(shadow)寄存器。
寄存器 13 典型的用做 OS 栈指针,但可被用做一个通用寄存器。这是一个操作系统问题,不是一个处理器问题,所以如果你不使用栈,只要你以后恢复它,你可以在你的代码中自由的占用(corrupt)它。每个处理器模式都有这个寄存器的影子寄存器。
寄存器 14 专职持有返回点的地址以便于写子例程。当你执行带连接的分支的时候,把返回地址存储到 R14 中。同样在程序第一次运行的时候,把退出地址保存在 R14 中。R14 的所有实例必须被保存到其他寄存器中(不是实际上有效)或一个栈中。这个寄存器在各个处理器模式下都有影子寄存器。一旦已经保存了连接地址,这个寄存器就可以用做通用寄存器了。
寄存器 15 是程序计数器。它除了持有指示程序当前使用的地址的二十六位数之外,还持有处理器的状态。
为更清晰一些... 提供下列图表:
User 模式 SVC 模式 IRQ 模式 FIQ 模式 APCS
R0 ------- R0 ------- R0 ------- R0 a1
R1 ------- R1 ------- R1 ------- R1 a2
R2 ------- R2 ------- R2 ------- R2 a3
R3 ------- R3 ------- R3 ------- R3 a4
R4 ------- R4 ------- R4 ------- R4 v1
R5 ------- R5 ------- R5 ------- R5 v2
R6 ------- R6 ------- R6 ------- R6 v3
R7 ------- R7 ------- R7 ------- R7 v4
R8 ------- R8 ------- R8 R8_fiq v5
R9 ------- R9 ------- R9 R9_fiq v6
R10 ------ R10 ------ R10 R10_fiq sl
R11 ------ R11 ------ R11 R11_fiq fp
R12 ------ R12 ------ R12 R12_fiq ip
R13 R13_svc R13_irq R13_fiq sp
R14 R14_svc R14_irq R14_fiq lr
------------- R15 / PC ------------- pc
最右侧的列是 APCS 代码使用的名字,关于 APCS 的详情参见这里。
程序计数器构造如下:
位 31 30 29 28 27 26 25------------2 1 0
N Z C V I F 程 序 计 数 器 S1 S0
对 R15 的详细解释,请参见 psr.html。
下面是你想知道的"模式",比如上面提及的"FIQ"模式。
用户模式,运行应用程序的普通模式。限制你的内存访问并且你不能直接读取硬件设备。
超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。这个模式有额外的特权,允许你进一步控制计算机。例如,你必须进入超级用户模式来读取一个插件(podule)。这不能在用户模式下完成。
中断模式(IRQ 模式),用来处理发起中断的外设。这个模式也是有特权的。导致 IRQ 的设备有键盘、 VSync (在发生屏幕刷新的时候)、IOC 定时器、串行口、硬盘、软盘、等等...
快速中断模式(FIQ 模式),用来处理发起快速中断的外设。这个模式是有特权的。导致 FIQ 的设备有处理数据的软盘,串行端口(比如在 82C71x 机器上的 A5000) 和 Econet。
IRQ 和 FIQ 之间的区别是对于 FIQ 你必须尽快处理你事情并离开这个模式。IRQ 可以被 FIQ 所中断但 IRQ 不能中断 FIQ。为了使 FIQ 更快,所以有更多的影子寄存器。FIQ 不能调用 SWI。FIQ 还必须禁用中断。如果一个 FIQ 例程必须重新启用中断,则它太慢了并应该是 IRQ 而不是 FIQ。
手册查询memory map信息

我们查询到Internal SRAM有96KB,而SVC Stack有1.5KB
对应地址是:0xD003_7D80
查阅文档并设置栈指针至合法位置
结合iROM_application_note中的memory map,可知SVC栈应该设置为0xd0037D80
设置svc栈
设置SVC栈地址相关宏定义
#define SVC_STACK 0xd0037D80
ldr sp, =SVC_STACK //使用如果不定义,则可以直接:
ldr sp, =0xd0037D80现在SVC栈就已经设置好了,也就是说其实就是在我们关闭看门狗后设置一个栈空间即可
相关代码
这里我们不做c语言调用,仅仅展示如何设置
// 定义看门狗设置寄存器
#define WTCON 0xE2700000
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define SVC_STACK 0xd0037D80
.global _start
_start:
// 关闭看门狗
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
ldr sp, =SVC_STACK
// 从这里之后就可以开始调用C程序了
// 配置寄存器
ldr r0, =0x11111111
ldr r1, =GPJ0CON
str r0, [r1]
// 流水灯部分
flash:
// 点亮1其他熄灭
ldr r0, =~(1<<3)
ldr r1, =GPJ0DAT
str r0, [r1]
bl delay
// 点亮2其他熄灭
ldr r0, =~(1<<4)
ldr r1, =GPJ0DAT
str r0, [r1]
bl delay
// 点亮3其他熄灭
ldr r0, =~(1<<4)
ldr r1, =GPJ0DAT
str r0, [r1]
bl delay
b flash // 无限循环
// 延时函数:函数名:delay
delay:
ldr r2, =9000000
ldr r3, =0x0
delay_loop:
sub r2, r2, #1 //r2 = r2 -1
cmp r2, r3 // cmp会影响Z标志位,如果r2等于r3则Z=1,下一句中eq就会成立
bne delay_loop
mov pc, lr // 函数调用返回调用C语言
现在 我们将新建一个start的文件,来放置初始代码
start.S代码:
// 定义看门狗设置寄存器
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037D80
.global _start
_start:
// 关闭看门狗
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
ldr sp, =SVC_STACK
// 从这里之后就可以开始调用C程序了
// 从这里之后就可以开始调用C程序了
bl start_main //C语言实现的一个函数
// 汇编最后的这个死循环不能丢
b .接下来我们设置一个led.c来操作我们的led
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
//设置delay
void delay(void);
//
void start_main(void) {
// 初始化
// led初始化,也就是把GPJ0CON中设置为输出模式
unsigned int *p = (unsigned int *)GPJ0CON;
// DAT 地址
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();
}
}
// 不断消耗CPU运行时间来达到延迟
void delay(void) {
volatile unsigned int i = 900000; // volatile 让编译器不要优化,这样才能真正的减
while (i--); // 才能消耗时间,实现delay
}Makefile
cur-dir := $(shell pwd)
OUT_DIR = $(cur-dir)/out/
OUT_NAME = SVC_C
$(OUT_NAME).bin: start.o led.o
#@ $(shell mkdir -p out)
# arm-linux-ld -Tlink.lds -o $(OUT_DIR)$(OUT_NAME).elf $^
arm-linux-ld -Ttext 0x0 -o $(OUT_DIR)$(OUT_NAME).elf $^
arm-linux-objcopy -O binary $(OUT_DIR)$(OUT_NAME).elf $(OUT_DIR)$(OUT_NAME).bin
arm-linux-objdump -D $(OUT_DIR)$(OUT_NAME).elf > $(OUT_DIR)$(OUT_NAME)_elf.dis
gcc mkv210_image.c -o $(OUT_DIR)mkx210
$(OUT_DIR)mkx210 $(OUT_DIR)$(OUT_NAME).bin $(OUT_DIR)210.bin
mv -f *.o $(OUT_DIR)
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
OUT_DIR:
$(shell mkdir -p out)
clean:
rm $(OUT_DIR)* -f
led.c的升级
我们在led.c 操作地址的时候可以更优雅一点
也就是使用宏定义再定义一下长串复杂的代码
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//设置delay
void delay(void);
//
void start_main(void) {
// 初始化
// led初始化,也就是把GPJ0CON中设置为输出模式
rGPJ0CON = 0x11111111
while (1) {
// led亮
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
// 延时
delay();
// led灭
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 延时
delay();
}
}
// 不断消耗CPU运行时间来达到延迟
void delay(void) {
volatile unsigned int i = 900000; // volatile 让编译器不要优化,这样才能真正的减
while (i--); // 才能消耗时间,实现delay
}