一、概述
LPC54608 拥有支持外部存储器的设备的外部存储器控制器 EMC ( External Memory Controller ) 。 LPC54608 的 EMC 是 ARM PrimeCell™ 的多端口内存控制器外设,为异步静态内存设备提供支持,如 RAM、ROM 和 Flash,此外,它还可以用作与芯片外内存映射设备和外围设备的接口,如支持 SDRAM ( synchronous dynamic random-access memory,简称 SDRAM ) 等。EMC 是一个先进的微控制器总线架构 ( AMBA ) 兼容外设。
1.1 SDRAM 简介
SDRAM : Synchronous Dynamic Random Access Memory,同步动态随机存储器。同步是指其时钟频率和 CPU 前端总线的系统时钟相同,并且内部命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
二、SDRAM 基础知识
2.1 逻辑 Bank
简单地说,SDRAM 的内部是一个存储阵列。因为如果是管道式存储(就如排队买票),就很难做到随机访问了。阵列就如同表格一样,将数据“填”进去,你可以把它想象成一张表格。和表格的检索原理一样,先指定一个行 ( Row ),再指定一个列 ( Column ),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格(存储阵列)就是逻辑 Bank( Logical Bank,下文简称 L-Bank )。
图 1. 存储阵列 ( L-Bank ) 示意图
由于技术、成本等原因,不可能只做一个全容量的 L-Bank,而且最重要的是,由于 SDRAM 的工作原理限制,单一的 L-Bank 将会造成非常严重的寻址冲突,大幅降低内存效率。所以人们在 SDRAM 内部分割成多个 L-Bank,较早以前是两个,目前基本都是 4 个,这也是 SDRAM 规范中的最高 L-Bank 数量。这样,在进行寻址时就要先确定是哪个 L-Bank,然后再在这个选定的 L-Bank 中选择相应的行与列进行寻址。
图 2. 64Mbit SDRAM 内部结构框图
2.2 SDRAM 的容量计算
SDRAM 内存芯片一次传输率的数据量就是芯片位宽,那么这个存储单元的容量就是 SDRAM 芯片的位宽(也是 L-Bank 的位宽)。显然,SDRAM 内存芯片的容量就是所有 L-Bank 中的存储单元的容量总和。即:
存储单元数量 = 行数 × 列数(得到一个 L-Bank 的存储单元数量)× L-Bank 的数量
在很多 SDRAM 的数据手册中,都会用 M × W 的方式来表示芯片的容量(或规格 / 组织结构)。M 为该芯片中存储单元的总数,单位是兆(英文简写 M,即 2^20),W 代表每个存储单元的容量,也就是 SDRAM 芯片的位宽,单位是 bit。计算出来的芯片容量也是以 bit 为单位,我们可以采用除以 8 的方法换算为字节 ( Byte )。比如 4M × 16,这是一个 16bit 位宽芯片,有 4M 个存储单元,总容量是 64Mbit( 8MB )。
2.3 SDRAM 信号线
信号 |
说明 |
CLK |
时钟信号,在该时钟的上升沿采集输入信号 |
CKE |
时钟使能,禁止时钟时,SDRAM 会进入自刷新模式 |
CS |
片选信号,低电平有效 |
RAS |
行地址选通信号,低电平时,表示行地址 |
CAS |
列地址选通信号,低电平时,表示列地址 |
WE |
写使能信号,低电平有效 |
BS0,BS1 |
BANK 地址线 |
A0~A12 |
地址线(行 / 列) |
DQ0~15 |
数据线 |
LDQM, UDQM |
数据掩码,表示 DQ 的有效部分 |
表 1. SDRAM 信号线
三、SDRAM 工作时序
以下内容均以 W9864G2JH 为例,此 SDRAM 位宽为 32bit,包含 4 个 Bank,每个 Bank 大小为 512K。
3.1 初始化
在 SDRAM 芯片内部还有一个逻辑控制单元,并且有一个模式寄存器为其提供控制参数。因此,每次开机时 SDRAM 都要先对这个控制逻辑核心进行初始化。关键的阶段就在于模式寄存器( MR,Mode Register )的设置,简称 MRS( MR Set ),在设置到 MR 之后,就开始了进入正常的工作状态。
图 3. 设置 W9864G2JH SDRAM 模式寄存器
通过上图可以看到,Mode Register 的控制参数是通过地址线提供不同的 0 / 1 信号来写入的,具体的信号在上图表中有列出。这些参数中,Burst Length 是一个比较重要的参数,具体含义以及其设置将在下文谈到,这里我们先对它有个印象即可。
3.2 数据读取
初始化完成后,要想对一个 L-Bank 中的阵列进行寻址,首先就要确定行 ( Row ),使之处于活动状态 ( Active ),然后再确定列。虽然之前要进行片选和 L-Bank 的定址,但它们与行有效可以同时进行。行地址确定之后,就要对列地址进行寻址了。在行地址 CAS ( Column Address Strobe,列地址选通脉冲 )信号发出之后,仍要经过一定的时间才能有数据输出,从 CAS 与读取命令发出到第一笔数据输出的这段时间,被定义为 CL ( CAS Latency,CAS 潜伏期 )。由于 CL 只在读取时出现,所以 CL 又被称为读取潜伏期 ( RL,Read Latency )。CL 的单位为时钟周期数,具体耗时由时钟频率决定。
图 4. 读取数据时序
3.3 数据写入
数据写入的操作没有 CL (记住,CL 只出现在读取操作中),数据可以与 CAS 同时发送,也就是说写入延迟为 0。
图 5. 写入数据时序
3.4 Burst Length
大多数地方把这个翻译成“突发长度”,Burst 在这里更多的有着一种“连续”的意思。所谓的“突发”是指当我们对一个地址进行寻址并操作完成后,不必再重新对下一个地址进行寻址,而是直接进行操作。这样就节省了很多的时间,具体的情况也很简单,就是节省了延时的那段时间。
现在我们再回头看看模式寄存器里的参数,是不是知道了这个“Burst Length”的含义了,这里设置的长度受 SDRAM 本身和你所使用的 MCU 的限制,具体最大能设置为多少要看手册。这里以 LPC54608 为例,如下图。
图 6. MCU 手册描述
这里我们可以看到,LPC54608 这款单片机支持的最大突发长度为 128 bit,而这里我们使用的 W9864G2JH SDRAM 位宽为 32 bit,所以我们的突发长度最大可以设置为 4。即对于 32bit ,32 × 4 = 128,即最多支持 burst = 4;对于 16bit,则 16 × 8 = 128,即支持 burst = 8。
3.5 预充电
由于 SDRAM 的寻址具体独占性,所以在进行完读写操作后,如果要对同一 L-Bank 的另一行进行寻址,就要将原来有效(工作)的行关闭,重新发送行 / 列地址。L-Bank 关闭现有工作行,准备打开新行的操作就是预充电( Precharge )。预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。实际上,预充电是一种对工作行中所有存储体进行数据重写,并对行地址进行复位,以准备新行的工作。
图 7. 预充电
3.6 刷新
之所以称为 DRAM,就是因为它要不断进行刷新 ( Refresh ) 才能保留住数据,因此它是 DRAM 最重要的操作。刷新操作分为两种:自动刷新 ( Auto Refresh,简称 AR ) 与自刷新 ( Self Refresh,简称 SR )。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。对于 AR, SDRAM 内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址。由于刷新涉及到所有 L-Bank,因此在刷新过程中,所有 L-Bank 都停止工作,而每次刷新所占用的时间为 9 个时钟周期( PC133 标准 ),之后就可进入正常的工作状态,也就是说在这 9 个时钟期间内,所有工作指令只能等待而无法执行。64ms 之后则再次对同一行进行刷新,如此周而复始进行循环刷新。显然,刷新操作肯定会对 SDRAM 的性能造成影响,但这是没办法的事情,也是 DRAM 相对于 SRAM (静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。SR 则主要用于休眠模式低功耗状态下的数据保存,这里不做介绍。
四、LPC54608 读写 SDRAM 实验
4.1 实验目的
通过本实验,理解并掌握 SDRAM 的读写功能
4.2 开发环境
硬件:LPC54608 Demo 板
软件:MDK5 开发环境
4.3 实验描述
本实验以 W9864G2JH 为例,对 SDRAM 进行读写测试。W9864G2JH 是一个 512K * 4Bank * 32bit 的 SDRAM。
4.4 软件设计
(1)对 SDRAM 的参数进行配置,这些参数在 Datasheet 上都能找到。
图 8. 配置 SDRAM 参数
(2)初始化引脚,配置时钟,由于 SDRAM 引脚比较多,这里就不详细列举了,每个引脚配置到相应的功能即可。
图 9. 初始化引脚,配置时钟
(3)向 SDRAM 写入数据。
图 10. 写入 SDRAM
(4)读取刚刚写入的数据,并与写入的数据比较,看是否一致。
图 11. 读取 SDRAM
4.5 实验结果
在串口助手上可以看到 SDRAM 读写数据的比较,若没有错误提示,则说明读写数据一致,SDRAM 读写实验成功完成。
4.6 实验代码
/*********************** Standard C Included Files ***********************/
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_emc.h"
#include "stdbool.h"
#include "pin_mux.h"
/****************************** Definitions ******************************/
#define SDRAM_BASE_ADDR 0xa0000000
#define SDRAM_SIZE_BYTES (8 * 1024 * 1024)
#define SDRAM_EXAMPLE_DATALEN (SDRAM_SIZE_BYTES / 4)
/********************************* Code *********************************/
status_t SDRAM_DataBusCheck(volatile uint32_t *address)
{
uint32_t data = 0;
/* Write the walking 1's data test. */
for (data = 1; data != 0; data <<= 1)
{
*address = data;
/* Read the data out of the address and check. */
if (*address != data)
{
return kStatus_Fail;
}
}
return kStatus_Success;
}
status_t SDRAM_AddressBusCheck(volatile uint32_t *address, uint32_t bytes)
{
uint32_t pattern = 0x55555555;
uint32_t size = bytes / 4;
uint32_t offset;
uint32_t checkOffset;
/* write the pattern to the power-of-two address. */
for (offset = 1; offset < size; offset <<= 1)
{
address[offset] = pattern;
}
address[0] = ~pattern;
/* Read and check. */
for (offset = 1; offset < size; offset <<= 1)
{
if (address[offset] != pattern)
{
return kStatus_Fail;
}
}
if (address[0] != ~pattern)
{
return kStatus_Fail;
}
/* Change the data to the revert one address each time
* and check there is no effect to other address. */
for (offset = 1; offset < size; offset <<= 1)
{
address[offset] = ~pattern;
for (checkOffset = 1; checkOffset < size; checkOffset <<= 1)
{
if ((checkOffset != offset) && (address[checkOffset] != pattern))
{
return kStatus_Fail;
}
}
address[offset] = pattern;
}
return kStatus_Success;
}
int main(void)
{
uint32_t index;
uint32_t *sdram = (uint32_t *)SDRAM_BASE_ADDR; /* SDRAM start address. */
/* Hardware Initialization */
CLOCK_EnableClock(kCLOCK_InputMux);
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
BOARD_InitPins();
BOARD_BootClockPLL180M();
BOARD_InitDebugConsole();
BOARD_InitSDRAM();
// Data/address bus check. //BL = 4
if (SDRAM_DataBusCheck(sdram) != kStatus_Success)
{
PRINTF("\r\n SDRAM data bus check is failure.\r\n");
}
if (SDRAM_AddressBusCheck(sdram, SDRAM_SIZE_BYTES) != kStatus_Success)
{
PRINTF("\r\n SDRAM address bus check is failure.\r\n");
}
PRINTF("\r\n Start.\r\n");
//Prepare data and write to SDRAM.
for (index = 0; index < SDRAM_EXAMPLE_DATALEN; index++)
{
*(uint32_t *)(sdram + index) = 0x11223344; //0xAABBCCDD
}
//Read data from the SDRAM.
for (index = 0; index < SDRAM_EXAMPLE_DATALEN; index++)
{
if (*(uint32_t *)(sdram + index) != 0x11223344)
{
PRINTF("\r\n Error: %8X\n , %8X\n \r\n", (sdram + index), *(uint32_t *)(sdram + index));
}
}
PRINTF("\r\n SDRAM Write Data and Read Data Succeed.\r\n");
while (1)
{
}
}
五、参考资料
(1)LPC546XX 系列相关资料均可在 NXP 官网下载,网址如下:
(2)《高手进阶,终极内存技术指南》,网址如下:https://wenku.baidu.com/view/acfdfed733d4b14e8524687f.html
(3)《W9864G2JH Datasheet》,网址如下:https://pdf1.alldatasheet.com/datasheet-pdf/view/555629/WINBOND/W9864G2JH.html
评论