第二季-专题10-C语言环境初始化
专题10-C语言环境初始化
Bootloader的前半部分需要汇编语言的使用,后半部分需要 C语言的使用。
第一课.栈的初始化
一.概念解析
- 栈
栈是一种具有后进先出性质的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈的数据所处的位置,栈顶是最后一个进栈的数据所处的位置。
- 根据SP指针指向的位置,栈可以分为满栈和空栈
(1)满栈:当堆栈指针SP总是指向最后压入堆栈的数据
(2)空栈:堆栈指针SP指向下一个将要放入数据的空位置
ARM采用满栈。
- 升降栈
根据SP指针的移动方向,栈可以分为升栈和降栈
(1)升栈:随着数据的入栈,SP指针从低地址指向高地址。
(2)降栈:随着数据的入栈,SP指针从高地址指向低地址。
ARM采用降栈
- 栈帧
简单的讲,栈帧(stack frame)就是一个函数所使用的的那部分栈,所有函数的栈帧串起来就是一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。
二. 栈的作用
(1)保存局部变量
#include <stdio.h>
int main()
{
int a;
a++;
return a;
}
(2)参数传递
#include <stdio.h>
void func1(int a,int b,int c,int d,int e,int f)
{
int k;
k=e+f;
}
int main()
{
func1(1,2,3,4,5,6);
return 0;
}
(3)保存寄存器变量
#include <stdio.h>
void func2(int a,int b)
{
int k;
k=a+b;
}
void func1(int a,int b)
{
int c;
func2(3,4);
c=a+b;
}
int main()
{
func1(1,2);
return 0;
}
三.写代码
reset:
bl set_svc
bl disable_watchdog
bl disable_interrupt
bl disable_mmu
bl init_clock
bl init_sdram
bl copy_to_ram
bl init_stack
bl light_led
init_stack: @2440,内存地址加64Mb
ldr sp, =0x34000000
mov pc ,lr
init_stack: @6410,内存地址加64Mb
ldr sp, =0x54000000
mov pc ,lr
init_stack: @210,内存地址加64Mb
ldr sp, =0x24000000
mov pc ,lr
第二课.初始化Bss段
·Bss段的作用
初始化的全局变量存在数据段,局部变量存在栈,malloc申请的数据存在堆,未初始化全局变量存放在bss段。找到bss段的起始地址和结束地址,往其中放的数都是0,在链接器脚本中有bss_start记录起始地址和bss_end终止地址。
bl clean_bss @2440、6410、210通用
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1 @若相等的话
moveq pc, lr @直接返回,什么不做
clean_loop: @不相等,执行循环语句
mov r2, #0
str r2, [r0], #4
cmp r0, r1
bne clean_loop
mov pc, lr
第三课.一跃进入C大门
这一阶段开始从汇编语言转换成C语言来进行
我们思考的两个问题:
采用什么方式跳转?检验是否跳转成功?
一. 跳转
当前我们的程序运行在垫脚石中,我们希望跳转到内存中的main函数,这种方式是绝对跳转。相对跳转需要两个位置之间的差值,相对跳转的会发现SRAM(垫脚石)之中与内存之中都有main函数,会直接跳转到垫脚石之中的main函数。所以我们采用绝对跳转。
代码:
.global _start
reset:
bl set_svc
bl disable_watchdog
bl disable_interrupt
bl disable_mmu
bl init_clock
bl init_sdram
bl copy_to_ram
bl init_stack
bl clean_bss
ldr pc, =gboot_main
@ bl light_led
在C语言文件中,写下如下的代码:
#define GPBCON (volatile unsigned long*)0x56000010
#define GPBDAT (volatile unsigned long*)0x56000014
int gboot_main()
{
*(GPBCON) = 0x15400;
*(GPBDAT) = 0x6BF;
return 0;
}
Makefile中修改:
all: start.o main.o
arm-linux-ld -Tgboot.lds -o gboot.elf $^
arm-linux-objcopy -O binary gboot.elf gboot.bin
%.o : %.S
arm-linux-gcc -g -c $^
%.o : %.c
arm-linux-gcc -g -c $^
.PHONY: clean
clean:
rm *.o *.elf *.bin
二. 使用C语言点亮led
将汇编语言中的点亮led操作注销了,将这部分代码导入到C文件中。
#define GPBCON (volatile unsigned long*)0x56000010
#define GPBDAT (volatile unsigned long*)0x56000014
int gboot_main()
{
*(GPBCON) = 0x15400; //控制结存器
*(GPBDAT) = 0x6BF; //数据寄存器
return 0;
}
C语言中的地址都是unsigned long*,为了减少不必要的优化,外加volatile unsigned long,给地址赋值在前面加星号,加入汇编代码的值就好。
注意:
使用210开发板编写的代码,是运行不出来的。在使用210的时候,我们加了16字节的头,这是给bootloader使用的。当把垫脚石中的代码复制到内存的时候,这个头就不需要使用的,需要将这个头文件去掉。210的垫脚石复制从第16个字节开始,其中内存初始化程序是:
copy_to_ram:
ldr r0, =0xd0020010
ldr r1, =0x20008000
add r3, r0, #1024*4
第四课.第4课-C与汇编混合编程
C语言与汇编的混合编程不仅出现在,我们的bootloader设计中,出现在驱动中,在其他的嵌入式开发中也是需要的。下面我们看一下两种语言各自的优点。
汇编语言:执行效率高;编写繁琐。
C语言:可读性强,移植性好,调试方便。
在一些执行效率需要很高,能更直接地控制处理器的地方,我们必须要使用汇编语言。
- 汇编调用C函数
若是下汇编语言下调用C函数,直接在程序中,将函数名给pc指针就好。我们之前写的代码就是汇编调用C函数。
- C语言调用汇编
将汇编语言的函数名放在C语言中,单在这之前要把调用的函数升级为全局的。也就是在相应的函数之前加上.global。
- C语言内嵌汇编语言
这部分我们不是进行引用,而是直接在C语言中写汇编语言。
(1)格式
_asm_(
汇编语言部分
:输出部分
:输入部分
:破坏描述部分
);
C语言内嵌汇编以关键字“_asm_”或者“asm”开始,下辖四个部分,各部分之间使用“:”分开,第一部分是必须写的,后面的三部分是可以省略的,但是分号:不能省略!
l 汇编语句部分;汇编语句的集合,可以包含多条汇编语句,每条语句之间需要使用换行符“\n”合开或者分号“;”隔开。
l 输出部分:在汇编中被修改的C变量列表。
l 输出部分:作为参数输入到会汇编中的变量列表。
l 破坏描述部分:执行汇编指令会被破坏的寄存器描述
(2)范例
void write_p51_c1 (unsigned long value)
{
_asm_(
“mcr p15, 0, %0, c1, c0, 0\n” @其中%0表示0号参数
:
:”r”(value)@编译器选择一个R*寄存器 将value的值给%0
:”memory”);
}
Unsigned long read_p15_c1 (void)
{
unsigned long value;
_asm_(
“mcr p15, 0, %0, c1, c0, 0\n”
: ”=r” (value) @ ’=’ 表示只写操作数,用于输出部
:
: “memry”);
return value;
}
unsigned long old;
unsigned long temp;
_asm_ volatile(
“mrs %0, cpsr \n”
“orr %1, %0, #128 \n”
“msr cpsr_c, %1\n”
: ”=r” (old), “=r” (temp)
:
: “memory”);
使用volatile来告诉编译器,不要对接下来的这部分代码进行优化。