lpcware

DK-57VTS-LPC1788 - Configuring the EMC for SDRAM

Discussion created by lpcware Employee on Jun 15, 2016
Latest reply on Jun 15, 2016 by lpcware
Content originally posted in LPCWare by Dave on Tue Jul 19 10:04:19 MST 2011
The SDRAM has to be programmed before you can use it as a display buffer for the LCD.  And before you can program the SDRAM, you have to setup the LPC1788 so it can talk to the SDRAM.

The SDRAM on this development board is a MT48LC2M32B2, which is a 2Mx32bit chip ( 8.38MB or 67.108Mb ).

Since all timing values for the LPC1788 are in units of CLK counts, and all timing values in the data sheet are in nanoseconds, the first thing I needed was a macro to convert these units.

Here is a macro for converting nanoseconds to CLK counts:
<code>
#include "LPC177x_8x.h"
#include "system_LPC177x_8x.h"

#define tCLK_ns ((double)EMCClock / 1000000000.0 )                   // CCLK period in ns      
#define NS_2_CLKS(ns) ( (uint32_t)( (double)(ns) * tCLK_ns ) + 1 )   // convert ns to CCLKs    
</code>

The EMCClock value is from the CMSIS, located in "system_LPC177x_8x.h", and is the current EMC clock value.  The macro NS_2_CLKS converts a given value in nanoseconds to the equivalent number of clock cycles for the current EMC clock...

Another feature of the SDRAM is the order in which the BANK, ROW, and COLUMN are set.  You can have BANK-ROW-COLUMN or ROW-BANK-COLUMN.  I did some experimenting with this, and found a slight increase in performance when I used ROW-BANK-COLUMN order, but you can experiment with this yourself.

I defined a compiler switch to make it easier to change:
<code>
#define ROW_BANK_COLUMN    // default is BANK_ROW_COLUMN (ROW_BANK_COLUMN is ~0.2% faster...   
</code>

