AT45 Series SPI Flash

This experiment is to take the widely available AT45 SPI flash parts from Atmel, hide all the awkwardness that you have to go through to use SPI Flash, and present an interface that is far more friendly..

The story so far is that I i've actually already done all this, but i'm not happy with the implementation, and it's not properly tested. Also, I have negleced to build it for an audience wider than myself, and I didnt write it up as I went. So the plan now is to throw away what i've done so far, and start again keeping in mind the lessons i've learnt, getting some other input, and write it up as I go too!

AT45 Background

There are a range of parts in the AT45 series, with a specific part being AT45DBxxxD, were the numbers denote the memory size

Code Density (MBit) Page size (bytes) Number of Pages Package ID Register
011 1 256 512 8 SOIC
021 2 256 1024 8 SOIC
041 4 256 2048 8 SOIC
081 8 256 4096 8 SOIC 0x1F25
161 16 512 4096 8 SOIC 0x1F26
321 32 512 8192 8 SOIC
641 64 1024 9192 28 TSOP

A data sheet for the AT45DB081 is linked bellow. The data sheets are pretty much the same for each.

Hardware

Firstly, there are several parts in the AT45 range, from 1Mbit to 64Mbit. They are essentially identical in their operation, but they vary in the number of pages and thier size. Additionally, the 1,2,4,8,16 and 32Mbit parts are all available in the same SMD SO8 package. The 64Mbit parts is in a crazy TSOP. My solution to this was to buy one of each from Rapid Electronics, and mount each one on a 8 pin dip style module made from strip board. The 8 pin DIP layout mimics the original device pinout. I mounted the 64Mbit TSOP part on an 8 pin DIP module too, and wired it so that it is pin compatible with the others. I eventually want to build a generic AT45 class which can detect which part is it talking to, hide the page number/size difference, and present a uniform interface. By having an API independent from the part size, and putting the different parts in the same DIP package, i should be able to test the whole range with the same binary.

Wiring it up

The AT45 family uses a standard 4 wire SPI interface, and i'll be wiring it up like this :

Signal AT45 pin mbed Pin
MOSI 1 p5
SCK 2 p7
nRST 3 -
nCS 4 p8
nWP 5 -
Vcc 6 Vo
Gnd 7 Gnd
MISO 8 p6

Software

This is the sort of API that I am aiming for. all suggestions welcome :

// definite :

void AT45::AT45 (int mosi, int miso, int sck, in ncs);  // constructor
int AT45::size (void); // return the size of the part in bytes

// RAM functionality
int AT45::read (int address);
void AT45::write (int address, int data);

// erase functions
void AT45::erase (void); // erase the full device

// block functions
// regardless of the size of the pages, the parts will be exposed and a 512byte block device.
int AT45::blocks (void); // return the size of the part in bytes
void AT45::bread (char* data, int block); // Read the numbered block into the 512 byte char array
void AT45::bwrite (char* data, int block); // Write the numbered block from the 512 byte char array
void AT45::berase (int block); // erase the numbered block

// speculative :
// Might add some buffered write functionality
// This is stop the pages being hammered with read-modify-write operations when doing a burst access
// Flushing could be manual, or periodic, now that we have a ticker capability
void AT45::mode ([buffered|unbuffered]); // buffer writes or not?
void AT45::flush (void); // flush write buffers
void AT45::bRead (int* buf, int address, int length); // read from Flash into an array
void AT45::bWrite (int* buf, int address, int length); // Write to Flash from an array

I'm still in two minds about the buffering. If it is implemented, it will enormously increase the write performance of writes to consecutive addresses because for single writes we have to do read-modify-write on an entire page at a time. However, it will mean that if power fails you might loose data that you thought was safely in non volatile flash memory. Standard tradeoffs I guess.

Hello World!

the first thing to do is prove that I can talk to the device, by reading it's ID register. this register also tells me a lot about the device I am talking to, such as capacity and page size.

#include "mbed.h"

Serial pc (USBTX,USBRX);

DigitalOut _ncs(p8);
SPI _spi (p5,p6,p7);

