9.栈初始化:bl init_stack 栈是一种具有后进先出性质的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈的数据所处的位置,栈顶是最后一个进栈的数据所处的位置 。如下左图:
根据SP指针指向的位置,栈可以分为满栈和空栈。 1. 满栈:当堆栈指针SP总是指向最后压入堆栈的数据 2. 空栈:当堆栈指针SP总是指向下一个将要放入数据的空位置。ARM采用满栈! 如上图中: 根据SP指针移动的方向,栈可以分为升栈和降栈。
1. 升栈:随着数据的入栈,SP指针从低地址->高地址移动;
2. 降栈:随着数据的入栈,SP指针从高地址->低地址移动。ARM采用降栈! 如下图:
栈帧(stack frame)就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。 如下图:
总结下来,栈主要用来保存局部变量、传递参数、保存寄存器值,显然初始化栈很重要。代码如下: @ 栈初始化 64M内存用于栈init_stack: ldr sp, =0x54000000 @ 将0x54000000写入sp寄存器中,栈大小64M 0x5400000000-0x500000000 mov pc, lr @ 返回调用处继续往下执行虽然就是这么一小段代码,但是大家不能小看他,没有他,后面的c语言开发环境就无从说起了。 10.清除bss段:bl clean_bss bss段主要存放的是未初始化的全局变量,为了防止影响以后的使用,要将其初始化为0。也是为C语言环境做准备,代码如下: @ 清除bss段clean_bss: ldr r0, =bss_start @ bss段开始地址,参见链接器脚本 ldr r1, =bss_end @ bss段结束地址 cmp r0, r1 @ 比较bss段开始与结束地址 moveq pc, lr @ 若相等,返回调用处继续往下执行 clean_loop: @ 若不相等,执行下面的代码 mov r2, #0 @ r2=0 str r2, [r0], #4 @ 将r2的值(0)写入r0所指定的地址中,同时r0指定的地址+4后存入r0 cmp r0, r1 @ 比较bss段新开始与结束地址 bne clean_loop @ 若不相等,跳转到clean_loop标号处执行 mov pc, lr @ 否则,返回调用处继续执行11.进入c编程环境:ldr pc, =main 到此,也就是海宽任鱼跃,天高任鸟飞了。在c语言环境中,大家不用再为记不住汇编命令感到头疼了,是不是很爽呢?废话少说。上一段c语言环境下的点亮led代码: #define GPKCON (volatile unsigned long*)0x7F008820#define GPKDAT (volatile unsigned long*)0x7F008824// 延时函数void delay(){ int i = 0; int j = 0; for(i = 0; i < 10000; i++) { for(j = 0; j < 110; j++) { ; } }}int main(){ int val = 0x1e; *(GPKCON) = 0x1111; while(1) { *(GPKDAT) = val; delay(); val = val<<1; if((val&0x10) != 0x10) val = 0x1f; } return 0; }本节内容也就讲完了,最后贴上整体代码: start.S .text.global _start_start: b reset ldr pc, _undifined_instruction @ 未知指令异常 ldr pc, _software_interrupt @ 软件中断 ldr pc, _prefetch_abort @ 预取异常 ldr pc, _data_abort @ 数据终止异常 ldr pc, _not_used @ 未使用,预留 ldr pc, _irq @ 中断 ldr pc, _fiq @ 快速中断 _undifined_instruction: .word undifined_instruction_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word resetundifined_instruction: nop software_interrupt: nopprefetch_abort: nopdata_abort: nop not_used: nopirq: nop fiq: nopreset: bl set_svc @ 设置cpu为svc模式 bl set_peri_port @ 外设基地址初始化 bl disable_watchdog @ 关闭看门狗 bl disable_interrupt @ 关闭所有中断 bl disable_mmu @ 关闭mmu和cache bl init_clock @ 时钟初始化 bl mem_init @ 内存初始化 bl copy_to_ram @ 拷贝bl到内存中 bl init_stack @ 栈初始化 bl clean_bss @ 初始化bss段 ldr pc, =main @ 进入c语言编程环境,main函数 @bl light_led @ set the cpu to SVC32 modeset_svc: mrs r0,cpsr @ 将程序状态寄存器取出保存在通用寄存器r0中,参看am架构参考手册:A2.5 Program status registers bic r0,r0,#0x1f @ 将从状态寄存器取出的值(r0)低5位清零后存入r0中 orr r0,r0,#0xd3 @ 将r0中的值的第0 1 4 6 7位置1后存入r0中,关闭中断与快速中断,进入svc模式 msr cpsr,r0 @ 将r0的中的值写入程序状态寄存器 mov pc, lr @ 返回调用处继续往下执行 @ 初始化外设地址,必须做这一步,要不外设无法使用。set_peri_port: ldr r0, =0x70000000 @ 将基地址0x70000000存入寄存器r0中 orr r0, r0, #0x13 @ 将寄存器r0的值设为0x70000013 mcr p15,0,r0,c15,c2,4 @ 将寄存器r0的值传送到协处理器p15的c15寄存器中,参看arm11内核手册:c15, Peripheral Port Memory Remap Register mov pc, lr @ 返回调用处继续往下执行 @ Disable Watchdog 参看s3c6410手册:Watch Dog章节#define pWTCON 0x7e004000 @ addr of watchdog timer control register (WTCON)disable_watchdog: ldr r0, =pWTCON @ 将看门狗控制寄存器地址写入寄存器r0中 mov r1, #0x0 @ 将0x0保存在寄存器r1中 str r1, [r0] @ 将0x0传送到看门狗控制寄存器中 mov pc, lr @ 返回调用处继续往下执行 @ Disable all interrupts (VIC0 and VIC1),参考s3c6410手册:Vectored Interrupt Controller章节disable_interrupt: mvn r1,#0x0 ldr r0,=0x71200014 @ VIC0INTENCLEAR地址 str r1,[r0] @ 将VIC0INTENCLEAR设为0 ldr r0,=0x71300014 @ VIC1INTENCLEAR地址 str r1,[r0] @ 将VIC1INTENCLEAR设为0 mov pc, lr @ 返回调用处继续往下执行 @ 关闭MMU和cache disable_mmu: mcr p15,0,r0,c7,c7,0 @ 使I/D cache全部失效 参考arm11内核手册:c7, Cache operations mrc p15,0,r0,c1,c0,0 @ 取出协处理器p15的控制寄存器(c1)内容,存放在r0中 参考arm11内核手册:c1, Control Register bic r0, r0, #0x00000007 @ 将r0的[2:0]设为0后存入r0 mcr p15,0,r0,c1,c0,0 @ 将r0的值传送到协处理器p15的控制寄存器(c1)中,完成mmu和cache配置 mov pc, lr @ 返回调用处继续往下执行 @ 初始化时钟 参考s3c6410手册系统控制章节、时钟相关章节#define CLK_DIV0 0x7e00f020 @ 时钟分频寄存器0地址#define OTHERS 0x7e00f900 @ 其他控制寄存器地址#define MPLL_CON 0x7e00f010 @ MPLL控制寄存器地址#define APLL_CON 0x7e00f00c @ APLL控制寄存器地址#define CLK_SRC 0x7e00f01c @ 时钟源选择寄存器地址#define DIV_VAL ((0x0<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12)) @ 分频设置值 2b0000_0000_0000_0000_0011_0011_0000_0000 参考uboot设置#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0)) @ PLL设置值 2b1000_0001_0000_1010_0000_0011_0000_0001 参考uboot设置init_clock: @ 设置时钟分频系数 ldr r0, =CLK_DIV0 @ 将时钟分频寄存器0的地址存入寄存器r0中 ldr r1, =DIV_VAL @ 将需要配置到时钟分频寄存器0的数值存入寄存器r1中 str r1, [r0] @ 将r1的数值传送到时钟分频寄存器0中 @ 设置cpu为异步模式,将OTHERS寄存器内容设置为0xc0 ldr r0, =OTHERS @ 将OTHERS寄存地址存入寄存器r0中 ldr r1, [r0] @ 将其他控制寄存器中的数据读出,存入r1中 bic r1,r1,#0xc0 @ 将r1中的数值第6 7 bit位清0后再存入r1中 str r1, [r0] @ 将r1中的数值传送到OTHERS寄存器中,Asynchronous mode 和 MOUTmpll @ 设置APLL时钟频率 533MHz ldr r0, =APLL_CON @ 将APLL控制寄存器地址写入r0中 ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中 str r1, [r0] @ 将r1中的数值写入APLl控制寄存器中 @ 设置MPLL时钟频率 533MHz ldr r0, =MPLL_CON @ 将MPLL控制寄存器地址写入r0中 ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中 str r1, [r0] @ 将r1中的数值写入MPLL控制寄存器中 @ 设置时钟源选择控制寄存器,使用APLL和MPLL输出时钟。 ldr r0, =CLK_SRC @ 将时钟源选择寄存器地址写入r0中 mov r1, #0x3 @ 将0x03写入寄存器r1中 str r1, [r0] @ 将r1中的数据写入时钟源选择控制寄存器中 mov pc, lr @ 返回调用处继续往下执行 @ 代码搬移,拷贝bl到内存中copy_to_ram: ldr r0, =0x0c000000 @ Stepping Stone (Boot Loader)基地址,即要搬移的源地址存入r0 ldr r1, =0x50008000 @ 内存地址,要搬移的目标地址存入r1 add r3, r0, #1024*4 @ 将r0处的地址加4k大小存入r3,要搬移的内存Size 4k,copy_loop: @ 等待搬移完成 ldr r2, [r0], #4 @ 将地址0x0c000000处数据存入r2后,在增加4后(0x0c000004)存入r0中 str r2, [r1], #4 @ 将r2的数据传送到r1指定的地址处后,在+4(0x500080004)存入r1中 cmp r0, r3 @ 比较r0和r3处地址 bne copy_loop @ 若不相等,跳到标号copy_loop处继续执行 mov pc, lr @ 返回调用处继续往下执行 @ 栈初始化 64M内存用于栈init_stack: ldr sp, =0x54000000 @ 将0x54000000写入sp寄存器中,栈大小64M 0x5400000000-0x500000000 mov pc, lr @ 返回调用处继续往下执行 @ 清除bss段clean_bss: ldr r0, =bss_start @ bss段开始地址,参见链接器脚本 ldr r1, =bss_end @ bss段结束地址 cmp r0, r1 @ 比较bss段开始与结束地址 moveq pc, lr @ 若相等,返回调用处继续往下执行 clean_loop: @ 若不相等,执行下面的代码 mov r2, #0 @ r2=0 str r2, [r0], #4 @ 将r2的值(0)写入r0所指定的地址中,同时r0指定的地址+4后存入r0 cmp r0, r1 @ 比较bss段新开始与结束地址 bne clean_loop @ 若不相等,跳转到clean_loop标号处执行 mov pc, lr @ 否则,返回调用处继续执行 @ 点亮led#define GPMCON 0x7F008820#define GPMDAT 0x7F008824#define GPMPUD 0x7F008828#define LED1 0x1E#define LED2 0x1D#define LED3 0x1B#define LED4 0x17light_led: ldr r0,=GPMCON ldr r1,=0x1111 str r1,[r0] ldr r0,=GPMPUD ldr r1,=0x55 str r1,[r0] ldr r0,=GPMDAT mov r1,#0x0e str r1,[r0] mov pc,lr其中有一段汇编下的点亮led的代码,大家正好可以和c语言环境下的代码对比一下,看一下孰优孰劣。可能大家发现,怎么内存初始化的代码不见了,别急,马上就来: 内存初始化代码 mem.S .text.global mem_initmem_init: ldr r0, =0x7e00f120 @配置内存系统子程序寄存器地址 mov r1, #0x8 @将0x8存入r1寄存器 str r1, [r0] @将r1寄存器里的数据写入配置内存系统子程序寄存器中 ldr r0, =0x7e001004 @内存控制命令寄存器 mov r1, #0x4 @根据手册得知需要先进入配置模式 str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中 ldr r0, =0x7e001010 @刷新寄存器地址 ldr r1, =( ( 7800 / ( 1000000000/133000000 ) + 1 ) ) @设置刷新时间 str r1, [r0] @将寄存器r1内容写入刷新寄存器寄存器中 ldr r0, =0x7e001014 @CAS latency寄存器 mov r1, #(3 << 1) @r1=0x6 str r1, [r0] @将寄存器r1内容(0x06)写入CAS latency寄存器中 ldr r0, =0x7e001018 @t_DQSS寄存器 mov r1, #0x1 @r1=0x1 str r1, [r0] @将寄存器r1内容(0x01)写入t_DQSS寄存器中 ldr r0, =0x7e00101c @T_MRD寄存器 mov r1, #0x2 @r1=0x2 str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中 ldr r0, =0x7e001020 @t_RAS寄存器 ldr r1, =( ( 45 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=6.95 str r1, [r0] @将寄存器r1内容(6.95)写入t_RAS寄存器中 ldr r0, =0x7e001024 @t_RC寄存器 ldr r1, =( ( 68 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=10.044 str r1, [r0] @将寄存器r1内容(10.044)写入t_RC寄存器中 ldr r0, =0x7e001028 @t_RCD寄存器 ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99 str r1, [r0] @将寄存器r1内容(3.99)写入t_RCD寄存器中 ldr r0, =0x7e00102c @t_RFC寄存器 ldr r1, =( ( 80 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=11.64 str r1, [r0] @将寄存器r1内容(11.64)写入t_RFC寄存器中 ldr r0, =0x7e001030 @t_RP寄存器 ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99 str r1, [r0] @将寄存器r1内容(3.99)写入t_RP寄存器中 ldr r0, =0x7e001034 @t_rrd寄存器 ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995 str r1, [r0] @将寄存器r1内容(2.995)写入t_rrd寄存器中 ldr r0, =0x7e001038 @t_wr寄存器 ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995 @ ldr r2, [r0] str r1, [r0] @将寄存器r1内容(2.995)写入T_MRD寄存器中 ldr r0, =0x7e00103c @t_wtr寄存器 mov r1, #0x07 @r1=0x07 str r1, [r0] @将寄存器r1内容(0x07)写入T_MRD寄存器中 ldr r0, =0x7e001040 @t_xp寄存器 mov r1, #0x02 @r1=0x02 str r1, [r0] @将寄存器r1内容(0x02)写入t_xp寄存器中 ldr r0, =0x7e001044 @t_xsr寄存器 ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69 str r1, [r0] @将寄存器r1内容(16.69)写入t_xsr寄存器中 ldr r0, =0x7e001048 @t_esr寄存器 ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69 str r1, [r0] @将寄存器r1内容(16.69)写入t_esr寄存器中 ldr r0, =0x7e00100c @内存控制配置寄存器 P1MEMCFC ldr r1, =0x00010012 @配置控制器 r1=2b0000_0000_0000_0001_0000_0000_0001_0010 str r1, [r0] @将寄存器r1内容(0x10012)写入P1MEMCFC寄存器中 ldr r0, =0x7e00104c @32位DRAM配置控制寄存器 ldr r1, =0x0b45 @r1=2b0000_0000_0000_0000_0000_1011_0100_0101 选择Mobile DDR SDRAM str r1, [r0] @将寄存器r1内容(0x0b45)写入T寄存器中 ldr r0, =0x7e001200 @片选寄存器 ldr r1, =0x150f8 @r1=0x150f8 r1=2b0000_0000_0000_0001_0101_0000_1111_1000 Bank-Row-Column organization str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中 ldr r0, =0x7e001304 @用户配置寄存器 mov r1, #0x0 @r1=0x0 str r1, [r0] @将寄存器r1内容(0x00)写入用户配置寄存器中 ldr r0, =0x7e001008 @直接命令寄存器 ldr r1, =0x000c0000 @r1=2b0000_0000_0000_1100_0000_0000_0000_0000 makes DRAM Controller issue ‘NOP’ memory str r1, [r0] @将寄存器r1内容(0xc0000)写入直接命令寄存器中 ldr r1, =0x00000000 @r1=0 makes DRAM Controller issue‘PrechargeAll’ memory command. str r1, [r0] @将寄存器r1内容(0x0)写入直接命令寄存器中 ldr r1, =0x00040000 @r1=2b0000_0000_0000_0100_0000_0000_0000_0000 makes DRAM Controller issue ‘Autorefresh’ memory command. str r1, [r0] @将寄存器r1内容(0x40000)写入直接命令寄存器中 ldr r1, =0x000a0000 @r1=2b0000_0000_0000_1010_0000_0000_0000_0000 makes DRAM Controller issue ‘MRS’ memory command. str r1, [r0] @将寄存器r1内容(0xa0000)写入直接命令寄存器中 ldr r1, =0x00080032 @r1=2b0000_0000_0000_1000_0000_0000_0011_0010 str r1, [r0] @将寄存器r1内容(0x80032)写入直接命令寄存器中 makes DRAM Controller issue ‘EMRS’ memory command. ldr r0, =0x7e001004 @内存控制命令寄存器 mov r1, #0x0 @根据手册得知需要进入ready模式 str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中@检查内存初始化是否准备好check_dmc1_ready: ldr r0, =0x7e001000 @DRAM控制状态寄存器 ldr r1, [r0] @将DRAM控制状态寄存器中内容写入寄存器r1中 mov r2, #0x3 @将0x03写入寄存器r2中 and r1, r1, r2 @将r1和r2与运算后存在r1中,即只保持读出的值的[1:0]位 cmp r1, #0x1 @将读出的值与0x01比较 bne check_dmc1_ready @若不相等,跳转到标号check_dmc1_ready处继续执行,检查内存状态 nop mov pc, lr @返回调用处继续往下执行不要问我为什么这么设置,我也不是很清楚,参考uboot设置的,总之能用,以后慢慢去理解吧! C语言环境下的代码: #define GPKCON (volatile unsigned long*)0x7F008820#define GPKDAT (volatile unsigned long*)0x7F008824// 延时函数void delay(){ int i = 0; int j = 0; for(i = 0; i < 10000; i++) { for(j = 0; j < 110; j++) { ; } }}int main(){ int val = 0x1e; *(GPKCON) = 0x1111; while(1) { *(GPKDAT) = val; delay(); val = val<<1; if((val&0x10) != 0x10) val = 0x1f; } return 0; }终于写完了,也算是给自己这段时间通宵达旦学习一个交代吧!感觉自己没有白白浪费时间,还是学到一点东西,这些记不住,以后只能通过这篇博文复习巩固了。加油,继续接下来的学习。
|