Interfacing SDRAM to PIC32
As of this writing, none of the members of the PIC32 family have a built-in SDRAM interface. There are competing MCUs in the same class that offer this functionality, but for whatever reason, if you would like to interface SDRAM to a PIC32 (or to any other MCU that does not have a built-in SDRAM interface for that matter), this page provides a working example of how it can be emulated in software.
This kind of emulated SDRAM interface works best when slow-access, large-capacity storage is needed. If an application needs to perform fast computation on the data stored in SDRAM, it is probably better to move the data to the built-in SRAM first, if feasible. Depending on SDRAM access patterns, implementing a suitable caching policy will definitely help here.
Theory of Operation
SDRAM datasheets specify that a stable clock must be provided to the SDRAM for proper operation. This is achieved by using one of the Output Compare modules in the PIC32. The provided source code code is written based on the assumption that the OC module is setup to output the clock signal at 1/4 sysclk frequency (sdram_init() implements this).
All control lines (CS, CAS, RAS, WE, CKE), address lines and data lines are connected to the PIC32 I/O ports. The emulated SDRAM interface manipulates the control and data lines with precise timing to maintain synchronization with the SDRAM clock. For this reason, the provided source code must be run from PIC32 internal RAM, as flash wait states will disrupt the timing. In addition, interrupts must be disabled during calls to SDRAM access routines, as an interrupt occuring during a SDRAM operation will certainly result in data corruption. Interrupts may be enabled in between SDRAM operations.
It is crucial to put all control lines on the same port so that they can be updated in a single cycle. The same goes for the data lines. It is preferable to have the address lines on the same port for performance reasons, but this is not critical for SDRAM operation.
To access the SDRAM in your application, do the following:
- Initialize the SDRAM in the beginning of your application by calling sdram_init().
- Select bank by calling sdram_bank() (if necessary).
- Activate the row to be accessed by calling sdram_active().
- Issue reads or writes to desired columns by calling sdram_read() or sdram_write().
- Precharge (close) the row by calling sdram_precharge() when done.
- Go to step 2.
In addition to the steps above, SDRAM needs to be refreshed frequently by calling sdram_auto_refresh() so that it will not lose data. The frequency usually depends on the SDRAM chip used. For the MT48LC16M8A2, this is 4096 times every 64ms. Those 4096 refresh commands can be issued all at once or in a distributed fashion. One way of doing this is by setting up a timer interrupt that will issue an auto refresh every 15.625us. Care must be taken though, since the SDRAM must be in “all banks precharged” state before an auto refresh command can be issued.
Alternatively, if the SDRAM will not be accessed for an extended period of time, it can be put into the “self refresh” mode by calling sdram_sleep(). In this mode, the SDRAM retains data by executing refresh operations internally. Calling sdram_wake() makes the SDRAM accessible again and auto refresh commands must be issued manually as usual.
Generally, it is convenient to think of the different SDRAM banks as functionally-independent SDRAM “chips” residing in the same package, sharing control, address and data lines. Therefore, it is possible to use a sequence more complex than the one given above by keeping different rows open in different banks, although the improvement in throughput would be marginal, especially in comparison with what can be obtained by an advanced SDRAM controller.
The assembly sources along with the C headers are available for download here.
The SDRAM pin to PIC32 port mapping can be found in the comments at the top. The mapping can be changed, but care must be taken. All control lines must be on one port. Similarly, all data lines must be on one port as well. The address lines are manipulated in many places (as part of some commands such as “Load Mode Register” issued during initialization) so these must be preserved.
The provided source code works with a MT48LC16M8A2 (4 Meg x 8 x 4 banks, 16Mbyte) chip. The SDRAM is accessed in 8 byte bursts (64-bits total). Column addresses have the least significant 3 bits dropped. The code can be adapted to other burst sizes, provided that the Mode Register is properly set during initialization.
The assembly source and the C headers are properly setup to place and execute the functions from PIC32 internal RAM. However, I’ve had to include an empty dummy function to be placed in RAM (with __longramfunc__) in my main C file to get this to work with the C32 suite.