int main() {

  _ncs = 1;
  _spi.format(8,0);
  _spi.frequency(5000000);

  _ncs = 0;
  _spi.write(0x9f);
  int ID  = _spi.write(0x00) << 8; // write dummy word to get upper byte
  ID |= _spi.write(0x00);          // write dummy word to get lower byte
  _ncs = 1;
  pc.printf("ID register is 0x%X\n",ID);
}

Output :

"ID register is 0x1F25"

Yeah! It works :-) I am reading the ID register of an 8Mbit part.

By reading 0x1F25 from the ID register, we can conclude that the part is an Abut T45DB081 .. This is really useful information, especially as I managed to entirely encapsulate the device in hot melt glue, so without the ID register, i'd not have a clue what part I actually have :-)

Accessing the SRAM buffers

Right, the next thing to do is to start accessing the SRAM buffers inside the AT45 parts. Each buffer is the size of a single flash page. We can read a page from flash into a buffer, write a buffer back to a flash page, and read/write it over SPI.

First thing to do is write a bunch of stuff into each buffer, then read it back to make sure it got there.

So, looking at the datasheet I see that :

  • There are two buffers to write to
  • Each buffer/page is either 256 or 264 bytes long
    • Note, i'll always only implment the 2n pages, even if the device is configured otherwise
  • The sequence to write a buffer is :
    • select
    • Command 0x84/0x87 buffer1/buffer2
    • Three bytes of address. (8Mbit part, 2n page size this is 16 dont care bits, plus 8 bit address)
    • 1-256 bytes of data.
    • deselect
  • The sequence to read a buffer is :
    • select
    • Command 0xd4/0xd6 buffer1/buffer2
    • Three bytes of address. (8Mbit part, 2n page size this is 16 dont care bits, plus 8 bit address)
    • one dont care byte
    • 1-256 bytes of data.
    • deselect

So i'll give the following a bash

#include "mbed.h"

Serial pc (USBTX,USBRX);

DigitalOut _ncs(p8);
SPI _spi(p5,p6,p7);

int main() {

  _ncs = 1;
  _spi.format(8,0);
  _spi.frequency(5000000);

  _ncs = 0;
  _spi.write(0x9f);
  int ID  = _spi.write(0x00) << 8; // write dummy word to get upper byte
  ID |= _spi.write(0x00);          // write dummy word to get lower byte
  _ncs = 1;

  pc.printf("ID register is 0x%X\n",ID);
  
  wait(1.0);
  
  pc.printf("Writing to SRAM buffer #1\n");
  
  _ncs = 0;
  _spi.write(0x84); // Buffer 1 write command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  
  // Write 8 bytes of data
  for(int i = 0 ; i < 8 ; i++){
    _spi.write(i);
  }
  _ncs = 1;
  
  wait(1.0);
  
  pc.printf("Reading from SRAM buffer #1\n");
  
  _ncs = 0;
  _spi.write(0xd4); // Buffer 1 write command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0

  _spi.write(0x0);  // dont care byte
  
  // read 8 bytes of data
  for(int i = 0 ; i < 8 ; i++){
    pc.printf("Data at %d is %d\n",i,_spi.write(0));
  }
  _ncs = 1;
  
}

Output :

ID register is 0x1F25
Writing to SRAM buffer #1
Reading from SRAM buffer #1
Data at 0 is 0
Data at 1 is 1
Data at 2 is 2
Data at 3 is 3
Data at 4 is 4
Data at 5 is 5
Data at 6 is 6
Data at 7 is 7

Yay!

Accessing Flash

I can now read and write the SRAM buffers, so the next things I need to master are

  • Writing a buffer to Flash
  • Reading from Flash into a buffer
  • Reading directly from flash
  • Erasing flash

Once I can do these four things, my basic interaction with the AT45 is complete. All the other functionality I want to implement is built out of these basic operations.

One thing to watch out for is that Flash interaction takes a few milliseconds, so doing lots of operations back to back will mean checking for the BUSY bit in the status register. When this is built into a proper class i'd propose doing this at the start of a flash operation, rather than waiting for the operation to complete before exiting. This will give non-blocking behavior to single acesses

Looking at the datasheet, the sequence for writing a buffer to flash is:

  • Select
  • Command 0x83/0x86 for buffer1/buffer2
  • 3 byte address
    • Page address. Slightly complicated by the 2n/non-2n issue, but we'll be writing to page0, so it wont make a difference for now
    • bottom byte of address is dont care
  • Deselect

