lpc1788开发之SDRAM
来源:互联网 发布:学而知不足 出处 编辑:程序博客网 时间:2024/05/16 08:30
最近一直在调试SDRAM,花了一个多月才把sdram完全跑通。于是接下来的工作不是其他的,当然是写一个关于SDRAM调试的总结啦!话不多说,开始正题
开发板:SK-1788
CPU:cortex-M3 STM32 LPC1788FBD208
SDRAM:H57V2562GTR-75C (两片)
H57V2562GTR-75C简介:
16位数据总线(D0-D15),13位地址总线(A0-A12),两片,构成32位数据总线,26位地址总线,每片4个bank,由BA0,BA1两个管脚选择寻址,CS为片选管脚。
HY57V2562GTR挂到LPC1788的EMC_DYCS0,起始地址为0xA0000000,
片选管脚EMC_CS0 地址范围:0xA0000000-0xA3FFFFFF
存储器类型:动态
lpc1788 的封装是208PIN(所以ECC配置应该使用208的配置)
HY57V2562GTR大小:
2片32M组成的64MB的内存, 32*2=64MB
每一块SDRAM:8192行(2^13)*512列(2^9)*4个bank = 16M * 16位宽 = 256Mb = 32MB
原理:
研究过SDRAM的人都知道,我们的sdram是动态的,它要保存数据,依靠的是里面一个一个的微小电容排成的电容阵列(姑且这么认为),数据在每次的读写都会让电容的电荷流失,即使不对数据操作,间隔一定的时间,电容上的电荷也会流失。因此,sdram需要不断的刷新数据,为电容充电,以保证上面的数据不丢失。
不少sdram芯片datasheet上都会标出
等字样,(有的是4096)这就是说:每隔64ms刷新8192行。Sdram刷新是以“行”进行的,我们的sdram是8192行,因此刷新一次需要64ms。如果刷新的这个值比64ms大,那么我们的数据将得不到保存。64ms也是晶体存储容量的极限。因此在配置寄存器的时候,需要注意。
时钟配置:
为了使整个系统运行流畅,有必要说一下该开发板对于时钟的配置。
CPU上电复位,晶振稳定工作之后,通过配置CLKSRCSEL[0]=1,选择芯片的震荡源为外部12MHz晶振。
通过配置PLL0寄存器,将时钟倍频到108MHz输出,此时pll_clk为108MHz,sysclk为12MHz。(PLL1模块是单独一个给USB提供时钟的模块,占时不用管。)
通过配置CCLKSEL[8]=1,选择输入为108MHz,将108MHz送到CPU模块,EMC(SDRAM)模块和外设模块。
通过配置CPU Clock Divider 来选择时钟分频(不分频,108MHz),即cclk=108MHz,
通过配置EMC Clock Divider 来选择时钟(和CPU一致,108MHz),即emc_clk=108MHz,
通过配置Peripheral Clock Divider 来配置外设时钟分频,暂时不涉及到。
该系统的时钟部分工作原理如下图
下图是sdram模块的原理框图。
这里有两个比较重要的地方:
CMDDLY(EMCDLYCTL[4:0]),命令延迟模式下EMC 输出的可编程延迟值
FBCLKDLY(EMCDLYCTL[12:8]),控制输入数据采样的反馈时钟的可编程延迟值
这两个延时是非常重要的,1788芯片的SDRAM有一个很重要的寄存器, EMCDLYCTL 寄存器延时的设置,就算你和官方所使用芯片一样,只要电路板有差异,这个寄存器的设置将有可能导致SDRAM在使用过程中出现错误。
我在配置这两个寄存器的时候,花费了很长的时间才把延时配好。具体怎么配,下面再说。
初始化EMC模块和sdram
#define SDRAM_BASE 0xA0000000
#define EMC_BASE_ADDR 0x2009C000
#define EMC_RS_BASE_ADDR 0x400FC1DC
#define SDRAM_SIZE 0x4000000 // 512Mbit=64MB
#define EMCControl_ENABLE (1<<0)
#define EMCConfig_SMALL (0<<0)
#define EMCConfig_PERC_OF_CCLK (0<<8)
#define EMCDLYCTL_CMDDLY (0x0D<<0)
#define EMCDLYCTL_FBCLKDLY (0x10<<8)
#define EMCDLYCTL_CLKOUT0DLY (0<<16)
#define EMCDLYCTL_CLKOUT1DLY (0<<24)
#define EMC_DRC_CLKDELAY (1<<0)
#define EMC_Ras_late_3 (3<<0)
#define EMC_Cas_late_3 (3<<8)
#define EMCDCF0_BUF_EN (1<<19)
#define EMCDCF0_AM (1<<7)|(3<<9)|(1<<14)
#define EMCDC_DIS_CE (0<<0)
#define EMCDC_CE (1<<0)
#define EMCDC_DIS_CS (0<<1)
#define EMCDC_CS (1<<1)
#define EMCDC_SDRAM_INIT_NOP (3<<7)
#define EMCDC_SDRAM_INIT_PALL (2<<7)
#define EMCDC_SDRAM_INIT_MODE (1<<7)
#define EMCDC_SDRAM_INIT_NORMAL (0<<7)
void EMC_Init(void)
{
// float SDRAM_PERIOD;
int i;
// volatile unsigned long Dummy;
volatile unsigned int mhz = 0;
volatile unsigned int nsPerClk = 0;
unsigned int wtemp = wtemp;
unsigned int dwtemp = dwtemp;
// unsigned int j = 0;
lpc178x_emc_typedef *emc = get_emc_addr(EMC_BASE_ADDR);
lpc178x_emc_rs_typedef *emc_rs = get_emc_rs_addr(EMC_RS_BASE_ADDR);
/*PCONP 开启EMC*/
lpc178x_periph_enable(LPC178X_SCC_PCONP_PCEMC_MSK,ENABLE);
/*配置延时寄存器,这个延时寄存器配置比较麻烦,后面还要讲到,先设置一个例程里面的初始值,CMD-DELAY:4.25ns,FBCLK-DELAY:4.25ns*/
emc_rs->EMCDLYCTL = EMCDLYCTL_CMDDLY | EMCDLYCTL_FBCLKDLY | EMCDLYCTL_CLKOUT0DLY | EMCDLYCTL_CLKOUT1DLY;
emc->EMCControl = EMCControl_ENABLE; /*ECC 使能*/
/*配置sdram为小端模式,时钟和CPU一致,CCLK:CLKOUT = 1:1*/
emc->EMCConfig = EMCConfig_SMALL | EMCConfig_PERC_OF_CCLK;
/*配置相应的管脚为EMC模式,设置的值均为0x21,管脚有哪些,详细见问题3*/
lpc178x_gpio_config_table(lpc178x_ecc_gpio, ARRAY_SIZE(lpc178x_ecc_gpio));
mhz = SystemCoreClock / 1000000; /*mhz = 108MHz*/
if (LPC_SC->EMCCLKSEL) /*EMCCLKSEL = 0; EMC-CLK = 108MHz*/
mhz >>= 1; / *EMCCLKSEL = 1; EMC-CLK = 54MHz*/
/*配置相关的参数,详细见问题2*/
nsPerClk = 1000 / mhz; /*ns-clk = 1000/mhz = 9.259259*/
emc->EMCDynamictRP =EMC_NS2CLK(20, nsPerClk); /* 20ns, 编写预充电的命令周期——tRP*/
emc->EMCDynamictRAS = SDRAM_TRAS; //编写有效至预充电的命令周期——tRAS
emc->EMCDynamictSREX = SDRAM_SREX; //编写自刷新退出时间——tSREX
emc->EMCDynamictAPR = SDRAM_TAPR; //编写最后数据输出到有效的命令时间——tAPR
emc->EMCDynamictDAL = EMC_NS2CLK(20, nsPerClk) + 2; //编写数据输入到有效的命令时间——tDAL
emc->EMCDynamictWR = SDRAM_TWR; //编写写入恢复时间——tWR
emc->EMCDynamictRC = EMC_NS2CLK(63, nsPerClk);//编写有效至有效命令周期——tRC
emc->EMCDynamictRFC = EMC_NS2CLK(63, nsPerClk);//编写自动刷新周期以及有效命令自动刷新周期——tRFC
emc->EMCDynamictXSR = SDRAM_TXSR;/*编写从自刷新退出到有效的命令时间——tXSR*/
emc->EMCDynamictRRD = EMC_NS2CLK(63, nsPerClk); /*编写有效组A至有效组B的延时——tRRD*/
emc->EMCDynamictMRD = SDRAM_TMRD;/*编写装载模式寄存器到有效的命令时间——tMRD*/
//命令延时策略,使用EMCCLKDELAY(命令延时,时钟输出未延时)。
emc->EMCDynamicReadConfig = EMC_DRC_CLKDELAY;
/*RAS3个CCLK周期(上电复位值),CAS 3个CCLK周期(上电复位值)*/
emc->EMCDynamicRasCas0 = EMC_Ras_late_3 | EMC_Cas_late_3;
/*设置行列相关的配置,SDRAM0 14 12~7 bit: 1 001101 256Mb (16Mx16),4组,行长度=13,列长度=9 。值得注意的是:sdram给地址是分为两次给的,先给行地址(13位),再给列地址(9位),这样需要寻址的寄存器就找到,然后再对寄存器操作*/
emc->EMCDynamicConfig0 = EMCDCF0_AM;
/*delay a period*/
delay_ms(100);
/*发布 NOP 命令*/
emc->EMCDynamicControl = EMCDC_CE|EMCDC_CS|EMCDC_SDRAM_INIT_NOP;
delay_ms(200);
/*发布SDRAM PALL(全部预充电)命令*/
emc->EMCDynamicControl = EMCDC_CE|EMCDC_CS|EMCDC_SDRAM_INIT_PALL;
//0x2 = 2 x 16 = SDRAM刷新周期中32个CCLK。
emc->EMCDynamicRefresh = 2;
for(i = 0; i < 0x80; i++);
/*计算刷新周期:64ms / 8192 * 108MHz / 16bit */
wtemp = 64000000 / (1 << 13);
wtemp -= 16;
wtemp >>= 4;
wtemp = wtemp * mhz / 1000;
emc->EMCDynamicRefresh = wtemp;
/*发布SDRAM MODE命令*/
emc->EMCDynamicControl = EMCDC_CE|EMCDC_CS|EMCDC_SDRAM_INIT_MODE;
/*一次“读出",在地址线上输出一个合适的数字逻辑,
这个地方如果是16位,则选择0x33<<12,如果是32位,则选择0x32<<13 */
// dwtemp = *((volatile uint32_t *)(SDRAM_BASE | (0x33<<12)));
dwtemp = *((volatile uint32_t *)(SDRAM_BASE | (0x32<<13)));
// 发布SDRAM NORMAL操作命令(上电复位值)
emc->EMCDynamicControl = EMCDC_DIS_CE|EMCDC_DIS_CS|EMCDC_SDRAM_INIT_NORMAL;
/*缓冲使能,以读取该片选*/
emc->EMCDynamicConfig0 |= EMCDCF0_BUF_EN;
delay_ms(1);
/*以下的代码为配置EMCDLYCTL的,为了让延时精确,数据不出现错误,在调试的时候,将代码加入,一旦调试好延时,可将此代码注释,详细说明见问题4*/
// i = CalibrateOsc();
// FindDelay(0); // EMCDLY
// FindDelay(1); // FBCLKDLY
// AdjustEMCTiming(i);
}
初始化SDRAM之后,就需要我们编写测试程序对sdram进行测试,具体实现为:按照8位,16位,32位数据写入,然后延时一段时间(也可以不作延时),将数据读出,看看与写入的值是否一致,如果一致,则sdram的驱动没有问题,可以使用。
下面是测试程序(返回值:0 测试通过;返回值1:测试失败)
unsigned int sdram_test(void)
{
uint32_t i;//,j,val;
volatile uint8_t *char_wr_ptr;
volatile uint16_t *short_wr_ptr;
volatile uint32_t *wr_ptr;
volatile uint32_t *int_wr_ptr;
#if 1
int_wr_ptr = (uint32_t *)SDRAM_BASE;
char_wr_ptr = (uint8_t *)int_wr_ptr;
printf("8 bit test sdram ...\r\n");
/*SDRAM data clear as zero for 8bit test*/
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
*int_wr_ptr++ = 0;
}
/*8 bit data write*/
for (i=0; i<SDRAM_SIZE/4; i++)
{
*char_wr_ptr++ = 0xa5;
*char_wr_ptr++ = 0x5a;
*char_wr_ptr++ = 0xa5;
*char_wr_ptr++ = 0x5a;
}
delay_ms(100);
int_wr_ptr = (uint32_t *)SDRAM_BASE;
char_wr_ptr = (uint8_t *)int_wr_ptr;
/*data read as 32bit model*/
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
if ( *int_wr_ptr != 0x5aa55aa5 ) /* be aware of endianess */
{
printf("8 bit test sdram failure,address :%p error\r\n",int_wr_ptr);
printf("address : %p ;value : 0x%x\r\n",int_wr_ptr,*int_wr_ptr);
goto error; /* fatal error */
}
*int_wr_ptr ++;
}
printf("8 bit test sdram succeed!\r\n");
#endif
#if 1
int_wr_ptr = (uint32_t *)SDRAM_BASE;
short_wr_ptr = (uint16_t *)int_wr_ptr;
/*SDRAM data clear as zero for 16 bit test*/
printf("16 bit test sdram ...\r\n");
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
*int_wr_ptr++ = 0;
}
/*16 bit data write*/
for (i=0; i<SDRAM_SIZE/4; i++)
{
*short_wr_ptr++ = 0xAAAA;
*short_wr_ptr++ = 0x5555;
}
delay_ms(100);
int_wr_ptr = (uint32_t *)SDRAM_BASE;
/*data read as 32bit model*/
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
if ( *int_wr_ptr != 0x5555AAAA ) /* be aware of endianess */
{
printf("16 bit test sdram failure,address :%p err\r\n",int_wr_ptr);
printf("address : %p ;value : 0x%x\r\n",int_wr_ptr,*int_wr_ptr);
goto error; /* fatal error */
}
int_wr_ptr++;
}
printf("16 bit test sdram succeed!\r\n");
#endif
#if 1
int_wr_ptr = (uint32_t *)SDRAM_BASE;
wr_ptr = (uint32_t *)int_wr_ptr;
printf("32 bit test sdram ...\r\n");
/*SDRAM data clear as zero for 32 bit test*/
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
*int_wr_ptr++ = 0;
}
/*8 bit data write*/
for (i=0; i<SDRAM_SIZE/4; i++)
{
*wr_ptr++ = 0x5A5A5A5A;
}
delay_ms(100);
int_wr_ptr = (uint32_t *)SDRAM_BASE;
wr_ptr = (uint32_t *)int_wr_ptr;
/*data read as 32bit model*/
for ( i= 0; i < SDRAM_SIZE/4; i++ )
{
if ( *int_wr_ptr != 0x5A5A5A5A ) /* be aware of endianess */
{
printf("32 bit test sdram failure,address :%p error\r\n",int_wr_ptr);
printf("address : %p ;value : 0x%x\r\n",int_wr_ptr,*int_wr_ptr);
goto error; /* fatal error */
}
*int_wr_ptr ++;
}
printf("32 bit test sdram succeed!\r\n");
#endif
return 0;
error:
return 1;
}
后记:在调试sdram的过程中,遇到了很多问题,有些问题是网上出现的,但是没有具体的答案,自己摸索出来的,有些问题是网上没有遇到过的。
问题1:
开始调试1788,芯片硬件仿真时,调试SDRAM寄存器配置错误,导致1788芯片无法进入仿真状态,只能用Flash Magic才能擦除。
问题2:
设定sdram参数的时候,由于没有深入的研究datasheet,导致后来测试的时候,跑一万年,调死了也不正确,后来才发现原来芯片datasheet上的参数跟例程上面的不一样,改了之后,就可以了。(果然datasheet是王道啊,信datasheet,得永生)
#define EMC_NS2CLK(ns, nsPerClk) ((ns +nsPerClk - 1) / nsPerClk)
#define SDRAM_REFRESH 7513
#define SDRAM_TRP 20
#define SDRAM_TRAS 42
#define SDRAM_TRRD 15
#define SDRAM_TMRD 2
#define SDRAM_TDAL 22
#define SDRAM_TRC 63
#define SDRAM_SREX 1
#define SDRAM_TAPR 1
#define SDRAM_TXSR 0xF
#define SDRAM_TWR 1
#define SDRAM_TRFC 70
问题3:
配置芯片的管脚出错:
sdram用到的芯片管脚为:
P2_16:列地址选通
P2_17:行地址选通
P2_18 :EMC_CLK0,sdram的时钟管脚0
P2_20:EMC_DYCS0选通线(片选)
P2_24: EMC_CKE0—SDRAM时钟使能0
P2_28 - P2_31:数据屏蔽线0,1,2,3
P3_0 - P3_31: 数据线D0-D31
P4_0 - P4_12: 地址线A0-A12
P4_13 – P4_14:BA0,BA1,bank选择管脚
P4_25: EMC_WE,写使能,低电平有效写入使能信号
管脚配置值为0x21
问题4:
sdram的延时设置错误(即EMCDLYCTL),具体表现为:写入的数据和读出的数据有一位乃至更多位的数据出错,我的方法是通过官方的测试程序,将EMCDLYCTL的值调试到最优情况,可以使数据不出错。
下图是EMC的延时模块,从图中我们可以看出,EMC的延时可以以一个很小的尺度(0.25ns)增大和减小,范围在0.25-8ns之间变化,通过配置EMCDLYCTL的值可以配置延时。
下表有关延时详细的配置信息:
例如:如果我需要配置CMDDLY为5ns,根据下面的公式
Delay_time = (CMDDLY+1) * 250ps
即:5ns=(CMDDLY+1)*250ps => CMDDLY = 19=0x13
根据上面的原理,我们可以设计程序,来找到最佳的延时时间。不过网上有官方的调试的程序,我们可以参照着来调试。
具体做法是:
设置一个延时最小值,看看测试的时候是否出错;
逐渐增大延时,当测试成功时,记录这个值,然后继续增大,
当再次增大,测试数据出现错误的时候,记下最大值,然后通过最大值和最小值计算平均,得出比较稳定的延时。
static void FindDelay(int DelayType) {
uint32_t Delay;
uint32_t Min;
uint32_t Max;
uint32_t v;
Delay = 0x00;
Min = 0xFF;
Max = 0xFF;
//
// Test for DLY min./max. values
//
while (Delay < 32) {
//
// Setup new DLY value to test
//
if (DelayType == 0) {
v = LPC_SC->EMCDLYCTL & ~0x001Ful;
LPC_SC->EMCDLYCTL = v | Delay;
} else {
v = LPC_SC->EMCDLYCTL & ~0x1F00ul;
LPC_SC->EMCDLYCTL = v | (Delay << 8);
}
//
// Test configured DLY value and find out min./max. values that will work
//
if (sdram_test() == 0) {
//
// Test passed, remember min. DLY value if not done yet
//
if (Min == 0xFF) {
Min = Delay;
}
} else {
//
// Test failed, if a min. value has been found before, remember the current value for max.
//
if (Min != 0xFF) {
Max = Delay;
}
}
Delay++;
}
//
// Calc DLY value
//
if (Max != 0xFF) { // If we found a min. and max. value we use the average of the min. and max. values to get an optimal DQSIN delay
Delay = (Min + Max) / 2;
} else if (Min != 0xFF) { // If we found only a min. value we use the average of the min. value and the longest DLY value to get an optimal DQSIN delay
Delay = (Min + 0x1F) / 2;
} else { // No working max. and/or min. values found
while (1); // Fatal error
}
//
// Setup DLY value to work with
//
if (DelayType == 0) {
v = LPC_SC->EMCDLYCTL & ~0x001Ful;
LPC_SC->EMCDLYCTL = v | Delay;
} else {
v = LPC_SC->EMCDLYCTL & ~0x1F00ul;
LPC_SC->EMCDLYCTL = v | (Delay << 8);
}
}
static uint32_t CalibrateOsc(void) {
uint32_t Cnt;
uint32_t v;
uint32_t i;
//
// Init start values
//
Cnt = 0;
//
// Calibrate osc.
//
for (i = 0; i < 10; i++) {
LPC_SC->EMCCAL = (1 << 14); // Start calibration
v = LPC_SC->EMCCAL;
while ((v & (1 << 15)) == 0) { // Wait for calibration done
v = LPC_SC->EMCCAL;
}
Cnt += (v & 0xFF);
}
return (Cnt / 10);
}
static void AdjustEMCTiming(uint32_t Delay) {
uint32_t v;
uint32_t CmdDly;
uint32_t FBDelay;
uint32_t FBClkDly;
FBDelay = CalibrateOsc();
v = LPC_SC->EMCDLYCTL;
CmdDly = ((v & 0x001Ful) * Delay / FBDelay) & 0x1F;
FBClkDly = ((v & 0x1F00ul) * Delay / FBDelay) & 0x1F00;
LPC_SC->EMCDLYCTL = (v & ~0x1F1Ful) | FBClkDly | CmdDly;
}
- lpc1788开发之SDRAM
- Cortex-M3 (NXP LPC1788)之开发环境搭建
- 【转】s3c44b0x开发板之SDRAM配置
- 奔奔 LPC1788 入门学习笔记系列之---LPC1788时钟配置
- Cortex-M3 (NXP LPC1788)之GPIO
- Cortex-M3 (NXP LPC1788)之PWM
- Cortex-M3 (NXP LPC1788)之UART用法
- Cortex-M3 (NXP LPC1788)之RTC
- Cortex-M3 (NXP LPC1788)之EEPROM存储器
- Cortex-M3 (NXP LPC1788)之IIC控制器
- Cortex-M3 (NXP LPC1788)之IIS控制器
- Cortex-M3 (NXP LPC1788)之IIC控制器
- Cortex-M3 (NXP LPC1788)之UART用法
- Cortex-M3 (NXP LPC1788)之EEPROM存储器
- 裸奔之sdram
- ARM体系结构之SDRAM
- EMC之SDRAM
- SDRAM之我见(转)
- B-Tree和B+Tree
- 获取DatePickerDialog的时间
- POJ-1135 Domino Effect 单源最短路径
- p2p shareaza 老牌多功能跨协议P2P客户端
- 【解惑】Java动态绑定机制的内幕
- lpc1788开发之SDRAM
- 俄罗斯总统-普京
- 解决oracle scott/tiger帐号被锁定
- 抽象工厂模式(C#)
- x86平台转x64平台关于内联汇编不再支持的解决
- Win8下开启.net3.5/2.0
- Length of Last Word
- 数据结构
- 控件与布局