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公司的EM63A165TS
SDRAM芯片,该芯片是一个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,根据数据时候中给出的计算公式得出:
自动刷新周期的设置也需要参照SDRAM数据手册:
计算一下刷新周期为,刚好是数据手册中给出的值,所以该计数值设置为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)的结果都成功:
上一篇: SDRAM控制器设计
下一篇: C++(内联函数的使用----第6课)