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
