ARM技术论坛
直播中

小伍

9年用户 4827经验值
擅长:电源/新能源 嵌入式技术
私信 关注
[经验]

周末聊聊ARM的存储控制器

1、存储控制器概述
S3C2440存储控制器提供了访问外部存储设备所需的内存控制信号。有以下特点:
--支持小端/大端字节序(通过软件选择)
--地址空间:每个BANK有128M(总共1G,8个BANK)
--可编程的访问位宽:BANK0为16/32位,其他BANK为8位/16位/32位
--总共8个存储器BANK,其中6个用于ROM,SRAM,等等。其余的2个用于ROM,SRAM,SDRAM等等
--7个BANK的起始地址是固定的(BANK0~BANK6)
--1个BANK的起始地址和大小可编程(BANK7)
--所有BANK的访问周期可编程
--外部的wait信号可延长总线周期
--外接SDRAM支持自刷新和掉电模式
(以上内容摘自三星S3C2440数据手册)
注意:BANK6和BANK7的地址空间大小必须相等(BANK6和BANK7的地址空间大小是可编程的)
S3C2440对外引出了27根地址线ADDR0~ADDR26,所以访问地址空间范围是2^27位=128M。另外,CPU还对外引出了8根片选信号nGCS0~nGCS7,对应于BANK0~BANK7,当访问BANKx的地址空间是,nGCSx引脚输出低电平用来选中对应的外接设备。这样,8个片选信号总共对应了8个128M即1G的地址空间。这8个BANK的地址空间如下图所示(图摘自S3C2440芯片手册):
图1.png
图1-1 S3C2440的内存映射空间
图中左半部分对应的是不使用NAND Flash作为启动设备时的地址空间布局,右半部分对应的是使用NAND Flash作为启动设备时的地址空间布局。
S3C2440是32位的CPU,可以使用的地址范围理论上最大可达到4GB,除上述用于连接外设的1GB地址空间,还有一部分是CPU内部寄存器的地址,还有一部分未使用。
S3C2440的寄存器地址范围处于0x48000000~0x5FFFFFFF之间,如下表所示
图2.png
表1-1 S3C2440各功能部件寄存器地址范围
2、存储控制器与外设的对应关系
我使用的开发板是JZ2440V3,本开发板使用了存储控制器的BANK0~BANK6,所接设备分别为:NOR Flash、IDE接口、10M网卡CS8900、扩展串口芯片16C2550、SDRAM。
根据上图1可以知道各个BANK的起始地址,但是相关外设的具体访问地址还需要用到相应外设硬件连接所用到的地址线。这些所用的的地址线所确定的地址值,再加上这个BANK的起始地址,就是这个外设的具体访问地址。
以扩展串口为例:
图3.png
图2-1 S3C2440与扩展串口16C2550的连线图
它使用的nGCS5作为片选信号线,根据图1可知,它的起始地址为0x28000000。扩展串口芯片有个片选信号引脚nCS(图2中是接了2片16C2550,nCSA和nCSB分别是两个芯片的片选信号引脚)。根据图2可知nCSA=nGCS5 || ADDR24,nCSB=nGS5 || !ADDR24,即nGCS5为低电平,ADDR24也为低电平时选中扩展串口A;当nGCS5为低电平,ADDR24为高电平时选中扩展串口B。所以地址线ADDR24决定了扩展串口A、B的起始地址分别为0x28000000和0x29000000。CPU的ADDR0~ADDR2连接扩展串口的A0~A2,所以访问空间范围为8个字节,所以CUP对扩展串口A的访问空间范围是0x28000000~0x28000007,对扩展串口B的访问空间范围是0x29000000~0x29000007。
///
实验目的:
由于开发板(JZ2440 V3)上电后从Nand flash启动CPU时,CPU会通过内部的硬件将Nand flash的前4K数据复制到称为“Steppingstone”的4K内部SRAM中(起始地址为0),然后跳到地址0开始执行。
这个实验我们先用汇编语言设置好S3C2440芯片内部的存储控制器,使外接的SDRAM可以操作使用:然后把程序本身从“Steppingstone”(即芯片内部的4K SRAM)复制到SDRAM处,最后跳到SDRAM中执行。

