Electronic – arduino – Controlling 23K256 SPI RAM with Arduino

arduinomemoryramspi

I'm trying to interface with a 23K256 SPI RAM IC using an Arduino Mega 2560. I can't use the standard SPI pins, since I'm using an ethernet shield and it doesn't play nicely when SS is set high.

So, instead, I'm trying to bit-bang it. I've tried a few methods of my own, and eventually borrowed the code from the link in this video's description, but it doesn't seem to work at all.

// hold pin is wired directly to VCC (3.3V)
int RAMClockPin = 14;
int RAMInPin = 15;
int RAMOutPin = 11; 
int RAMSelectPin = 12;

// These are hard-wired in the chip
int RAM_READ_COMMAND = 3;    // 00000011
int RAM_WRITE_COMMAND = 2; // 00000010 

//================================================

// Send a byte into the chip's input, bit by bit
void RAMRawWrite(byte b) {

  // Cycle through the 8 bits in the byte
  for (int j = 0; j < 8; j++) {

     // Send MSB (Most Significant Bit) to chip
     if ((b & 0x80) == 0x80) digitalWrite(RAMInPin, HIGH);
       else digitalWrite(RAMInPin, LOW);
     // Send a clock pulse
     digitalWrite(RAMClockPin, HIGH); 
     digitalWrite(RAMClockPin, LOW); 
     // Shift the rest of the bits, one to the left
     b = b << 1;

   }  

}

//================================================

// The whole procedure of sending a data byte
void writeToRAM(unsigned int wAddr, byte wValue) {

  // Pointer to a byte
  byte *bp;

  // Point to higher byte of address (wAddr is 2 bytes long)
  bp = (byte *)&wAddr + 1;
  // Start session
  digitalWrite(RAMSelectPin, LOW);
  // Send the "write" command
  RAMRawWrite(RAM_WRITE_COMMAND);
  // Send the address, one byte at a time
  RAMRawWrite(*bp); // Higher byte
  bp--;
  RAMRawWrite(*bp); // Lower byte
  // Send the data byte
  RAMRawWrite(wValue);
  // Close session
  digitalWrite(RAMSelectPin, HIGH);   

}

//================================================

// The whole procedure of reading a data byte
byte readFromRAM(unsigned int rAddr) {

  byte b, currBit;
  byte *bp;

  // Point to higher byte of address
  bp = (byte *)&rAddr + 1;
  // Start session
  digitalWrite(RAMSelectPin, LOW);
  // Send the "read" command
  RAMRawWrite(RAM_READ_COMMAND);
  // Send address
  RAMRawWrite(*bp); // Higher byte
  bp--;
  RAMRawWrite(*bp); // Lower byte

  // Get bits, MSB-first
  b = 0;
  for (byte currBit = 0x80; currBit > 0; currBit = currBit >> 1) {

     // Clock signal start
     digitalWrite(RAMClockPin, HIGH); 
     // Get one bit of data, put it in the appropriate place 
     if (digitalRead(RAMOutPin) == HIGH) b += currBit;
     // Clock signal end
     digitalWrite(RAMClockPin, LOW); 

  }  

  // End session and return result
  digitalWrite(RAMSelectPin, HIGH);   
  return b;

}

void setup() {
  Serial.begin(9600);
  pinMode(RAMClockPin, OUTPUT);
  pinMode(RAMInPin, OUTPUT);
  pinMode(RAMOutPin, INPUT);
  pinMode(RAMSelectPin, OUTPUT);  
  digitalWrite(RAMSelectPin, HIGH);

  Serial.println("Writing 0xA3 to 16");
  writeToRAM(16, 0xA3);
  Serial.println("Reading back...");
  byte value = readFromRAM(16);
  Serial.println(value, HEX);
}

void loop() {}

The result of the read is 0x00 every time. I've double and triple checked my pin numbers and tried reversing SI/SO, but it still isn't working. Unfortunately I don't have a scope to test the data.

Any ideas?

Best Answer

Your problem is likely logic level mismatch. Since your RAM IC is powered with 3.3V, its output HIGH level on the SO pin can't be larger than 3.3V. Your microcontroller however seems to use 5V (looking at the schematics). Now I haven't consulted AVR datasheets, but most CMOS chips recognize a HIGH level when the voltage on their inputs is larger than 0.7Vcc. This threshold is 0.7*5V = 3.5V, so the microcontroller has every right to not recognize 3.3V as HIGH. This may well explain why you're reading all zeros from your RAM.

Note that the maximum input HIGH level your RAM tolerates is Vcc+0.3V (table 1.1) which in your case equals to 3.6V. That's much smaller than the 5V your MCU outputs, so you might have cooked your RAM already. The difference is certainly enough to bias protection diodes into conduction. Some 3.3V ICs have 5V tolerant inputs, this one does not.

Your options are:

  • Run your MCU at 3.3V. This is often best if your MCU supports that voltage (I think it does) as it avoids the need for level translation completely. Unfortunately this hardly seems possible within the confines of your platform.

  • Increase Vcc of your RAM (datasheet says it can tolerate up to 3.6V). However, this is going to be unreliable since the threshold is only marginally exceeded, so any voltage variations (and even internal variations between individual chips) may well lead to problems. This also doesn't solve the input level mismatch, so half of the problem remains.

  • Do logic level translation in both directions. Many solutions to this problem exist, you should search the web (including this website) to get the picture. In short, it's easier to lower the voltage from 5V to 3.3V (a divider is enough, perhaps with a diode) than to do the inverse (you'll need to use a FET or an IC as a buffer). This appnote shows some circuits, as does this page. See also this product from Sparkfun for a similar idea. HEF4050BT is a buffer IC with absolute input thresholds (HIGH is 3V) and can tolerate up to 15V on inputs. I'm sure more possibilities exist, but I personally believe it's best to power your microcontrollers with 3.3V and save the trouble. Many useful ICs aren't 5V compatible (or tolerant) these days.