Interfacing a PIC with a Microchip 24LC65 8K X 8 EEPROM
copyright, Nicole Ambrose, Dept of Electrical
Engineering,
Morgan State University, Baltimore, MD 21239, August 3,
'97
Introduction.
This discussion deals with interfacing a Microchip 24LC65 serial EEPROM
with a PIC.
A data sheet for the 24LC65 is available from Microchip and devices are available
from Digikey for about $3.50.
The 24LC65 is organized as 8192 8-bit bytes. Thus, valid addresses range
from 0000H - 7FFF.
Typical applications include using EEPROM to store constants or
calibration data specific to a particular installation. A specific
example is saving the 64-bit serial numbers associated with addressing the
Dallas 1-wire devices such as the DS1820 digital thermometers.
Another application is to perform calculations using the EEPROM as a
lookup table. For example, an 12 bit A/D value might be mapped into one
of 4096 addresses which contains a two byte value corresponding to some
kind of real world quantity. Dedicating an EEPROM to avoid performing
such functions as sin(x) or ln(x) may seem extravagant, but the cost of
this device is but $3.50.
An obvious application is data logging. Various parameters may be logged
with a 4-channel A/D such as the PCF8591 and the results of a measurement
sequence might be written to EEPROM for later retrieval such as download
to a PC for evaluation using such tools as spreadsheet analyis.
Program 24LC65_1.ASM.
This program illustrates how to write a single data byte to an address
location and how to read the content of an address.
The address byte consists of the manufacturer's asssigned group code of
1010, followed by the users setting of the A2, A1 and A0 leads, followed
by the R/W bit. The R/W bit is 0 for writes and 1 for reads.
In routine RANDOM_WRITE, the address is passed in variables ADR_HI and
ADR_LO and the data is passed in variable DAT_VAL. The sequence begins
with the START command, followed by sending the I2C address byte with the
R/W bit set to zero, followed the high and low bytes of the address in the
EEPROM, followed by the single data byte. Finally, the STOP command is
sent. Note that a 25 msec delay is included to provide for the time
required to burn the value into the EEPROM.
In routine RANDOM_READ, the address is passed in variables ADR_HI and
ADR_LO. The sequence bgins with the START command followed by the two I2C
address bytes with the R/W bit cleared to 0 (write). Initially, I found
this is a bit confusing as we are performing a read operation. However,
one might think of it as "writing" the two address bytes to the EEPROM.
After the two address bytes have been written, another START command is
initiated without any STOP command, followed by the address byte with the
R/W bit set to a one (read), followed by reading the data using the
IN_BYTE routine. This byte is returned to the calling program in the W
register.
; Program 24LC65_1.ASM
;
; Illustrates how to write a byte to an address and read a byte from an
; an address.
;
; Program writes the value 6CH to location 0341H and then reads the
; location
;
; PIC16C84 24LC65
;
; RB7 (term 13) ------------------- SCL (term 6) ----- To Other
; RB6 (term 12) ------------------- SDA (term 5) ----- I2C Devices
;
; Note that the slave address is determined by A2 (term 3), A1
; (term 2) and A0 (term 1) on the 24LC65. The above SCL and SDA leads
; may be multipled to eight group "1010" devices, each strapped for a
; unique A2 A1 A0 setting.
;
; 10K pullup resistors to +5VDC are required on both signal leads.
;
; copyright, Nicole L. Ambrose, MSU, 14 July, '97
LIST p=16c84
#include <c:\mplab\p16c84.inc>
__CONFIG 11h
CONSTANT SDA=6
CONSTANT SCL=7
CONSTANT VARS=0CH
DAT_VAL EQU VARS+0
NUM_VAL EQU VARS+1
ADR_HI EQU VARS+2
ADR_LO EQU VARS+3
DEV_ADR EQU VARS+4 ; A2, A1, A0
_N EQU VARS+5 ; used for I2C routines
O_BYTE EQU VARS+6
I_BYTE EQU VARS+7
LOOP1 EQU VARS+8 ; timing
LOOP2 EQU VARS+9 ; timing
ORG 000H ;program code to start at 000H
BSF STATUS, RP0 ; RP1 = 0, RP0 = 1, BANK1
CLRF TRISB ; make all PortB bits outputs
BCF STATUS, RP0
MOVLW 00H
MOVWF DEV_ADR ; A2 A1 A0
MOVLW 03H ; dummy up ADR_HI and ADR_LO
MOVWF ADR_HI
MOVLW 41H
MOVWF ADR_LO
MOVLW 6CH ; dummy data set to 6CH
MOVWF DAT_VAL
CALL RANDOM_WRITE ; write it
MOVLW .25 ; 25 msec delay to allow for programming
; EEPROM
CALL DELAY_N_MS
CALL RANDOM_READ ; now read it back
CALL DISPLAY
DONE: GOTO DONE
; end main
RANDOM_WRITE: ; write DAT_VAL to 16-bit address ADR_HI and ADR_LO
CALL START
BCF STATUS, C ; send address byte
RLF DEV_ADR, W
IORLW 0A0H
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W ; send high byte of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_LO, W ; send low byte of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF DAT_VAL, W ; send the actual data
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
CALL STOP
RETURN
RANDOM_READ: ; reads data at location specified in ADR_HI & ADR_LO
; returns result in W
CALL START
BCF STATUS, C ; send address byte - write
RLF DEV_ADR, W
IORLW 0A0H
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W ; send high and low bytes of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
CALL START ; note there is no STOP
BCF STATUS, C
RLF DEV_ADR, W
IORLW 0A1H ; R/W set to one for read operation
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
CALL IN_BYTE ; fetch the byte
CALL NACK
CALL STOP
MOVF I_BYTE, W ; return the byte in W
RETURN
DISPLAY: ; this is a dummy routine used as a convenient
; break point
RETURN
; The following routines are low level I2C routines applicable to most
; interfaces with I2C devices.
IN_BYTE ; read byte on i2c bus
CLRF I_BYTE
MOVLW .8
MOVWF _N ; set index to 8
CALL HIGH_SDA ; be sure SDA is configured as input
IN_BIT
CALL HIGH_SCL ; clock high
BTFSS PORTB, SDA ; test SDA bit
GOTO IN_ZERO
GOTO IN_ONE
IN_ZERO
BCF STATUS, C ; clear any carry
RLF I_BYTE, F ; i_byte = i_byte << 1 | 0
GOTO CONT_IN
IN_ONE
BCF STATUS, C ; clear any carry
RLF I_BYTE, F
INCF I_BYTE, F ; i_byte = (i_byte << 1) | 1
GOTO CONT_IN
CONT_IN
CALL LOW_SCL ; bring clock low
DECFSZ _N, F ; decrement index
GOTO IN_BIT
RETURN
;;;;;;
OUT_BYTE: ; send o_byte on I2C bus
MOVLW .8
MOVWF _N
OUT_BIT:
BCF STATUS,C ; clear carry
RLF O_BYTE, F ; left shift, most sig bit is now in carry
BTFSS STATUS, C ; if one, send a one
GOTO OUT_ZERO
GOTO OUT_ONE
OUT_ZERO:
CALL LOW_SDA ; SDA at zero
CALL CLOCK_PULSE
CALL HIGH_SDA
GOTO OUT_CONT
OUT_ONE:
CALL HIGH_SDA ; SDA at logic one
CALL CLOCK_PULSE
GOTO OUT_CONT
OUT_CONT:
DECFSZ _N, F ; decrement index
GOTO OUT_BIT
RETURN
;;;;;;
NACK: ; bring SDA high and clock
CALL HIGH_SDA
CALL CLOCK_PULSE
RETURN
ACK:
CALL LOW_SDA
CALL CLOCK_PULSE
RETURN
START:
CALL LOW_SCL
CALL HIGH_SDA
CALL HIGH_SCL
CALL LOW_SDA ; bring SDA low while SCL is high
CALL LOW_SCL
RETURN
STOP:
CALL LOW_SCL
CALL LOW_SDA
CALL HIGH_SCL
CALL HIGH_SDA ; bring SDA high while SCL is high
CALL LOW_SCL
RETURN
CLOCK_PULSE: ; SCL momentarily to logic one
CALL HIGH_SCL
CALL LOW_SCL
RETURN
HIGH_SDA: ; high impedance by making SDA an input
BSF STATUS, RP0 ; bank 1
BSF TRISB, SDA ; make SDA pin an input
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
LOW_SDA:
BCF PORTB, SDA
BSF STATUS, RP0 ; bank 1
BCF TRISB, SDA ; make SDA pin an output
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
HIGH_SCL:
BSF STATUS, RP0 ; bank 1
BSF TRISB, SCL ; make SCL pin an input
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
LOW_SCL:
BCF PORTB, SCL
BSF STATUS, RP0 ; bank 1
BCF TRISB, SCL ; make SCL pin an output
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
DELAY_SHORT: ; provides nominal 25 usec delay
MOVLW .5
MOVWF LOOP2
DELAY_SHORT_1:
NOP
DECFSZ LOOP2, F
GOTO DELAY_SHORT_1
RETURN
DELAY_LONG
MOVLW .250 ; 250 msec delay
MOVWF LOOP1
DELAY_N_MS:
OUTTER
MOVLW .110 ; close to 1.0 msec delay when set to .110
MOVWF LOOP2
INNER
NOP
NOP
NOP
NOP
NOP
NOP
DECFSZ LOOP2, F ; decrement and leave result in LOOP2
; skip next statement if zero
GOTO INNER
DECFSZ LOOP1, F
GOTO OUTTER
RETURN
END
Program 24LC65.ASM.
This program illustrates various tools that might be used in implementing
a data logger.
A data buffer at variable location DAT_BUFF (18H) is used as a common
interface point between modules. For example, the result of a measurment
sequence might be written to the data buffer. The data may then be read
from the data buffer and written to EEPROM. Data may later be retrieved
from EEPROM and written to the data buffer for either display or serial
transfer to a PC.
In this program, routine MAKE_MEAS_SEQ is used to dummy four values to the
data buffer. This might be the result of four readings from a PCF8591 A/D
converter, an eight byte serial number from a Dallas 1-wire device or a
four byte counter value from a DS1602 elapsed time counter.
Routine SEQ_WRITE fetches each value from the data buffer and writes it to
EEPROM. The address in the EEPROM is passed in variables ADR_HI and
ADR_LO. A sequential write is the same command sequence as a write,
except that after sending the EEPROM high and low addresses, the data
bytes are send in sequence with a nack after each byte. Up to 64 bytes
may be programmed in this manner and the bytes are stored sequentially
beginning at the defined address.
Note that in sequentially writing bytes, a page boundary (64 bytes) must
not be crossed.
Thus, when using the data buffer approach, it is important that each
measurement consist of 1, 2, 4, 8, 16 or 32 bytes.
For example, when using a DS1621 (or DS1820 or DS1821) digital
thermometer, each measurement might consist of T_C, the remaining count
and the slope. As this is only three bytes, a page boundary will
eventually be crossed when performing a sequential write. This can be
avoided by dummying a fourth variable in the data buffer. Note, that even
with this apparent "waste", a single 24LC65 has the ability to store over
2,000 4-byte data sample.
The SEQ_READ routine is similar to reading a single byte, except that
multiple bytes are sequentially read, with the master sending an ACK to
acknowledge the receipt of each byte.
In the program, this data is written to the data buffer.
It is then displayed on a serial LCD by reading from the data buffer.
; Program 24LC65_2.ASM
;
; Illustrates how to sequentially write a series of bytes to serial
; EEPROM, beginning at a defined EEPROM address. Also illustrates a
; sequential read from EEPROM.
;
; Program calls MAKE_MEAS_SEQ which is a dummy routine to simulate
; the raw data obtained from a PCF8591, DS1621 or similar. Raw data
; is written to the data location beginning at 18H.
;
; SEQ_WRITE then reads these values and writes them to the 24LC65
; EEPROM.
;
; SEQ_READ then reads the data from the 24LC65 and writes it to the data
; locations beginning at 18H. These values are then displayed.
;
; Doing this back to back is, of course, foolish. But, the program is
; intended to show how to transfer data from a measurement and save it to
; EEPROM and how to read it.
;
; PIC16C84 24LC65
;
; RB7 (term 13) ------------------- SCL (term 6) ----- To Other
; RB6 (term 12) ------------------- SDA (term 5) ----- I2C Devices
;
; PORTA, Bit 1 (terminal 18) ------ TX ----------> to RX on Serial LCD
;
; Note that the slave address is determined by A2 (term 3), A1
; (term 2) and A0 (term 1) on the 24LC65. The above SCL and SDA leads
; may be multipled to eight group "1010" devices, each strapped for a
; unique A2 A1 A0 setting.
;
; 10K pullup resistors to +5VDC are required on both signal leads.
;
; Note that LCD_CTRL is included at the end of this program.
;
; copyright, Nicole L. Ambrose, MSU, 14 July, '97
LIST p=16c84
#include <c:\mplab\p16c84.inc>
__CONFIG 11h
CONSTANT SDA=6
CONSTANT SCL=7
CONSTANT DATA_BUFF=18H ; measurements saved to 18, 19, 1A and 1B
CONSTANT BUFF_SIZE=.4 ; number of measurments
CONSTANT VARS=0CH
N EQU VARS+0
ADR_HI EQU VARS+1
ADR_LO EQU VARS+2
DEV_ADR EQU VARS+3 ; A2, A1, A0
_N EQU VARS+4 ; used for I2C routines
O_BYTE EQU VARS+5
I_BYTE EQU VARS+6
LOOP1 EQU VARS+7 ; timing
LOOP2 EQU VARS+8 ; timing
ORG 000H ;program code to start at 000H
BSF STATUS, RP0 ; RP1 = 0, RP0 = 1, BANK1
CLRF TRISB ; make all PortB bits outputs
BCF STATUS, RP0
MOVLW 00H
MOVWF DEV_ADR ; A2 A1 A0
CALL MAKE_MEAS_SEQ ; dummy some values to the data buffer
MOVLW 03H ; dummy up ADR_HI and ADR_LO
MOVWF ADR_HI
MOVLW 00H
MOVWF ADR_LO
CALL SEQ_WRITE ; write the values to the 24LC65
MOVLW DATA_BUFF ; clear the data buffer just to be sure
MOVWF FSR ; the following seq read works
MOVLW BUFF_SIZE
MOVWF N
CLR:
CLRF INDF ; clear each location in the data buffer
INCF FSR, F
DECFSZ N, F
GOTO CLR
MOVLW 03H ; dummy up ADR_HI and ADR_LO
MOVWF ADR_HI
MOVLW 00H
MOVWF ADR_LO
CALL SEQ_READ ; now read data back from EEPROM
CALL DISPLAY ; and display the values
DONE: GOTO DONE
; end main
MAKE_MEAS_SEQ: ; dummy up the data buffer with 4 values
MOVLW DATA_BUFF ; presummable this would be the result
MOVWF FSR ; of a measurement process
MOVLW 11H ; data is 11H, 22H, 33H, 44H
MOVWF INDF
INCF FSR, F
MOVLW 22H
MOVWF INDF
INCF FSR, F
MOVLW 33H
MOVWF INDF
INCF FSR, F
MOVLW 44H
MOVWF INDF
RETURN
SEQ_WRITE: ; write DAT_VAL to 16-bit address ADR_HI and ADR_LO
CALL START
BCF STATUS, C ; send address byte
RLF DEV_ADR, W
IORLW 0A0H
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W ; send high byte of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_LO, W ; send low byte of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVLW DATA_BUFF ; now write BUFF_SIZE bytes to EEPROM
MOVWF FSR
MOVLW BUFF_SIZE
MOVWF N
SEQ_WRITE_1:
MOVF INDF, W ; fetch the data byte from the buffer
MOVWF O_BYTE ; and save it to EEPROM
CALL OUT_BYTE
CALL NACK
MOVLW .25 ; 25 msec delay for EEPROM to burn
CALL DELAY_N_MS
INCF FSR, F
DECFSZ N, F
GOTO SEQ_WRITE_1
CALL STOP
RETURN
SEQ_READ: ; reads data bytes beginning at location specified
; in ADR_HI & ADR_LO and places in DATA_BUFF
CALL START
BCF STATUS, C ; send address byte - write
RLF DEV_ADR, W
IORLW 0A0H
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W ; send high and low bytes of address
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVF ADR_HI, W
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
CALL START ; note there is no STOP
BCF STATUS, C
RLF DEV_ADR, W
IORLW 0A1H ; R/W set to one for read operation
MOVWF O_BYTE
CALL OUT_BYTE
CALL NACK
MOVLW DATA_BUFF ; now sequentially read bytes from EEPROM
MOVWF FSR
MOVLW BUFF_SIZE
MOVWF N
SEQ_READ_1:
CALL IN_BYTE ; fetch each byte from EEPROM
MOVF I_BYTE, W
MOVWF INDF ; and save in data buffer
INCF FSR, F
DECFSZ N, F
GOTO SEQ_READ_2 ; not done
GOTO SEQ_READ_3
SEQ_READ_2:
CALL ACK ; if not done, send an ACK and continue
GOTO SEQ_READ_1
SEQ_READ_3:
CALL NACK ; if done, send a NACK
CALL STOP
RETURN
DISPLAY: ; display BUFF_SIZE bytes on Line 1 of serial LCD
CALL LCD_CLR
CALL LCD_LINE1
MOVLW DATA_BUFF ; now read from buffer and display
MOVWF FSR
MOVLW BUFF_SIZE
MOVWF N
DISPLAY_1:
MOVF INDF, W ; fetch the data byte from the buffer
CALL LCD_VAL ; display it
MOVLW " "
CALL LCD_CHAR ; send two spaces
CALL LCD_CHAR
INCF FSR, F
DECFSZ N, F
GOTO DISPLAY_1
RETURN
; The following routines are low level I2C routines applicable to most
; interfaces with I2C devices.
IN_BYTE ; read byte on i2c bus
CLRF I_BYTE
MOVLW .8
MOVWF _N ; set index to 8
CALL HIGH_SDA ; be sure SDA is configured as input
IN_BIT
CALL HIGH_SCL ; clock high
BTFSS PORTB, SDA ; test SDA bit
GOTO IN_ZERO
GOTO IN_ONE
IN_ZERO
BCF STATUS, C ; clear any carry
RLF I_BYTE, F ; i_byte = i_byte << 1 | 0
GOTO CONT_IN
IN_ONE
BCF STATUS, C ; clear any carry
RLF I_BYTE, F
INCF I_BYTE, F ; i_byte = (i_byte << 1) | 1
GOTO CONT_IN
CONT_IN
CALL LOW_SCL ; bring clock low
DECFSZ _N, F ; decrement index
GOTO IN_BIT
RETURN
;;;;;;
OUT_BYTE: ; send o_byte on I2C bus
MOVLW .8
MOVWF _N
OUT_BIT:
BCF STATUS,C ; clear carry
RLF O_BYTE, F ; left shift, most sig bit is now in carry
BTFSS STATUS, C ; if one, send a one
GOTO OUT_ZERO
GOTO OUT_ONE
OUT_ZERO:
CALL LOW_SDA ; SDA at zero
CALL CLOCK_PULSE
CALL HIGH_SDA
GOTO OUT_CONT
OUT_ONE:
CALL HIGH_SDA ; SDA at logic one
CALL CLOCK_PULSE
GOTO OUT_CONT
OUT_CONT:
DECFSZ _N, F ; decrement index
GOTO OUT_BIT
RETURN
;;;;;;
NACK: ; bring SDA high and clock
CALL HIGH_SDA
CALL CLOCK_PULSE
RETURN
ACK:
CALL LOW_SDA
CALL CLOCK_PULSE
RETURN
START:
CALL LOW_SCL
CALL HIGH_SDA
CALL HIGH_SCL
CALL LOW_SDA ; bring SDA low while SCL is high
CALL LOW_SCL
RETURN
STOP:
CALL LOW_SCL
CALL LOW_SDA
CALL HIGH_SCL
CALL HIGH_SDA ; bring SDA high while SCL is high
CALL LOW_SCL
RETURN
CLOCK_PULSE: ; SCL momentarily to logic one
CALL HIGH_SCL
CALL LOW_SCL
RETURN
HIGH_SDA: ; high impedance by making SDA an input
BSF STATUS, RP0 ; bank 1
BSF TRISB, SDA ; make SDA pin an input
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
LOW_SDA:
BCF PORTB, SDA
BSF STATUS, RP0 ; bank 1
BCF TRISB, SDA ; make SDA pin an output
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
HIGH_SCL:
BSF STATUS, RP0 ; bank 1
BSF TRISB, SCL ; make SCL pin an input
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
LOW_SCL:
BCF PORTB, SCL
BSF STATUS, RP0 ; bank 1
BCF TRISB, SCL ; make SCL pin an output
BCF STATUS, RP0 ; back to bank 0
CALL DELAY_SHORT
RETURN
DELAY_SHORT: ; provides nominal 25 usec delay
MOVLW .5
MOVWF LOOP2
DELAY_SHORT_1:
NOP
DECFSZ LOOP2, F
GOTO DELAY_SHORT_1
RETURN
DELAY_LONG
MOVLW .250 ; 250 msec delay
MOVWF LOOP1
DELAY_N_MS:
OUTTER
MOVLW .110 ; close to 1.0 msec delay when set to .110
MOVWF LOOP2
INNER
NOP
NOP
NOP
NOP
NOP
NOP
DECFSZ LOOP2, F ; decrement and leave result in LOOP2
; skip next statement if zero
GOTO INNER
DECFSZ LOOP1, F
GOTO OUTTER
RETURN
#include <a:\lcd\lcd_ctrl.asm>
END
Назад
|