知识预备:
存储控制器(memory controller):它为CPU提供了访问外部设备所需的信号,这是一种通过总线方式来访问拓展的外设,在嵌入式中,这些拓展的外设可能不仅仅是内存,也包括网卡、Nor flash、Nand flash等等。(这里我们可以把它想像成是CPU与拓展外设沟通的桥梁!)

S3C2440芯片对外引出了27根地址线ADDR0~ADDR26,那么它的访问范围即2^7*2^20=128MB。但是这个芯片可以达到1GB的访问空间,怎么达到的?因为CPU还对外引出了8根片选信号:nGCS0~nGCS7,对应于BANK0~BANK7。当访问BANKx的地址空间时,nGCSx引脚输出低电平用来选中外界的设备,故:这个芯片最多8个拓展外设,因为只有8个bank,想用相应的外设直接让相应的nGCSx输出低电平即可   ^~^   看来也不是很难,就是这么个理。

那么问题来了,如果我们想通过CPU访问一个芯片需要哪些条件呢?
1.地址线(我们需要知道芯片的访问地址)
2.数据线(我们需要知道一次传输多少数据,8bit/16bit/32bit 即我们常说的数据宽度)
3.时钟/频率(我们需要CPU与外设约定好传输的速率,不能太快,不能太慢,必须能满足双方的要求)
4.芯片相关的一些要求(比如说我们这里是访问SDRAM,那么就得知道它的行地址,列地址,哪个l_bank,这样我们就可以具体确定访问芯片的哪个存储单元了)

现在我们实验的是通过CPU访问SDRAM,那么我们就大概的想象一下,我们可以大致的分为4个步骤:
①CPU发出的片选信号nGCS6有效,它选中SDRAM芯片。(从V3原理图可得)
②SDRAM中有4个L-Bank,需要两根地址信号线选中哪一个L-Bank。(从V3原理图我们可以知道 CPU的ADDR24、ADDR25作为L-Bank的选择信号)
③对SDRAM进行统一的 行/列 寻址。(这样我们就可以确定具体访问芯片内部哪一个存储单元了)
④找到存储单元后,就要对SDRAM进行数据传输了。(这里我们就需要知道数据宽度等等)

如果你开始没有了解过SDRAM,也许上面这四个步骤你有些不大明白,没关系啊,现在我就来穿插一下,大概的说一下这个SDRAM内部的存储结构到底是个什么鬼:

(这里你可以先天马行空一下,你眼前现在应该浮现出一个长方体的蛋糕,然后你拿刀切切切,切成四块,一块你给它想成是一个面,然后一个面你就给它看成是Excel表格)
图4.png
图5.png
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芯片手册上的
图6.png
所以此时访问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)相连。
图7.png
完成这个实验的预备知识就还剩最后一个问题了,大家想一下,我们通过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).位宽和等待控制寄存器
图8.png
BWSCON寄存器中每4位控制一个BANK,从高往低一次类推BANK7~BANK0。
STx:启动/禁止SDRAM的数据掩码引脚。对于SDRAM此位为0,对于SRAM此位为1。
WSx:是否使用存储器的WAIT信号,通常都设为0。
DWx:用来设置相应的BANK位宽。
2).BANK控制寄存器(BAN0~BANK5)
图9.png
这几个寄存器用来控制BANK0~BANK5外接设备的访问时序。设置的时候具体需要参考外接芯片的手册中时序图。针对我们这个开发板我们使用0x0700即可。
3).BANK控制寄存器(BAN6、BANK7)


