S3C2440-裸机篇-08 | 使用S3C2440操作SDRAM(配置内存控制器)

Posted Neutionwei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了S3C2440-裸机篇-08 | 使用S3C2440操作SDRAM(配置内存控制器)相关的知识,希望对你有一定的参考价值。

1. 前言

提起SDRAM,大家都会觉得太难了,要编程写出SDRAM的控制时序更是难上加难,对的,没错!一年前我也是这样想的,学习这一节内容的时序觉得非常难,视频看了好几遍不太懂,对于SDRAM的控制原理更是没看懂,一年后回过头来再看视频,茅塞顿开,看不懂的原因是因为:我自己把它想的太难了,其实,它很简单,总共也就5行代码,设置5个寄存器即可

简单的原因要归功于S3C2440内部的内存控制器,它的作用就是负责向外部扩展的存储类设备提供控制信号,所以当CPU要去访问属于SDRAM时,只需要去访问属于SDRAM的映射地址即可,内存控制器会发出信号,控制时序去和SDRAM打交道,写入数据或者是读出数据。

尽管我们不用手写操作时序了,我们仍然要编写程序去控制时序

因为S3C2440的内存控制器是普遍的,不可能只能接一种SDRAM芯片,而是所有的SDRAM芯片都可以接,但是不同厂商的SDRAM芯片性能有差异,也就是说,虽然所有SDRAM芯片的控制时序都一样,但是A厂商生产的SDRAM芯片在发出控制信号之后70ns之后才有数据,B厂商生产的SDRAM芯片在发出控制信号后60ns就有有效数据,这就叫做不同的SDRAM芯片的性能不同。

所以内存控制器设置了一些寄存器,允许用户根据实际接的SDRAM芯片性能去配置具体的时间参数即可,称之为SDRAM初始化或者内存控制器初始化,之后就可以尽情的访问SDRAM地址即可。

在实际操作之前,请先阅读这两篇文章,对S3C2440的内存控制器和SDRAM芯片的内部构造和工作原理有个具体的了解:

2. JZ2440开发板上的SDRAM芯片

JZ2440开发板上使用的是EtronTech公司的EM63A165TSSDRAM芯片,该芯片是一个16M x 16 bit Synchronous DRAM (SDRAM)的芯片,就是说共有16M存储单元,每个存储单元的数据宽度是16bit,即整个芯片大小是32MB。

S3C2440CPU的数据宽度是32bit,所以JZ2440开发板上使用两片16bit的SDRAM组合构成一个SDRAM模组,从CPU的角度来看,就是一个位宽为32bit的SDRAM模组,大小是64M Byte的SDRAM模组

实物图如下:

两片SDRAM连接的原理图如下:

从原理图可以看到,两片SDRAM都由nGcs6片选信号线控制,即SDRAM接在Bank6上,所以访问SDRAM时的基地址为0x30000000

3. 阅读芯片手册,配置内存控制器

阅读芯片手册的目的是:根据实际接的SDRAM芯片性能来配置内存控制器Bank6相关寄存器

3.1. 设置Bank6数据总线宽度(BWSCON)

实际是由两片16bit的芯片组合,但是由CPU看来是一片32bit的芯片,所以数据总线宽度设置为32bit,另外开启按字节写入使能引脚,后续可以按字节访问SDRAM

3.2. 设置Bank6控制寄存器(BANK6CON)

根据实际情况配置Bank6控制寄存器:

因为MT设置为了11,所以接下来其它的就不用管了,只管MT=0x11的情况即可,配置这4位:

Trcd就是地址线上从发出行地址到发出列地址的时长,有三种情况,因为之前配置HCLK=100Mhz,即1clocks=10ns,所以此处有20ns、30ns、40ns三个值可选,这里就要根据SDRAM实际性能而定了,查看SDRAM芯片数据手册即可:

这里我们取18ns,将Trcd配置为20ns,即2clocks

SCAN是列地址宽度,同样查看SDRAM芯片手册:

可以看到该SDRAM芯片的列地址宽度为9bit,所以SCAN这两位设置为0x01即可

3.3. 设置自动刷新(REFRESH)

SDRAM是动态存储,不是静态存储器,需要不停的刷新,该刷新寄存器用来控制刷新SDRAM的一些配置:

预充电时长Trp和行地址时长都可以在SDRAM芯片数据手册中查看到:

这里trp值取中间的18ns,预充电时长寄存器中选择2clocks,即20ns,trc值取中间的60ns,根据数据时候中给出的计算公式 T r c = T s r c + T r p Trc=Tsrc+Trp Trc=Tsrc+Trp得出:
T s r c = T r c − T r p = 60 − 20 = 40 n s = 4 c l o c k s Tsrc=Trc−Trp=60−20=40ns=4clocks Tsrc=TrcTrp=6020=40ns=4clocks

自动刷新周期的设置也需要参照SDRAM数据手册:

计算一下刷新周期为 64000 u s ÷ 8192 = 7.8125 u s 64000us÷8192=7.8125us 64000us÷8192=7.8125us,刚好是数据手册中给出的值,所以该计数值设置为1269,换算为十六进制为0x4F5‬

