[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Connecting 512 Kbyte EEPROM (flash memory) to your processor using SPI

Connecting 512 Kbyte EEPROM (flash memory) to your processor using SPI

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (22,982 posts)  [Biography] bio   Forum Administrator
Date Thu 10 Mar 2011 11:56 PM (UTC)

Amended on Fri 11 Mar 2011 09:27 PM (UTC) by Nick Gammon

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
[Go to top] top

Posted by Manuel   (4 posts)  [Biography] bio
Date Reply #1 on Mon 25 Jul 2011 05:04 PM (UTC)
Message
Hi Nick.
I have some problem in using the code you posted.

I am using an ATMega328 with an external memory chip AT25DF161.

I verified both the EEPROM commands and the wire connections (CS, MOSI, MISO, SCK) but I cannot R/W from the flash.

The atmel is an 8MHz chip, Any idea?

P.S.
This is the first time I write code for microprocessors

[Go to top] top

Posted by Nick Gammon   Australia  (22,982 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Mon 25 Jul 2011 09:52 PM (UTC)
Message
It's a different chip, although the spec looks similar. It's hard to say without more detail. Personally I wouldn't be doing this as your first project for microprocessors, it's too complex and there is too much to go wrong.

Did you use the buffer chip? Is everything wired exactly as I had it? Did you use the code exactly as posted?

For something like this I would connect up a logic analyzer and make sure that the data is reaching the chip, and seeing what response you get. It isn't clear if there is a wiring problem, a writing problem, or a reading problem.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Manuel   (4 posts)  [Biography] bio
Date Reply #3 on Wed 27 Jul 2011 07:40 AM (UTC)
Message
Hi Nick,
Thanks for the suggestion (starting from an easier project) but I had no choice...
I answer to your questions:
No, I did not use the buffer chip, there's no buffer chip on the board, everything is +3.3V.
And no, I did not copy-paste the code: I tried just to read the manufacturer ID and everything hangs or in the best case, I don't receive any data.

If I correctly understand the wiring diagram, if I directly wire the microprocessor to the flash, bypassing the buffer chip, everything should still work, right?

I try to use the code as-it-is and let you know what happens.



[Go to top] top

Posted by Manuel   (4 posts)  [Biography] bio
Date Reply #4 on Tue 02 Aug 2011 12:57 PM (UTC)
Message
...finally!

Hi Nick, Finally I successfully communicated with the chip.
It seems a speed problem: once set up the lowest SPI speed everithing worked fine.

This is the code for setup:

/.../
initPins(); //initialize data pins
digitalWrite(SLAVESELECT,HIGH); //disable device

DDRB = (1<<DDB5)|(1<<DDB3)|(1<<DDB2); // set DDRB register
delay(100); // delay

SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR1)|(0<<SPR0);

clr=SPSR;
clr=SPDR;

delay(100); // delay
/.../

char spi_transfer(char data)
{
SPDR = data; // Start the transmission

while (!(SPSR & (1<<SPIF))) // Wait for the end of the transmission
{
};

return SPDR; // return the received byte
}

void initPins()
{
pinMode(DATAOUT, OUTPUT);
pinMode(DATAIN, INPUT);
pinMode(SPICLOCK,OUTPUT);
pinMode(SLAVESELECT,OUTPUT);

}

void getManufacturerID()
{
digitalWrite(SLAVESELECT,LOW);

spi_transfer(RMDID);//transmit read device and manf. ID
eeprom_output_data = spi_transfer(0xFF);
Serial.print("Data: ");
Serial.print(eeprom_output_data,HEX);

eeprom_output_data = spi_transfer(0xFF);
Serial.print("Data2: ");
Serial.print(eeprom_output_data,HEX);
digitalWrite(SLAVESELECT,HIGH);

delay(500);

}

I did not use SPI primitives. I was not able to set up SPI speed.

I'll work on that ASAP.
[Go to top] top

Posted by Nick Gammon   Australia  (22,982 posts)  [Biography] bio   Forum Administrator
Date Reply #5 on Tue 02 Aug 2011 09:13 PM (UTC)
Message
Glad that's working. You should be able to slow down SPI like this:


  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);


(or SPI_CLOCK_DIV16).

You shouldn't need all that low-level stuff.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Manuel   (4 posts)  [Biography] bio
Date Reply #6 on Wed 03 Aug 2011 08:25 AM (UTC)
Message
Hi Nick,
I tried to use that primitive (also with DIV64) but without success.

I wonder how, having only 2 bits for clock speed management, the SPI gives at least 8 possible speeds.

I will do some heavy debug in the next few days.
[Go to top] top

Posted by Nick Gammon   Australia  (22,982 posts)  [Biography] bio   Forum Administrator
Date Reply #7 on Wed 03 Aug 2011 08:35 PM (UTC)
Message
Three bits, see page 174 of the datasheet:

Quote:

* Bits 1, 0 – SPR1, SPR0: SPI Clock Rate Select 1 and 0
* Bit 0 – SPI2X: Double SPI Speed Bit



- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Andrewr123   (2 posts)  [Biography] bio
Date Reply #8 on Tue 13 Sep 2011 07:07 PM (UTC)
Message
Hi Nick

My first post and many congratulations on the site - a great resource for electronics and Arduino interfacing.

My question on this topic relates to your use of the sn54ahc125. As I understand it, this will accept 5v logic and translate to 3.3v. But I don't see how it translate the MISO signal back from 3.3v to 5v. Can you explain?

I'm really interested in using this device as it comes in a DIP package, much easier than the SOIC or SSOP packages most things seem to come in these days such as the SN74LVC4245ADWR, which otherwise seems perfect.

Best regards, Andrew
[Go to top] top

Posted by Nick Gammon   Australia  (22,982 posts)  [Biography] bio   Forum Administrator
Date Reply #9 on Wed 14 Sep 2011 09:13 PM (UTC)
Message
Good question. It doesn't, as far as I can see.

However looking at the Atmega328 datasheet the minimum voltage needed to be considered "high" on a digital pin is 0.6 * Vcc.

Since Vcc is 5V, then a high would be 3V upwards.

Since the buffer chip will output 3.3V that would be adequate to be considered high. In fact, for the MISO line you might have omitted the buffer chip altogether, but I didn't test that.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Andrewr123   (2 posts)  [Biography] bio
Date Reply #10 on Fri 16 Sep 2011 08:21 PM (UTC)
Message
Thanks, I can see how that works. I've got a similar situation with an ePIR device which has Vcc @ 3.3v but will accept 5v in. As you say, assuming the slave produces at least 3v then the ATMega should be happy.

Shame the only genuine converter is an SMD device. Will have to get my magnifying glass out!
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


43,769 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]