Using SDRAM with AVRs

I could've sworn I had a page up about this, already... but I don't see it... so here's a brain-dump that I'll try to remember to update, later, with better explanations, code-snippets, and notes from past experiences. NOTE THAT THIS IS ALL FROM MEMORY, so some details may not be correct!

(do email me to remind me!)

SCOPE:

The idea is how to connect a simple microcontroller, such as an Atmel AVR, to SDRAM, to give huge amounts of RAM to your microcontroller project...

This page is about SDR ('single-data-rate') SDRAM only.

DDR ('dual-data-rate') SDRAM is *very* different, and downright daunting to me to consider interfacing with a system not designed specifically to do-so.

(see notes at the bottom, if you don't believe me).

My advice: Unless you want to break new ground, stick with SDR SDRAMs, such as PC-66, PC-100, PC-133.

SDRAM. SINGLE-DATA-RATE

Initialization

The initialization-process basically makes use of nearly every function needed to use SDRAM, including some you'll never use again. Which is a bit unfortunate, because it means you have to code-up nearly everything *before* you can even do a simple read/write experiment. So you kinda have to go into it blindly trusting that it was coded-up right.

On top of that, the procedure I read was worded poorly, so it sounded *way* more confusing than it really is. I've tried to simplify it a bit in my code. https://github.com/ericwazhung/sdramThingZero/blob/master/sdram_general.c scroll down to sdram_init() (line 313, currently). Here it is:

void sdram_init(uint8_t devNum)

{

//why?

uint8_t CLI_SAFE(oldI);

//TODO: Do we need to consider pull-resistors for this early test?

//Start the ports/pins to match their reset/pulled-states:

sdram_initAddrCmdIO();

//Initialization/Power-Up sequence per Micron 64mb data-sheet:

//1) Simultaneously apply power to VDD and VDDQ --OK

//2) Assert and hold CKE at logic LOW

// This is probably unnecessary,

// the specs say it's OK to tie CKE high...

sdram_clockDisable();

//3) Provide a stable CLOCK signal... --OK

//4) Wait at least 100us prior to issuing any command other than

// Inhibit or NOP...

//AND (now less-confusingly-worded):

//5) within there, bring CKE High, issuing a few CMD_INHIBITS (or NOPs)

_delay_us(50);

sdram_clockEnable();

//since nCS is high, CMD_INHIBITS are issued hereafter

_delay_us(50);

//6-13) These are all prepping-for/related-to loadModeRegister:

// (In fact, sdramThing4.0 and earlier called these loadModeReg()

//6&7 make up precharge()...

//6) Perform a PRECHARGE ALL command:

//7) Wait at least tRP (20ns!) ( included in precharge() )

sdram_precharge(devNum, PRECHARGE_ALL);

//8&9 make up autoRefresh()...

//8) Issue an AUTO REFRESH command:

//9) Wait at least tRFC (66ns)

sdram_autoRefresh(devNum);

//10&11) Repeat 8&9:

sdram_autoRefresh(devNum);

//12) Now for the ACTUAL LMR command:

// FYI: (0)0 0000 0011 0111 (BA 00)

// a) Set BA1,0=0, Set Address=Mode

sdram_setupAddress( SDRAM_MODE__WRITEBURST

| SDRAM_MODE__PAGEBURST

| SDRAM_MODE__CASLATENCY(3), //Don't change

0); //BankAddress == 0

// b) Send the LMR command

sdram_executeCommand(devNum, SDRAM_CMD__LOAD_MODE_REGISTER);

//13) Wait at least tMRD (2 SDRAM clocks)

asm("nop;"); asm("nop;"); asm("nop;");

//"At this point the DRAM is ready for any valid command."

SEI_RESTORE(oldI);

}

I never tried read/write *without* an init() first, but it's plausible they'd function, to some extent, without. The potential-randomness of what could happen might be more daunting than coding up that init() routine (and all its dependencies) correctly in the first place. (And, on the plus-side, once you've done that, all that stuff is "ret-to-go" for the remainder of your project).

Pull Resistors

It's handy to have a few pull-resistors setup e.g. to stop burst-reads/writes when In-System-Programming your AVR with a new code-version... it really depends on your wiring, but consider e.g. CKE, /CS, and DQMs.

Commands

SOME commands can be issued back-to-back.

Some CANNOT (allegedly).

This is important when considering interfacing with e.g. an AVR!

(Unfortunately, some that *cannot* are necessary during initialization! WEEEE!)

Say your AVR and your SDRAM are running from the same clock...

Now say you're connecting one pin on your AVR to the SDRAM's Chip-Select input.

Thus, when /CS is High, whatever's output to your SDRAM's other command-inputs is *INHIBITED*.

Excellent!

So you can write your SDRAM command to PORTA... and then Strobe PB0 (connected to /CS) Low, to execute the command.

Great!

But!

if you did, say,

PORTB &= ~(0x01);

PORTB |= 0x01;

you might "luck out" that your compiler would compile that as the following assembly-instructions.

cbi PORTB, 0

sbi PORTB, 0

Two instructions! Great!

No. Those two instructions are *two clock-cycles* apiece.

So that means your /CS will be active for *two clock-cycles*.

Which means that, say, your command was "Activate Row 0, bank 0"...

Now you've actually activated row 0 on bank 0 *twice*. And, technically, you're not supposed to do that. I don't know what'd happen, in this case... it *may* be OK, or it might cause some oddities.

Further, as I recall, there are some other instructions that are even more sensitive to being executed back-to-back like this.

So, what do you do?

How about:

uint8_t portB_unstrobed = PORTB | (0x01);

uint8_t portB_strobed = portB_prior & ~(0x01);

PORTB = portB_strobed;

PORTB = portB_unstrobed;

These assignments will actually be one cycle apiece. Excellent!

No...

The compiler will *most-likely* optimize this to use up as few resources as possible... so, most-likely, will compile this as something like:

mov r3, PORTB

and r3, ~(0x01)

mov PORTB, r3

or r3, 0x01

mov PORTB, r3

See, it's using only one register! Smart, huh?

But, now, we've got an OR between the two mov PORTB's. Two Clock Cycles. Back-to-back execution of the same instruction, again.

To get around this, you've got to use inline-assembly, something like:

asm("

mov r3, PORTB

mov r4, r3

or r3, 0x01

and r4, ~(0x01)

mov PORTB, r4

mov PORTB, r3

");

Now PB0 will be low for *exactly* one clock-cycle.

(Beware! inline-assembly is a bit convoluted... look into it! Or, maybe, insert explicit pushes/pops for r3 and r4 before and after your code?).

-----------

Another trick, I haven't tried this, but it should work: Use a timer/counter to output a pulse exactly one clock-cycle wide (e.g. use PWM with a duty-cycle of 1/256). (Note that different AVRs' PWM systems work differently... some may allow exactly 0/256 - 255/256 duty-cycles, while others may allow 1/256 - 256/256 duty-cycle. And, still others can be configured to allow the entire range... https://hackaday.io/project/5268-avr-random-potentially-obsure-potentially-usefuls).

-----------

SOME instructions *CAN* be executed "back-to-back" and can actually be handy to do-so...

For instance a Burst-Read instruction can be "interrupted" by another burst-read instruction.

So, say you want to read column 0, but it takes time to switch your data-lines back to inputs, or other preparations, or maybe you even want this part to be run while interrupts are enabled...

PSEUDO CODE!

PORTA = <READ-BURST command>

PB0 = 0; //enable chip-select

DDRC = ALL_INPUTS;

asm("nop"); //maybe a few times, for CAS-Latency, and whatnot

uint8_t read_val = PORTC;

PB0 = 1; //disable chip-select

Now, note... what you've essentially done is issued several, maybe even *dozens*, of "Read-Burst" commands, back-to-back, one with every clock-cycle between PB0 = 0 and PB0 = 1. BUT: all those read-burst commands begin a read-burst at column 0! So, every response, at every clock-cycle (after the initial CAS-Latency) will be *from* column zero. It's almost like having a /RD pin on an SRAM, in that the output data remains active until you change the control/address pins.

--------------

The same is true for WRITE commands!

Whew!

-------------

REFRESH

Technically, you're supposed to refresh the entire memory every 64 (or 16?) milliseconds!

Trick1: Opening a page and then closing it (using the activate and precharge commands) is all that's necessary to refresh that page. You don't have to use any of the explicit refresh commands!

Trick2: My experience has been that refresh really only has to occur on the order of about once every *TEN SECONDS*. (no kidding). OF COURSE, this will be dependent on the device itself, so do experiment. But, if you're busy doing something and forget to do a refresh every once in a while, it really shouldn't matter.

Combine the two, and consider, e.g., a frame-buffer. Well, your frame-buffer's going to cycle through every memory location to draw to the screen, then you have to open/close every page... so by refreshing the screen, you're inherently refreshing the SDRAM. Handy.

(You may note that my sdramThing4.5 project refreshes an LCD at a rate of about once every two seconds. That's enough to refresh the SDRAM reliably enough for my needs... I ran it for 7 hours straight, once!)

DIMMs

168-pin SDRAM DIMMs have multiple chip-selects. A single-sided DIMM usually has *two*. Thus, you can divide your RAM into two 32-bit groups (often they're called "banks", but that's confusing since SDRAM has an internal "bank" which is entirely different). These two "groups" can do *entirely* different things, pretty much simultaneously. They do, however, share the same command/address inputs, so you have to interleave the commands.

E.G. Say you wanted to create a continuous "echo" sound-effect... You could have one "group" recording audio-samples while the other group is repeating audio-samples it recorded previously. Then, swap their roles, repeating the audio-samples from the first group, and sampling new ones into the second.

Dividing a DIMM into two separate groups is a fundamental concept used in my "Free-Running" setup, described later.

DATA MASKs

DIMMs have 8 "data-mask" inputs... (DQMB7:0). This allows you to select which byte(s) you wish to read/write. Masked bytes are NOT [re]written!

So, you could, e.g. wire your 64-bit-wide DIMM down to 8-bits-wide with no additional circuitry, just a few GPIOs to select. (It's kinda like having a bunch of 8-bit-wide SRAM chips all connected to the same 8-bit bus, and using their /CS lines to choose one).

Note, however, that the commands are executed on all the chips... So, if you issue an "activate row" command, all the chips connected to that SDRAM-/CS input will activate that same row, regardless of the DQMB inputs. Similarly, a read-burst will execute *in the background* on *all* the chips, it's just that they will only *output* that data if their DQMB input is not-masked.

BURSTS

SDRAM (SDR, not DDR!) allows for burst-lengths of 1, 2, 4, 8, and "full-page". Cool.

When using "full-page" bursts, they even wrap-around. So, say your SDRAM's page-width is 1024 bytes... And, say you've connected those bytes to a Digital-to-Analog Converter...

You could, e.g., output a continuous sine-wave, 1024 samples long, just issuing one "read-burst" command, and letting it run indefinitely.

CLOCK SPEEDS

I haven't seen any specifications regarding a lower-limit for SDR (NOT DDR) SDRAM clock-speeds. I have definitely run it down to 8MHz, and maybe even 4MHz, but no slower.

Technically, the clock-rate is supposed to be somewhat stable; changing speeds is only supposed to be done by putting the device in "standby" first.

I have a feeling this is more just a specification such that all devices from different manufacturers will be satisfied, it may be that some designs could accept a completely arbitrary clock-input... unlike DDR, SDR devices don't have internal PLL's (or DLL's) that need to sync with the external clock.

CLOCK-SHIELDING

A lot of oddities can be blamed on a noisy clock-signal. I've found that using a quality shielded cable can make a world of difference. (in sdramThing1.0, running at 16MHz, it went from 75% read/write/verify failures to 0%, over several hours' testing!). My preferred is the wire usually found in "pho'd"-laptops leading to the WiFi antenna. I've also used a board-to-board video cable from inside a VCR with decent results. But have found that lower-quality shielded cables (e.g. those used for board-to-board audio) aren't ideal.

ALSO

The clock signal "fans-out" to every chip on the DIMM... that's a lot of loading! It might be wise to consider a clock-buffer, maybe as simple as a few 7404 inverters, or e.g. a CY2305.

Keep in mind that adding gates will also add delays, which could be to your advantage or disadvantage.

Clock-buffers weren't especially necessary, in most of my projects, but once I used the CY2305, it was nice enough to include in all my later projects. The main thing I use it for is: Most of my crystal-oscillators (yahknow, the metal cans with 4 pins) are 5V-only, the CY2305 is 5V-tolerant, and outputs several parallel perfectly-sync'd 3.3V clock outputs. Similarly, some of my crystal-oscillators don't have very strong drive-strength.

CLOCK-ENABLE (CKE)

Clock-Enable allows pausing of read/write bursts... and that's pretty much it.

According to the specs, as I understand them, it shouldn't be disabled at other times.

So, if you're using Reads/Writes without making use of bursts (as in the example, above), then you can probably just tie CKE to *enabled*.

OTOH, it's quite handy if you need it: e.g. my project sdramThingZero wouldn't be possible without it.

"Free Running"

This is something I came up with on my own... I dunno if others have done this.

Wherein you connect the SDRAM's command/address signals back to its data I/O's. You can then load the memory with commands to be executed by the memory, later.

It allows for things like bursting the *entire* memory contents (rather than a single 256-1024Byte page) for things like Frame-Buffers, and Logic-Analyzers, and samping/repeating, and doing-so at speeds faster than the microcontroller-clock, and without microcontroller intervention.

It's the basis for all my SDRAM-based projects, but is probably a bit overkill for most uses of SDRAM. Check out the pages, about it.

Why Not DDR, DDR2, DDR3, etc...

I have not looked into anything beyond DDR[1]... But here's what I know from what I've read:

    • DDR has a minimum clock-frequency of 85MHz

      • Way higher than, e.g., an AVR's

      • It must be *stable*, changes in clock-frequency require a convoluted process and resynchronization.

    • DDR has "weird" voltage-levels, termination-requirements, and some differential-signals

    • DDR does *not* have CKE ("Clock-Enable")

    • DDR's longest "burst" is 8 address-locations

    • (Do bursts automatically terminate? Or do they cycle, if allowed?)

All but the first could probably be worked-around, depending on your project's needs. The first requirement (85MHz), however, would be very difficult to work with (especially when lacking CKE). Though, who knows, maybe they'll run slower, if given a chance.

Workaround ideas:

2.7V(?)... Some AVRs can handle that...

DDR's *commands* occur in the normal way... They're only registered on the rising-edge (?) of the clock. (Single-Data-Rate commands, Dual-Data-Rate data I/O).

E.G. It might be possible to treat DDR as though it doesn't have bursts, at all... so, say you've got a 256MB DIMM, you might be able to use it as 256/8 = 64MB, by treating each "burst" as a single memory-location... Maybe using the technique earlier (repeated "burst-read" commands), that 256MB could be treated as 128MB.

Maybe, if it can run slower than 85MHz (much slower), it could be run at half the AVR's clock-frequency, and every data-location could be accessible.

-------

Actually, most of the caveats in that list have more to do with Free-Running, rather than merely being able to interface it with an AVR, period.... So, maybe you should give it a try!

My RELATED PROJECTS/PAGES

https://hackaday.io/project/5268-avr-random-potentially-obsure-potentially-usefuls

https://hackaday.io/project/5624-potentiallyusefulfrustratingobscure-cgccmake

https://hackaday.io/project/10119-sdramthingzero-133mss-32-bit-logic-analyzer

https://github.com/ericwazhung/sdramThingZero - not well-updated.

https://hackaday.io/project/4159-sdramthing45-logic-analyzer

https://sites.google.com/site/geekattempts/home-1/sdramthing