MPLAB C30 Library - Serial EEPROM (25LC256)

While making my way through the book "Learning To Fly The PIC24" - one of the projects was to construct a library that encapsulated the low level interface with an external EEPROM. The original program listed in the book was limited to read/writes of integer type variables - I wanted to expand on that capability to allow use of any variable type.

If you are familiar with the book, you will know that all of the projects are compatible with the Explorer 16 Development Board. One of the many devices featured on the board is a 25LC256 Serial EEPROM. This article does not delve into the ins-and-outs of SPI or Serial EEPROMs, rather is a library that can be used/examined by others with similar hardware. Here are some brief features of the 25LC256 Serial EEPROM:


  • Max. clock 10 MHz
  • Low-power CMOS technology:
    • Max. Write Current: 5 mA at 5.5V, 10 MHz
    • Read Current: 6 mA at 5.5V, 10 MHz
    • Standby Current: 1 μA at 5.5V
  • 32,768 x 8-bit organization
  • 64 byte page
  • Self-timed erase and write cycles (5 ms max.)
  • Datasheet link

25LC256

The 25LC256 schematic as sourced from the Explorer 16 User Guide

The Code

With a fair amount of support from Jerry, the latest revision of the program is quite usable. There are still features that could be implemented (such as timeouts), though this makes a good platform to begin with. Browse the code below, or download the MPLAB C30 Project Files.

NVMtest.c
// NVM Library test
 
#include <p24fxxxx.h>               // PIC24 definitions
#include "nvm.h"
 
_CONFIG1(JTAGEN_OFF & FWDTEN_OFF)   // disable JTAG and WDT
_CONFIG2(POSCMOD_XT & FNOSC_PRIPLL) // configure oscillator
 
// test data
char d[256];
unsigned int ui;
unsigned long long ull;
 
typedef struct {
    char a;
    int b;
    long c;
    long long d;
} mytype;
mytype dt;   
 
int main(void) {
    int i;
 
    // initialise the SPI2 port and CS to access the 25LC256
    InitNVM();
 
    // preload some data
    for (i=0; i<256;i++)
        d[i] = i;
    ui = 0x1234; 
    ull = 0x1234567890;
    dt.a = 0x123;
    dt.b = 0x1234;
    dt.c = 0x1234567;
    dt.d = 0x1234567890;
 
    // write various objects to the Serial EEPROM
    write_spiee(2, &ui, sizeof(ui));
    write_spiee(510, &ull, sizeof(ull));
    write_spiee(101, &d, sizeof(d));
    write_spiee(380, &dt, sizeof(dt));
 
    // clear all data..
    for (i=0; i<256;i++)
        d[i] = 0;    
    ui = 0; 
    ull = 0;
    dt.a = 0;
    dt.b = 0;
    dt.c = 0;
    dt.d = 0;
 
    // read various objects from the Serial EEPROM
    read_spiee(2, &ui, sizeof(ui));
    read_spiee(510, &ull, sizeof(ull));
    read_spiee(101, &d, sizeof(d));
    read_spiee(380, &dt, sizeof(dt));
 
    while(1);
}

The flexibility of object passing was created by using pointers (originally I wrote functions for different object types, though Jerry soon pointed out a better way - pun!). As the pointer being passed is of type void, it can point to any object type (int/long/float/structure/string). Once in the routine; a second pointer is cast which points to the same object location though is of type unsigned char. By doing so, the target object can be updated a single char at a time - until the whole object is filled.

Being new to C, it was a powerful concept that I will likely use many times again in the future.

nvm.h
/*
 
NVM Storgage Library (25LC256)
 
Notes:
 - Designed for PIC24 + Explorer16
 
*/
 
#ifndef NVM_H_
#define NVM_H_
 
// I/O definitions
#define SPI_MASTER 0x0120           // select 8-bit master mode, CKE=1, CKP=0
#define SPI_ENABLE 0x8000           // enable SPI port, clear status
 
// peripheral configurations
#define CSEE    _RD12               // select line for Serial EEPROM
#define TCSEE   _TRISD12            // tris control for CSEE pin
 
// 25LC256 Serial EEPROM commands
#define SEE_WRSR    1               // write status register
#define SEE_WRITE   2               // write command
#define SEE_READ    3               // read command
#define SEE_WDI     4               // write disable
#define SEE_STAT    5               // read status register
#define SEE_WEN     6               // write enable
 
// intialise access to memory device
void InitNVM(void);
 
// Serial EEPROM read/write functions
int write_spiee(unsigned int eeaddr, void *src, unsigned int count);
int read_spiee(unsigned int eeaddr, void *target, unsigned int count);
 
// Serial EEPROM page size (device dependant)
#define EE_PAGE_SIZE 64
 
