标签:
一、
.text .global _start _start: ldr sp, =4*1024 @调用C函数之前,要设置栈指针;栈的作用:保存上下文,传递参数,保存临时变量;因为堆栈向下生长,所以要将栈指针设置到地址空间的顶层;总共可用的只有4K,也就是4*1024 bl close_watchdog @关看门狗 bl mem_ctrl_set @设置存储控制器 bl copy_code_sdram @复制代码到sdram bl set_page @设置页表 bl start_mmu @启动mmu ldr sp, =0xb4000000 @sdram的地址范围:0x30000000 ~ 0x3fffffff ; 取虚拟地址:0xb0000000 ~ 0xbfffffff 与之相对应映射;设置栈指针指向虚拟地址顶层 ldr pc, =0xb0004000 @pc也就是下一条指令执行前的地址,设置后就接着运行下一条指令,所以,功能也就是跳到sdram中继续运行;虚拟地址VA,变换后的虚拟地址MVA,物理地址PA,CPU看到的是VA, @caches和MMU利用MVA转换得到PA,设备看到的是PA;32位CPU也就是虚拟寻址:2^32=4G内存,将4G内存分为4096个段,每个段为1M,每个段对应一个描述符用于索引该段,每个描述符 @的大小是4字节(4B),共有4096个描述符,所以描述符需要4*4096=16K的内存来存放,把段当作一本书的所有章,描述符也就是这本书的目录,也就是用16k的内存来存放它的目录, @在sdram中,用开始的16K的地址来存放这个目录,剩余的就用来去存放代码,所以,运行代码的物理地址是0xb0000000+16k(0x4000)=0xb0004000作为开始首地址的 halt_loop: b halt_loop: @b和bl的区别就是跳转不返回下一条指令地址到lr(r14连接寄存器)寄存器,直接死循环
二、
/********************************************************************************************************************* 第一部分,关看门狗,设置控制寄存器 *********************************************************************************************************************/ /************** 宏定义地址 **************/ #define WTCON (*(volatile unsigned long *) 0x53000000 ) //看门狗控制寄存器 #define mem_ctrl_reg 0x48000000 //存储控制器首地址 /************ 关看门狗 ************/ void close_watchdog(void) { WTCON=0; } /****************** 设置存储控制器 ******************/ void mem_ctrl_set(void) { int i; unsigned long const ctrl_list[]={ //const,变量不允许改变 0x22011110, 0x00000700, 0x00000700, 0x00000700, 0x00000700, 0x00000700, 0x00000700, 0x00018005, 0x00018005, 0x008c07a3, 0x000000b1, 0x00000030, 0x00000030, }; volatile unsigned long *p = (volatile unsigned long *) mem_ctrl_reg //volatile是一个修饰符,告诉编译器此段代码不要优化; unsigned long *p 表示无符号长整形指针p; (volatile unsigned long *) mem_ctrl_reg 也就是把 //这个指针指向一个地址;相当于前面定义一个指针,然后后面把这个指针指向一个地址 for(i=0;i<13;i++) p[i]=ctrl_list[i]; //内存单位: 1Byte =4bit,1KB=1024Byte,1M=1024kb,1G=1024M ,最小的是bit(称位或者比特),二进制中每个0或者1就是一位,Byte称为字节; //一个指针通常分配4个字节,为什么呢?因为,首先,计算机的地址我们可以看作是一个十六进制数,一般由6位数(也就是6位16进制数)构成,换成二进制就是 } //6*4=24bit,指针要保存这个地址,那么指针的内存一定要大于等于24bit=3Byte才行,因此,4个字节,能完全保存地址,并留有余地 /************************************************************************************************************************ 第二部分,把steppingstone中的代码(也就是led.c实现点灯功能的代码)复制到SDRAM ************************************************************************************************************************/ void copy_code_sdram(void) { unsigned int *p_step=(unsigned int *)2048 //启动过程:1、上电,判断启动方式,因为本板里只有nandflash启动,所以设置启动方式为nandflash;2、设置为nandflash启动方式后,nanflash控制器自动将 //存放在 bootloader的最前面4k代码复制到stepping stone中(stepping stone俗称起步石,也就是个4k的sdram,相当于起缓冲作用),stepping stone被映射到 //NGCS0对应的bank0存储空间(地址就是0-4*1024内) unsigned int *p_sdram=(unsigned int *)0x30004000 //因为前面16k(0X400)已经用来存放目录了,所以sdram中地址要从0x30004000开始 while(1) { *p_sdram=*p_step; //对指向的地址赋值 p_step++; //地址+1 p_sdram++; //地址+1 } } /* C语言指针 1、指针: 指针就是地址,指向一个变量的指针,也就是这个变量的地址 2、指针变量: 本质是变量,也就是把另一个变量的地址(指针)放入这个变量 3、指针变量定义: 数据类型 *指针变量 如:int *p 在int *p中,p是指针变量,*表示该变量是指针变量,仅仅这样定义并没说明地址,因为变量没赋值,对它赋值后所赋的值就是地址 4、对指针变量赋值: 如:int x=5 ,*p; p=&x; &x也就表示p的地址 5、p和*p的区别: p是指针变量,也就是所指的那个值的地址;*p就相当于是所指的那个值 6、指针和数组: 定义指针变量: 如: int *p, a[5]={1,2,3,4,5}; p=a; 引用数组元素: 1、第k个元素:a[k];第k个元素的地址:&a[k] 2、第k个元素:*(a+k);第k个元素的地址:a+k 3、第k个元素:*(p+k);第k个元素的地址:p+k */ /*************************************************************************************************************************** 第三部分,设置页表,启动MMU ***************************************************************************************************************************/ /***************** 设置页表 *****************/ void set_page(void) { /* C语言移位: 1、<<,左移,低位补0; _crol_循环左移,低位补之前移走的高位 2、>>,右移,高位补0; _cror_循环右移,高位补之前移走的低位 */ #define MMU_FULL_ACCESS (3<<10) //倒数第[11:10]位,描述符为段时是这两位是AP,AP用于设置权限,取11时,表示权限在所有模式下都允许访问,因此先取这两位为1,后面一定要或上 #define MMU_MOMAIN (0<<5) //域的地址[8:5]位,段描述符domain为0000(必须的),表示这1MB内存属于域0,因此先给这四位取0,后面一定要或上 #define MMU_SPECIAL (1<<4) //倒数第4位在描述符无效时是0,任何一种描述都是1,先取1选择,所以后面一定要或上 #define MMU_CACHABLE (1<<3) //倒数第3位是C即cacheable,先取1选择,后面需要就或上它也就是加上它,不需要就不或上它 #define MMU_BUFFERABLE (1<<2) //倒数第2位是B即BUFFERABLE,先取1选择,后面需要就或上它也就是加上它,不需要就不或上它 #define MMU_SECTION (2) //最低2位为是选择描述符方式,00无效,01粗页表,10段,11细页表,表示这是段描述符 #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU—SPECIAL | MMU_SECTION) //不使用B和C,映射寄存器空间时使用 #define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU—SPECIAL | MMU—CACHEABLE | MMU_BUFFERABLE | MMU_SPECTION) //回写式,映射steppingstone和sdram等内存时使用 #define MMU_SECTION_SIZE 0X100000 unsigned long virtuladdr, physicaladdr; //定义虚拟地址和物理地址 unsigned long *mu_tlb_base =(unsigned long *)0x30000000 //SDRAM的起始地址,用来存放段的目录 /* steppingstone的起始物理地址是0,第一部分程序的起始运行地址也是0,为了在开启MMU后仍能运行第一部分的程序,将0-1M的虚拟地址映射到同样的物理地址*/ virtuladdr=0; // physicaladdr=0; *(mmu_tlb_base + (virtuladdr>>20)) = (physicaladdr & 0xfff00000) | MMU_SECDESC_WB; // 一级页表:物理地址=段基址+段内地址(p2), 段基址=一级页表索引(p1)+页表基址(TTB Base), // 页表基址也就是SDRAM的首地址,p1也就是虚拟地址MVA[31:20]即virtuladdr>>20,段基址也就是物理地址的[31:20],[19:0]用来段内寻址(1M) //虚拟地址32位=2^32=4G=4096*1M,因此共4096个段,每个段1M;4096=2^12,因此4096需要12位地址来存放,1M=2^20,因此段内的1M需要20位来存放; //用物理地址的高12位来存放4096,物理地址的低20位来存放1M //将虚拟地址0xA0000000开始的1M虚拟空间映射到从0x56000000开始的1M物理地址空间 virtuladdr = 0xA0000000; //虚拟地址的起始位置 physicaladdr=0x56000000; //GPIO的起始位置 *(mmu_tlb_base + (virtuladdr>>20))=(physicaladdr & 0xfff00000) | MMU_SECDESC; //把从0xA0000000开始1M的虚拟地址空间映射到从0x56000000开始的1MB的物理地址空间 //将虚拟地址0xB0000000开始的1M虚拟空间映射到0x3000000开始的1M物理地址空间 virtuladdr=0xB0000000; //虚拟地址的起始地址 physicaladdr=0x3000000; //物理地址的起始地址 while(virtuladdr<0xB4000000) { *(num_tlb_base + (virtuladdr>>20))=(physicaladdr & 0xfff00000) | MMU_SEDESC_WB; virtuladdr +=0x100000; physicaladdr+=0x100000; } } /************************************************************************************************************************* 第四部分 启动MMU *************************************************************************************************************************/ /************* 启动MMU *************/ void mmu_init(void) { unsigned long ttb=0x30000000; _asm_ //在C语言中可以嵌入汇编,_asm_(),括号内的是汇编指令,里面每条指令都要用双引号括起来 ( /* CP15包括15个具体寄存器: C0:ID号寄存器, C0:缓冲类型寄存器(有两个R0,根据MCR操作数的不同传送不同的值,这也一个只读寄存器) C1:控制寄存器 C2:转换表基址寄存器(TTB) C3:域访问控制寄存器 C4:保留 C5:异常状态寄存器 C6:异常地址寄存器 C7:缓存操作寄存器 C8:TLB操作寄存器 C9:缓存锁存寄存器 C10:TLB锁存寄存器 C11:12、14:保留 C13:处理器ID C15:测试配置寄存器2-24 */ "mov r0,#0\n" //mov r0,#0 是指把立即数0给r0;mov r0, 0 是指把0单元的值送给R0* "mcr p15, 0, r0, c7, c7, 0\n" //功能是使无效ICaches和DCaches; mcr p15 0 这前面是必须的,mcr就是把ARM处理器寄存器r0的值放到协处理器P15的寄存器c7中,c7是缓存操作寄存器 "mcr p15, 0, r0, c7, c10, 4\n" // 使无效write buffer "mcr p15, 0, r0, c8, c7, 0\n" //使无效TLB "mov r4, %0\n" //r4=页表基址 "mcr p15, 0, r4, c2, c0, 0\n" //设置页表基址寄存器 "mvn r0, #0\n" //mvn跟mov一样都是把一个立即数给一个寄存器,不同在于mov是给的这个立即数+1取负,如:MVN R0, #4; R0 = -5 "mcr p15, 0, r0, c3, c0, 0\n" //访问控制寄存器设为0xffffffff,不进行权限检查 /*对于控制寄存器,先读出其值,然后在这基础上修改感兴趣的位,然后再写入*/ "mrc p15, 0, r0, c1, c0, 0\n" //读取c1控制寄存器内容到主处理器r0内,mrc的过程跟mcr相反,mcr是主处理器数据到协处理器,mrc是协处理器数据到主处理器 /* 控制寄存器C1共32位: M[0]: 0禁止MMU或者MPU,1使能MMU或MPU; A[1]: 0禁止地址对齐检查功能,1使能地址对齐检查功能; C[2]: 0禁止Catche,1使能Catche W[3]: 0禁止,1使能写缓冲 P[4]: 0异常终端处理程序进入32位地址模式,异常中断处理程序进入26位地址模式 D[5]: 0禁止26位地址异常检查,使能26位地址异常检查 L[6]: 0选择早期中止模式 B[7]: 0使用小端,1使用大端 S[8]: 支持MMU的存储系统中,本控制位用作系统保护 R[9]: 支持MMU的存储系统中,本控制位用作ROM保护 F[10]: 本控制位由生产厂商定义,对于支持跳转预测的ARM系统,本控制位禁止/使能跳转预测功能 Z[11]: 0禁止跳转预测功能,1使能跳转预测功能 I[12]: 0禁止指令Catche,1使能指令Catche V[13]: 0选择0x00000000 - 0x0000001c, 1选择0Xffff0000~0xffff001c RR[14]: 0选择常规的淘汰算法,1选择预测性的淘汰算法 L4[15]: 0保持当前ARM版本的正常功能,1对于一些根据跳转地址的bit[10]进行状态切换的指令,忽略bit[0],不进行状态切换 Bit[31:16]: 保留 */ /*清除不需要的位*/ "bic r0, r0, #0x3000\n" "bic r0, r0, #0x300\n" "bic r0, r0, #0x0087\n" /*设置不需要的位*/ "orr r0, r0, #0x0002\n" "orr r0, r0, #0x0004\n" "orr r0, r0, #0x1000\n" "orr r0, r0, #0x0001\n" "mcr p15, 0, r0, c1, c0, 0\n" //设定值写入寄存器 : //无输入 : " r " (ttb) ) }
三、
/************************************************************************************************************************************************* C语言点亮LED *************************************************************************************************************************************************/ #define GPBCON (*(volatile unsigned long *) 0xa0000010 #define GPBDAT (*(volatile unsigned long *) 0xa0000014 #define GPB5_out (1<<(5*2)) //GPB5设置输出 #define GPB6_out (1<<(6*2)) //GPB6设置输出 #define GPB7_out (1<<(7*2)) //GPB7设置输出 #define GPB8_out (1<<(8*2)) //GPB8设置输出 static inline void wait(unsigned long dly) // static inline的内联函数,一般情况下不会产生函数本身的代码,而是全部被嵌入在被调用的地方,调用几次就嵌入几次,如果不加static,则表示该函数有可能会被其他编译单元所调用, //所以一定会产生函数本身的代码。所以加了static,一般可令可执行文件变小,编译结果只有一个main函数 { for(;dly>0;dly--); } int main(void) { unsigned long i=0; GPBCON=GPB5_out | GPB6_out | GPB7_out | GPB8_out ; //全部设置为输出 while(1) { wait(3000000); GPBDAT = (~ (i<<5) ); if(++i==16) i=0; } return 0; }
四、
SECTIONS { firtst 0x00000000 : { head.o init.o} second 0xB0004000 : AT(2048) { leds.o} }
五、
1 objs: = head.o init.o leds.o #使用变量objs代替后面的,后面使用时要用$(objs) 2 3 mmu.bin:$(objs) #关联 4 arm-linux-ld -Tmmu.lds -o mmu_elf $^ 5 arm-linux-objcopy -O binary -S mmu_elf $@ 6 arm-linux-objump -D -m arm mmu_elf>mmu.dis 7 8 %.o:%.c 9 arm-linux-gcc -Wall -O2 -c -o $@ $< 10 %.o:%.c 11 arm-linux-gcc -Wall -O2 -c -o $@< 12 clean: 13 rm -f mmu.bin mmu_elf mmu.dis *.o 14 15
标签:
原文地址:http://www.cnblogs.com/liubo118/p/4256806.html