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_20EMC_DYCS0选通线(片选)

P2_24: EMC_CKE0SDRAM时钟使能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;

 }

原创粉丝点击