图10.png
在8个BANK中只有BANK6和BANK7可以外接SRAM或SDRAM,因此这俩个BANK控制寄存器和上面的不太相同。
MT:确定这个BANK外接的设备是什么芯片,由于我们外接SDRAM,所以设置为0b11。
Trcd:推荐值0b01。
SCAN:列地址位数,我们这里是设置为9位,即0b01。
其它的和BANK0~BANK5一样设置。
4).刷新控制寄存器(REFRESH CONTROL REGISTER )
图11.png
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寄存器
图12.png
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)
图13.png
通过上面我们可以看到能修改的只有一个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 中执行)
  1. .equ  MEM_CTL_BASE, 0x48000000  @ MEM_CTL_BASE 为芯片内存中的13个存储控制器中寄存器的起始地址  
  2. .equ  SDRAM_BASE,   0x30000000  @ SDRAM_BASE 为SDRAM在芯片中的内存地址  
  3.   
  4. .text  
  5. .global _start  
  6. _start:  
  7.             bl disable_watch_dog    @ 关闭WATCHDOG,否则CPU会不断重启  
  8.             bl memsetup             @ 设置存储控制器(配置其相应的寄存器)  
  9.             bl copy_steppingstone_to_sdram  @ 复制代码到SDRAM中  
  10.             ldr pc,=on_sdram        @ 跳转到SDRAM中继续执行  
  11. on_sdram:  
  12.             ldr sp,=0x34000000      @ 设置堆栈  
  13.             bl main  
  14. halt_loop:  
  15.             b halt_loop                       
  16.               
  17. disable_watch_dog:  
  18.             mov r1,  #0x53000000   @ WATCHDOG在芯片中的内存地址  
  19.             mov r2,  #0x0  
  20.             str r2, [r1]     @ 把WATCHDOG寄存器写0  
  21.             mov pc, lr       @ 把连接寄存器 lr 的值赋值给程序计数器 PC。用来子程序返回继续执行主程序  
  22.   
  23. copy_steppingstone_to_sdram:  
  24.             @ 我们这里将steppingstonede 4K数据全部复制到SDRAM中去  
  25.             @ steppingstone的 起始地址为0x00000000, SDRAM中的起始地址为0x30000000  
  26.               
  27.             mov r1,#0  
  28.             ldr r2,=SDRAM_BASE  
  29.             mov r3,#4*1024      @ 因为我们要复制4K的大小  
  30. 1:  
  31.             ldr r4,[r1],#4    @ 从steppingstone 读取4字节的数据,然后让源地址加4  
  32.             str r4,[r2],#4    @ 将r4 里4字节的数据复制SDRAM中,然后目的地址加4  
  33.               
  34.             cmp r1, r3        @ 参考我下面的解释  
  35.             bne 1b  
  36.             mov pc, lr  
  37.          
  38. memsetup:  
  39.             mov r1, #MEM_CTL_BASE  
  40.             adrl r2, mem_cfg_val   @ 注意这里 adr 后面是L 而不是数字1,反正我开始就是傻傻分不清。  
  41.                                    @ 这个语句就是把 mem_cfg_val 段定义的几个数据的其实地址传递给 r2  
  42.               
  43.             add r3, r1,#52         @ 把 r1+13*4 传递给r3,cpu是32bit的,即一个寄存器包含4个字节,所以13个就包含52个字节  
  44.                                    @ 那么,如果把13个寄存器全部赋值结束后地址就应该是 r1+13*4。  
  45. 1:  
  46.             ldr r4, [r2],#4        @ 读取设置值,并让r2+4。  
  47.                 str r4, [r1],#4       @ 将此值写入寄存器,并让r1+4,以便下一个数据写入下一个寄存器  
  48.                         
  49.                         cmp r1, r3             @ 关于 cmp 指令其实也就是计算r1-r3,但是它的结果并不改变其寄存器的值,只是改变程序状态寄存器 CPSR的标志位  
  50.                                        @ 然后下一条语句在指令后加上条件判断就能完成我们想要的循环。  
  51.                         bne 1b                 @ ne(如果不相等) 1b 这里的b(backwark),向后跳转到局部标签1处执行。  
  52.                            @ 相应的还有1f(foward),向前跳转到局部标签1处执行,注意理解这里的前和后,前代表地址增的方向,后代表地址减的方向  
  53.                         mov pc, lr  
  54.   
  55. .align 4  
  56. mem_cfg_val:  
  57.                 .long  0x22011110  @ BWSCON 寄存器要写入的值            
  58.                 .long  0x00000700  @ BANKCON0 寄存器要写入的值  
  59.                 .long  0x00000700  @ BANKCON1 寄存器要写入的值  
  60.                 .long  0x00000700  @ BANKCON2 寄存器要写入的值  
  61.                 .long  0x00000700  @ BANKCON3 寄存器要写入的值  
  62.                 .long  0x00000700  @ BANKCON4 寄存器要写入的值  
  63.                 .long  0x00000700  @ BANKCON5 寄存器要写入的值  
  64.                 .long  0x00018005  @ BANKCON6 寄存器要写入的值  
  65.                 .long  0x00018005  @ BANKCON7 寄存器要写入的值  
  66.                 .long  0x008C07A3  @ REFRSH 寄存器要写入的值  
  67.                 .long  0x000000B1  @ BANKSIZE 寄存器要写入的值  
  68.                 .long  0x00000030  @ MRSRB6 寄存器要写入的值  
  69.                 .long  0x00000030  @ MRSRB7 寄存器要写入的值  
