PIC C, Using the 12CE5X Internal EEPROM


// EEPROM_1.C (CCS PCB - PIC12CE519)
//
// Illustrates how to write to and read from EEPROM on 12CE519.
//
// Function _12c5_eeprom(int op, int EEADDR, int *p_dat) is used to
// write to or read from EEPROM.  op indicates whether the operation is
// write or read.  Note that the data is passed by reference.  The 
// function returns PC_OFFSET which is useful in debugging.
//
// Function _12c5_eeprom is adapted from Microchip's "FLASH51X" routine.
// It uses nominally 100 program words.  Note that it uses table loop_up 
// to facilitate a "switch" and thus must be located on the lower half
// of a page.
//
// Serial LCD on GP0.
//
// copyright, Peter H. Anderson, Baltimore, MD, April, '99

#case
#include <12ce519.h>
#include <defs_12c.h> // standard definitions for 12C series

int _12c5_eeprom(int op, int EEADDR, int *p_dat);

void set_dirs(int d);
void set_options(int d);

// timing routines
void delay_ms(long t);
void delay_10us(int t);

// LCD routines
void lcd_init(void);
void out_RAM_str(int *s);
void lcd_hex_byte(int val);
void lcd_dec_byte(int val, int digits);
int num_to_char(int val);
void lcd_char(int ch);
void lcd_new_line(void);

#define TxData 0

#define WR_EE 1		
#define RD_EE 0

void main(void)
{
   int n, adr, y, dat;

   lcd_init();

   for(n=0, adr=0x06; n<4; n++, adr++)  // write some data to EEPROM
   {
      dat=adr+2;           // generate some data
      y=_12c5_eeprom(WR_EE, adr, &dat);
      lcd_char('W');	// used for debugging
      lcd_hex_byte(y);	// for debugging
      delay_ms(25);
   }
   lcd_new_line();
   delay_ms(500);
   for (n=0, adr=0x06; n<4; n++, adr++)	
    	               // now read it back and display it
   {
      dat=0;   // just to show it is working
      _12c5_eeprom(RD_EE, adr, &dat);
      lcd_hex_byte(dat);
      lcd_char(' ');
   }
   lcd_new_line();
   delay_ms(500);
}

