1.本实验相关管脚介绍
1)原理图
2)由上图可以看出,4个LED灯所对应的管脚:
名称 |
对应管脚 |
管脚功能 |
对应逻辑 |
LED1 |
GPB5 |
内部上拉输出 |
0:灯亮 1:灯灭 |
LED2 |
GPB6 |
内部上拉输出 |
0:灯亮 1:灯灭 |
LED3 |
GPB7 |
内部上拉输出 |
0:灯亮 1:灯灭 |
LED4 |
GPB8 |
内部上拉输出 |
0:灯亮 1:灯灭 |
2.相关的寄存器:
1)与LED相关:
注释:
① GPBCON : 用于配置端口B的各个管脚功能
② GPBDAT : 端口B的数据寄存器
③ GPBUP : 用于配置端口B是否使用上拉功能
2)与MMU相关
① CP15 register 1(control register)
相关bit(M,A,S,R,RR,V(可有可无))
注:
这个放在最后连着开启MMU时一次性设置就好了,需要设置的有:使能Icache,Dcache;使能数据地址对齐异常检查;关于覆写算法默认是ramdom,我们选择round-robin;关于异常向量表的起始位置,我们选择放在最低地址0x0就可以了(这个实验我们还不会用到向量表,所以也可以先忽略掉);endianness选择小端就行了,默认也是小端;开启MMU;
关于R,S这两个值,要与AP关联上,由下图可以看出,当AP=00时,我们这里选择S:R=10:这样在AP=00时,SP为RO,UP为NA;AP=01时,SP为R/W,UP为NA;AP=10,SP为R/W,UP为RO;AP=11,SP为W/R,UP为R/W.
综上所属,使用配置命令如下:
MRC p15 , 0 , R1 , c1 , c0 , 0 (R1 [29:0]= 000000000000000 1 0 1 00 0 1 0 1111 1 1 1B)
②CP15 register 2(Translation table base register(TTB))
注:
在启动MMU之前我们需要设置好TTB,使用配置命令如下:
MCR p15 , 0 , R1 , c2 , c0 , 0 (R1=页表基址)
③CP15 register 3(Domainaccess control register)
以下为在domain access control寄存器里的access control bits的取值及说明:
这里我们根据顺序来设置:D0=00(No access)D1=01(Client)D2=11(Manager)其它都设置为10(reserved),使用命令配置如下:
MCR p15 , 0 , R1 , c3 , c0 , 0 (R1 = 10101010101010101010101010110100B)
④CP15 register 5(fault status register)
⑤CP15 register 7(cacheoperations register)(write-only)
这个寄存器用于管理ICache 和 DCache. 提供功能如下:
格式如下:
注:
开启MMU之前我们需要InvalidateCache,并且清除write buffer,所以使用命令配置如下:
MCR p15 , 0 , R1 , c7 , c7 , 0 (R1=SBZ)
MCR p15 , 0 , R1 , c7 , c10 , 4 (R1=SBZ)
⑥CP15 register 8(TLB operation register )
可以使无效整个TBL(s),….其中还可以无效单独有一个记录项(通过使用MVA),MVA被提供在Rd里.当需要用上MVA时,register 10 的MVA格式如下:
注:
在开启MMU之前我们需要进行一次InvalidateTLBs。这样在开启MMU之后就可以自动重新设置TLB的内容了。所以使用命令配置如下:
MCR p15 , 0 , Rd , c8 , c7 , 0 (Rd=SBZ)
⑦CP15 register 10(TLB lockdown register)
格式如下:
复位时为0x0
3.程序流程图设计:
1)主程序流程图:
2)建立页表子程序流程图:
注:这里需要注意的是:要把物理地址为0~代码的结束地址,不然使用开启MMU指令后并不正确执行后续的指令,我个人的理解是:在开启MMU之后,CPU上所看到的指令地址会变成VA,这时如果不进行映射到相同的PA地址,那经过MMU之后就可能映射到不可预测的物理地址了或者出现异常。
3)拷贝测试代码子程序流程图:
4)初始化MMU子程序流程图:
4.程序设计:
①Makefile
demo_mmu.bin : demo_mmu.o led_display.o
arm-linux-ld -Tmmu.lds -o demo_mmu_elf demo_mmu.o led_display.o
arm-linux-objcopy -O binary -S demo_mmu_elf demo_mmu.bin
arm-linux-objdump -D -m arm demo_mmu_elf > demo_mmu.dis
%.o : %.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o : %.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean :
rm -f demo_mmu.bin demo_mmu_elf demo_mmu.dis *.o
②mmu.lds
SECTIONS {
first 0x00000000 : {demo_mmu.o}
second 0xB0005000 : AT(2048) {led_display.o}
}
③demo_mmu.S
#define WTCON 0x53000000
#define BWSCON 0x48000000
#define BANKCON6 0x4800001C
#define REFRESH 0x48000024
#define BANKSIZE 0x48000028
#define MRSRB6 0x4800002C
#define MMU_TTB 0x30000000
#define MMU_FPTB (0x30004000)
#define NCNB_CTTBTT (0x00<<2)
#define WT_CTTBTT (0x03<<2)
#define MANAGER_DOMAIN (0x02<<5)
#define SPUP_WR_SECTIONAP (0x03<<10)
#define SECTION_FLAG (0x01<<4)|(0x02)
#define FINE_FLAG (0x13)
#define SPUP_WR_TINYAP (0x03<<4)
#define TINY_FLAG (0x03)
.Text
.global _start
_start:
/******关闭看门狗**********/
ldr r0 , =WTCON
mov r1 , #0x0
str r1 , [r0]
/*****END关闭看门狗********/
/***调用初始化内存子程序***/
bl memory_init
/**********END*************/
/*****调用建立页表子程序***/
bl build_page
/***********END************/
/*****调用初始化MMU子程序**/
bl mmu_init
/***********END************/
@启动MMU
mcr p15 , 0 , r1 , c1 , c0 , 0 @设置control register.启动MMU
/**********让灯全亮************
@把LED1-4管脚置为输出
ldr r0 , =GPBCON
ldr r1 , [r0] @把GPBCON里的内容加载到r1里
ldr r2 , =(0xFF<<10)
bic r1 , r1 ,r2 @操作数取反码或上r1,用于清零工作
ldr r2 , =(0x55<<10)
orr r1 , r1 , r2
str r1 , [r0]
@灯全亮
ldr r0 , =GPBDAT
ldr r1 , [r0]
ldr r2 , =(0x0F<<5)
bic r1 , r1 , r2
str r1 , [r0]
***********END***************/
/****调用测试代码子程序****/
bl copy_from_bootsram_to_sdram
/***********END************/
@设置栈顶指针
ldr sp , =0xB4000000
/****************灯全灭**************/
ldr r0 , =GPBCON
ldr r1 , [r0] /*把GPBCON里的内容加载到r1里*/
bic r1 , r1 , #0x3FC00 /*操作数取反码或上r1,用于清零工作*/
orr r1 , r1 , #0x15400
str r1 , [r0]
ldr r0 , =GPBDAT
; ldr r1 , [r0]
; bic r1 , r1 , #0x1E0
; orr r1 , r1 , #0x1E0
; str r1 , [r0] /*此时4个LED等全灭*/
/***************END灯全灭***************/
@跳转到测试代码执行
ldr pc , =main
halt_loop:
b halt_loop
memory_init:
/*******内存初始化子程序*********/
@BWSCON[27:24] = 0 0 10B
ldr r0 , =BWSCON
ldr r1 , [r0]
ldr r2 , =(0x0F<<24)
bic r1 , r1 , r2
ldr r2 , =(0x02<<24)
orr r1 , r1 , r2
str r1 , [r0]
@BANKCON6[16:15]=11B;BANKCON6[3:0]=00 01B
ldr r0 , =BANKCON6
ldr r1 , [r0]
ldr r2 , =(0x03<<15)
bic r1 , r1 , r2
orr r1 , r1 , r2
ldr r2 , =0x0F
bic r1 , r1 , r2
ldr r2 , = 0x01
orr r1 , r1 , r2
str r1 , [r0]
@REFRESH[23:18] = 1 0 00 00B;REFRESH[10:0] = 0x7A3
ldr r0 , =REFRESH
ldr r1 , [r0]
ldr r2 , =(0x3F<<18)
bic r1 , r1 , r2
ldr r2 , =(0x20<<18)
orr r1 , r1 , r2
ldr r2 , =0x7FF
bic r1 , r1 , r2
ldr r2 , = 0x7A3
orr r1 , r1 , r2
str r1 , [r0]
@BANKSIZE[7:0] = 1 0 1 1 0 001 B
ldr r0 , =BANKSIZE
ldr r1 , [r0]
ldr r2 , =0xFF
bic r1 , r1 , r2
ldr r2 , =0xB1
orr r1 , r1 , r2
str r1 , [r0]
@MRSRB6[11:0] = 0 00 011 0 000 B
ldr r0 , =MRSRB6
ldr r1 , [r0]
ldr r2 , =0x3FF
bic r1 , r1 , r2
ldr r2 , =0x030
orr r1 , r1 , r2
str r1 , [r0]
mov pc , lr @函数返回
/******END内存初始化子程序*******/
build_page:
/********建立页表子程序***********/
/*对0地址处的映射*/
ldr r1 , =0x00000000 @VA起始地址
ldr r2 , =0x00000000 @PA起始地址
@*(MMU_TTB&0xFFFFC000+VA>>20<<2) = (PA&0xFFF00000)|(SPUP_WR(0x03)<<AP(10))|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(NCNB_CTTBTT(0x00)<<Ctt_Btt(2))|(SECTION_FLAG(0x02))
ldr r0 , =(MMU_TTB & 0xFFFFC000)
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入段描述符所在一级页表条目地址
mov r3 , r2 , LSR#20
mov r3 , r3 , LSL#20
mov r4 , #0x0
ldr r4 , =SPUP_WR_SECTIONAP | MANAGER_DOMAIN | WT_CTTBTT | SECTION_FLAG
orr r3 , r3 , r4 @r3存入要放进页表的段描述符
str r3 , [r0] @往一级描述符地址放入对应的段描述符
/*END对0地址处的映射*/
/*对内存的映射*/
@VA:0xB0000000~0xB3FFFFFF------>PA:0x30000000~0x33FFFFFF
@using section-mapped
@总共64M,需要映射64次,每一次VA跟PA都要自加1M
ldr r1 , =0xB0000000 @VA起始地址
ldr r2 , =0x30000000 @PA起始地址
ldr r5 , =0xB4000000 @循环计数初值
l: @*(MMU_TTB&0xFFFFC000+VA>>20<<2) = (PA&0xFFF00000)|(SPUP_WR(0x03)<<AP(10))|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(WT(0x03)<<Ctt_Btt(2))|(SECTION_FLAG(0x02))
ldr r0 , =(MMU_TTB & 0xFFFFC000)
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入段描述符所在一级页表条目地址
mov r3 , r2 , LSR#20
mov r3 , r3 , LSL#20
mov r4 , #0x0
ldr r4 , =SPUP_WR_SECTIONAP | MANAGER_DOMAIN | WT_CTTBTT | SECTION_FLAG
orr r3 , r3 , r4 @r3存入要放进页表的段描述符
str r3 , [r0] @往一级描述符地址放入对应的段描述符
add r1 , r1 , #0x100000
add r2 , r2 , #0x100000
cmp r1 , r5
bne l @如果VA没加到结尾就跳转到前一个l执行
/*END对内存的映射*/
/*对IO相关寄存器的映射*/
@VA:0xA0000000~0xA00003FF------>PA:0x56000000~0x560003FF
@using tiny page-mapped
ldr r1 , =0xA0000000 @VA起始地址
ldr r2 , =0x56000000 @PA起始地址
@因为IO相关寄存器地址范围不超过1K,所以我这里选择tiny page直接对1K进行设置
@Level one addr=(MMU_TTB&0xFFFFC000+VA>>20<<2)
@*(Level one addr)=(MMU_FPTB&0xFFFFF000)|(MANAGER(0x02)<<DOMAIN(5))|(0x01<<4)|(FINE_FLAG(0x03))
@Level two addr=(MMU_FPTB&0xFFFFF000+(VA>>8)&0xFFC)
@*(Level two addr)=(PA&0xFFFFFC00)|(SPUP_WR(0x03)<<AP(4))|(NCNB(00B)<<Ctt_Btt(2))|(TINY_FLAG(0x03))
ldr r0 , =MMU_TTB & 0xFFFFC000
mov r3 , r1 , LSR#20
add r0 , r0 , r3 , LSL#2 @r0存入小页描述符所在的一级页表条目地址
ldr r3 , =(MMU_FPTB & 0xFFFFF000) | MANAGER_DOMAIN | FINE_FLAG
str r3 , [r0] @往一级页表描述符地址放入对应的小页描述符
ldr r0 , =(MMU_FPTB & 0xFFFFF000)
mov r3 , r1 , LSR#8
ldr r4 , =0xFFC
and r3 , r3 , r4
add r0 , r0 , r3 @r0存入微小页描述符所在的二级细页表条目地址
mov r3 , r2 , LSR#10
mov r3 , r3 , LSL#10
ldr r4 , =SPUP_WR_TINYAP | NCNB_CTTBTT | TINY_FLAG
orr r3 , r3 , r4 @r3存入微小页描述符
str r3 , [r0] @往一级页表描述符索引到的地址放入对应的小页描述符
/*END对IO相关寄存器的映射*/
@返回到主程序
mov pc , lr
/********END建立页表子程序********/
copy_from_bootsram_to_sdram:
/*******拷贝测试代码子程序********/
@我们把测试代码加载在0x800=2048处(加载地址)
@拷贝到0x30005000=0x30000000(转换表基址)+0x4000(转换表大小)+0x1000(细页表大小)
mov r1 , #0x800 @源地址
ldr r2 , =0xB0005000 @目标地址
l1:
ldr r3 , [r1] , #0x04
str r3 , [r2] , #0x04
cmp r1 , #0x1000
bne l1
mov pc , lr
/******END拷贝测试代码子程序******/
mmu_init:
/*********初始化MMU子程序*********/
mov r1 , #0x0
mcr p15 , 0 , r1 , c7 , c7 , 0 @Invalidate Caches
mcr p15 , 0 , r1 , c7 , c10, 4 @清除write buffer
mcr p15 , 0 , r1 , c8 , c7 , 0 @Invalidate TLBs
ldr r1 , =MMU_TTB
mcr p15 , 0 , r1 , c2 , c0 , 0 @设置TTB register
ldr r1 , =0xAAAAAAB4
mcr p15 , 0 , r1 , c3 , c0 , 0 @设置domain access control register
mrc p15 , 0 , r1 , c1 , c0 , 0 @读取cp15 control register放入r1
ldr r2 , =0x3FFFFFFF
bic r1 , r1 , r2
ldr r2 , =0x517F
orr r1 , r1 , r2
@如果现在就启动了MMU,那么下面的返回地址就变成虚拟地址,所以跳转后再启动MMU
mov pc , lr @返回主程序
/******END初始化MMU子程序*********/
/************************************************END************************************************************/
④led_display.c
#define GPBCON (*(volatile unsigned long *)0xA0000010)
#define GPBDAT (*(volatile unsigned long *)0xA0000014)
/**
*如果不开启-O2以上优化,gcc编译器不提供inline优化,所以写入的inline只在编译时加入了-O2优化选项才会有效,这时for里面的循环参数可以不加volatile
*如果开启了-O2以上优化,gcc提供的inline优化有效,如果for循环里做编译器认为没意义的事,循环参数加上volatile声明。
*/
static inline void delay(volatile unsigned long dly)
{
for(; dly > 0; dly--)
}
int main()
{
unsigned long i = 0;
GPBCON &= ~(0xFF<<10);
GPBCON |= (0x55<<10); //把GPB5~8都置为输出功能
while(1){
delay(500000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1,2,3,4
if(++i == 16)
i = 0;
}
return 0;
}