嵌入式入门4(内存控制器和SDRAM)

一般ARM芯片,都包含以下几类接口:

  1. GPIO、门电路
    这类接口通过操作某些寄存器,来设置对应引脚为输入、输出引脚,以及其引脚的电平。
  2. 协议类接口
    如UART、I2C、I2S、SPI等。CPU将数据写入某些地址和寄存器,对应引脚就会发出特定的波形。
  3. 内存接口
    如Nor、SDRAM、DM9000(网卡芯片)等,通过读写不同的地址,可访问不同的外设。这些设备参与CPU的统一编址。
    注意:Nand Flash不是内存接口,因为它是直接连接到Nand Flash控制器上的。
嵌入式入门4(内存控制器和SDRAM)_第1张图片
JZ2440接口

CPU把地址发送到内存控制器,内存控制器根据地址,将数据发送到不同的模块中,或从模块中读出数据,并返回给CPU。

GPIO以及协议类接口,CPU发送的地址信号并不会传到外面,CPU只是用地址选择不同的寄存器,然后CPU把数据发送到对应的寄存器,CPU并不知道该寄存器的作用。而内存类接口,CPU发送的地址可以直接传给这些设备。

一、内存控制器

S3C2440A芯片内部集成了内存控制器,几乎所有的外设和寄存器,都与它相连接,它们参与CPU的统一编址。cpu负责发出命令,其它的一切工作都交给了内存管理器。

那么内存管理器是如何来管理这些外设的呢?

1、地址空间被分为8个bank(bank0-bank7),每个bank对应128m地址空间。
2、每个bank拥有对应的片选引脚(nGCS0-nGCS7),当引脚为低电平时表示该bank被选通。
3、内存控制器根据不同的地址范围,发出不同的片选信号,只有被选中的芯片才会工作,没被选中的芯片就像不存在一样。

一个bank需要128M地址空间,需要27根地址线来表示。

那么8个bank,意味着外接内存类芯片可访问地址范围最多为1GB(128M*8)。

嵌入式入门4(内存控制器和SDRAM)_第2张图片
s3c2440的地址空间

我们看到地址空间是0-0x40000000,总共是1G。那么s3c2440的cpu是32位的,它理论上可以使用的地址可以达到4G,除了用于连接外设的1G地址空间之外,还有一部分是cpu内部寄存器的地址,其余的地址没有用。其中cpu的内部寄存器的地址都在:0x48000000-0x5fffffff之间。


二、外接芯片的地址线接法

外接芯片的位宽不同,则地址线的接法也会不同。可以参考S3C2440A的第5章Memory Controller,里面的ROM Memory Interface Examples

  • 当芯片位宽为8bit时,地址线A0接外接芯片A0。此时外接芯片返回8bit数据到内存控制器,内存控制器直接将数据返回给CPU。
  • 当芯片位宽为16bit时,地址线A1接外接芯片A0。此时外接芯片返回16bit数据到内存控制器,内存控制器通过地址线A0,选出这16bit中对应的8bit数据返回给CPU。
  • 当芯片位宽为32bit时,地址线A2接外接芯片A0。此时外接芯片返回32bit数据到内存控制器,内存控制器通过地址线A0、A1,选出这32bit中对应的8bit数据返回给CPU。

例如:

嵌入式入门4(内存控制器和SDRAM)_第3张图片
600px-Chapter12_lesson2_005.jpg
ROM/bit CPU发出地址 ROM收到地址 ROM返回数据 内存控制器挑选出数据给CPU
8bit(ROM) 000011 000011 编号3的存储单元中的8数据 编号3的存储单元中的8bit数据
16bit(ROM) 000011 000001 编号1的存储单元中的16数据 根据”A0=1”,挑出低8bit数据
32bit(ROM) 000011 000000 编号0的存储单元中的32数据 根据“A0A1=11”,挑出最低8bit数据

三、时序图分析

