Posted by
| Nick Gammon
Australia (23,133 posts) Bio
Forum Administrator |
Message
| This post describes how I extended my Arduino's flash (EEPROM) memory by adding on an Atmel AT25DF041A "4 megabit SPI Serial Flash Memory" device. Four megabits is 512K bytes. The device itself is very physically small, as shown here:
It's not a huge amount larger than the head of a match, which brings us to the first challenge - actually handling it. To solder it in place I bought an 8-SOIC adapter board, like this:
Then by carefully holding the chip with tweezers, and using a very fine tipped soldering iron (tip size of 0.2 mm) I was able to solder the chip onto the board, like this:
Chip features
The chip can hold 512 Kb of flash memory (that is, the memory is retained even with the power off, like a USB stick). You can program up to 256 bytes at a time (after which you have to wait while the chip "writes" the data into memory). Programming and access times are claimed to be fast, I'll investigate that further down.
The memory can be read back quickly. It can be erased in blocks of 4 Kb (or larger blocks). Presumably this is designed to allow for the chip to be used as a file system, where you would have a cluster size of 4 Kb or more. That is, when creating a file, memory would be allocated in blocks of 4 Kb.
Pinouts
The memory chip:
Since this chip runs at 3.3 volts rather than 5 volts, I had to use a "buffer" chip which could handle 5V and convert it to 3.3V. I chose to use the Texas Instruments SN54AHC125 "quadruple bus buffer gate with 3-state outputs". Quadruple means it has four buffers on it, which is the exact number needed for SPI (MOSI, MISO, SCK and SS). Here are its pinouts:
And here is how the pins interact:
For each channel there is an input (A), an output (Y) and an output-enable (OE). The OE line needs to be driven low to enable the output, otherwise it is high-impedence (floating). I connected all four OE lines together and pulled them up with a 4.7K resistor, to make sure they were high (that is, disabled) on power-up. Then the Arduino program drove pin 9 (of the Arduino) low to enable the chip, when it was ready.
The 0.1uF capacitor is there to decouple the supply line, otherwise quite a bit of noise can be present, which might affect device stability.
Wiring diagram
This is how the two chips, and the Arduino, were connected together:
Three of the lines from the Arduino (MOSI, SCK and SS) are outputs, so they go to the "A" pins on the buffer. The final one (MISO) is an input, so it goes to the "Y" pin on the buffer.
Example code
The code below erases a 4K block of memory, writes "Hello, World!" to address 0x1000 and reads it back to confirm it was written OK.
The code needs a bit of work to be really useful. For example, you would need to make sure that a block of memory was erased before using it (otherwise you can't write to it).
However it shows the general idea:
// Written by Nick Gammon
// 10th March 2011
#include <SPI.h>
#define CHIP_SELECT 10 // for EEPROM
#define BUFFER_ENABLE 9 // for SN54AHC125 buffer
// AT25DF041A EEPROM commands
// reading
#define ReadArray 0x0B
#define ReadArrayLowFrequency 0x03
// programming
#define BlockErase4Kb 0x20
#define BlockErase32Kb 0x52
#define BlockErase64Kb 0xD8
#define ChipErase 0x60
#define ByteProgram 0x02
#define SequentialProgram 0xAD
// protection
#define WriteEnable 0x06
#define WriteDisable 0x04
#define ProtectSector 0x36
#define UnProtectSector 0x39
#define ReadSectorProtection 0x3C
// status
#define ReadStatus 0x05
#define WriteStatus 0x01
// miscellaneous
#define ReadManufacturer 0x9F
#define DeepPowerDown 0xB9
#define ResumeFromPowerDown 0xAB
// wait until chip not busy
void notBusy ()
{
digitalWrite (CHIP_SELECT, LOW);
SPI.transfer (ReadStatus);
// wait until busy bit cleared
while (SPI.transfer (0) & 1)
{}
digitalWrite (CHIP_SELECT, HIGH);
} // end notBusy
// enable writing
void writeEnable ()
{
notBusy ();
digitalWrite (CHIP_SELECT, LOW);
SPI.transfer (WriteEnable);
digitalWrite (CHIP_SELECT, HIGH);
} // end of writeEnable
// read device status
byte readStatus (void)
{
digitalWrite (CHIP_SELECT, LOW);
SPI.transfer (ReadStatus);
byte status = SPI.transfer (status);
digitalWrite (CHIP_SELECT, HIGH);
return status;
} // end of readStatus
// write status register
void writeStatus (const byte status)
{
writeEnable ();
notBusy (); // wait until ready
digitalWrite (CHIP_SELECT, LOW);
SPI.transfer (WriteStatus);
SPI.transfer (status);
digitalWrite (CHIP_SELECT, HIGH);
} // end of writeStatus
// send a command to the EEPROM followed by a 3-byte address
void sendCommandAndAddress (const byte command, const unsigned long addr)
{
SPI.transfer (command);
SPI.transfer ((addr >> 16) & 0xFF);
SPI.transfer ((addr >> 8) & 0xFF);
SPI.transfer (addr & 0xFF);
} // end of sendCommandAndAddress
// write len (max 256) bytes to device
// Note that if writing multiple bytes the address plus
// length must not cross a 256-byte boundary or it will "wrap"
void writeEEPROM (const unsigned long addr, byte * data, int len)
{
// now write to it
writeEnable ();
notBusy (); // wait until ready
digitalWrite (CHIP_SELECT, LOW);
sendCommandAndAddress (ByteProgram, addr);
for ( ; len ; --len)
SPI.transfer (*data++);
digitalWrite (CHIP_SELECT, HIGH);
notBusy ();
} // end of writeEEPROM
// write one byte to device
void writeEEPROM (unsigned long addr, byte data)
{
writeEEPROM (addr, &data, 1);
} // end of writeEEPROM
// read len bytes from device
void readEEPROM (const unsigned long addr, byte * data, unsigned int len)
{
notBusy (); // wait until ready
digitalWrite (CHIP_SELECT, LOW);
sendCommandAndAddress (ReadArray, addr);
SPI.transfer (0); // clock in "don't care" byte
for ( ; len ; --len)
*data++ = SPI.transfer (0);
digitalWrite (CHIP_SELECT, HIGH);
} // end of readEEPROM
// erase a 4Kb block of bytes which contains addr
void eraseEEPROM (const unsigned long addr)
{
writeEnable ();
notBusy (); // wait until ready
digitalWrite (CHIP_SELECT, LOW);
sendCommandAndAddress (BlockErase4Kb, addr);
digitalWrite (CHIP_SELECT, HIGH);
notBusy (); // wait until done
} // end of eraseEEPROM
// show device info and status
void info ()
{
notBusy (); // wait until ready
digitalWrite (CHIP_SELECT, LOW);
SPI.transfer (ReadManufacturer);
Serial.print ("Manufacturer: ");
Serial.println (SPI.transfer (0), HEX);
Serial.print ("Device ID Part 1: ");
Serial.println (SPI.transfer (0), HEX);
Serial.print ("Device ID Part 2: ");
Serial.println (SPI.transfer (0), HEX);
Serial.print ("Extended Information Length: ");
Serial.println (SPI.transfer (0),HEX);
digitalWrite (CHIP_SELECT, HIGH);
Serial.print ("Status: ");
Serial.println (readStatus (), HEX);
} // end of info
void setup ()
{
Serial.begin (9600);
SPI.begin ();
// enable the SN54AHC125 output enable peons (more work?)
pinMode (BUFFER_ENABLE, OUTPUT); // buffer chip enable
digitalWrite (BUFFER_ENABLE,LOW); // enable
// global unprotect
writeStatus (0);
info ();
// test: write a string to address 0x1000
#define TESTADDRESS 0x1000
eraseEEPROM (TESTADDRESS);
byte hello [] = "Hello, World!";
writeEEPROM (TESTADDRESS, hello, sizeof hello);
// read back to confirm
byte test [sizeof hello] = { 0 } ;
readEEPROM (TESTADDRESS, test, sizeof test);
Serial.println ((char *) test); // display to confirm
} // end of setup
void loop()
{
} // end of loop
Timing
Various operations were timed with the logic analyzer. Units are:
- ms (milliseconds) = 0.001 seconds
- us (microseconds) = 0.000001 seconds
Times were:
- Global unprotect (ie. writeStatus (0)): 55 us
- Erase a 4Kb block of memory: 35 ms
- Write "Hello, World!" as in program above: 63 us
- Read "Hello, World!" back, as in program above: 63 us
- Write 256 bytes to memory: 1960 us (1.96 ms)
- Read 256 bytes back: 869 us
Writing a whole 256 bytes takes somewhat longer than reading it back. This would be right, because the chip has to "commit" the data to memory by flashing the EEPROM. The "Hello, World!" string seemed to write as fast as it was read, leading me to think that probably the chip had cached it up, ready for writing later for efficiency purposes.
The figures above agree roughly with the typical times quoted in the data sheet. The writing maybe took a bit longer than predicted, but that was because I counted setting write-enable, and checking the chip was ready. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|