My test program also need to read from flash to verify that the data has arrived. I'll do this by reading from flash into buffer#2, and then reading out off buffer#2 into my program.

The sequence for doing this is :

  • Select
  • Command 0x53/0x55 for buffer1/buffer2
  • 3 byte address
    • Page address. Slightly complicated by the 2n/non-2n issue, but we'll be writing to page0, so it wont make a difference for now
    • bottom byte of address is dont care
  • Deselect

Lastly, I will erase page0 each time this program runs, just to make sure there are no contents left from previous runs.

#include "mbed.h"

Serial pc (USBTX,USBRX);

DigitalOut _ncs(p8);
SPI _spi(p5,p6,p7);

int main() {

  _ncs = 1;
  _spi.format(8,0);
  _spi.frequency(5000000);

  _ncs = 0;
  _spi.write(0x9f);
  int ID  = _spi.write(0x00) << 8; // write dummy word to get upper byte
  ID |= _spi.write(0x00);          // write dummy word to get lower byte
  _ncs = 1;

  pc.printf("ID register is 0x%X\n",ID);


  pc.printf("Erasing flash\n");

  // erase chip
  _ncs=0;
  // 4 byte command sequence
  _spi.write(0xc7);
  _spi.write(0x94);
  _spi.write(0x80);
  _spi.write(0x9a);
  _ncs=1;

  wait(0.5);  // wait for flash operation to complete

  pc.printf("Reading from Flash page 0 into SRAM buffer #2\n");

  _ncs = 0;
  _spi.write(0x55); // Read Flash to buffer 2
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _ncs = 1;

  wait(0.5);  // wait for flash operation to complete

  pc.printf("Reading from SRAM buffer #2\n");

  pc.printf("Reading 16 bytes. All should be random i.e. blank\n");
  
  _ncs = 0;
  _spi.write(0xd6); // Buffer 2 read command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0

  _spi.write(0x0);  // dont care byte
  
  // read 16 bytes of data
  for(int i = 0 ; i < 16 ; i++){
    pc.printf("Data at %d is %d\n",i,_spi.write(0));
  }
  _ncs = 1;
  
  wait(1.0);
  
  pc.printf("Writing to SRAM buffer #1\n");
  
  _ncs = 0;
  _spi.write(0x84); // Buffer 1 write command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  
  // Write 8 bytes of data
  for(int i = 0 ; i < 8 ; i++){
    _spi.write(i);
  }
  _ncs = 1;
  
  wait(1.0);
  
  pc.printf("Reading from SRAM buffer #1\n");
  
  _ncs = 0;
  _spi.write(0xd4); // Buffer 1 read command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0

  _spi.write(0x0);  // dont care byte
  
  // read 8 bytes of data
  for(int i = 0 ; i < 8 ; i++){
    pc.printf("Data at %d is %d\n",i,_spi.write(0));
  }
  _ncs = 1;
  

  wait(1.0);
  
  pc.printf("Writing SRAM buffer #1 to Flash\n");

  _ncs = 0;
  _spi.write(0x83); // Write Buffer 1 to Flash
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _ncs = 1;

  wait(0.5);  // wait for flash operation to complete

  pc.printf("Reading from Flash into SRAM buffer #2\n");

  _ncs = 0;
  _spi.write(0x55); // Read Flash to buffer 2
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _ncs = 1;

  wait(0.5);  // wait for flash operation to complete

  pc.printf("Reading from SRAM buffer #2\n");

  pc.printf("Reading 16 bytes. Last 8 bytes will be random\n");
  pc.printf("as SRAM #1 bytes 8-255 was in a raond state when it was written\n");
  
  _ncs = 0;
  _spi.write(0xd6); // Buffer 2 read command
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0
  _spi.write(0x0);  // address 0

  _spi.write(0x0);  // dont care byte
  
  // read 16 bytes of data
  for(int i = 0 ; i < 16 ; i++){
    pc.printf("Data at %d is %d\n",i,_spi.write(0));
  }
  _ncs = 1;


}

Ouput :

