1.栈为何物
(2)mem.s
(4)Makefile
(5)链接器脚本
是一种数据组织形式,具有后进先出的特点。其中栈底是第一个进栈的数据所处的位置。栈顶是最后一个进栈数据所处的位置。
2.满栈:堆栈指针SP总是指向当前最后进栈的数据
空栈:SP总是指向下一个将要存放数据的空位置
3.升栈:(升降都是针对 数据的入栈形式)SP由低地址向高地址移动
降栈:SP由高地址向低地址移动
4.ARM采用满降栈形式
5.栈帧:
一个程序对应一个进程,一个进程对应一个栈空间,然后一个程序对应多个函数,每个函数都会在该栈空间分配到一个完全属于自己的子空间,该子空间就是栈帧。栈帧有边界,上边界是FP(r11),下边界是SP(r13),每一个主调函数的边界都会存放在她的被调函数的栈帧中。
6.栈的作用
(1)保存局部变量
(2)当参数大于四个时候可以传递参数
(3)可以保存寄存器的值
7.代码:
@初始化栈
init_stack:
ldr sp, =0x5FFFFFFF
mov pc, lr
8.BSS段的作用
(1)初始化的全局变量存放在data段
(2)局部变量存放在栈
(3)malloc出的变量存放在堆
(4)未初始化的全局变量存放在BSS段
9.代码(为避免直接使用未初始化的全局变量引起异常,将BSS清0):
@清除BSS段
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1
moveq pc, lr
clean_loop:
mov r2, #0x0
str r2,[r0],#4
cmp r0, r1
bne clean_loop
mov pc, lr
10.进入C语言开发(进入BL2)
(1)要用绝对跳转,因为相对跳转只能在同一个代码段进行
(2)210代码搬移的时候,要跳过16B的头部
(3)其实就是一条指令:
然后修改Makefile,在里面添加
main.o
all : start.o mem.o main.o
arm-linux-ld -Tg-boot.lds -o g-boot.elf $^
arm-linux-objcopy -O binary g-boot.elf g-boot.bin
arm-linux-objdump -D -S g-boot.elf > g-boot.dis
%.o : %.S
arm-linux-gcc -g -c $^ -o $@
%.o : %.c
arm-linux-gcc -g -c $^ -o $@
.PHONY: clean
clean:
rm *.o *.elf *.bin *.bak *.dis -rf
11.混合编程
(1)汇编的效率高,直接转换为机器语言,而C要经过转换为汇编
(2)汇编直接控制硬件的寄存器
基于以上原因,有时候必须的用汇编。但大多数场合都是用C,因为编写方便简洁。
(3)汇编调用c函数:
ldr pc,=函数名
(4)C调用汇编函数(标号)
先在汇编里声明为全局属性
然后在c里面直接:
标号名();
12.C内嵌汇编:
(1)格式:
__asm__(
汇编语句部分
:输出部分
:输入部分
:破坏描述部分
);
(2)以关键字__asm__或者asm开头,后面是(),注意后面不能忘记分号。四个部分都要用到分号
(3)汇编语句部分要用双引号括起来,如果有多条语句,要用换行符‘\n’或者分号隔开,每条指令都要用一个双引号括起来。
(4)第一部分不可少,后面三部分可以是空的,但是冒号也不能少。
(5)输出部分是在汇编里要修改的C的变量组成的列表,在分号表示的变量后面用()写入要传回到c里的变量,如:“=r”(var),var就是在c里定义的变量,就是汇编的输出。用%数字表示变量顺序。一般从%0开始。对应的从输出到输入。
(6)输入部分和输入部分类似,只不过他是要将c里面变量的值传递到汇编用。如:“r”(var)。
(7)破坏描述,就是修改部分描述,描述了哪些地方,变量,寄存器被修改了。
(8)优化,在sam后面加上volatile可以避免编译器对代码的优化。在硬件操作中,有时候优化很危险。
(9)实例:
10完整代码
(1)start.s
@****************************
@File:start.S
@
@Tiny6410裸机上学期代码
@
@Author:小君君
@****************************
.text
.global _start
@异常向量表
_start:
b reset
ldr pc, _Undefined_instruction
ldr pc, _Software_interrupt
ldr pc, _Command_abort
ldr pc, _Data_abort
ldr pc, _Nothing_used
ldr pc, _IRQ
ldr pc, _FIQ
@存放实际异常入口地址开辟的存储单元
_Undefined_instruction:
.word Undefined_instruction
_Software_interrupt:
.word Software_interrupt
_Command_abort:
.word Command_abort
_Data_abort:
.word Data_abort
_Nothing_used:
.word Nothing_used
_IRQ:
.word IRQ
_FIQ:
.word FIQ
@各种实际的异常处理函数
Undefined_instruction:
nop
Software_interrupt:
nop
Command_abort:
nop
Data_abort:
nop
Nothing_used:
nop
IRQ:
nop
FIQ:
nop
@上电复位以后就执行这里
reset:
bl set_svc
bl set_peri_port
bl disable_watchdog
bl disable_interrupt
bl disable_mmu
bl clock_init
bl mem_init
bl copy_to_ddr
bl init_stack
bl clean_bss
bl light_led
ldr pc, =main
halt:
b halt
@通过读-修改-写的方式控制CPSR从而改变工作模式的svc模式
set_svc:
mrs r0, cpsr
bic r0,r0,#0x1F
orr r0,r0,#0xD3
msr cpsr, r0
mov pc, lr
@关闭看门狗
#define pWTCON 0x7E004000
disable_watchdog:
ldr r0, =pWTCON
mov r1, #0x00
str r1, [r0]
mov pc, lr
@屏蔽中断,要操作两个寄存器,目的是将使能中断寄存器的相关位清除
#define VIC0INTENCLEAR 0x71200014
#define VIC1INTENCLEAR 0x71300014
disable_interrupt:
ldr r0, =VIC0INTENCLEAR
mvn r1, #0x0
str r1, [r0]
ldr r0, =VIC1INTENCLEAR
str r1, [r0]
mov pc, lr
@关闭MMU和Cache
disable_mmu:
mcr p15,0,r0,c7,c7,0
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0x07
mcr p15,0,r0,c1,c0,0
mov pc, lr
@外设地址初始化,要放在进入svc模式以后的第一步操作
set_peri_port:
ldr r0, =0x70000000 @对于6410来说,内存(0x00000000~0x60000000),外设(0x70000000-0x7fffffff)
orr r0, r0, #0x13 @外设大小:256M
mcr p15,0,r0,c15,c2,4 @把r0的值(包括了外设基地址+外设大小)告诉cpu
mov pc, lr
@时钟初始化
#define APLL_LOCK 0x7E00F000
#define CLK_DIV0 0x7E00F020
#define OTHERS 0x7E00F900
#define MPLL_CON 0x7E00F010
#define APLL_CON 0x7E00F00C
#define CLK_SRC 0x7E00F01C
@#define ARM_RATIO 0 @ARMCLK = DOUTAPLL / (ARM_RATIO + 1) = 532/(0+1) = 532 MHz
@#define MPLL_RATIO 0 @ DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1) = 532/(0+1) = 532 MHz
@#define HCLKX2_RATIO 1 @ HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1) = 532/(1+1) = 266 MHz
@#define HCLK_RATIO 1 @ HCLK = HCLKX2 / (HCLK_RATIO + 1) = 266/(1+1) = 133 MHz
@#define PCLK_RATIO 3 @ PCLK = HCLKX2 / (PCLK_RATIO + 1) = 266/(3+1) = 66.5 MHz
#define DIV_VAL ((0 << 0)|(0 << 4)|(1 << 8)|(1 << 9)|(3 << 12)) @注意这里不能用#define DIV_VAL (ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12)
@原因暂时未查明,不知道是不是我电脑最近鼠标抽风,把字符编码格式改变了导致的结果
clock_init:
@ 1. 设置各PLL的LOCK_TIME,使用默认值
ldr r0, =APLL_LOCK @ APLL_LOCK,供cpu使用
ldr r1, =0x0000FFFF
str r1, [r0]
str r1, [r0, #4] @ MPLL_LOCK,供AHB(存储/中断/lcd等控制器)/APB(看门狗,定时器,SD等)总线上的设备使用
str r1, [r0, #8] @ EPLL_LOCK,供UART,IIS,IIC使用
@ 2. 设置为异步模式
ldr r0, =OTHERS @ OTHERS
@ 《linux installation for u-boot》3.7中:用MPLL作为HCLK和PCLK的Source是异步(ASYNC)模式
@ 用APLL是同步(SYNC)模式
ldr r1, [r0]
bic r1, r1, #0xc0 @ bit[6:7]清0,即SYNCMODE=0/SYNCMUXSEL=0
str r1, [r0]
loop_clock:
ldr r0, =OTHERS
ldr r1, [r0]
and r1, r1, #0xf00
cmp r1, #0
bne loop_clock
@ 3. 设置分频系数
ldr r0, =CLK_DIV0 @CLK_DIV0
ldr r1, =DIV_VAL
str r1, [r0]
@ 4. 设置PLL,放大时钟
@ 4.1 配置APLL
#define APLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1))
ldr r0, =APLL_CON @ APLL_CON
ldr r1, =APLL_CON_VAL @ FOUT = MDIV X FIN / (PDIV X 2SDIV) = 266*12/(3*2^1) = 532MHz
str r1, [r0]
@ 4.2 配置MPLL
#define MPLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1))
ldr r0, =MPLL_CON @ MPLL_CON
ldr r1, =MPLL_CON_VAL @ FOUT = MDIV X FIN / (PDIV X 2SDIV) = 266*12/(3*2^1) = 532MHz
str r1, [r0]
#define MPLL_SEL 1
#define APLL_SEL 1
@ 5.选择PLL的输出作为时钟源
ldr r0, =CLK_SRC @ CLK_SRC
ldr r1, =(MPLL_SEL<<1) | (APLL_SEL<<0)
str r1, [r0]
mov pc, lr
@将bin文件从_start开始到bss_start结束的数据搬移到_start指定的链接地址(0x50008000)
copy_to_ddr:
adr r0, _start
ldr r1, =_start
ldr r2, =bss_start
copy_loop:
ldr r3,[r0],#4
str r3,[r1],#4
cmp r1,r2
bne copy_loop
mov pc, lr
@初始化栈
init_stack:
ldr sp, =0x5FFFFFFF
mov pc, lr
@清除BSS段
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1
moveq pc, lr
clean_loop:
mov r2, #0x0
str r2,[r0],#4
cmp r0, r1
bne clean_loop
mov pc, lr
@点亮LED
#define GPKCON0 0x7F008800
#define GPKDAT 0x7F008808
light_led:
@设置GPKCON0
ldr r1, =GPKCON0
ldr r0, =0x11110000
str r0, [r1]
@设置GPKDAT点亮4颗LED
ldr r0, =GPKDAT
ldr r1, =0x0
str r1, [r0]
@延时函数,将会使得开机的时候4颗led先亮一段时间,大约4秒钟左右,之后只亮两颗LED表明进入C语言的main函数
ldr r0, =0xFFFFFF
loop_led:
sub r0,r0,#1
cmp r0, #1
bne loop_led
mov pc, lr
(2)mem.s
@****************************
@File:mem.S
@
@Tiny6410内存初始化
@
@Author:小君君
@****************************
.text
.global mem_init
#define MEMCCMD 0x7e001004
#define P1REFRESH 0x7e001010
#define P1CASLAT 0x7e001014
#define MEM_SYS_CFG 0x7e00f120
#define P1MEMCFG 0x7e00100c
#define P1T_DQSS 0x7e001018
#define P1T_MRD 0x7e00101c
#define P1T_RAS 0x7e001020
#define P1T_RC 0x7e001024
#define P1T_RCD 0x7e001028
#define P1T_RFC 0x7e00102c
#define P1T_RP 0x7e001030
#define P1T_RRD 0x7e001034
#define P1T_WR 0x7e001038
#define P1T_WTR 0x7e00103c
#define P1T_XP 0x7e001040
#define P1T_XSR 0x7e001044
#define P1T_ESR 0x7e001048
#define P1MEMCFG2 0X7e00104c
#define P1_chip_0_cfg 0x7e001200
#define P1MEMSTAT 0x7e001000
#define P1MEMCCMD 0x7e001004
#define P1DIRECTCMD 0x7e001008
#define refresh_val (( 7800 / ( 1000000000/133000000 ) + 1 )) @刷新周期:(7.8us)/((1/HCLK)s)=(7.8*10^3)/(1/133*10^6)
mem_init:
@0. 将data[31:26]设置为data pins
ldr r0, =MEM_SYS_CFG
mov r1, #0x0
str r1, [r0]
@1. 使dramc进入"config"状态
ldr r0, =P1MEMCCMD
mov r1, #0x4
str r1, [r0]
@2. 设置timing parameter, chip configuration,id configuration registers
@2.1 刷新周期
ldr r0, =P1REFRESH
ldr r1, =refresh_val
str r1, [r0]
@2.2 时间参数,下列设置全都是取了最小值
ldr r0, =P1CASLAT @CAS Latency:指的是内存存取数据所需的延迟时间,
@简单的说,就是内存接到CPU的指令后的反应速度。
@一般的参数值是2和3两种。K4X1G163PQ的芯片手册上CAS Latency=3
mov r1, #6 @下列设置均在sdram手册中可查询到
str r1, [r0]
ldr r0, =P1T_DQSS
mov r1, #0x1
str r1, [r0]
ldr r0, =P1T_MRD
mov r1, #0x2
str r1, [r0]
ldr r0, =P1T_RAS
ldr r1, =(( 42 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
ldr r0, =P1T_RC
ldr r1, =(( 60 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
#define trcd (( 18 / ( 1000000000/133000000 ) + 1 ))
#define trcd_val ((trcd | ((trcd-3)*8)))
ldr r0, =P1T_RCD
ldr r1, =trcd_val
str r1, [r0]
#define trfc (( 72 / ( 1000000000/133000000 ) + 1 ))
#define trfc_val ((trfc | ((trfc-3)*32)))
ldr r0, =P1T_RFC
ldr r1, =trfc_val
str r1, [r0]
#define trp (( 18 / ( 1000000000/133000000 ) + 1 ))
#define trp_val ((trp | ((trp-3)*8)))
ldr r0, =P1T_RP
ldr r1, =trp_val
str r1, [r0]
ldr r0, =P1T_RRD
ldr r1, =(( 12 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
ldr r0, =P1T_WR
ldr r1, =(( 12 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
ldr r0, =P1T_WTR
mov r1, #0x1
str r1, [r0]
ldr r0, =P1T_XP
mov r1, #0x1
str r1, [r0]
ldr r0, =P1T_XSR
ldr r1, =(( 120 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
ldr r0, =P1T_ESR
ldr r1, =(( 120 / ( 1000000000/133000000 ) + 1 ))
str r1, [r0]
@2.3 chip configuration
@****************************
@结合DDR的手册3. Address configuration这个部分
@1.column address(10):A0~A9列地址P1MEMCFG[2:0]=0x2
@2.row address(14):A0~A13,行地址P1MEMCFG[5:3]=0x3
@3.A10/AP 设置自动预充电地址P1MEMCFG[6]=0x0
@4.Burst Length (2, 4, 8, 16)设置内存突发长度P1MEMCFG[17:15]=0x2
@****************************
ldr r0, =P1MEMCFG
ldr r1, =0x0001001a
str r1, [r0]
@****************************
@
@1.Sets the level for the cke outputs after reset. 设置reset以后的cke引脚的输出电平P1MEMCFG2[3],一般设置为0,P1MEMCFG2[0:2]用默认的
@所以P1MEMCFG2[3:0] = 0x5
@2.设置存储长度。6410是32位机器,所以就是01了P1MEMCFG2[7:6]=0x1
@3.设置DDR类型,6410用的是Mobile DDR SDRAMP1MEMCFG2[10:8]=0x3
@4.Read delay设置读取延时,针对
@用01 = Read delay 1 cycle (usually for DDR SDRAM and mobile DDR SDRAM)DDRP1MEMCFG2[12:11]=0x1
@****************************
ldr r0, =P1MEMCFG2
ldr r1, =0x00000B45
str r1, [r0]
@****************************
@设置芯片(片选)
@1.设定地址组织格式Bank-Row-Column organization。P1_chip_0_cfg[16]=0x1
@2.设定AXI_chip_base。因为6410的DDR从0x50000000开始,所以AXI_chip_base是0x50.P1_chip_0_cfg[15:8]=0x50
@3.设定AXI_chip_mask。要想让AXI_chip_mask和AXI_addr[31:24]按位与以后与AXI_chip_base相等,所以AXI_chip_mask选择0XF0.P1_chip_0_cfg[7:0]=0xF0
@****************************
ldr r0, =P1_chip_0_cfg
ldr r1, =0x000150F0
str r1, [r0]
@3. 初始化sdram
@NOP
ldr r0, =P1DIRECTCMD
ldr r1, =0xc0000
str r1, [r0]
@precharge
ldr r0, =P1DIRECTCMD
ldr r1, =0x000
str r1, [r0]
@auto refresh
ldr r0, =P1DIRECTCMD
ldr r1, =0x40000
str r1, [r0]
@auto refresh
ldr r0, =P1DIRECTCMD
ldr r1, =0x40000
str r1, [r0]
@auto refresh
ldr r0, =P1DIRECTCMD
ldr r1, =0xa0000
str r1, [r0]
@auto refresh
ldr r0, =P1DIRECTCMD
ldr r1, =0x80032
str r1, [r0]
@4. 使dramc进入"ready"状态
ldr r0, =P1MEMCCMD
mov r1, #0x0
str r1, [r0]
@5. 等待dramc进入"ready"状态
wait_for_ready:
ldr r0, =P1MEMSTAT
ldr r1, [r0]
mov r2, #0x3
and r1, r1, r2
cmp r1, #0x1
bne wait_for_ready
nop
mov pc, lr
(3)main.c
/****************************
@File:main.c
@
@Tiny6410裸机上学期代码
@进入C语言以后只点亮两颗LED,在汇编阶段是点亮4颗LED,以此作对比表明进入C语言成功
@Author:小君君
@****************************/
#define LED_CON 0x7F008800
#define LED_DAT 0x7F008808
#define vi *( volatile unsigned int * )
int main(void)
{
(vi LED_CON) = 0x11110000;
(vi LED_DAT) = 0xa0;
while(1)
;
return 0;
}
(4)Makefile
all : start.o mem.o main.o
arm-linux-ld -Tg-boot.lds -o g-boot.elf $^
arm-linux-objcopy -O binary g-boot.elf g-boot.bin
arm-linux-objdump -D -S g-boot.elf > g-boot.dis
%.o : %.S
arm-linux-gcc -g -c $^ -o $@
%.o : %.c
arm-linux-gcc -g -c $^ -o $@
.PHONY: clean
clean:
rm *.o *.elf *.bin *.bak *.dis -rf
(5)链接器脚本
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = 0x50008000;
. = ALIGN(4);
.text :
{
start.o(.text)
*(.text)
}
. = ALIGN(4);
.data :
{
*(.data)
}
. = ALIGN(4);
bss_start = .;
.bss :
{
*(.bss)
}
bss_end = .;
}