#endif

 

nvm.c
#include <p24fxxxx.h>               // PIC24 definitions
#include "nvm.h"
 
// initialise the Serial EEPROM
void InitNVM(void) {
    TCSEE = 0;                      // configure CSEE direction
    CSEE = 1;                       // default CSEE state
    SPI2CON1 = SPI_MASTER;          // sekect 8-bit master mode, CKE=1, CKP=0
    SPI2STAT = SPI_ENABLE;          // enable SPI port, clear status
}
 
// write 8-bit data to Serial EEPROM
int WriteSPI2(int data) {
    SPI2BUF = data;                 // write to buffer for TX
    while(!SPI2STATbits.SPIRBF);    // wait for the transfer to complete
    return SPI2BUF;                 // return the value received.
}
 
// Check the Serial EEPROM status register
int ReadSR(void) {
    int i;                          
    CSEE = 0;                       // select the Serial EEPROM
    WriteSPI2(SEE_STAT);            // send Read Status command
    i = WriteSPI2(0);               // send dummy, read status
    CSEE = 1;                       // deselect Serial EEPROM
    return i;                       // return status
}
 
// send a Write Enable command
void WriteEnable(void) {
    CSEE = 0;                       // select the Serial EEPROM
    WriteSPI2(SEE_WEN);             // send Write Enabled command
    CSEE = 1;                       // deselect Serial EEPROM
}
 
//
// this routine supports spi eeproms up to 32k bytes in size that use 2-byte addresses
// to support a 64k device, change count, num_bytes, and bytes_remaining to 'unsigned long'
// to support larger devices, change eeaddr to 'unsigned long', and change the routine to send
// out the address bytes to the device accordingly.
//
int write_spiee(unsigned int eeaddr, void *src, unsigned int count)
{
   unsigned char *saddr;                     // pointer to data to write
   unsigned int num_bytes;          // bytes to write per iteration
   unsigned int bytes_remaining;    // total bytes to write
   int i;
 
   // get ptr to the data (not really needed... could just cast src, but makes it easier to read)
   saddr = (unsigned char *)src;
 
   bytes_remaining = count;
 
   while (bytes_remaining)
   {
      // limit number of bytes written per loop iteration to either
      //  - numer of bytes to end of page
      //  - a full page
      //  - remaining partial page
 
      // figure out how many bytes to end of current page
      num_bytes = EE_PAGE_SIZE - (eeaddr % EE_PAGE_SIZE);
 
      // check to see if we want to limit bytes written this iteration
      if (bytes_remaining < num_bytes)
         num_bytes = bytes_remaining;
 
      // send data to eeprom
      // wait until any work in progress is completed
      while (ReadSR() & 0x1);       // check WIP
      // set the write enable latch
      WriteEnable();
      // perform write
      CSEE = 0;                     // select the Serial EEPROM
      WriteSPI2(SEE_WRITE);         // write command
      WriteSPI2(eeaddr >> 8);       // address MSB first
      WriteSPI2(eeaddr & 0xff);     // address LSB
      for (i=num_bytes; i; i--)
         WriteSPI2(*saddr++);
      CSEE = 1;                     // deselect the Serial EEPROM
 
      bytes_remaining -= num_bytes;
      eeaddr += num_bytes;
   }
 
   // return status
   // 0=ok, others = timeout, size error, etc (not implemented)
   return 0;
}
 
//
// this routine supports spi eeproms up to 32k bytes in size that use 2-byte addresses
// to support a 64k device, change count, num_bytes, and bytes_remaining to 'unsigned long'
// to support larger devices, change eeaddr to 'unsigned long', and change the routine to send
// out the address bytes to the device accordingly.
//
int read_spiee(unsigned int eeaddr, void *target, unsigned int count)
{
    unsigned char *taddr;                    // pointer to data to write
 
    // get ptr to the data (not really needed... could just cast target, but makes it easier to read)
    taddr = (unsigned char *)target;
 
    // read data from eeprom
    // wait until any work in progress is completed
    while (ReadSR() & 0x1);         // check WIP
 
    // select the Serial EEPROM and send address
    CSEE = 0;                       // select the Serial EEPROM
    WriteSPI2(SEE_READ);            // send Read command
    WriteSPI2(eeaddr>>8);           // address MSB first
    WriteSPI2(eeaddr & 0xff);       // address LSB (word aligned)
 
    while (count--)
        *taddr++ = WriteSPI2(0);    // send dummy, read data msb
 
    CSEE = 1;                       // deselect Serial EEPROM
 
    // return status
    // 0=ok, others = timeout, size error, etc (not implemented)
    return 0;
}


If you have any additions or comments, please feel free to share them!

Forum Activity

Member Access