ID register is 0x1F25
Erasing page 0
Reading from Flash page 0 into SRAM buffer #2
Reading from SRAM buffer #2
Reading 16 bytes. All should be random i.e. blank
Data at 0 is 255
Data at 1 is 255
Data at 2 is 255
Data at 3 is 255
Data at 4 is 255
Data at 5 is 255
Data at 6 is 255
Data at 7 is 255
Data at 8 is 255
Data at 9 is 255
Data at 10 is 255
Data at 11 is 255
Data at 12 is 255
Data at 13 is 255
Data at 14 is 255
Data at 15 is 255
Writing to SRAM buffer #1
Reading from SRAM buffer #1
Data at 0 is 0
Data at 1 is 1
Data at 2 is 2
Data at 3 is 3
Data at 4 is 4
Data at 5 is 5
Data at 6 is 6
Data at 7 is 7
Writing SRAM buffer #1 to Flash
Reading from Flash into SRAM buffer #2
Reading from SRAM buffer #2
Reading 16 bytes. Last 8 bytes will be random
as SRAM #1 bytes 8-255 was in a raond state when it was written
Data at 0 is 0
Data at 1 is 1
Data at 2 is 2
Data at 3 is 3
Data at 4 is 4
Data at 5 is 5
Data at 6 is 6
Data at 7 is 7
Data at 8 is 175
Data at 9 is 202
Data at 10 is 92
Data at 11 is 102
Data at 12 is 185
Data at 13 is 236
Data at 14 is 66
Data at 15 is 118

Yay! It works

So the main interactions that I want are working quite happily now. The only thing I have not tested in the reading directly from Flash. Having said that, everything has worked smoothly so far, and the code is getting a bit cumbersome, so I will defer this one last thing until I need it.

In the mean time, I think it is time to start wrapping this up into a class.

Building A Class

As I have been access address 0x0-0xf, I have not yet encountered any of the adress calculation issue or other funny quirks. So I hit them all, here is the list:

  • The class needs to interogate the device to find the size, number of pages and page size. These will be used internally for address calculation and may be interrogated.
  • The parts come shipped as non-2n pages. Once the command has been sent to make then 2n pages is can not be reversed. Even when the device is set to non-2n, the class should map out the additional bytes.
  • The class needs to implement a proper blocking function to stop flash commands proceeding before the previous one has completed.

Building A Class (reprise)

Okay, so I had good intentions of writing all this up as I went along, but the truth is I've had a bit of a nightmare in getting this all working and verified. It all boiled down to a typo in the code that detected the page size (2n or non-2n) and set an internal flag accordingly. This meant all the addresses translations were going slightly wrong, so it was sort of working but not quite.

Anyway, it is now, fixed and I have verified it all works, with the follwing constraints :

  • Random accesses are just they, they do a read-modify-write cycle on the entire page.
  • No write buffering functionality implemented.
    • Still in two minds about this, as it is a trade off between performance and certainty the data was written
  • Block access methods added, cos they seemed useful
  • Block erase not implented
  • full chip erase uses the full chip erase command - there have been errata about this. Need to follow up on tihs. Might be bettter to use a bunch of sector erases instead.

the final API for this is :

    AT45(PinName mosi, PinName miso, PinName clk, PinName ncs);

    void erase (void);

    // Integer RAM access
    char read (int address);  // read int from address. Automatically word-aligns address
    void write (int address, char data); // Write int to address. Automatically world-aligns address

    // Block device access
    int blocks (void);  // returns the number of 512 byte blocks
    int blockread (char* data, int block);  // read a block
    int blockwrite (char* data, int block); // write a block
    int blockerase (int block); // erase a block
    
    // Part interrogation
    int size (void);      // Device size in bytes
    int pages (void);      // Device size in bytes
    int pagesize (void);      // Device size in bytes

    int id (void);        // ID of part
    int status (void);    // Status register
    void pollbusy (void); // Wait until Flash is not busy

Available at :

http://mbed.org/projects/cookbook/svn/AT45/trunk

Have a bash with it - all feeback welcome!

To do

  • Investigate the full chip erase errata, and reimplement erase() as neccessary
  • implement the block erase function
  • try using both SRAM buffers for block accesses where the page size is 256 (possible performance increase)
  • try implementing buffered writes
  • implementt read4 and write4 for aligned integer access