Posted by
| Nick Gammon
Australia (23,070 posts) Bio
Forum Administrator |
Message
| I've been thinking of doing some data collection (eg. make a logic analyzer) and for cases like that the inbuilt SRAM of the Arduino (Atmega328 chip) of 2Kb isn't really enough.
So I looked around for expansion RAM. And found this chip: 23A256. This has 256 K bits (ie. 32 K bytes) of SRAM. You could have multiple extra SRAM chips connected by using a different slave-select line for each one.
[EDIT] As suggested below, the 23K256 would be more appropriate, and you might need some logic-level conversion for the data lines. See reply further down.
Now the difference between SRAM and EEPROM is:
- It is faster because it doesn't have to be "burnt" into the chip.
- It can be used many times without "wearing out".
- It does not retain the data once powered off - so it can't be used for long-term storage.
So, it is useful for "scratch" RAM, something you need during this session.
The chip costs around $US 1.70 in quantities of 10+.
This particular chip interfaces using SPI, which is a fast interface protocol. For more details about SPI see:
http://www.gammon.com.au/forum/?id=10892
Wiring up
Wiring is really simple, check out the pinouts:
Basically for the Arduino Uno (and similar boards) you connect:
- CS (pin 1) to Arduino D10 (SPI slave select)
- SO (pin 2) to Arduino D12 (SPI MISO)
- Pin 3 is not connected
- Vss (pin 4) to ground
- SI (pin 5) to Arduino D11 (SPI MOSI)
- SCK (pin 6) to Arduino D13 (SPI SCK)
- HOLD (pin 7) to +5V (disables "hold" feature)
- Vcc (pin 8) to +5V (power)
Like this:
Now you don't have to use D10 as slave select. If you have other SPI devices using D10 just choose a spare pin as slave select, configure it for output, and set it to high initially. In the code below I have a "define" for the slave select pin (SRAM_DEVICE), so you can easily change it.
Example code
#include <SPI.h>
#define SRAM_DEVICE 10 // SRAM on pin 10
// 23A256 commands
#define SRAM_READ 0b011 // Read data from memory array beginning at selected address
#define SRAM_WRITE 0b010 // Write data to memory array beginning at selected address
#define SRAM_RDSR 0b101 // Read STATUS register
#define SRAM_WRSR 0b001 // Write STATUS register
// operation modes (status register)
#define SRAM_BYTE_MODE 0b00000000 // Byte mode (default operation)
#define SRAM_PAGE_MODE 0b10000000 // Page mode
#define SRAM_SEQN_MODE 0b01000000 // Sequential mode
#define SRAM_RSVD_MODE 0b11000000 // Reserved
#define SRAM_HOLD_MODE 0b00000001 // Set this bit to DISABLE hold mode
// setup SRAM device by turning into sequential mode
void sram_setup (const byte device)
{
digitalWrite (device, LOW); // select device
SPI.transfer (SRAM_WRSR);
SPI.transfer (SRAM_SEQN_MODE);
digitalWrite (device, HIGH); // deselect device
} // end of sram_setup
// write 'length' bytes from 'data' to device at address 'address'
void sram_write (const byte device, unsigned int address, byte * data, unsigned int length)
{
if (length == 0) // don't bother if nothing to do
return;
digitalWrite (device, LOW);
SPI.transfer (SRAM_WRITE); // write mode
SPI.transfer (address >> 8); // high-order address byte
SPI.transfer (address & 0xFF); // low-order address byte
for ( ; length ; --length)
SPI.transfer (*data++); // data byte
digitalWrite (device, HIGH); // deselect device
} // end of sram_write
// read 'length' bytes into 'data' to device from address 'address'
void sram_read (const byte device, unsigned int address, byte * data, unsigned int length)
{
if (length == 0) // don't bother if nothing to do
return;
digitalWrite (device, LOW); // select device
SPI.transfer (SRAM_READ); // read mode
SPI.transfer (address >> 8); // high-order address byte
SPI.transfer (address & 0xFF); // low-order address byte
for ( ; length ; --length)
*data++ = SPI.transfer (0); // data byte
digitalWrite (device, HIGH); // deselect device
} // end of sram_write
void setup (void)
{
Serial.begin (9600);
SPI.begin ();
sram_setup (SRAM_DEVICE);
}
char test [] = "hello, world";
void loop (void)
{
sram_write (SRAM_DEVICE, 100, (byte *) test, sizeof test);
char buf [100];
sram_read (SRAM_DEVICE, 100, (byte *) buf, sizeof buf);
Serial.println (buf);
delay (1000);
}
The example code writes "hello, world" to address 100, and then reads it back into another buffer and displays it to confirm it got it back OK.
I have set the device to sequential mode, which means you can write as many bytes as you like - the address will eventually wrap around and go back to zero if you overflow its size (that is, address beyond 0x7FFF).
Timing
- Time taken to write "hello, world" : 52.2 microseconds
- Time taken to read 100-byte buffer: 338 microseconds
Basically the SPI protocol transfers a byte every 3 microseconds. To read or write from the chip it needs to send an addition 3 bytes at the start:
- The command (1 byte) - that is, read, write, get/set status
- The address (2 bytes)
So it is more efficient to do batches of bytes than one, because the overhead of the command/address is spread over more bytes. Basically the time to read or write n bytes is going to be (n + 3) * 3 microseconds ... plus probably a bit more for the overhead of the loop.
To put that into perspective, I measured the time taken to read back 1000 bytes, which was 3.22 ms, so it is probably fair to say that you can read or write n bytes at the rate of (n + 3) * 3.2 microseconds.
So is this an acceptable speed? I think it is. For example this code:
digitalWrite (8, HIGH);
int val = analogRead(A0);
digitalWrite (8, LOW);
I'm using D8 as a timing pin. By measuring how long it is high I can see how long the analogRead took. And the answer is 115 microseconds (pretty much what the reference predicts, which is around 100 microseconds).
Now adding in saving that data into SRAM, like this:
digitalWrite (8, HIGH);
int val = analogRead(A0);
sram_write (SRAM_DEVICE, 100, (byte *) &val, sizeof val);
digitalWrite (8, LOW);
The time is now 143 microseconds, an additional 28 microseconds. We would predict from the formula above an extra 16 microseconds, so the extra 12 on top of that must have been the time taken to execute the write function and return.
So that shows that storing the value in memory took under half the time taken to read the value in the first place. That isn't too bad. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|