Xicor SPI Serial Memory Interface Routines (Source Code)
The following is the source code for the Xicor SPI Serial Memory Interface Routines article. Refer to the complete article for implementation details and explanations.
XICOR25.C
/*
*********************************************************************************************************
* Module : XICOR25.C
* Author : Randy Rasa
* Description: Xicor X25xxxx Serial Non-Volatile Memory Interface Routines
*
* This module consists of functions to communicate with any of the Xicor non-volatile memory devices
* which use the SPI interface (X25-series). It should work with the following devices:
*
* X25F008/016/032/064/128 .... SerialFlash (1K/2K/4K/8K/16K)
* X25080/160/320/640/128 ..... EEPROM (1K/2K/4K/8K/16K)
*
* The SerialFlash series of devices is available in various densities, ranging from 8K-bits
* (1K x 8) to 128K-bits (16K x 8). All devices share the same pinout and software interface.
* The devices communicate with the host via an SPI-compatible interface, consisting of the
* following signals:
*
* CS = Chip Select ....... Low = Selected, High = De-Selected
* SCLK = Serial Clock ...... May be clocked at up to 1MHz
* SI = Serial Data In .... Data on SI is latched in on the rising edge of SCLK
* SO = Serial Data Out ... Data on SO is clocked out on the falling edge of SCLK
* PP = Program Protect ... Low = Protected, High = Normal Operation
* HOLD = Hold .............. Can be used to "pause" a serial transmission
*
* The following instructions (and their opcodes) are available:
*
* PREN ...... 0x06 ... Enable Programming Operations
* PRDI ...... 0x04 ... Disable Programming Operations
* RDSR ...... 0x05 ... Read Status Register
* PRSR ...... 0x01 ... Program Status Register
* READ ...... 0x03 ... Read from memory array, beginning at selected address
* PROGRAM ... 0x02 ... Write to memory array, beginning at selected address (32 bytes)
*
* Note that the memory must be written to in 32-byte blocks (this is the sector size).
* Before writing, the PREN command must be issued. Also,
*
* PREN Command Sequence:
* 1. Pull CS low
* 2. Send 8-bit PREN instruction (MSB first)
* 3. Bring CS high to end sequence
*
* PRDI Command Sequence:
* 1. Pull CS low
* 2. Send 8-bit PRDI instruction (MSB first)
* 3. Bring CS high to end sequence
*
* RDSR Command Sequence:
* 1. Pull CS low
* 2. Send 8-bit RDSR instruction (MSB first)
* 3. Read 8-bit status register (MSB first)
* 4. Bring CS high to end sequence
*
* PRSR Command Sequence:
* 1. Send PREN command
* 2. Pull CS low
* 3. Send 8-bit PRSR instruction (MSB first)
* 4. Send 8-bit status register (MSB first)
* 5. Bring CS high to end sequence
* 6. Send PRDI command
*
* Read Sequence:
* 1. Pull CS low
* 2. Send 8-bit READ instruction (MSB first)
* 3. Send 16-bit address (MSB first)
* 4. Read 8-bit data (MSB first)
* 5. Data bytes can continue to be read as long as CS is held low
* 6. Bring CS high to end sequence
*
* Write Sequence:
* 1. Send PREN command
* 2. Pull CS low
* 3. Send 8-bit PROGRAM instruction (MSB first)
* 4. Send 16-bit address (MSB first)
* 5. Send 32 8-bit data bytes (MSB first)
* 6. Bring CS high to start programming cycle
* 7. Read status register until PIP bit (LSB) is "0"
* 8. Send PRDI command
*
* This code assumes that the signals are provided by microcontroller I/O pins configured
* as outputs. Macros are provided to simplify control of these signals. You may also use
* memory-mapped output ports such as a 74HC374, but you'll need to replace the macros with
* functions, and use a shadow register to store the state of all the output bits. You can
* also use an SPI port to simplify (and speed up) the data transfer.
*
* I have tested this code using an X25F032 and X25F128 connected to a PC parallel port.
* The "TARGET" symbol selects whether to use the printer port or microcontroller pins.
* To use microcontroller pins, you'll need to set up the macros within the "#if TARGET
* == TARGET_UC" section...
*
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Specify Target
*********************************************************************************************************
*/
#define TARGET_PC 0 // target = PC printer port
#define TARGET_UC 1 // target = microcontroller
#define TARGET TARGET_PC // select the target
/*
*********************************************************************************************************
* Include Header Files
*********************************************************************************************************
*/
#include "xicor25.h"
#if TARGET == TARGET_PC
#include "prn_io.h"
#endif
/*
*********************************************************************************************************
* Constants
*********************************************************************************************************
*/
// SerialFlash Command Opcodes:
#define CMD_PRSR 0x01 // Program Status Register
#define CMD_PROGRAM 0x02 // Write to memory array
#define CMD_READ 0x03 // Read from memory array
#define CMD_PRDI 0x04 // Disable programming
#define CMD_RDSR 0x05 // Read Status Register
#define CMD_PREN 0x06 // Enable programming
// Status Register bits:
#define SR_PIP 0x01 // PIP = Programming In Progress
#define SR_PEL 0x02 // PEL = Program Enable Latch
#define SR_BL0 0x04 // BL0 = Block Lock 0 (LSB)
#define SR_BL1 0x08 // BL0 = Block Lock 1 (MSB)
#define SR_PPEN 0x80 // PPEN = Program-Protect-Enable
/*
*********************************************************************************************************
* Macros for Hardware Access
*********************************************************************************************************
*/
#if TARGET == TARGET_PC // if target is PC printer port,
#define CS_0() (PrnIO_Out0(0)) // "CS" is on output 0 (D0)
#define CS_1() (PrnIO_Out0(1))
#define SI_0() (PrnIO_Out1(0)) // "SI" is on output 1 (D1)
#define SI_1() (PrnIO_Out1(1))
#define SCLK_0() (PrnIO_Out2(0)) // "SCLK" is on output 0 (D2)
#define SCLK_1() (PrnIO_Out2(1))
#define SO_STATE() (PrnIO_In3()) // "SO" is on input 3 (Acknowledge)
#endif
#if TARGET == TARGET_UC // if target is microcontroller,
#define CS_PORT PORTC // "CS" is on PC1
#define CS_DDR DDRC
#define CS_BIT 0x01
#define CS_0() (CS_PORT &= ~CS_BIT)
#define CS_1() (CS_PORT |= CS_BIT)
#define SI_PORT PORTC // "SI" is on PC2
#define SI_DDR DDRC
#define SI_BIT 0x02
#define SI_0() (SI_PORT &= ~SI_BIT)
#define SI_1() (SI_PORT |= SI_BIT)
#define SCLK_PORT PORTC // "SCLK" is on PC3
#define SCLK_DDR DDRC
#define SCLK_BIT 0x04
#define SCLK_0() (SCLK_PORT &= ~SCLK_BIT)
#define SCLK_1() (SCLK_PORT |= SCLK_BIT)
#define SO_PORT PORTC // "SO" is on PC4
#define SO_DDR DDRC
#define SO_BIT 0x08
#define SO_STATE() (SO_PORT & SO_BIT)
#endif
/*
*********************************************************************************************************
* Private Data
*********************************************************************************************************
*/
// ... none ...
/*
*********************************************************************************************************
* Private Function Prototypes
*********************************************************************************************************
*/
static void X25_ByteSend (unsigned char data);
static unsigned char X25_ByteRead (void);
// ...................................... Public Functions ..............................................
/*
*********************************************************************************************************
* X25_Init()
*
* Description: Initialize module; must be called before any other functions in this module.
* Arguments : none
* Returns : none
*********************************************************************************************************
*/
void X25_Init (void)
{
#if TARGET == TARGET_PC // if target is PC printer port,
PrnIO_Init(); // intialize printer port
#endif
CS_1(); // start with CS = 1
SI_1(); // SI = 1
SCLK_0(); // SCLK = 0
#if TARGET == TARGET_UC // if target is microcontroller,
CS_DDR |= CS_BIT; // configure "CS" as output
SI_DDR |= SI_BIT; // configure "SI" as output
SCLK_DDR |= SCLK_BIT; // configure "SCLK" as output
SO_DDR &= ~SO_BIT; // configure "SO" as input
#endif
}
/*
*********************************************************************************************************
* X25_ReadByte()
*
* Description: Read a byte (8 bits) of memory from the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3fff)
* Returns : data read from serial memory
*********************************************************************************************************
*/
unsigned char X25_ReadByte (unsigned int address)
{
unsigned char data;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_READ); // send READ command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
data = X25_ByteRead(); // read byte
CS_1(); // take CS high to end
return data;
}
/*
*********************************************************************************************************
* X25_ReadWord()
*
* Description: Read a word (16 bits) of memory from the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3ffe)
* Returns : data read from serial memory
* Note : You may have to change the order in which the bytes are
* written, depending whether this is running on a big-endian
* or little-endian machine ...
*********************************************************************************************************
*/
unsigned int X25_ReadWord (unsigned int address)
{
union {
unsigned char _8[2];
unsigned int _16;
} data;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_READ); // send READ command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
data._8[0] = X25_ByteRead(); // read byte (upper 8 bits)
data._8[1] = X25_ByteRead(); // read byte (lower 8 bits)
CS_1(); // take CS high to end
return data._16;
}
/*
*********************************************************************************************************
* X25_ReadLong()
*
* Description: Read a long (32 bits) of memory from the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3ffc)
* Returns : data read from serial memory
* Note : If you're using a compiler that does not support 32-bit
* long data, remove this function!
* Note : You may have to change the order in which the bytes are
* written, depending whether this is running on a big-endian
* or little-endian machine ...
*********************************************************************************************************
*/
unsigned long X25_ReadLong (unsigned int address)
{
union {
unsigned char _8[4];
unsigned long _32;
} data;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_READ); // send READ command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
data._8[0] = X25_ByteRead(); // read byte (upper 8 bits)
data._8[1] = X25_ByteRead(); // read byte
data._8[2] = X25_ByteRead(); // read byte
data._8[3] = X25_ByteRead(); // read byte (lower 8 bits)
CS_1(); // take CS high to end
return data._32;
}
/*
*********************************************************************************************************
* X25_ReadData()
*
* Description: Read an arbitrary number of bytes of memory from the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3fe0)
* dest = address of memory where block will be stored
* numbytes = number of bytes to read
* Returns : none
*********************************************************************************************************
*/
void X25_ReadData (unsigned int address, unsigned char *dest, int numbytes)
{
int i;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_READ); // send READ command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
for (i=0; i<numbytes; i++) {
*dest++ = X25_ByteRead(); // read byte, save to destination
}
CS_1(); // take CS high to end
}
/*
*********************************************************************************************************
* X25_WriteData()
*
* Description: Write an arbitrary number of bytes from RAM to serial memory.
* Arguments : address = address within serial memory (0x0000-0x3fe0)
* source = address of memory where block to be written is stored
* numbytes = number of bytes to write
* Returns : none
* Note : This routine will not work with SerialFlash devices, but will work with
* other non-volatile memory such as EEPROMs ...
*********************************************************************************************************
*/
void X25_WriteData (unsigned int address, unsigned char *source, int numbytes)
{
int i;
X25_PREN(); // enable programming
CS_0(); // take CS low to begin
X25_ByteSend(CMD_PROGRAM); // send PROGRAM command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
for (i=0; i<numbytes; i++) {
X25_ByteSend(*source++); // read byte from buffer, send
}
CS_1(); // take CS high to end
while (X25_ReadStatusRegister() & 0x01); // wait for PIP bit to go low ...
X25_PRDI(); // disable programming
}
/*
*********************************************************************************************************
* X25_ReadBlock()
*
* Description: Read a block (32 bytes) of memory from the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3fe0)
* dest = address of memory where block will be stored
* Returns : none
*********************************************************************************************************
*/
void X25_ReadBlock (unsigned int address, unsigned char *dest)
{
char i;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_READ); // send READ command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
for (i=0; i<32; i++) {
*dest++ = X25_ByteRead(); // read byte, save to destination
}
CS_1(); // take CS high to end
}
/*
*********************************************************************************************************
* X25_WriteBlock()
*
* Description: Write a block (32 bytes) of memory from RAM to the serial memory.
* Arguments : address = address within serial memory (0x0000-0x3fe0)
* source = address of memory where block to be written is stored
* Returns : none
*********************************************************************************************************
*/
void X25_WriteBlock (unsigned int address, unsigned char *source)
{
char i;
X25_PREN(); // enable programming
CS_0(); // take CS low to begin
X25_ByteSend(CMD_PROGRAM); // send PROGRAM command
X25_ByteSend(address / 0x0100); // send address (upper 8 bits)
X25_ByteSend(address & 0x00ff); // send address (lower 8 bits)
for (i=0; i<32; i++) {
X25_ByteSend(*source++); // read byte from buffer, send
}
CS_1(); // take CS high to end
while (X25_ReadStatusRegister() & 0x01); // wait for PIP bit to go low ...
X25_PRDI(); // disable programming
}
/*
*********************************************************************************************************
* X25_PREN()
*
* Description: Send PREN command to serial memory
* Arguments : none
* Returns : none
*********************************************************************************************************
*/
void X25_PREN (void)
{
CS_0(); // take CS low to begin
X25_ByteSend(CMD_PREN); // send command
CS_1(); // take CS high to end
}
/*
*********************************************************************************************************
* X25_PRDI()
*
* Description: Send PRDI command to serial memory
* Arguments : none
* Returns : none
*********************************************************************************************************
*/
void X25_PRDI (void)
{
CS_0(); // take CS low to begin
X25_ByteSend(CMD_PRDI); // send command
CS_1(); // take CS high to end
}
/*
*********************************************************************************************************
* X25_ReadStatusRegister()
*
* Description: Read serial memory status register
* Arguments : none
* Returns : contents of status register
*********************************************************************************************************
*/
unsigned char X25_ReadStatusRegister (void)
{
unsigned char data;
CS_0(); // take CS low to begin
X25_ByteSend(CMD_RDSR); // send command
data = X25_ByteRead(); // read byte
CS_1(); // take CS high to end
return data; // return result
}
/*
*********************************************************************************************************
* X25_WriteStatusRegister()
*
* Description: Write serial memory status register
* Arguments : data to write to status register
* Returns : none
*********************************************************************************************************
*/
void X25_WriteStatusRegister (unsigned char reg)
{
X25_PREN(); // enable programming
CS_0(); // take CS low to begin
X25_ByteSend(CMD_PRSR); // send command
X25_ByteSend(reg); // send data
CS_1(); // take CS high to end
while (X25_ReadStatusRegister() & 0x01); // wait for PIP bit to go low ...
X25_PRDI(); // disable programming
}
// ..................................... Private Functions ..............................................
/*
*********************************************************************************************************
* ByteSend()
*
* Description: Send one byte to the serial memory
* Arguments : data = data to send
* Returns : none
*********************************************************************************************************
*/
static void X25_ByteSend (unsigned char dataout)
{
char i;
const unsigned char bitmask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
for (i=0; i<8; i++) {
if (dataout & bitmask[i]) // output one data bit
SI_1(); // "1"
else // or
SI_0(); // "0"
SCLK_1(); // bring SCLK high
SCLK_0(); // bring SCLK low
}
}
/*
*********************************************************************************************************
* X25_ByteRead()
*
* Description: Read one byte from the serial memory
* Arguments : none
* Returns : byte read
*********************************************************************************************************
*/
static unsigned char X25_ByteRead (void)
{
char i;
unsigned char data = 0x00;
const unsigned char bitmask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
for (i=0; i<8; i++) {
if (SO_STATE() != 0) // read one data bit
data |= bitmask[i]; // "1"
SCLK_1(); // bring SCLK high
SCLK_0(); // bring SCLK low
}
return data;
}
XICOR25.H
/*
*********************************************************************************************************
* Module : XICOR25.H
* Author : Randy Rasa
* Description: Header file for XICOR25.C (Xicor Serial Memory Interface Routines)
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Public Function Prototypes
*********************************************************************************************************
*/
void X25_Init (void);
void X25_PREN (void);
void X25_PRDI (void);
unsigned char X25_ReadStatusRegister (void);
void X25_WriteStatusRegister (unsigned char);
unsigned char X25_ReadByte (unsigned int address);
unsigned int X25_ReadWord (unsigned int address);
unsigned long X25_ReadLong (unsigned int address);
void X25_ReadData (unsigned int address, unsigned char *dest, int numbytes);
void X25_WriteData (unsigned int address, unsigned char *source, int numbytes);
void X25_ReadBlock (unsigned int address, unsigned char *dest);
void X25_WriteBlock (unsigned int address, unsigned char *source);
This source code is provided as-is, with no warranties or guarantees.