So, here's how to setup the LPC1788 and the SDRAM...
<code>
/***********************************************************************************************
** Function name:    SDRAM_init                                                                
**                                                                                             
** Descriptions:     Initialize SDRAM (Synchronous Dynamic Random Access Memory)               
**                                                                                             
** parameters:       None                                                                      
** Returned value:   TRUE when complete                                                        
**                                                                                             
***********************************************************************************************/
uint32_t INIT_SDRAM( void )
   {
   uint32_t Temp = Temp;
   uint32_t CAS_Latency, RAS_Latency;  // these can be 1, 2, or 3 clks                         
   uint32_t DelayConstant;
</code>

I used variables for the CAS and RAS latency values, as well as the delay constant so I could adjust them easily during development.

So the first thing to do is setup the LPC1788.  This entails defining the SDRAM control signals, defining how we want the device to react to a reset signal, and enabling the power to the device (peripheral power), as follows:
<code>
   // configure SDRAM control signals                                                          
   // ------------------------------------------------------------------------------------------
   LPC_IOCON->P2_16 |= 1;              // CASN @ P2.16         (SDRAM Column Address Strobe)   
   LPC_IOCON->P2_17 |= 1;              // RASN @ P2.17         (SDRAM Row Address Strobe)      
   LPC_IOCON->P2_18 |= 1;              // CLK[0] @ P2.18       (SDRAM System Clock)            
   LPC_IOCON->P2_20 |= 1;              // DYCSN[0] @ P2.20     (SDRAM Chip Select)             
   LPC_IOCON->P2_24 |= 1;              // CKE[0] @ P2.24       (SDRAM Clock Enable)            
   LPC_IOCON->P2_28 |= 1;              // DQM[0] @ P2.28       (SDRAM Data Input/Output Mask)  
   LPC_IOCON->P2_29 |= 1;              // DQM[1] @ P2.29       (SDRAM Data Input/Output Mask)  
   LPC_IOCON->P2_30 |= 1;              // DQM[2] @ P2.30       (SDRAM Data Input/Output Mask)  
   LPC_IOCON->P2_31 |= 1;              // DQM[3] @ P2.31       (SDRAM Data Input/Output Mask)  

   // configure for 32-bit external data bus                                                   
   // ------------------------------------------------------------------------------------------
   LPC_IOCON->P3_0 |= 1;               // D0 @ P3.0                                            
   LPC_IOCON->P3_1 |= 1;               // D1 @ P3.1                                            
   LPC_IOCON->P3_2 |= 1;               // D2 @ P3.2                                            
   LPC_IOCON->P3_3 |= 1;               // D3 @ P3.3                                            
   LPC_IOCON->P3_4 |= 1;               // D4 @ P3.4                                            
   LPC_IOCON->P3_5 |= 1;               // D5 @ P3.5                                            
   LPC_IOCON->P3_6 |= 1;               // D6 @ P3.6                                            
   LPC_IOCON->P3_7 |= 1;               // D7 @ P3.7                                            
   LPC_IOCON->P3_8 |= 1;               // D8 @ P3.8                                            
   LPC_IOCON->P3_9 |= 1;               // D9 @ P3.9                                            
   LPC_IOCON->P3_10 |= 1;              // D10 @ P3.10                                          
   LPC_IOCON->P3_11 |= 1;              // D11 @ P3.11                                          
   LPC_IOCON->P3_12 |= 1;              // D12 @ P3.12                                          
   LPC_IOCON->P3_13 |= 1;              // D13 @ P3.13                                          
   LPC_IOCON->P3_14 |= 1;              // D14 @ P3.14                                          
   LPC_IOCON->P3_15 |= 1;              // D15 @ P3.15                                          
   LPC_IOCON->P3_16 |= 1;              // D16 @ P3.16                                          
   LPC_IOCON->P3_17 |= 1;              // D17 @ P3.17                                          
   LPC_IOCON->P3_18 |= 1;              // D18 @ P3.18                                          
   LPC_IOCON->P3_19 |= 1;              // D19 @ P3.19                                          
   LPC_IOCON->P3_20 |= 1;              // D20 @ P3.20                                          
   LPC_IOCON->P3_21 |= 1;              // D21 @ P3.21                                          
   LPC_IOCON->P3_22 |= 1;              // D22 @ P3.22                                          
   LPC_IOCON->P3_23 |= 1;              // D23 @ P3.23                                          
   LPC_IOCON->P3_24 |= 1;              // D24 @ P3.24                                          
   LPC_IOCON->P3_25 |= 1;              // D25 @ P3.25                                          
   LPC_IOCON->P3_26 |= 1;              // D26 @ P3.26                                          
   LPC_IOCON->P3_27 |= 1;              // D27 @ P3.27                                          
   LPC_IOCON->P3_28 |= 1;              // D28 @ P3.28                                          
   LPC_IOCON->P3_29 |= 1;              // D29 @ P3.29                                          
   LPC_IOCON->P3_30 |= 1;              // D30 @ P3.30                                          
   LPC_IOCON->P3_31 |= 1;              // D31 @ P3.31                                          

   // configure for 32-bit external address bus                                                
   // ------------------------------------------------------------------------------------------
   LPC_IOCON->P4_0 |= 1;               //A0 @ P4.0                                             
   LPC_IOCON->P4_1 |= 1;               //A1 @ P4.1                                             
   LPC_IOCON->P4_2 |= 1;               //A2 @ P4.2                                             
   LPC_IOCON->P4_3 |= 1;               //A3 @ P4.3                                             
   LPC_IOCON->P4_4 |= 1;               //A4 @ P4.4                                             
   LPC_IOCON->P4_5 |= 1;               //A5 @ P4.5                                             
   LPC_IOCON->P4_6 |= 1;               //A6 @ P4.6                                             
   LPC_IOCON->P4_7 |= 1;               //A7 @ P4.7                                             
   LPC_IOCON->P4_8 |= 1;               //A8 @ P4.8                                             
   LPC_IOCON->P4_9 |= 1;               //A9 @ P4.9                                             
   LPC_IOCON->P4_10 |= 1;              //A10 @ P4.10                                           
   LPC_IOCON->P4_11 |= 1;              //A11 @ P4.11                                           
   LPC_IOCON->P4_12 |= 1;              //A12 @ P4.12                                           
   LPC_IOCON->P4_13 |= 1;              //A13 @ P4.13                                           
   LPC_IOCON->P4_14 |= 1;              //A14 @ P4.14                                           
   LPC_IOCON->P4_15 |= 1;              //A15 @ P4.15                                           
   LPC_IOCON->P4_16 |= 1;              //A16 @ P4.16                                           
   LPC_IOCON->P4_17 |= 1;              //A17 @ P4.17                                           
   LPC_IOCON->P4_18 |= 1;              //A18 @ P4.18                                           
   LPC_IOCON->P4_19 |= 1;              //A19 @ P4.19                                           
   //LPC_IOCON->P4_20 |= 1;             //A20 @ P4.20                                            
   //LPC_IOCON->P4_21 |= 1;             //A21 @ P4.21                                            
   //LPC_IOCON->P4_22 |= 1;             //A22 @ P4.22                                            
   //LPC_IOCON->P4_23 |= 1;             //A23 @ P4.23                                            
  
   // Configure write enable for SDRAM...                                                      
   // ------------------------------------------------------------------------------------------
   LPC_IOCON->P4_25 |= 1;              // WEN @ P4.25                                          

   // Enable EMC Reset, (both EMC resets are asserted when any type of reset occurs)           
   // [Ref: LPC178x_7x_UM_1.4 page 32 ]                                                        
   // ------------------------------------------------------------------------------------------
   LPC_SC->SCS &= ~(1<<1);    // clears bit 1, which is the power on default state anyway...   
  
   // Enable EMC power/clock control bit...                                                    
   // ------------------------------------------------------------------------------------------
   LPC_SC->PCONP   |= (1<<11);        
</code>

Next thing to do is define the values for the variables at the beginning (CAS_Latency, RAS_Latency, and the DelayConstant).
I chose these values for functionality, but you can play around with them to see the effect they have on SDRAM performance.
The datasheet for the MT48LC2M32B2 has recommend values, based on the operating frequency.

I chose an arbitrary frequency of 100Mhz as a switch from lower latency to higher latency, as follows:
<code>
   if( SystemCoreClock < 100000000 )   // longer delay constant, and shorter CAS and RAS latency
      {                                //                                                      
      LPC_SC->EMCCLKSEL = 0x00000001;  // EMC uses a clock rate at 1/2 CPU clock rate...       
      LPC_SC->CLKOUTCFG = 0x00000110;  // enables clock, sets clock source as CPU clk / 2...   
      CAS_Latency = 1;
      RAS_Latency = 1;
      DelayConstant = 0x00000604;
      }
   else
      {
      LPC_SC->EMCCLKSEL = 0x00000000;  // EMC uses a clock rate equal to CPU clock rate...     
      LPC_SC->CLKOUTCFG = 0x00000100;  // enables clock, sets clock source as CPU clk / 1...   
      CAS_Latency = 2;
      RAS_Latency = 2;
      DelayConstant = 0x00000A05;
      }

   // [Ref: LPC178x_7x_UM_1.4 page 183 ]                                                                                   
   // ----------------------------------------------------------------------------------------------------------------------
   // Bit      Symbol         Description                                                                              Reset
   //                                                                                                                  Value
   // ----------------------------------------------------------------------------------------------------------------------
   // 4:0      CMDDLY         Programmable delay value for EMC outputs in command delayed mode. See                    0x10
   //                         Section 10.12.6. The delay amount is roughly (CMDDLY+1) * 250 picoseconds.                   
   // 7:5      -              Reserved. Read value is undefined, only zero should be written.                          NA  
   // 12:8     FBCLKDLY       Programmable delay value for the feedback clock that controls input data sampling. See   0x02
   //                         Section 10.5.3. The delay amount is roughly (FBCLKDLY+1) * 250 picoseconds.                  
   // 15:13    -              Reserved. Read value is undefined, only zero should be written.                          NA  
   // 20:16    CLKOUT0DLY     Programmable delay value for the CLKOUT0 output. This would typically be used in clock   0x00
   //                         delayed mode. See Section 10.12.6 The delay amount is roughly (CLKOUT0DLY+1) *               
   //                         250 picoseconds.                                                                             
   // 23:21    -              Reserved. Read value is undefined, only zero should be written.                          NA  
   // 28:24    CLKOUT1DLY     Programmable delay value for the CLKOUT1 output. This would typically be used in clock   0x00
   //                         delayed mode. See Section 10.12.6 The delay amount is roughly (CLKOUT1DLY+1) *               
   //                         250 picoseconds.                                                                             
   // 31:29    -              Reserved. Read value is undefined, only zero should be written.                          NA  
   LPC_SC->EMCDLYCTL   = DelayConstant;

   // Reset the EMC and put configuration back to power up reset (little-endian mode, 1:1 clock)
   // [Ref: LPC178x_7x_UM_0.01 page 156]                                                       
   // ------------------------------------------------------------------------------------------
   LPC_EMC->Control = 0x00000001;   // Disable Address mirror, leave EMC enabled...            
   LPC_EMC->Config  = 0x00000000;
  
   // define connection configuration [32 bit external bus address mapping]                    
   // ------------------------------------------------------------------------------------------
   #ifdef ROW_BANK_COLUMN
      LPC_EMC->DynamicConfig0 = 0x00004300;  // row, bank, column, 32-bit
   #else
      LPC_EMC->DynamicConfig0 = 0x00005300;  // bank, row, column, 32-bit
   #endif 
  
   // define delays for RAS and CAS...                                                         
   // bits 1:0-RAS latency, bits 9:8-CAS latency (in CCLK cycle counts)                        
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicRasCas0 = RAS_Latency + (CAS_Latency<<8);    // reset value is 3 and 3       
     
   // configure the dynamic memory read strategy.  Note: This register is used for all four    
   // dynamic memory chip selects. Therefore the worst case value for all of the chip selects  
   // must be programmed.                                                                      
   // [Ref: LPC178x_7x_UM_0.01 page 169 Table 117]                                             
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicReadConfig = 0x00000001;  // Command delayed strategy, using EMCCLKDELAY    
                                             // (command delayed, clock out not delayed)       
</code>

Also, in the above code, the Control, Config, DynamicConfig, DynamicRasCas, and DynamicReadConfig registers are programmed.  The notes will give you an idea of what's going on, but I recommend you read the user's manual for a complete description of these registers.

The next thing to do is program the controller with the part specific timing values.  This is where the macros at the beginning will come in handy.

These values are programmed as follows:
<code>
   // *** Taken from the MT48LC2M32B2 data sheet, page 47 ***                                  
   // configure timing, from Table 18:                                                         
   // -6 (6ns part) timings (see marking on actual chip: MT48LC2M32B2-6)                       
   // NOTE: all timing values for LPC1788 are in units of CLK counts                           
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicRP   = NS_2_CLKS(18);     // tRP: precharge command period             (18ns)
   LPC_EMC->DynamicRAS  = NS_2_CLKS(42);     // tRAS: active to precharge command period  (42ns)
   LPC_EMC->DynamicSREX = NS_2_CLKS(70);     // tXSR: self-refresh exit time              (70ns)
   LPC_EMC->DynamicAPR  = NS_2_CLKS(18);     // tAPR: last-data-out to active command time     
                                             // note: no tAPR value, using tRCD value     (18ns)
   LPC_EMC->DynamicDAL  = CAS_Latency+2;     // tDAL: data-in to active command time           
                                             //       for CL=3, tDAL = 5 tCK                   
                                             //       for CL=2, tDAL = 4 tCK                   
                                             //       for CL=1, tDAL = 3 tCK                   
   LPC_EMC->DynamicWR   = (NS_2_CLKS(6)+1);  // tWR: write recovery time is (12ns) UNLESS we're
                                             //      using AUTO PRECHARGE, then it's   (tCK+6ns)
   LPC_EMC->DynamicRC   = NS_2_CLKS(60);     // tRC: ACTIVE-to-ACTIVE command period      (60ns)
   LPC_EMC->DynamicRFC  = NS_2_CLKS(60);     // tRFC: AUTO REFRESH period                 (60ns)
   LPC_EMC->DynamicXSR  = NS_2_CLKS(70);     // tXSR: Exit self refresh to ACTIVE command (70ns)
   LPC_EMC->DynamicRRD  = NS_2_CLKS(12);     // tRRD: active bank A to active bank B command   
                                             //       latency                             (12ns)
   LPC_EMC->DynamicMRD  = 2;                 // tMRD: LOAD MODE REGISTER command to ACTIVE or  
                                             // REFRESH command time                      (2tCK)
</code>

With all of these parameters programmed, we are now ready to send commands to the SDRAM chip.

An important note:
<code>
   // *** Taken from the MT48LC2M32B2 data sheet, page 10 ***                                  
   // ------------------------------------------------------------------------------------------
   // SDRAMs must be powered up and initialized in a predefined manner. Operational            
   // procedures other than those specified may result in undefined operation. After power is  
   // applied to VDD and VDDQ (simultaneously) and the clock is stable (stable clock is        
   // defined as a signal cycling within timing constraints specified for the clock pin), the  
   // SDRAM requires a 100us delay prior to issuing any command other than a COMMAND           
   // INHIBIT or NOP. Starting at some point during this 100us period, and continuing at least 
   // through the end of this period, COMMAND INHIBIT or NOP commands must be                  
   // applied.                                                                                 
   // When the 100us delay has been satisfied with at least one COMMAND INHIBIT or NOP         
   // command having been applied, a PRECHARGE command should be applied. All banks            
   // must then be precharged, thereby placing the device in the all banks idle state.         
   // ------------------------------------------------------------------------------------------
</code>

so the first command we send to the SDRAM is a NOP command, and we wait for the 100us...
<code>
   // *** Taken from the MT48LC2M32B2 data sheet, page 17 ***                                  
   // The NO OPERATION (NOP) command is used to perform an NOP to an SDRAM that is             
   // selected (CS# is LOW). This prevents unwanted commands from being registered during      
   // idle or wait states. Operations already in progress are not affected.                    
   // ------------------------------------------------------------------------------------------
   // Send command: NOP                                                                        
   // (CLKOUT runs continuously; All clock enables are driven HIGH continuously)               
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicControl = 0x00000183;
  
   // wait > 100us                                                                             
   // ------------------------------------------------------------------------------------------
   for( Temp=NS_2_CLKS(100000); Temp; Temp-- );
</code>

The next command is the PRECHARGE-ALL command, and we set the correct refresh period, as follows:
<code>
   // *** Taken from the MT48LC2M32B2 data sheet, page 18 ***                                  
   // The PRECHARGE command is used to deactivate the open row in a particular bank or         
   // the open row in all banks. The bank(s) will be available for a subsequent row access a   
   // specified time (tRP) after the PRECHARGE command is issued. Input A10 determines         
   // whether one or all banks are to be precharged, and in the case where only one bank is to 
   // be precharged, inputs BA0 and BA1 select the bank. Otherwise BA0 and BA1 are treated     
   // as “Don’t Care.” After a bank has been precharged, it is in the idle state and must be   
   // activated prior to any READ or WRITE commands being issued to that bank.                 
   // ------------------------------------------------------------------------------------------
   // Send command: PRECHARGE-ALL and set the shortest possible refresh period                 
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicControl = 0x00000103;
   LPC_EMC->DynamicRefresh = 0x00000001;  // 1 x 16 = 16 CCLKs between SDRAM refresh cycles    
  
   // wait >128 clks                                                                           
   // ------------------------------------------------------------------------------------------
   for( Temp=128; Temp; Temp-- );

   // *** Taken from the MT48LC2M32B2 data sheet, page 18 ***                                  
   // Regardless of device width, the                                                          
   // 64Mb SDRAM requires 4,096 AUTO REFRESH cycles every 64ms (commercial and industrial)     
   // or 16ms (automotive). Providing a distributed AUTO REFRESH command every                 
   // 15.625us (commercial and industrial) or 3.906us (automotive) will meet the refresh       
   // requirement and ensure that each row is refreshed. Alternatively, 4,096 AUTO REFRESH     
   // commands can be issued in a burst at the minimum cycle rate (tRFC), once every 64ms      
   // (commercial and industrial) or 16ms (automotive).                                        
   // so... Refresh period (4,096 rows) tREF – 64ms                                            
   // which gives a time of 15.625us per row, or 15625ns                                       
   // ------------------------------------------------------------------------------------------
   // Set correct refresh period                                                               
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicRefresh = NS_2_CLKS(15625)>>4;     // Refresh units are x16
  
   // wait >128 clks                                                                           
   // ------------------------------------------------------------------------------------------
   for( Temp=128; Temp; Temp-- );
</code>

And finally, we send the mode command, which tells the SDRAM what the burst length is, the type of burst, the latency, etc., as follows:

<code>
   // Set mode register in SDRAM                                                               
   // A0–A10 define the op-code written to the mode register                                   
   // Bank select is A13-A14, address is A0-A12, data I/O mask is DQM0-DQM3...                 
   // ------------------------------------------------------------------------------------------
   // Mode register table for Micron's MT48LCxx (MT48LC2M32B2P ON FDI BOARD)                   
   //    bit 9:   Write Burst Mode: Programmed burst length(0), Single Location Access(1)      
   //    bit 8~7: Operating Mode: Standard Operation(0) is the only thing defined              
   //    bit 6~4: CAS latency: 001(1), 010(2), 011(3)                                          
   //    bit 3:   Type of Burst: Sequential(0) or Interleaved(1)                               
   //    bit 2~0: Burst length: 000(1), 001(2), 010(4), 011(8), 111(Full Page)                 
   //                                                                                          
  
   #ifdef ROW_BANK_COLUMN  // shift includes bank                                              
      Temp = *((volatile uint32_t *)(SDRAM_BASE_ADDR|((0x02+(CAS_Latency<<4))<<12))); 
   #else                   // shift excludes bank                                              
      Temp = *((volatile uint32_t *)(SDRAM_BASE_ADDR|((0x02+(CAS_Latency<<4))<<10))); 
   #endif
  
   // wait >128 clks                                                                           
   // ------------------------------------------------------------------------------------------
   for( Temp=128; Temp; Temp-- );
</code>

Notice the shift distance changes depending on which configuration we're using?  (ROW_BANK_COLUMN versus BANK_ROW_COLUMN)

The final step is to put the SDRAM into NORMAL operation by sending the NORMAL command, as follows:
<code>
   // ------------------------------------------------------------------------------------------
   //Send command: NORMAL                                                                      
   // ------------------------------------------------------------------------------------------
   LPC_EMC->DynamicControl = 0x00000000;
  
   //Enable buffer
   LPC_EMC->DynamicConfig0 |= 0x00080000;
  
   return( TRUE );
   }
</code>

This will setup your SDRAM properly for the DK-57VTS-LPC1788 development kit.

Hope this helps,

-Dave

Outcomes