c语言main函数:
  1. #define  GPFCON  (*(volatile unsigned long *)0x56000050)  
  2. #define  GPFDAT  (*(volatile unsigned long *)0x56000054)  
  3.   
  4. #define  GPF4_out  (1<<(4*2))  
  5. #define  GPF5_out  (1<<(5*2))  
  6. #define  GPF6_out  (1<<(6*2))  
  7.   
  8. void wait(volatile unsigned long dly)   // 简单的延时函数  
  9. {  
  10.     for(;dly>0;dly--);  
  11. }  
  12.   
  13. int main(void)  
  14. {  
  15.     unsigned long i=0;  
  16.       
  17.     GPFCON = CPF4_out | GPF5_out | GPF6_out; // 将LED对应的引脚设置为输出  
  18.       
  19.     while(1)  
  20.     {  
  21.         wait(30000);  
  22.         GPFDAT = (~(1<<4)); // 根据i的值,点亮LED  
  23.         if(++1 == 8)  
  24.             i=0;  
  25.     }  
  26.     return 0;  
  27. }
Makefile:
  1. sdram.bin : head.S leds.C  
  2.     arm-linux-gcc -c -o head.o head.S  
  3.     arm-linux-gcc -c -o leds.o leds.c  
  4.     arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf  
  5.     arm-linux-objcopy -O binary -S sdram_elf sdram.bin  
  6.     arm-linux-objdump -D -m arm sdram_elf > sdram.dis  
  7. clean:  
  8.     rm -f sdram.dis sdram_elf sdram.bin *.o   
注: objdump 命令是Linux下的反汇编目标文件或者可执行文件的命令
    -D 表示反汇编 (要反汇编的文件) 中的所有section
    -m 后面跟的cpu架构,arm就表示arm架构的cpu
    >  表示将这个程序的反汇编文件西写入到led1.dis这个文件中,在终端中不显示出来
       如果不加这个> ,那么你在终端上就可以看到输出的LED1_elf反汇编程序 .

回帖(1)

ids122

2020-8-23 20:19:32
好资料,非常有用,正在学习这部分内容,希望有帮助。
1 1 举报
  • 小伍: 好的,祝你学习进步,哈哈哈

更多回帖

发帖
×
20
完善资料,
赚取积分