查看s3c2440的PROGRAMMABLE ACCESS CYCLE

嵌入式入门4(内存控制器和SDRAM)_第4张图片
PROGRAMMABLE ACCESS CYCLE

由时序图可知:
1、CPU先发出地址信号。
2、经过Tacs时间后再发出片选信号。
3、经过Tcos时间后发出读信号。
4、发出读信号后,数据才有效。
5、读出数据后,再移除释放读信号、片选信号、地址信号。

由我们的JZ2440电路图可知,Nor Flash为MX29LV160DBTI-70G,然后查看其芯片手册。其交流特性如下:

嵌入式入门4(内存控制器和SDRAM)_第5张图片
image.png
嵌入式入门4(内存控制器和SDRAM)_第6张图片
交流特性
参数 含义
Taa 发出地址数据后多久数据才有效
Tce 发出片选信号后多久数据才有效
Toe 发出读信号后多久数据才有效

四、开始编程

为了简单起见,我们设置片选信号、读信号、地址信号同时发出,然后保持70ns,再读取数据。就满足Nor Flash的时序了。

4.1、设置位宽

首先,设置BWSCON[2:1],用于设置bank0的位宽。

嵌入式入门4(内存控制器和SDRAM)_第7张图片
image.png

而DW0,是只读的,它是通过OM[1:0]引脚来决定的

嵌入式入门4(内存控制器和SDRAM)_第8张图片
image.png

查看硬件电路图:

嵌入式入门4(内存控制器和SDRAM)_第9张图片
image.png
嵌入式入门4(内存控制器和SDRAM)_第10张图片
Nor boot switch

可以发现,我们通过开关,决定了S3C2440A的启动方式:32bit Nor Flash 或 Nand-boot。

4.2、配置BANKCON0

我们Nor Flash接到Bank0,所以需要配置BANKCON0。

嵌入式入门4(内存控制器和SDRAM)_第11张图片
image.png

设备上电,采用晶振做为时钟源,12MHz,Tacc=0x111,14个周期。

14个周期=14/12MHz秒 = 14/12_000_000秒 = 14000/12纳秒 = 1166.6纳秒,远大于Nor Flash要求的70ns。所以当我们上电时,即使不设置时钟,设备也能正常运行。

而我们现在HCLK=100MHz:1个周期=10纳秒,Tacc只需设置为0x101,即8个时钟周期,就能满足Nor Flash大于70纳秒的时序要求。

4.3、所有源代码

init.c

#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

led.c

#include "s3c2440_soc.h"

void delay(volatile int d)
{
    while (d--);
}

int led_test(void)
{
    /* 设置GPFCON让GPF4/5/6配置为输出引脚 */
    GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
    GPFCON |=  ((1<<8) | (1<<10) | (1<<12));

    /* 循环点亮 */
    int i = 4;
    while (1) {
        for(i=4; i<=6; i++) {
            //对数据寄存器4~6位取反
            GPFDAT ^= (1<

main.c

#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;
}

start.S

.text
.global _start

_start:

    /* 关闭看门狗 */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]

    /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]

    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    ldr r0, =0x4C000014
    ldr r1, =0x5
    str r1, [r0]

    /* 设置CPU工作于异步模式 */
    mrc p15,0,r0,c1,c0,0
    orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
    mcr p15,0,r0,c1,c0,0

    /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
     *  m = MDIV+8 = 92+8=100
     *  p = PDIV+2 = 1+2 = 3
     *  s = SDIV = 1
     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
     */
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]
    /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
     * 然后CPU工作于新的频率FCLK
     */

    /* nor启动 */
    ldr sp, =0x40000000+4096 

    bl main

halt:
    b halt

uart.c

#include "s3c2440_soc.h"