int _12c5_eeprom(int op, int EEADDR, int *p_dat)
{
   int COUNTER, PC_OFFSET, EEBYTE, EEDATA;
   if (op==WR_EE)
   {
      EEDATA=*p_dat;
      goto WRITE_BYTE;
   }
   goto READ_RANDOM;

#asm
READ_CURRENT:
    MOVLW   0x84    // PC offset for read current addr.  EE_OK bit7='1'
    MOVWF   PC_OFFSET    // Load PC offset
    GOTO    INIT_READ_CONTROL

WRITE_BYTE:
    MOVLW   0x80   // PC offset for write byte.  EE_OK: bit7 = '1'
    GOTO    INIT_WRITE_CONTROL

READ_RANDOM:
    MOVLW   0x83   // PC offset for read random.  EE_OK: bit7 = '1'

INIT_WRITE_CONTROL:
    MOVWF   PC_OFFSET     // Load PC offset register, value preset in W
    MOVLW   0xA0
    // Control byte with write bit, bit 0 = '0'

START_BIT:
    BCF     GPIO, SDA  // Start bit, SDA and SCL preset to '1'

//******* Set up output data (control, address, or data) and counter 
//*****
PREP_TRANSFER_BYTE:
    MOVWF   EEBYTE        // Byte to transfer to EEPROM already in W
    MOVLW   8            // Counter to transfer 8 bits
    MOVWF   COUNTER

//************  Clock out data (control, address, or data) byte  
//******
OUTPUT_BYTE:
        BCF     GPIO, SCL  // Set clock low during data set-up
        RLF     EEBYTE, F  // Rotate left, high order bit into carry bit
        BCF     GPIO, SDA  // Set data low, if rotated carry bit is
        BTFSC   STATUS, C  //   a '1', then:
        BSF     GPIO, SDA  // reset data pin to a one, otherwise leave low
        NOP
        BSF     GPIO, SCL  // clock data into EEPROM
        DECFSZ  COUNTER, F // Repeat until entire byte is sent
        GOTO    OUTPUT_BYTE
        NOP                // Needed to meet Timing (Thigh=4000nS)

//**********************  Acknowkedge Check 
//*******
        BCF     GPIO, SCL  // Set SCL low, 0.5us < ack valid < 3us
        NOP                // Needed to meet Timing (Tlow= 4700nS)
        BSF     GPIO, SDA
        NOP
        NOP
        NOP                // Necessary for SCL Tlow at low voltage,
        NOP                // Tlow=4700nS
        BSF     GPIO, SCL  // Raise SCL, EEPROM acknowledge still valid
        BTFSC   GPIO, SDA  // Check SDA for acknowledge (low)
        BCF     PC_OFFSET, 7 // If SDA not low (no ack), set error flag
        BCF     GPIO, SCL  // Lower SCL, EEPROM release bus
        BTFSS   PC_OFFSET, 7 // If no error continue, else stop bit
        GOTO    STOP_BIT

//*****  Set up program counter offset, based on EEPROM operating mode  
//*****
        MOVF    PC_OFFSET, W
        ANDLW   0x0F
        ADDWF   PCL, F
        GOTO    INIT_ADDRESS
        // PC offset=0, write control done, send address
        GOTO    INIT_WRITE_DATA
        // PC offset=1, write address done, send data
        GOTO    STOP_BIT
        // PC offset=2, write done, send stop bit
        GOTO    INIT_ADDRESS
        // PC offset=3, write control done, send address
        GOTO    INIT_READ_CONTROL
        // PC offset=4, send read control
        GOTO    READ_BIT_COUNTER
        // PC offset=5, set counter and read byte
        GOTO    STOP_BIT
        // PC offset=6, random read done, send stop
//**********  Initalize EEPROM data (address, data, or control) bytes  
//******
INIT_ADDRESS:
    INCF    PC_OFFSET, F // Increment PC offset to 2 (write) or to 4 (read)
    MOVF    EEADDR,W     // Put EEPROM address in W, ready to send to EEPROM
    GOTO    PREP_TRANSFER_BYTE

INIT_WRITE_DATA:
    INCF    PC_OFFSET, F // Increment PC offset to go to STOP_BIT next
    MOVF    EEDATA,W     // Put EEPROM data in W, ready to send to EEPROM
    GOTO    PREP_TRANSFER_BYTE

INIT_READ_CONTROL:
    BSF     GPIO, SCL // Raise SCL
    BSF     GPIO, SDA // raise SDA
    INCF    PC_OFFSET, F // Inc PC offset to go to READ_BIT_COUNTER next
    MOVLW   0xA1  // Set up read control byte, ready to send to EEPROM
    GOTO    START_BIT    //   bit 0 = '1' for read operation

//**************************  Read EEPROM data  
//*****
READ_BIT_COUNTER:
    BSF     GPIO, SDA // set data bit to 1 so we're not pulling bus down.
    NOP
    BSF     GPIO, SCL
    MOVLW   8           // Set counter so 8 bits will be read into EEDATA
    MOVWF   COUNTER

READ_BYTE:
    BSF     GPIO, SCL // Raise SCL, SDA valid.  SDA still input from ack
    BSF     STATUS, C              // Assume bit to be read = 1
    BTFSS   GPIO, SDA // Check if SDA = 1
    BCF     STATUS, C         // if SDA not = 1 then clear carry bit
    RLF     EEDATA, F // rotate carry bit (=SDA) into EEDATA;
    BCF     GPIO, SCL // Lower SCL
    BSF     GPIO, SDA // reset SDA
    DECFSZ  COUNTER, F //Decrement counter
    GOTO    READ_BYTE  // Read next bit if not finished reading byte

    BSF     GPIO, SCL
    NOP
    BCF     GPIO, SCL
//******************  Generate a STOP bit and RETURN  ***********************
//*****
STOP_BIT:
    BCF     GPIO, SDA // SDA=0, on TRIS, to prepare for transition to '1'
    BSF     GPIO, SCL // SCL = 1 to prepare for STOP bit
    NOP       // equivalent 4 NOPs neccessary for I2C spec Tsu:sto = 4.7us
    NOP
    NOP
    NOP
    BSF     GPIO, SDA // Stop bit, SDA transition to '1' while SCL high

#endasm
    *p_dat = EEDATA;
    return(PC_OFFSET);	// 1 is OK, 0 is NO
}