3.4. 设置BANK6访问空间大小(BANKSIZE)

3.5. SDRAM模式寄存器设置寄存器(MRSRB6)

SDRAM中有模式寄存器,S3C2440的内存控制器有一个SDRAM模式设置寄存器,工作时内存控制器会根据该寄存器的值发出对应的控制信号去设置SDRAM中的模式寄存器:

其中CAS值用来设置在发出BANK地址、行地址、列地址之后,经过多长时间可以有有效数据,查看SDRAM芯片手册:

可以看到这个值为2和3都可以,方便起见设置为2clocks

4. 编写代码

4.1. 串口发送工程

在之前串口发送的工程上进行实验,参考:

4.2. 内存控制器初始化代码

新建一个文件init.c,编写以下代码:

#include "s3c2440.h"
#include "init.h"
/* 初始化内存控制器 */
void sdram_init(void)
{
    //1. 设置总线位宽
    BWSCON = 0x2000000;
    //2. 设置BANK6
    BANKCON6 = 0x18001;
    //3. 设置Refresh寄存器
    REFRESH = 0x8004F5;
    //4. 设置BANKSIZE寄存器
    BANKSIZE = 0xB1;
    //5. 设置SDRAM mode设置寄存器
    MRSRB6 = 0x20;
}
/* 按字节读写测试 */
int sdram_byte_wr_test(void)
{
    volatile unsigned char *ptr = (unsigned char*)0x30000000;
    unsigned int i;
    //写入
    for(i = 0; i < 1000;i++)
    {
        *(ptr+i) = i%256;
    }
    //读取
    for(i = 0;i < 1000;i++)
    {
        if(*(ptr+i) != (i%256))
        {
            return -1;
        }
    }
    //测试成功
    return 0;
}
/* 按字读写测试 */
int sdram_word_wr_test(void)
{
    volatile unsigned int *ptr = (unsigned int*)0x30000000;
    unsigned int i;
    //写入
    for(i = 0; i < 1000;i++)
    {
        *(ptr+i) = i;
    }
    //读取
    for(i = 0;i < 1000;i++)
    {
        if(*(ptr+i) != i)
        {
            return -1;
        }
    }
    //测试成功
    return 0;
}

在头文件中init.h声明:

#ifndef _INIT_H_
#define _INIT_H_
void sdram_init(void);
int sdram_byte_wr_test(void);
int sdram_word_wr_test(void);
#endif /* _INIT_H_ */

4.3. 在main函数中测试

main.c文件中编写:

/**
 * @breif   测试修改内存控制器读取sdram程序
 * @author  mculover666
 * @date    2020/1/29
*/
#include "bsp_uart_scan.h"
#include "init.h"
int main(void)
{
    //初始化uar0:115200,8N1
    uart0_init();
    sdram_init();
    puts("SDRAM byte write read Test...");    
    if(-1 == sdram_byte_wr_test())
    {
        puts("fail!\\r\\n");        
    }
    else
    {
        puts("success.\\r\\n");
    }
    puts("SDRAM word write read Test...");    
    if(-1 == sdram_word_wr_test())
    {
        puts("fail!\\r\\n");        
    }
    else
    {
        puts("success.\\r\\n");
    }
    while(1);
}

4.4. makefile

修改makefile:

TARGET = sdram_test
CFLAGS = -Wall	#输出所有warning
$(TARGET).bin:$(TARGET).elf
	arm-linux-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
#注意:启动文件必须第一个链接
$(TARGET).elf:start.o bsp_uart_scan.o init.o main.o 
	arm-linux-ld -Ttext 0 start.o bsp_uart_scan.o init.o main.o -o $(TARGET).elf
start.o:start.s
	arm-linux-gcc -c start.s $(CFLAGS) -o start.o
bsp_uart_scan.o:bsp_uart_scan.c
	arm-linux-gcc -c bsp_uart_scan.c $(CFLAGS) -o bsp_uart_scan.o
init.o:init.c
	arm-linux-gcc -c init.c $(CFLAGS) -o init.o
main.o:main.c
	arm-linux-gcc -c main.c $(CFLAGS) -o main.o	
clean:
	rm -rf *.o *.elf *.bin
download_to_nand:
	#下载到nand flash
	oflash 0 1 0 0 0 $(TARGET).bin 

5. 实验结果

编译下载后,在串口查看实验结果,可以看到,按字节读写SDRAM(8bit)和按字读写SDRAM(32bit)的结果都成功:

以上是关于S3C2440-裸机篇-08 | 使用S3C2440操作SDRAM(配置内存控制器)的主要内容,如果未能解决你的问题,请参考以下文章

S3C2440-裸机篇-09 | 使用S3C2440操作Nor Flash

S3C2440-裸机篇-08 | 使用S3C2440操作SDRAM(配置内存控制器)

S3C2440-裸机篇-03 | GPIO的使用(点亮LED按键检测)

S3C2440-裸机篇-05 | S3C2440时钟体系详解(FCLKPCLKHCLK)

S3C2440-裸机篇-07 | S3C2440内存控制器(Memory Controller)

S3C2440-裸机篇-02 | 安装和使用arm-linux-gcc交叉编译工具链