/* 115200,8n1 */
void uart0_init()
{
    /* 设置引脚用于串口 */
    /* GPH2,3用于TxD0, RxD0 */
    GPHCON &= ~((3<<4) | (3<<6));
    GPHCON |=  ((2<<4) | (2<<6));

    GPHUP &= ~((1<<2) | (1<<3));  /* 使能内部上拉 */
    

    /* 设置波特率 */
    /* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
     *  UART clock = 50M
     *  UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
     */
    UCON0 = 0x00000005; /* PCLK,中断/查询模式 */
    UBRDIV0 = 26;

    /* 设置数据格式 */
    ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */

    /*  */

}

int putchar(int c)
{
    /* UTRSTAT0 */
    while (!(UTRSTAT0 & (1<<2)));
    /* UTXH0 */
    UTXH0 = (unsigned char)c;
}

int getchar(void)
{
    while (!(UTRSTAT0 & (1<<0)));
    return URXH0;
}

int puts(const char *s)
{
    while (*s)
    {
        putchar(*s);
        s++;
    }
}

uart.h

#ifndef _UART_H
#define _UART_H

void uart0_init();
int putchar(int c);
int getchar(void);
int puts(const char *s);

#endif

Markfile

all:
    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 nor.elf
    arm-linux-objcopy -O binary -S nor.elf nor.bin
    arm-linux-objdump -D nor.elf > nor.dis
clean:
    rm *.bin *.o *.elf *.dis

编译烧写后,串口输入0~7,其中0~4,由于Tacc小于70ns,所以灯不会闪烁,只有5~7才会闪烁。

五、SDRAM配置

SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和CPU前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。

JZ2440接有2片SDRAM,每片SDRAM提供16位数据,如图所示:


嵌入式入门4(内存控制器和SDRAM)_第12张图片
image.png

其存储结构图如下:

嵌入式入门4(内存控制器和SDRAM)_第13张图片
SDRAM存储结构逻辑图

它可以看作有多个表格(Bank)组成,并通过行地址和列地址来进行访问,每个小格子表示一个16位的存储单元。

  • 1、CPU先发出片选信号,选中整个芯片。
  • 2、发出Bank地址,选择哪个表格(Bank)。
  • 3、然后依次发出行地址和列地址,才能定位到具体的存储单元。

这些信号都是由内存控制器发出,所以我们需要设置内存控制器。

我们查看电路图,知道SDRAM接到片选6上。

5.1、设置BANK6位宽

嵌入式入门4(内存控制器和SDRAM)_第14张图片
image.png
  • 设置BWSCON[25:24]为10,因为JZ2440是由2个16为SDRAM芯片组成的32位SDRAM。
  • BWSCON[26],是否使用WAIT信号,它是内存芯片向CPU发出来的。假设内存芯片非常慢,当CPU发出读写命令后,内存控制器开始驱动引脚,在这些时间里,内存芯片还没准备好数据,此时它可以向CPU发出nWAIT信号,请求CPU再宽限一点时间。
  • BWSCON[27],设置SDRAM是否使用UB/LB引脚。

nBE:Byte Enable,表示读或者写的时候,是否可以通过操作这个引脚,来操作这个Byte。
nWBE:Write Byte Enable,表示写某个字节的时候,是否真正的写进去。

由于我们内存是32位(4字节)的,如果我们只想修改某个字节,此时就需要通过nWEB引脚,来决定某个字节会被写入。而读的时候,我们总是读出4字节数据,然后内存控制器从中挑选出我们所感兴趣的数据。

BWSCON = 0x02000000

5.2、设置BANK6位SDRAM

嵌入式入门4(内存控制器和SDRAM)_第15张图片
image.png
嵌入式入门4(内存控制器和SDRAM)_第16张图片
image.png

BANKCON6[16:15]决定Bank6接入的是ROM/SRAM、SDRAM,我们设置为0x11。只有外接成SDRAM时,地址才会被拆分成Bank地址、行地址、列地址。

JZ2440的SDRAM芯片型号是K4S561632N-LC75,查看其芯片手册(K4S561632E):

嵌入式入门4(内存控制器和SDRAM)_第17张图片
image.png

知道其列地址有9条,即BANKCON6[1:0]=0x01
Trcd,表示内存控制器发出行地址多久后,才能发出列地址。查看SDRAM芯片手册的交流特性图:

嵌入式入门4(内存控制器和SDRAM)_第18张图片
image.png

知道Trcd最小取20ns,用于HLCK=100MHZ,即一个周期需要10ns,使用Trcd至少需要2个周期,即BANKCON6[3:2]=0x00

BANKCON6 = 0x00018001

5.3、设置SDRAM刷新寄存器

SDRAM不像静态SRAM那么可靠,在使用过程中,必须不断刷新它,不然数据会丢失。

嵌入式入门4(内存控制器和SDRAM)_第19张图片
image.png
  • 设置REFEN = 1,表示开启SDRAM刷新。
  • 设置TREFMD = 0,设置为自动刷新模式。
  • Refresh Counter,刷新周期,查看SDRAM芯片手册,自动其刷新为64ms refresh period (8K Cycle),即刷新周期为:

    而我们HLCK恰好也等于100MHZ,刚好与实例一致,所以Refresh Count = 1269 = 0x4F5
  • Trp,行地址的充电时间。由SDRAM芯片手册的交流特性图可知,Trp最小为20ns,所以REFERSH[21:20]=0x00
  • Tsrc,Trc = Tsrc + Trp。Trc为65ns,所以Tsrc=65-20=45ns。设置去0x01,即5个周期。
嵌入式入门4(内存控制器和SDRAM)_第20张图片
image.png
REFERSH = 0x008404f5

5.4、设置Bank寄存器

嵌入式入门4(内存控制器和SDRAM)_第21张图片
image.png

设置BANKSIZE[2:0]=001,因为SDRAM是由两个16位的32M芯片组成的32位64M芯片。
设置BANKSIZE[7]=1,允许突发访问,即同时可以连续一次访问多个字节。
设置BANKSIZE[5]=1,可以使用SCKE来使能休眠模式。
设置BANKSIZE[4]=1,推荐值。

BANKSIZE= 0x000000b1

5.5、设置SDRAM模式寄存器

嵌入式入门4(内存控制器和SDRAM)_第22张图片
image.png

除了CL,其余都是固定的。
CAS latency:内存控制器读SDRAM时,先发出Bank地址,再发出行地址,最后发出列地址。还要等一会,数据才好。
SDRAM芯片手册在FEATURES中告诉我们:CAS latency(2 & 3)。当我们设置CL后,CPU会发送给SDRAM,SDRAM会将其保存在自身的寄存器中,当SDRAM收到列地址后,等待一段时间后,才会返回数据。

MRSRB6 = 0x00000020

5.5、开始编程

init.h

#ifndef _INIT_H
#define _INIT_H

void sdram_init();
int sdram_test();

#endif

init.c

#include "s3c2440_soc.h"

void sdram_init()
{
    BWSCON = 0x02000000;
    BANKCON6 = 0x00018001;
    REFRESH = 0x008404f5;
    BANKSIZE = 0x000000b1;
    MRSRB6 = 0x00000020;
}

int sdram_test()
{
    volatile unsigned char* p = (volatile unsigned char*)0x30000000;
    int i;
    
    for(i=0; i<1000; i++){
        p[i] = 'A';
    }
    
    for(i=0; i<1000; i++){
        if(p[i] != 'A'){
            return -1;
        }
    }
    return 0;
}

main.c

#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"

int main(void)
{
    uart0_init();
    sdram_init();
    if(sdram_test()==0)
    {
        puts("ok\n\r");
        led_test();
    }else{
        puts("error\n\r");
    }
    return 0;
}

你可能感兴趣的:(嵌入式入门4(内存控制器和SDRAM))