// sermem.c - Copyright 1998 (c) Kevin W Ross, all rights reserved // // This file is full of routines for dealing with serial EEPROM // such as the Atmel 25128 or the Microchip 25C320 // // Almost all of these parts operate in the same way. The variables // are the page write size, and the block protection sizes. You can set the // particular type in the file sermem.h // // #include #include #include "sermem.h" // If you define DEBUG, then sermem_fVerbose will allow you to turn on and // off the debug output routines. #ifdef DEBUG int sermem_fVerbose = 0; #else #define spi_in spi_xfer #define spi_out spi_xfer #endif // // Write address tracks the current write address for handling the // StartWrite()/Write()/StopWrite() sequence. // static int si_WriteAddress; // spi_xfer - Exchange bytes with the SPI port // // Here is the workhorse routine for this module. spi_xfer will initiate a // data transfer on the SPI port. Since the SPI protocol is an 'exchange' // protocol, a read and a write operation are identical. // // On input, byte contains the byte value to send OUT the SPI port. // On exit, the byte shifted in is the return value // static unsigned char spi_xfer(unsigned char byte) { // Shift a byte out, wait for result, return shifted in byte SP0DR = byte; while((SP0SR & 0x80) == 0); return SP0DR; } // // spi_in and spi_out only differ in the way they report the action to the // user. On a non-debug system, spi_xfer is the real routine to work with // #ifdef DEBUG static unsigned char spi_in(unsigned char byte) { if(sermem_fVerbose)printf("I:%02x:",byte); byte = spi_xfer(byte); if(sermem_fVerbose) printf("%02x\n",byte); } static unsigned char spi_out(unsigned char byte) { if(sermem_fVerbose)printf("O:%02x:",byte); byte = spi_xfer(byte); if(sermem_fVerbose)printf("%02x\n",byte); } #endif // // The following two worker routines select and deselect the EEPROM. By // raising and lowering the CS line. If you wire up the SPI port differently, // then put the code in here that maintains the CS line for the EEPROM. // static void spi_select() { // Lower the CS line PORTS = PORTS & 0x7F; } static void spi_deselect() { // Raise the CS line PORTS = PORTS | 0x80; } // // Several commands are single byte. This routine selects, sends, and deselects // the EEPROM for single byte commands. // unsigned char sermem_byte_command(unsigned char byte) { spi_select(); byte = spi_out(byte); spi_deselect(); return byte; } // // This initializes the SPI port to suite the EEPROM part. Should be called // at the start of your program. // void sermem_Initialize() { // Setup PORTS 7 as the CS bit. 5,6,7 must be outputs PORTS = PORTS | 0xE0; DDRS = DDRS | 0xE0; // Set the SPI system for CPOL=0,CPHA=0,MASTER SP0CR1 = 0x50 ; // 0b01010000; // An 8MHZ ECLOCK makes the SCK frequency 4mhz! // Most of these parts would prefer 2mhz, so the // prescalar is set SP0BR = 1; } // // This routine sets the block protection level. Check out the docuementation // for your part. Most have 4 levels of protection (0 to 3), meaning that // protection level 3 is all protected, level 2 protects the upper 1/4, // level 1 protects the upper half, and level 0 leaves everything fair game. // void sermem_BlockProtection(unsigned char ucLevel) { ucLevel = ucLevel & 0x03; ucLevel = ucLevel << 2; spi_select(); spi_out(SERMEM_INST_WRSR); spi_out(ucLevel); spi_deselect(); } // // For the most part, to write data to the EEPROM, you want to use the // sermem_WriteBlock(...) command. This allows you to write and commit a // block of memory to the EEPROM. This is useful in most cases. There are, // however, cases where you will want to write a stream of data instead. // // The serial EEPROM's allow you to send streams of bytes in a write command. // I have written these routines to allow you access to the serial EEPROM // if you are streaming data. The basic sequence is: // // sermem_StartWrite(...) // sermem_Write(...) // sermem_StopWrite() // // Note that to insure that your data is actually committed, you need to call // StopWrite to end this operation. Otherwise, the last page will not be // committed to memory. // // Note that this sequence cannot be interrupted by other EEPROM operations. // Specifically, once StartWrite() has been called, you can only call Write() // or StopWrite(). Most any other operation will fail because the chip has // been left in a write mode. // void sermem_StartWrite(int iAddress) { // StartWrite begins by enabling the EEPROM sermem_WriteEnable(); // Now send the Write command followed by the address, MSB first spi_select(); spi_out(SERMEM_INST_WRITE); spi_out(iAddress>>8); spi_out(iAddress&0xFF); // Remember the starting address for the Write command si_WriteAddress = iAddress; // StartWrite leaves the EEPROM selected } // // sermem_Write requires that sermem_StartWrite has been called to initiate // the write operation. To finish, you MUST call sermem_StopWrite // void sermem_Write(unsigned char *pBuf,int cBuf) { // Using the address in si_WriteAddress, start sending bytes to the // memory part. After each write, determine if the address has // encountered a page boundary. If so, then handle the write while(cBuf) { // Send the byte to the part spi_out(*pBuf); cBuf--; pBuf++; si_WriteAddress++; if((si_WriteAddress & SERMEM_PAGE_MASK) == 0) { // We have page wrapped! sermem_StopWrite(); sermem_StartWrite(si_WriteAddress); } } } void sermem_StopWrite(void) { // // Raise the CS line. That makes the memory part start its write cycle // Then wait for the WIP bit to be cleared // do { spi_deselect(); // Need to select the EEPROM for the read status operation spi_select(); spi_out(SERMEM_INST_RDSR); // The Write In Progress (WIP) flag is bit 1. Wait for it to go off. } while (spi_in(SERMEM_INST_RDSR) & 0x01); spi_deselect(); // Disallow further writes to the EEPROM. Prevents rouge clock signals // from corrupting your data! sermem_WriteDisable(); } // // Here is the workhorse routine! // void sermem_WriteBlock(int iAddress,unsigned char *pBuf,int cBuf) { sermem_StartWrite(iAddress); sermem_Write(pBuf,cBuf); sermem_StopWrite(); } // // On occasion, you may want to just fill the EEPROM array with a byte // value. This routine does just that! // void sermem_Fill(int iAddress,unsigned char byte,int count) { sermem_StartWrite(iAddress); while(count) { // Send the byte to the part spi_out(byte); count--; si_WriteAddress++; if((si_WriteAddress & SERMEM_PAGE_MASK) == 0) { // We have page wrapped! sermem_StopWrite(); sermem_StartWrite(si_WriteAddress); } } sermem_StopWrite(); } // // For the most part, to read data from the EEPROM, you want to use the // sermem_ReadBlock(...) command. This allows you to read a // block of memory from the EEPROM. This is useful in most cases. There are, // however, cases where you will want to read a stream of data instead. // // The serial EEPROM's allow you to read streams of bytes in a read command. // I have written these routines to allow you access to the serial EEPROM // if you are streaming data. The basic sequence is: // // sermem_StartRead(...) // sermem_Read(...) // sermem_StopRead() // // // Note that this sequence cannot be interrupted by other EEPROM operations. // Specifically, once StartRead() has been called, you can only call Read() // or StopRead(). Most any other operation will fail because the chip has // been left in a read mode. // void sermem_StartRead(int iAddress) { // StartRead begins by enabling the EEPROM spi_select(); // Now send the Read command followed by the address, MSB first spi_out(SERMEM_INST_READ); spi_out(iAddress>>8); spi_out(iAddress&0xFF); } void sermem_Read(unsigned char *pBuf,int cBuf) { while(cBuf) { *pBuf = spi_in(0); pBuf++; cBuf--; } } void sermem_StopRead(void) { spi_deselect(); } // // Here is the 'Read' workhorse routine. // void sermem_ReadBlock(int iAddress,unsigned char *pBuf,int cBuf) { sermem_StartRead(iAddress); sermem_Read(pBuf,cBuf); sermem_StopRead(); }