void set_options(int d)
{
#asm
   MOVF d, W
   OPTION
#endasm
}

void set_dirs(int d)
{
#asm
   MOVF d, W
   TRIS GPIO
#endasm
}

// timing routines
void delay_10us(int t)
{
#asm

DELAY_10US_1:
      CLRWDT
      NOP
      NOP
      NOP
      NOP
      NOP
      NOP
      DECFSZ t, F
      GOTO DELAY_10US_1
#endasm
}

void delay_ms(long t)	// delays t millisecs
{
   do
   {
     delay_10us(100);
   } while(--t);
}

// LCD routines
int num_to_char(int val)	// converts val to hex character
{
   int ch;
   if (val < 10)
   {
     ch=val+'0';
   }
   else
   {
     val=val-10;
     ch=val + 'A';
   }
   return(ch);
}

void lcd_char(int ch)	// serial output to PIC-n-LCD, 9600 baud
{
   int n, dly;
   		// start bit + 8 data bits

#asm
       MOVLW 9
       MOVWF n
       BCF STATUS, C

LCD_CHAR_1:

       BTFSS STATUS, C
       BSF GPIO, TxData
       BTFSC STATUS, C
       BCF GPIO, TxData
       MOVLW 32
       MOVWF dly

LCD_CHAR_2:
       DECFSZ dly, F
       GOTO LCD_CHAR_2
       RRF ch, F
       DECFSZ n, F
       GOTO LCD_CHAR_1

       BCF GPIO, TxData
       CLRWDT
       MOVLW 96
       MOVWF dly

LCD_CHAR_3:
       DECFSZ dly, F
       GOTO LCD_CHAR_3
       CLRWDT
#endasm
}

void lcd_init(void)	// sets TxData in idle state and resets PIC-n-LCD
{
#asm

        BCF GPIO, TxData
        BCF DIRS, TxData
	     MOVF DIRS, W
	     TRIS GPIO

#endasm
   lcd_char(0x0c);
   delay_ms(250);
}

void lcd_new_line(void)	// outputs 0x0d, 0x0a
{
   lcd_char(0x0d);
   delay_ms(10);	// give the PIC-n-LCD time to perform the
   lcd_char(0x0a);	// new line function
   delay_ms(10);
}


void out_RAM_str(int s)
{
   while(*s)
   {
      lcd_char(*s);
      ++s;
   }
}

void lcd_hex_byte(int val) // displays val in hex format
{
   int ch;
   ch = num_to_char((val>>4) & 0x0f);
   lcd_char(ch);
   ch = num_to_char(val&0x0f);
   lcd_char(ch);
}

void lcd_dec_byte(int val, int digits)
// displays byte in decimal as either 1, 2 or 3 digits
{
   int d;
   int ch;
   if (digits == 3)
   {
      d=val/100;
      ch=num_to_char(d);
      lcd_char(ch);
   }
   if (digits >1)	// take the two lowest digits
   {
       val=val%100;
       d=val/10;
       ch=num_to_char(d);
       lcd_char(ch);
   }
   if (digits == 1)	// take the least significant digit
   {
       val = val%100;
   }

   d=val % 10;
   ch=num_to_char(d);
   lcd_char(ch);
}