Interfacing PIC with SPI memory

From Mech
Jump to navigationJump to search


Sometimes, it can be useful to interface the PIC with an external memory for data logging or other reasons, since there is limited memory available on the PIC itself. There are various types of memory which a PIC can communicate with. One common type of memory is EEPROM memory, which is non-volatile, so it keeps its data when power is turned off. One type of EEPROM memory uses SPI to communicate with the PIC. This article discusses connecting the 25AA1024, a one megabit EEPROM, to a PIC 18F4520. SPI is a three wire communication protocol which has a dedicated clock line, a serial in line, and a serial out line. The memory works by accepting commands on the clock in line, and then sending out data when requested on the clock out line.

Notes on the 25AA1024

The 25AA1024 has one megabit (125 kbytes) of memory organized in pages of 256 bytes each. Like other EEPROMS, it has a page buffer which stores data as it is received, and then writes the buffer all at once to the EEPROM memory. Writing takes a long time compared to the speed of data transfer- the SPI line can run up to 20 Mhz, but a write cycle is up to 6 ms. Because only one page can be written every 6 ms, the PIC cannot necessarily continuously send data, unless the rate is less than 256 bytes per 6 ms. When sending data, if the address reaches a page boundary the data will "wrap around" in the page buffer. For instance, if the start address is 250, and a page boundary is at 256, then the seventh byte sent will be actually stored in address 0, not 257. Because of this, pages must be written when a page boundary is met and a write cycle needs to be restarted at an address in the next page. Other than the 3-wire serial interface, the chip also has an enable pin that must be low during write and read operations. Because of this, multiple 25AA1024s could be used, with each CE leading to a different pin on the PIC, so that many 25AA1024s could be used by the same PIC.


The PIC is connected to the SPI memory as shown. The chip select signal (/CS) is pulled low during communication. It cannot be permanently tied low, even if only one memory is used, because the low-high transition of /CS latches in data. /HOLD and /WP (write protect) can be useful in some instances, see the datasheet for more information. Spi mem ckt.png


The code below includes support functions for reading, writing, and chip erase. A single write latch writes in all the data buffered for the current page. Because of this, it is the job of the code calling the write function to make sure that a memory write will not go past a page boundary, or the data will end up in the wrong spot. In the example below, the PIC accepts a string of data on the RS232 line, terminated by a carriage return (0x13). This packet is then written to memory. The packet size shouldn't be larger than the define COMM_SIZE. The code that calls the write function includes example handling of page boundaries.

Data logging (writing)

//Thomas Peterson

#include <18F4520.h>
#use delay(clock=40000000)

#include <string.h>

//SPI registers
#byte MCU_SSPSTAT = 0xFC7
#byte MCU_SSPCON1 = 0xFC6
#bit    MCU_CKP = MCU_SSPCON1.4
#bit    MCU_SSMP0 = MCU_SSPCON1.0
#bit    MCU_SSMP1 = MCU_SSPCON1.1
#bit    MCU_SSPM2 = MCU_SSPCON1.2
#bit    MCU_SSPM3 = MCU_SSPCON1.3

//Setup communications
#use rs232(baud=1000000,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8,UART1,DISABLE_INTS)

#define COMM_BYETS 16 //Number of bytes per packet

#define PAGESIZE 256

//function definitions
void spi_mem_read(int addmsb,int16 address, int16 num_bytes, char *buf);
int writing();
void spi_mem_write(int addmsb,int16 address,int num_bytes, char *data);

//mem isr
int8 READ = 0b00000011;    //Read data from memory array beginning at selected add
int8 WRITE = 0b00000010;   //Write data to memory array beginning at selected address
int8 WREN = 0b00000110;    //set the write enable latch (enable write)
//int8 WRDI = 0b00000100;    //reset the write enable latch (disable write)
int8 RDSR = 0b00000101;    //read status register
//int8 WRSR = 0b00000001;    //write status register
//int8 PE = 0b01000010;      //page erase
//int8 SE = 0b11011000;      //sector erase
int8 CE = 0b11000111;      //chip erase
//int8 RDID = 0b10101011;    //relase from deep power down mode, read sig
//int8 DPD = 0b10111001;     //deep power down mode

int32 current_address = 0;

/* Checks to see if the chip is currently writing to memory */
int writing() {
   int status;
   status = spi_read(0);
   return (status & 0x1);

/* Reads sequential bytes in memory, starting at address.
   Bytes are stored into *buf, read until num_bytes is reached */
void spi_mem_read(int addmsb,int16 address, int16 num_bytes, char *buf) {
   char *bufptr;
   output_low(MEM_SELECT); //active low
   spi_write(READ); //Start read cycle
   for (bufptr = buf; bufptr<buf+num_bytes; bufptr++) {
      *bufptr = spi_read(0);

/* Writes sequential data starting at ADDRESS 
   Note: Caller must ensure page boundary is not passed! 
   Page size is 256. */
void spi_mem_write(int addmsb,int16 address,int num_bytes, char *data) {
   char *bufptr;
   while(writing()) {
      delay_us(1); //Wait for last write to finish
   output_low(MEM_SELECT); //active low
   spi_write(WREN); //write enable latch
   output_high(MEM_SELECT); //latch
   spi_write(WRITE); //Send write command
   for (bufptr=data; bufptr<data+num_bytes; bufptr++) {
      spi_write(*bufptr); //send this byte
   output_high(MEM_SELECT); //Ends write operation, begins write to memory (5ms)

/* Erases all memory */
void chip_erase() {
   output_low(MEM_SELECT); //active low
   spi_write(WREN); //write enable latch
   output_high(MEM_SELECT); //latch

void main()
   char rcvmsg[30];
   int i;
   i = input(PIN_D1);

   //Setup the SPI
   //Note: setup_spi() may work, but it wouldn't for me
   MCU_SSMP0 = 0;
   MCU_SSMP1 = 0;
   MCU_SSPM2 = 0;
   MCU_SSPM3 = 0;

   while(1) {
      //Gets a line from the rs232 line, puts it in memory
      gets_n(rcvmsg); //get the data
      //Note: the following assumes current_address will NOT write past page
      //      boundary!
      spi_mem_write(current_address>>16,current_address,COMM_BYTES, rcvmsg);
      //While writing, setup next address
      current_address += COMM_BYTES;
      if ( (PAGESIZE-(current_address%PAGESIZE)) < COMM_BYTES ) {
         //The next operation will cross page boundary, should move to next page
         current_address = current_address - (current_address%PAGESIZE) + PAGESIZE;

Similarly, the memory could be read with mostly the same code but with the following in the main() function:

int32 temp_address = 0;
char tmp_buf[COMM_BYTES+1];
for (temp_address = 0; temp_address<current_address; temp_address=temp_address) {
  //Read data at current address
  //Print data
  fprintf("%x%x%x: %s \r\n", temp_address>>16, temp_address>>8,temp_address, tmp_buf);
  //Advance address
  temp_address += COMM_BYTES;
  //Cheeck for page boundary issue
  if ( (PAGESIZE-(temp_address%PAGESIZE)) < COMM_BYTES ) {
     //The next operation will cross page boundary, should move to next page
     temp_address = temp_address - (temp_address%PAGESIZE) + PAGESIZE;