预备知识:
起始地址 | 结束地址 | |
存储控制器 | 0x48000000 | 0x48000030 |
USB Host 控制器 | 0x49000000 | 0x49000058 |
中断控制器 | 0x4A000000 | 0X4A00001C |
DMA | 0x4B000000 | 0x4B0000E0 |
时钟和电源管理 | 0x4C000000 | 0x4C000018 |
LCD控制器 | 0x4D000000 | 0X4D000060 |
NAND Flash | 0x4E000000 | 0x4E00003C |
摄像头接口 | 0x4F000000 | 0x4F0000A0 |
UART | 0x50000000 | 0x50008028 |
脉宽调制计时器 | 0x51000000 | 0x51000040 |
USB设备 | 0x52000000 | 0x5200026F |
WATCHDOG计时器 | 0x53000000 | 0x53000008 |
IIC控制器 | 0x54000000 | 0x54000010 |
IIS控制器 | 0x55000000 | 0x55000012 |
I/O端口 | 0x56000000 | 0x560000CC |
RTC | 0x57000000 | 0x5700008B |
A/D转换器 | 0x58000000 | 0x58000014 |
SPI | 0x59000000 | 0x59000034 |
SD接口 | 0x5A000000 | 0x5A000043 |
AC97音频编码接口 | 0x5B000000 | 0x5B00001C |
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
实验目的:
由于开发板(JZ2440 V3)上电后从Nand flash启动CPU时,CPU会通过内部的硬件将Nand flash的前4K数据复制到称为“Steppingstone”的4K内部SRAM中(起始地址为0),然后跳到地址0开始执行。(这里你可以先天马行空一下,你眼前现在应该浮现出一个长方体的蛋糕,然后你拿刀切切切,切成四块,一块你给它想成是一个面,然后一个面你就给它看成是Excel表格)
SDRAM的内部是一个个存储阵列,阵列就如同表格一样,将数据“填”进去。和表格的检索原理一样,先指定一个行(Row)和一个列(Column),就可以准确的找到所需要的单元格,这就是SDRAM寻址的基本原理,这个单元格被称为存储单元,这个表格(存储阵列)就是逻辑Bank(Logical Bank,简称L-Bank)。SDRAM一般分为4个L-Bank。PS:这回上面的那4个步骤你就应该明白了,接下来我们就看看具体怎么操作。
对于①,我就不废话了。
对于②,[ADDR25:ADDR24]=0b00/0b01/0b10/0b11,就正好对应四个L-Bank了。
对于③,(这个我们就得SDRAM的芯片手册和原理图结合看一下了)根据SDRAM的列地址线数目设置CPU相关的寄存器后,CPU就会从32位的地址中自动分出L-Bank选择信号、行地址信号、列地址信号,然后先后
发出行地址信号,列地址信号。L-Bank选择信号在发出行地址信号的同时发出,并维持到列地址信号结束。
在我们这个实验中,行地址、列地址共用ADDR2~ADDR14,然后使用nSRAS、nSCAS来区分它们(Bank6的位宽是32,也就是CPU访问SDRAM,一次访问4个字节。而CPU的单位是Byte,eg:CPU内存地址0x00000000、0x00000001、0x00000002、0x00000003其实访问的都是SDRAM的0x00000000,即CPU真正有效的地址位是从ADDR2开始的,所以ADDR0和ADDR1没用)。
通过原理图我们可以看出,这个开发板的两根地址线ADDR24、ADDR25作为L-Bank的选择信号,行地址数为13,列地址数为9。当nSRAS信号有效时,ADDR2~ADDR14发出的是行地址信号,它对应32bit地址空间的
bit[23:11]。当nSCAS信号有效时,ADDR2~ADDR14发出的是列地址信号,它对应32bit地址空间的bit[10:2]。ADDR0、ADDR1恒为0,不参与译码。
这个图是SDRAM芯片手册上的所以此时访问SDRAM的内存地址范围为 0x30000000~0x30000000+0x03FFFFFF 即 0x30000000~0x33FFFFFF。
共64MB。为什么是64MB? (2^13*2^9*4)*32bit/8 = 2^26 Byte=64MB
对于④,看开发板原理图可以得到,我们使用了俩片16位的SDRAM芯片并联组成32位的位宽,与CPU的32根数据线(DATA0~DATA31)相连。
完成这个实验的预备知识就还剩最后一个问题了,大家想一下,我们通过CPU访问SDRAM,上面我们已经把关于SDRAM部分的操作说完了,接下来自然而然就剩下那个“桥梁”————配置CPU内部的存储控制器。
怎么配置?很简单,就是往一堆寄存器里写我们需要的配置就好。而通过S3C2440芯片手册我们可以了解到,CPU内部存储控制器相关寄存器地址为0x48000000~0x48000030(一共13个)。
//////////////////////////////////////////////////////////////////////////////////////////////////
接下来就是讲我们这个实验里需要用到的寄存器以及相关配置了:
S3C2440芯片有Bank0~Bank7,8个块。存储控制器有13个寄存器,Bank0~Bank5只用BWSCON(BUS WIDTH & WAIT CONTROL REGISTER)和BANKCONx(BANK CONTROL REGISTER x为0~5)两个寄存器。
Bank6和Bank7外接SDRAM时,除上面两种寄存器,还需要用到REFRESH、BANKSIZE、MRSRB6、MRSRB7等四个寄存器。
13个寄存器如下:
BWSCON (Bus Width & Wait Control Register)
BANKCONx (Bank Control Register x=0~7)
REFRESH (Refresh Control Register刷新控制寄存器)
BANKSIZE (Banksize Register)
MRSRB6/7 (SDRAM Mode Register Set Register)
1.位宽和等待控制寄存器
BWSCON寄存器中每4位控制一个BANK,从高往低一次类推BANK7~BANK0。
STx:启动/禁止SDRAM的数据掩码引脚。对于SDRAM此位为0,对于SRAM此位为1。
WSx:是否使用存储器的WAIT信号,通常都设为0。
DWx:用来设置相应的BANK位宽。
2.BANK控制寄存器(BAN0~BANK5)。
BANK CONTROL REGISTER (BANKCONN: NGCS0-NGCS5)
这几个寄存器用来控制BANK0~BANK5外接设备的访问时序。设置的时候具体需要参考外接芯片的手册中时序图。针对我们这个开发板我们使用0x0700即可。
3.BANK控制寄存器(BAN6、BANK7)。
在8个BANK中只有BANK6和BANK7可以外接SRAM或SDRAM,因此这俩个BANK控制寄存器和上面的不太相同。
MT:确定这个BANK外接的设备是什么芯片,由于我们外接SDRAM,所以设置为0b11。
Trcd:推荐值0b01。
SCAN:列地址位数,我们这里是设置为9位,即0b01。
其它的和BANK0~BANK5一样设置。
4.刷新控制寄存器(REFRESH CONTROL REGISTER )。
REFEN:使能/禁止 SDRAM刷新功能
TREFMD:SDRAM刷新模式,我们这里使用0b0。Self Refresh(一般在系统休眠时使用)。
Trp:设置当SDRAM中RAS(列地址信号)需要重新寻址时,要隔多久时间才能开始下次的寻址动作。理论上越短越好,我们这里设置为0b00。
Tsrc:SDRAM半行周期时间,我们这里设置0b11。
Refresh Counter:SDRAM刷新的计数值。HCLK即为SDRAM时钟频率
刷新周期=(2^11-刷新计数值+1)/HCLK 所以 刷新计数值=2^11+1-刷新周期*HCLK
我们这里通过查阅SDRAM芯片手册知道 Refresh period=64ms/8192=7.8125us
设HCLK=12MHz,所以refresh_count=2^11+1-12*7.8125=1955。
综上:我们这里的这个寄存器设置为0x008C0000+1955(0x7A3)=0x008C07A3。
5.BANKSIZE寄存器
BURST_EN: 使能/禁止 ARM核的突发传输。
SCKE_EN: 禁止/使能 通过SCKE信号让SDRAM进入省电模式。
SCLK_EN: 0: 一直使能SCLK信号 1:只有当使用SDRAM时才使能SCLK信号(推荐)
BK76MAP:设置BANK6/7的大小。
BANK0~BANK5的地址空间大小都是固定不变的128MB,而BANK6/7的大小是可变的,以保持这两个空间的大小连续,即BANK7的起始地址会随它们的大小变化。我们用的SDRAM是64MB,所以设置为0b001。
6.SDRAM模式设置寄存器(MRSBx)
通过上面我们可以看到能修改的只有一个CL:CAS(列地址信号)延迟,我们这里设置为0x30。
》》》》》》》》 至此,关于本实验中所有的存储控制器相关的寄存器学习完毕,那么接下来。就是最后一步了。写代码《《《《《《《《
////////////////////////////////////////////////////////////////
.equ:
.equ symbol, expression: 把某一个符号(symbol)定义成某一个值(expression),该指令并不分配空间,相当于C语言中的#define宏定义。
LR寄存器:
LR(link register)连接寄存器,在ARM体系结构中LR的特殊用途有两种:
1.用来保存子程序的返回地址。
2.当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的位置继续执行。
当通过BL或者BLX指令调用子程序时,硬件自动将子程序返回地址保存在R14(LR)寄存器中,在子程序返回时,把LR的值赋值到程序计数器PC即可实现子程序返回(eg:MOV PC,LR)。
ADR指令:
这是一条小范围的地址读取伪指令,它将基于PC的相对偏移地址值读到目标寄存器中。
使用格式: ADR register,exper
在编译源程序时,编译器首先计算出当前PC到exper的偏移值#offset_to_exper,然后使用一条ADD或者SUB指令来替换这些伪指令,例如,ADD resister,PC,#offset_to_exper
注意标号exper与指令必须在统一代码段。
ADRL指令:
这是一条中等范围的地址读取伪指令,它将基于PC的相对偏移地址值读到目标寄存器中。
原理和ADR一样,不同的是它在编译的时候,会被用两条合适的指令来替换伪指令。
eg:ADD register,PC,offset1
ADD register,register,offset2
所以上面adrl r2, mem_cfg_val 意思是把下面那13个值的起始存储地址存入到r2寄存器。
.long:
定义一个4字节数据,并为它分配空间
.align n:
它的作用是对指令或者数据存放的地址按 2^n 进行对齐。下面是指定按 2^4 对齐。
汇编语言head.S:(作用是将 RAM 程序复制到 SDRAM中,并在SDRAM 中执行)
.equ MEM_CTL_BASE, 0x48000000 @ MEM_CTL_BASE 为芯片内存中的13个存储控制器中寄存器的起始地址
.equ SDRAM_BASE, 0x30000000 @ SDRAM_BASE 为SDRAM在芯片中的内存地址
.text
.global _start
_start:
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
bl memsetup @ 设置存储控制器(配置其相应的寄存器)
bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中
ldr pc,=on_sdram @ 跳转到SDRAM中继续执行
on_sdram:
ldr sp,=0x34000000 @ 设置堆栈
bl main
halt_loop:
b halt_loop
disable_watch_dog:
mov r1, #0x53000000 @ WATCHDOG在芯片中的内存地址
mov r2, #0x0
str r2, [r1] @ 把WATCHDOG寄存器写0
mov pc, lr @ 把连接寄存器 lr 的值赋值给程序计数器 PC。用来子程序返回继续执行主程序
copy_steppingstone_to_sdram:
@ 我们这里将steppingstonede 4K数据全部复制到SDRAM中去
@ steppingstone的 起始地址为0x00000000, SDRAM中的起始地址为0x30000000
mov r1,#0
ldr r2,=SDRAM_BASE
mov r3,#4*1024 @ 因为我们要复制4K的大小
1:
ldr r4,[r1],#4 @ 从steppingstone 读取4字节的数据,然后让源地址加4
str r4,[r2],#4 @ 将r4 里4字节的数据复制SDRAM中,然后目的地址加4
cmp r1, r3 @ 参考我下面的解释
bne 1b
mov pc, lr
memsetup:
mov r1, #MEM_CTL_BASE
adrl r2, mem_cfg_val @ 注意这里 adr 后面是L 而不是数字1,反正我开始就是傻傻分不清。
@ 这个语句就是把 mem_cfg_val 段定义的几个数据的其实地址传递给 r2
add r3, r1,#52 @ 把 r1+13*4 传递给r3,cpu是32bit的,即一个寄存器包含4个字节,所以13个就包含52个字节
@ 那么,如果把13个寄存器全部赋值结束后地址就应该是 r1+13*4。
1:
ldr r4, [r2],#4 @ 读取设置值,并让r2+4。
str r4, [r1],#4 @ 将此值写入寄存器,并让r1+4,以便下一个数据写入下一个寄存器
cmp r1, r3 @ 关于 cmp 指令其实也就是计算r1-r3,但是它的结果并不改变其寄存器的值,只是改变程序状态寄存器 CPSR的标志位
@ 然后下一条语句在指令后加上条件判断就能完成我们想要的循环。
bne 1b @ ne(如果不相等) 1b 这里的b(backwark),向后跳转到局部标签1处执行。
@ 相应的还有1f(foward),向前跳转到局部标签1处执行,注意理解这里的前和后,前代表地址增的方向,后代表地址减的方向
mov pc, lr
.align 4
mem_cfg_val:
.long 0x22011110 @ BWSCON 寄存器要写入的值
.long 0x00000700 @ BANKCON0 寄存器要写入的值
.long 0x00000700 @ BANKCON1 寄存器要写入的值
.long 0x00000700 @ BANKCON2 寄存器要写入的值
.long 0x00000700 @ BANKCON3 寄存器要写入的值
.long 0x00000700 @ BANKCON4 寄存器要写入的值
.long 0x00000700 @ BANKCON5 寄存器要写入的值
.long 0x00018005 @ BANKCON6 寄存器要写入的值
.long 0x00018005 @ BANKCON7 寄存器要写入的值
.long 0x008C07A3 @ REFRSH 寄存器要写入的值
.long 0x000000B1 @ BANKSIZE 寄存器要写入的值
.long 0x00000030 @ MRSRB6 寄存器要写入的值
.long 0x00000030 @ MRSRB7 寄存器要写入的值
c语言main函数:
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPF4_out (1<<(4*2))
#define GPF5_out (1<<(5*2))
#define GPF6_out (1<<(6*2))
void wait(volatile unsigned long dly) // 简单的延时函数
{
for(;dly>0;dly--);
}
int main(void)
{
unsigned long i=0;
GPFCON = CPF4_out | GPF5_out | GPF6_out; // 将LED对应的引脚设置为输出
while(1)
{
wait(30000);
GPFDAT = (~(1<<4)); // 根据i的值,点亮LED
if(++1 == 8)
i=0;
}
return 0;
}
Makefile:
sdram.bin : head.S leds.C
arm-linux-gcc -c -o head.o head.S
arm-linux-gcc -c -o leds.o leds.c
arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf
arm-linux-objcopy -O binary -S sdram_elf sdram.bin
arm-linux-objdump -D -m arm sdram_elf > sdram.dis
clean:
rm -f sdram.dis sdram_elf sdram.bin *.o
注: objdump 命令是Linux下的反汇编目标文件或者可执行文件的命令
-D 表示反汇编 (要反汇编的文件) 中的所有section