本文是在韦东山的笔记上做一些自己的注释,方便学习理解。原文地址:
首先来分析下操作GPIO控制器和操作UART控制器两者的区别。
如图是S3C2440是个片上系统,有GPIO控制器(接有GPIO管脚),有串口控制器 (接有TXD RXD引脚)。
配置GPIO控制器相应的寄存器(如图中的,CPU传输某个值给GPFCON、GPFDAT),即可让引脚输出高低电平;配置UART控制器相应的寄存器(如图中的,CPU传输某个值给UCON0、UTXH0),即可让引脚输出波形。
前者相对简单,类似门电路,后者相对复杂,属于协议类接口(类似的协议类接口还有iic、iiGPFDATs、spi等)。
总结下来就是:对于CPU是不管什么接口的,它只写相应的寄存器,由控制器(如GPIO控制器、UART控制器)根据寄存器的配置去控制具体的引脚。
那么CPU是如何访问各个不同的寄存器(如GPFCON、UCON0等)的呢?
答:CPU只管发出一个地址,内存控制器根据该地址选择不同的模块,然后从模块中得到数据或者发送数据到模块中。(CPU发出地址让内存控制器去根据这个地址选择寄存器,然后CPU发出数据以配置寄存器)。
前面的GPIO/门电路接口、协议类接口,都不会把地址输出到外部,接下来的内存类接口,会把地址输出到外部,比如Nor Flash、网卡、SDRAM。
如图,SDRAM、DM9000网卡、Nor Flash都接在JZ2440的数据总线和地址总线上,CPU把数据和地址发送出去,然后内存控制器根据片选信号 选择相应的设备并让这个设备去接收地址和数据信号(这里的设备指的是SDRAM、DM9000网卡、Nor Flash),以达到SDRAM、DM9000和Nor Flash之间互不干扰的目的。
片选信号和地址的关系怎么确定?这个是由2440芯片特性决定的。
上图,当选择Nor Flash启动时:
(1)当CPU发出的指令的地址范围处于0x0000000 - 0x08000000时,内存控制器就会使nGCS0处于低电平(片选引脚被选中),Nor Flash被选中。
(2)当CPU发出的指令的地址范围处于0x20000000 - 0x28000000,内存控制器就会使nGCS4处于低电平(片选引脚被选中),网卡被选中。
(3)当CPU发出的指令的地址范围处于0x30000000 - 0x38000000,内存控制器就会使nGCS6处于低电平(片选引脚被选中),SDRAM被选中。
内存控制器根据不同的地址范围,发出不同的片选引脚,只有被片选引脚选中的芯片才能正常工作,不被选中的芯片就像不存在一样,不工作。
1.3.1 GPIO/门电路接口、协议类接口、内存类接口都属于CPU的统一编址。而对于Nand Flash,在原理图上它的地址线并没有连接到CPU,因此它不参与CPU的统一编址。但它的数据线也接到了数据总线上,为了防止干扰,它也有一个片选信号(CE)。当CPU访问Nand Flash时,Nand Flash控制器才会片选Nand Flash,让其接收数据总线上的数据。
1.3.2 再来看下Nor Flash的空间,0x00000000 * 0x08000000,为128M,即每一个片选信号可以选择的空间是128M=2^27,也就需要A0、A1……A26,共27根地址线。CPU发出的32位地址线给内存控制器,内存控制器根据地址范围,片选上相应的bank(这里的bank可以理解为设备即Nor flash,对应上上一幅图片的0x00000000 * 0x08000000即nGCS0片选信号,并对应2440中的BANKCON0寄存器),并将地址转化为27位。
参考2440芯片手册,可以看到内存接口与8-bit ROM连接时,2440的A0与外部芯片的A0相连。
(ROM是以前的名称,现在叫flash)
当与两个8-bit ROM拼接成的一个16-bit ROM连接时,2440的A1与外部芯片的A0相连。
当与四个8-bit ROM拼接成的一个32-bit ROM连接时,2440的A2与外部芯片的A0相连。
当与一个16-bit ROM连接时,2440的A1与外部芯片的A0相连。
可以看出的现象是:外接芯片的位宽有变化时,地址线的接法也会有变化(8bit的是A0接A0,两个8bit拼接为一个16位的是A1接A0,四个8bit的拼接为32位是A2接A0,一个16bit的是A1接A0,)。那这个变化有什么规律呢?
假设CUP执行如下命令:
MOV R0, #3 @去地址为3的内存上
LDRB R1, [R0] @ 从内存为3的地址上,读出一个字节(B表示Byte)
如图有8bitROM、16bitROM、32bitROM。
首先说明一点:8个bit组成一个字节,字节是计算机的最小的存储单位,因此我们读取数据肯定都是8bit的倍数。
(1)对于8bitROM ,8bit是一次读写的最小单位,即0地址是第一个8bit,1地址是第二个8bit;CPU发出的命令是读取地址为3上的数据,即A0和A1都为1,8bitROM的A0和A1收到的也都是1,于是找到了ROM上地址为3的8bit数据,包含了我们需要的数据。
(2)对于16bitROM ,16bit是一次读写的最小单位,即0地址是第一个16bit,里面有两个8bit数据;CPU发出的命令是读取地址为3上的数据,即A0和A1都为1,16bitROM的A0和A1分别收到的是1和0,于是找到了ROM上地址为1的16bit数据,包含了我们需要的数据,最后内存控制器再帮我们挑选出所需的8bit数据。
(3)对于32bitROM ,32bit是一次读写的最小单位,即0地址是第一个32bit,里面有四个8bit数据;CPU发出的命令是读取地址为3上的数据,即A0和A1都为0,32bitROM的A0和A1收到的都是0,于是找到了ROM上地址为0的32bit数据,包含了我们需要的数据,最后内存控制器再帮我们挑选出所需的8bit数据。
对这个例子进行总结:接到芯片(ROM)上的引脚用来确定读取芯片上的哪一个单元的数据,把这个单元的数据返回给内存控制器。内存控制器会根据没有连接芯片的引脚,来确定返回该单元数据中的哪一个字节(你这里传递的是8位,是一个字节),并将这个字节给CPU。
: 假如传递一个32位的数据时
MOV R0, #4
LDR R1, [R0] @去地址4,读取4字节数据(即4、5、6、7四个字节)
执行过程如下:
(1)8bitROM: 当CPU发出地址(000100),内存控制器会把000100,000101,000110,000111处的地址转发给ROM,ROM会把得到的地址000100,000101,000110,000111,上的数据返回给内存控制器,内存控制器会把得到的4个8bit的数据组装成一个32位的数据返回给CPU。
(2)16bitROM: 当CPU发出地址(000100),内存控制器会把00010,00011处的地址转发给ROM,ROM会把得到的地址00010,00011,上的数据返回给内存控制器,内存控制器会把得到的2个16bit的数据组装成一个32位的数据返回给CPU。
(3)32bitROM: 当CPU发出地址(000100),内存控制器会把0001处的地址发送给ROM,ROM会把得到的地址0001上的数据返回给内存控制器,内存控制器会把得到的1个32bit数据返回给CPU。
:怎样确定芯片的访问地址?
答: 1. 根据片选信号确定基地址, 2. 根据芯片所接地址线确定范围
2.3.1 对这个问题做一个实例:
(1) Nor Flash 使用的是片选0(nGCS0),基地址为0,用到A20,A19…A1,A0共21条地址线,所以地址范围为0x00000000 ~ 0x1FFFFF也就是2M的空间大小。
(2)网卡(Net)使用的是片选4(nGCS4),基地址为0x20000000,用到A2,A0共2根地址线,所以地址范围为0x20000000 ~ 0x20000005。
(3) SDRAM使用的是片选6(nGCS6),基地址为0x30000000。
注:这里的基地址是上面的第三张图(即2440芯片手册中的Figure 5-1. S3C2440A Memory Map after Reset)得出的;nGCS0对应的是 Nor Flash这是根据板子原理图得出的,同样nGCS4、nGCS6也是。
到这里你已经明白了内存控制器的作用以及它的伟大(CPU仅发出地址,剩下的工作都由内存控制器完成)。
在前两节学习的基础上,这节课我们了解一下时序图,信号之间是怎样一起工作的,以Nor Flash 为例。
2440和Nor Flash 之间有地址线,数据线,还有各种数据线连接。如下图所示:
Nor Flash为例,分析下如何设置它的时序。
下图是S3C2440芯片手册的Nor Flash控制器的读时序图,里面很多参数都需要根据外接芯片的性能进行设置。设置的目的是:有的芯片性能好、响应时间快,就可以把参数时间设置小一点,释放更好的性能。
注:
(1)图中A代表地址信号;nGCS代表片选信号;nOE代表读信号;D为数据有效。
(2)Tacs、Tcos、Tacc、Tacp、Tcoh、Tcah的含义是,相应段的时间,比如Tacs表示地址信号发出多长时间以后发出nGCS信号;Tacc表示nOE发出多长时间以后,D数据有效。
(3)Tcoh、Tcah是信号释放。
(4)这个图是2440芯片手册的所描述的一个原理,里面Tacs、Tcos等等这些数值要根据具体的外设芯片手册进行确定(下下一幅图是型号为MX29LV8000BBTC的Nor flash的芯片手册)。
如图是Nor Flash芯片的读时序。
我们需要做的就是设置S3C2440的Nor Flash控制器时序去满足Nor Flash芯片的时序。也就是说设置Nor Flash芯片的时序中的Txxx值,需要注意的是两个芯片手册中的Txxx的写法不一样但是意思是相通的。
每个参数(Txxx)的参考范围可以通过型号为MX29LV8000BBTC的Nor flash的芯片手册中的AC CHARACTERISTICS(如下图)得到。
对下图注:
(1)Taa表示发出Addr后多长时间,Data有效。
(2)其他意思依Taa类推。
结合Nor Flash芯片的两张图,可以得到如下信息:
(1)发出地址数据(Addresses)后,要等待Taa(要求大于等于70ns)时间,地址数据才有效;
(2)发出片选信号(CE#)后,要等待Tce(要求大于等于70ns)时间,片选信号才有效;
(3)发出读信号(OE#)后要等待Toe(要求大于等于30ns)时间,读信号才有效;
现在,我们对时序参数进行设置,然后进行编程,编程的目的是通过串口去在串口中输入不同的值Tacc从而设置了Tacc,然后执行test_led()函数,观察led闪烁的快慢变化。
为了简单我们把地址数据(Addresses),片选信号(CE#),读信号(OE#),同时发出,然后让它们都等待70ns(等待信号有效)。对应S3C2440的Nor Flash控制器的读时序图,需要让地址信号A[24:0]、片选信号nGCS、读信号nOE同时发出,保持Tacc大于等于70ns。
查阅S3C2240的参考手册(如下图),Nor Flash是接在BANKCON0上的,因此只需要设置BANKCON0即可。(也就是说Tacs、Tcos保持默认,即0clock,即可保持三者同步)。
注:clock可理解为周期
设置BANKCON0,一经上电系统其默认值为111,即14clock.。系统上电采用12MHz的晶振,HCLK=12MHz,Tacc=(1000/12*14)≈1166ns,这个值很大,几乎可以满足所有Nor Flash的要求(我们编这个程的目的就是改变这个值,并且是通过串口自己输入的)。
经过看Nor flash芯片手册我们知道:启动后,若将HCLK设置为100MHz,T=1000/100=10ns,Tacc需要大于等于70ns,因此设置Tacc等于101,8个clocks即可(也就是说Tacc最低得是8clock,即最起码得设置其为101)。
在前面uart实验的源码基础上,新建init.c和init.h两个文件。
在init.c里面只需要设置BANKCON0寄存器即可。
下面写程序:
程序组成:
(1)init.c------写bank0_tacc_set()函数,操作BANKCON0 ;
(2)init.h------声明bank0_tacc_set();
(3)main.c----主函数
(4)srart.S-----关看门狗、设置时钟、跳转main();
(5)uart.c()-----写uart0_init()函数用于串口初始化,写putchar()、getchar()函数用于在串口中输入输出;
(6)uart.h-------声明tart.c中的函数;
(6)led.c----写led_test(),使得三盏led等循环点亮;
注:这里没写的程序,是和03-第011课_串口(UART)的使用中的一样的,看那个就行。
#include "s3c2440_soc.h"
void bank0_tacc_set(int val)
{
BANKCON0 = val << 8;
}
init.h进行函数声明。
#ifndef _INIT_H
#define _INIT_H
void bank0_tacc_set(int val);
#endif
最后在主函数里面,通过串口获取输入的值,传入bank0_tacc_set()函数里,设置Tacc,然后再读取Nor Flash上的闪灯程序。
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
int main(void)
{
unsigned char c;
uart0_init();
puts("Enter the Tacc val: \n\r");
while(1)
{
c = getchar();
putchar(c);
if (c >= '0' && c <= '7')
{
bank0_tacc_set(c - '0');
led_test();
}
else
{
puts("Error, val should between 0~7\n\r");
puts("Enter the Tacc val: \n\r");
}
}
return 0;
}
实验效果:
输入0~4,Tacc小于70ns,无法读取Nor Flash上数据,LED不能闪烁。
输入4~7,Tacc大于70ns,可以读取Nor Flash上数据,LED不断闪烁,且值越小越快(区别不明显)。
我把u-boot下载到SDRAM后,然后运行u-boot,然后在串口里可以用u-boot对SDRAM进行操作。
而本节课对SDRAM的操作是和u-boot没关系的,也就是说,你想要在程序中用SDRAM的话,就需要你亲自去设置(配
置)SDRAM。
u-boot是集大成者,他可以直接操作SDRAM,但是你的程序中没有u-boot,你要用SDRAM就需要你先配置SDRAM,即需
要你写一个sdraminit(),然后你就可以对 属于SDRAM的 地址进行操作了(如写数据,p假如是SDRAM上的地址,就可
以操作*p = 1;)
本节将讲解如何设置SDRAM,如果想对内存有更多的了解,可以在网上搜索看下这篇文档“高手进阶_终极内存技术指南——完整/进阶版”。
在JZ2440上接有64M的SDRAM,如果想要使用SDRAM,需要对内存控制器做一些设置。 在前面第一节讲到,CPU将数据或地址发给内存控制器,内存控制器再去访问外部的SDRAM,因此设置内存控制器就说本节的核心。
如图是SDRAM存储结构逻辑图:
看上图:SDRAM总共有4个块(Banks),可以认为每个块就是一个表格,里面的每个格子(正方形,如L_Bank0中的行地址3列地址3对应的一个格)表示的是16bit数据。
问题1:怎样访问里面的某个格子呢?
问题2:那么多的信号有谁发出呢?
由内存控制器发出,所以我们需要设置内存控制器,CPU只是简单的执行读写内存的命令,其他的都交给内存控制起来处理。 例如:
LDR R0,=0x30000000
LDR R1,[R0]
综上所述:对SDRAM的访问可以分为如下步:
注:
(1)内存控制器共有13个寄存器,BANK0–BANK5只需要设置BWSCON和BANKCONx(x为0~5)两个寄存器;
(2)BANK6、BANK7外接SDRAM时,除BWSCON和BANKCONx(x为6、7)外,还要设置REFRESH、BANKSIZE、MRSRB6、MRSRB7等4个寄存器。
下面说明对SDRAM各个寄存器的设置。
BWSCON(BUSWIDTH&WAITCONTROLREGISTER)
BWSCON中每4位控制一个BANK,最高4位对应BANK7(没有使用)、接下来4位对应BANK6。
(1)ST6[27]: 启动/禁止SDRAM的数据掩码引脚,对于SDRAM,此位为0:对于SRAM此位为1。
数据掩码的作用:
1、咱这个SDRAM是两个16bit的芯片组成的。
2、读的时候,你读8位,那么SDRAM的32位都会被读到内存控制器中,内存控制器再根据你的地址将不需要的24bit过滤掉(与掉)。(这前面已经讲了)
3、那写呢,你想写8bit,但实际上却可能写到SDRAM上32位,将其中的24bit数据覆盖掉了。而数据掩码就是解决这个
问题的,因此在操作SDRAM时这个功能应该打开。
(2)WS6[26]:是否使用存储器的WAIT信号,通常设为0选择不使用。
(3)DW6[25:24]:使用两位来设置相应BANK的位宽,0b00对应8位,0b01对应16位,0b10对应32位(开发板用的就是32位的),0b11表示保留。
即设置BWSCON[25:24]为10,因此BWSCON寄存器的值为:0x22000000。
BANKCON6(BANKCONTROLREGISTER)
在8个BANK中,只有BANK6和BANK7可以外接SRAM或SDRAM。(下图中的DRAM就是SDRAM)。
(1)MT[16:15]:用于设置本BANK外接的是ROM/SRAM还是SDRAM,SRAM:0b00,SDRAM:0b11(开发板使用的是SDRAM)。
当MT[16:15]设置为00时,此寄存器与BANKC0N0、BANKCON5类似,不再赘述。
当MT[16:15]设置为11时,此寄存器其他值如下图设置。
(2)Trcd[3:2]:行地址和列地址间隔多长时间,看芯片手册时间间隔是20ns,本来开发板的HCLK是100MHZ,clocks为10ns,所以设置为推荐值0b00(2clocks)(其实具体多少ns你可以查看SDRAM的芯片手册,里面有推荐)。
(3)SCAN[1:0]:SDRAM的列地址位数,对于本开发板使用的 SDRAMK4S561632来说列地址位数为9(看其芯片手册),所以SCAN=0b001如果使用其他型号的SDRAM,需要查看其数据手册。来决定SCAN的取值。0b00表示列有8位,0b01表示9位,0b10表示10位。
综上所述,本开发板中BANKCON6设为0x018001(即MT[16:15]设为11,rcd[3:2]设为00,SCAN[1:0]设为01)。
REFRESH(REFRESHCONTROLREGISTER)刷新寄存器
(1)REFEN[23]:0=禁止SDRAM的刷新功能,1:开启SDRAM的刷新功能(设置开启SDRAM的刷新功能)。
(2)TREFMD[22]:SDRAM的刷新模式,0=CBR/AutoRefresh(自动刷新),1=SelfRefresh(一般在系统休眠时使用),我们设置默认值。
(3)Trp[21:20):根据芯片手册设为00。其是片选信号有效多长时间后行锁存信号有效,查芯片手册Trp最少为20ns。
(4)Tsrc[19:18]:根据芯片手册,设置Trc为70ns,则Tsrc = Trc - Trp = 70 - 20 =50ns。 故将Tsrc设为0b01。
(5)RefreshCounter[10:0](刷新周期):即R_CNT(RefreshCounter的缩写)。
R_CNT可如下计算(SDRAM时钟频率是HCLK):
R_CNT=2^11+1-SDRAM时钟频率(MZ)*SDRAM刷新周期(us)
SDRAM的刷新周期在SDRAM的数据手册上有标明,在本开发板使用的SDRAM:K4S561632的数据手册上,可看见这么一行:“64msrefreshpenod(8KCycle)”所以,刷新周期=64ms/8192=7.8125us。
则:
Refreshcount=2^11+1-100x7.8=1269=0x4F5。
因此,本开发板中REFRESH设为0x8404F5。
(BANKSIZEREG ISTER)
(1)BURST_EN[7]:0=ARM核禁上突发传输; 1=ARM核支持突发传输(即可以一次连续访问多个字节)(推荐);
(2)SCKEEN[5]:0=不使用SCKE信号令SDRAM进入省电模式,1=使用SCKE信号令SDRAM进入省电模式(即休眠模式)(推荐);
(3)SCLK-EN[4]:0=时刻发出SCLK信号,1=仅在访问SDRAM期间发出SCLK信号(推荐);
(4)BK76MAP[2:0]:设置BANK6的大小。本开发板BANK6外接64MB的SDRAM,令[2:0]=b001(64M/64M),表示BANK6/7的容量都是64MB,虽然BANK7没有使用。
因此,本开发板中BANKSIZE设为0xB1。
MRSRBx6(SDRAM MODE REGISTER SET REGISTER)
注:WBL、TM、BT、BL中的Fixed为固定不变不可以改的。
能修改的只有位CL[6:4],这是SDRAM时序的一个时间参数,表示SDRAM收到行、列地址后,等多久(CL)返回数据, CL可以取值为0b0l0(2 clocks)或0b011(3 clocks)。
本开发板取最保守的值0b010,所以MRSRB6的值为0x20。
在init.c里面进行对内存控制器的寄存器依次进行设置:
#include "s3c2440_soc.h"
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
int sdram_test(void)
{
volatile unsigned char *p = (volatile unsigned char *)0x30000000;
int i;
// write sdram
for (i = 0; i < 1000; i++)
p[i] = 0x55;
// read sdram
/*测试,如果读到的都是0x55,就成功返回0,即可在主函数里执行lec_test()*/
/*向SDRAM里面连续写1000个数,再读出数据对比是否是设置的数,返回对比结果:*/
for (i = 0; i < 1000; i++)
if (p[i] != 0x55)
return -1;
return 0;
}
init.h
#ifndef _INIT_H
#define _INIT_H
void sdram_init(void);
int sdram_test(void);
#endif
main.c
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
int main(void)
{
uart0_init();
sdram_init();
/*在主函数里调用sdram_test()测试函数,如果测试成功,LED闪烁:*/
if (sdram_test() == 0)
led_test();
return 0;
}
实验结果:
LED按预期闪烁,屏蔽掉sdram_init()后,LED不闪烁。
Makefile文件:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
arm-linux-ld -Ttext 0 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis