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

605 阅读9分钟

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

1、GPIO、门电路

这类接口通过操作某些寄存器,来设置对应引脚为输入、输出引脚,以及其引脚的电平。

2、协议类接口

CPU将数据写入某些地址和寄存器,对应引脚就会发出特定的波形。如:UART、I2C、I2S、SPI等。

3、内存接口

CPU通过地址可以直接访问这类设备,因为它们都参与CPU的统一编址。如:Nor、SDRAM、DM9000(网卡芯片)等,

注意:Nand Flash不参与CPU的统一编址,因为它是直接连接到Nand Flash控制器上的。

未命名文件.png

一、内存控制器

S3C2440A芯片内部集成了内存控制器,几乎所有的外设和寄存器都与它相连。CPU只负责发出命令,其余都交给了内存管理器处理。那么内存管理器是如何来管理这些外设的呢?

我们可以查看s3c2440的地址空间分布

image.png

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

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

128MB=1281MB=27220=227128MB = 128 * 1MB = 2^7 * 2^{20} = 2^{27}

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

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

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

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

image.png

ROM/bitCPU发出地址ROM收到地址ROM返回的数据内存控制器返回给CPU的数据
8bit(ROM)000011000011编号3的存储单元中的8数据编号3的存储单元中的8bit数据
16bit(ROM)000011000001编号1的存储单元中的16数据根据A0=1,挑出低8bit数据
32bit(ROM)000011000000编号0的存储单元中的32数据根据A0A1=11,挑出最低8bit数据

三、Memory时序图分析

s3c2440的第5章Memory ControllerPROGRAMMABLE ACCESS CYCLE

image.png

时序图流程如下:

  • 1、CPU先发出地址信号。
  • 2、经过Tacs时间后再发出片选信号。
  • 3、经过Tcos时间后发出读信号。
  • 4、发出读信号后,数据才有效。
  • 5、读出数据后,再依次释放读信号、片选信号和地址信号。

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

image.png

image.png

参数含义
Taa发出地址数据后多久数据才有效
Tce发出片选信号后多久数据才有效
Toe发出读信号后多久数据才有效

四、开始编程

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

4.1、设置位宽

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

image.png

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

image.png

查看硬件电路图:

image.png

image.png

发现OM[1]直接接地,而OM[0]的电平,由Nor boot switch决定。我们通过开关,决定了S3C2440A的启动方式:32bit Nor FlashNand-boot

4.2、配置BANKCON0

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

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<<i);
            delay(30000);
        }
    }

    return 0;
}

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前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。

一般SDRAM的存储结构如下:

image.png

它由多个表格(Bank)组成,并通过行地址和列地址来进行访问,每个小格子表示一个存储单元,单元的大小等于SDRAM的位宽。

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

5.1、JZ2440的SDRAM接法

查看JZ2440的SDRAM电路图:

image.png

可以发现:

  • 1、JZ2440外接2片SDRAM,每片提供16位的数据,容量为32M,共计64M,32位。
  • 2、SDRAM接在JZ2440的片选6引脚,也就是说SDRMAM在第六个Bank地址空间中。
  • 3、BA[0:1]分别接在ADDR[25:24]上,用于选中SDRAM里的哪个Bank。

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

image.png

发现以下有用信息:

  • 1、每片SDRAM组织形式:16M * 16 = 4M * 4Bank * 16bit = 32M
  • 2、行地址13条,列地址9条。

参考SDRAM BANK ADDRESS PIN CONNECTION EXAMPLE,可以知道Bank Address为A[25:24],对比JZ2440的SDRAM电路图,确实是BA[0:1]分别接在ADDR[24:25]上。

image.png

现在可以配置SDRAM,来测试SDRAM的读写操作。

5.2、设置BANK6为SDRAM

首先我们需要设置内存控制器,让Bank6设置为SDRAM,这样内存控制器在发送地址时,才会将其拆分为Bank地址、行地址和列地址。

image.png

BANKCON6[16:15]决定Bank6接入的是哪种类型的内存芯片,这里我们设置为SDRAM,即BANKCON6[16:15]=0x11

image.png

由于外接的SDRAM列地址有9条,所以BANKCON6[1:0]=0x01

Trcd,表示内存控制器发出行地址多久后,才能发出列地址。

查看SDRAM芯片手册的交流特性图:

image.png

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

BANKCON6 = 0x00018001

5.3、设置BANK6位宽

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引脚,UB/LB分别是存储器高8位,低8位的数据线使能信号。

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

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

BWSCON = 0x02000000

5.4、设置SDRAM刷新寄存器

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

image.png

  • 设置REFEN = 1,表示开启SDRAM刷新。
  • 设置TREFMD = 0,设置为自动刷新模式。
  • Refresh Counter,刷新周期,查看SDRAM芯片手册,自动其刷新为64ms refresh period (8K Cycle),即刷新周期为:

641000us81024=7.8125us\frac{64*1000us}{8*1024} = 7.8125us

而我们HCLK=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个周期。

image.png

REFERSH = 0x008404f5

5.5、设置Bank寄存器

image.png

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

5.6、设置SDRAM模式寄存器

image.png

除了CL,其余都是固定的。

CAS latency:内存控制器读SDRAM时,先发出Bank地址,再发出行地址,最后发出列地址。还要等一会,数据才好。

SDRAM芯片手册在FEATURES中告诉我们:CAS latency(2 & 3)。当我们设置CL后,CPU会发送给SDRAM,SDRAM会将其保存在自身的寄存器中,当SDRAM收到列地址后,等待一段时间后,才会返回数据。

MRSRB6 = 0x00000020

5.7、所有代码

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