		NAME	Control_AT24Cxx

; This collection of routines allows an AT89C2051 microcontroller to read
; and write the AT24Cxx family of serial CMOS EEPROMS. This version of the
; code is compatible only with the AT89C2051 due to the location of the
; data buffer and stack in RAM. The code may be modified to work with the
; AT89C1051 by relocating or resizing the buffer and stack to fit into the
; smaller amount of RAM available in the AT89C1051. Note that the minimum
; size of the buffer is determined by the page size of the AT24Cxx.
;
; All five AT24Cxx device operations are supported. Byte Write, Current
; Address Read and Random Read are implemented by the functions WRITE_BYTE,
; READ_CURRENT and READ_RANDOM, respectively. Page Write and Sequential Read
; are special cases of functions WRITE_BLOCK and READ_BLOCK, respectively.
; WRITE_BLOCK and READ_BLOCK process from one byte to the number of bytes
; in a page and transfer data between the AT24Cxx and the RAM buffer.
;
; The code supports multiple AT24Cxx devices per bus, each with a unique
; address determined by the wiring of the address pins (A0, A1, A2). The
; three-bit programmable address is passed to the WRITE_BYTE, READ_CURRENT,
; READ_RANDOM, WRITE_BLOCK and READ_BLOCK functions, where it is combined
; with the AT24Cxx fixed address (FADDR) and used to address a device on the
; bus. Refer to the AT24Cxx family data sheets for additional information on
; device addressing.
;
; Functions BYTE_FILL, VERIFY_BYTE_FILL, PAGE_FILL and VERIFY_PAGE_FILL are
; artifacts from the debug process and serve to illustrate the use of the
; device read and write functions with an AT24C64. To modify the code for a
; different member of the AT24Cxx family, simply redefine the values of SIZE
; (the number of bytes per device) and PSIZE (the number of bytes per page).
; To change the fill value, redefine FILL. To change the programmable portion
; of the device address, redefine PADDR to a value from zero to seven.
;
; The code meets all AT24Cxx family timing requirements when executed by an
; AT89Cx051 microcontroller with a 12 MHz clock. Code modifications may be
; required if a faster clock is substituted.


FADDR		EQU	0a0h		; fixed address for AT24Cxx EEPROMs
PADDR		EQU	0		; programmable address (0..7)
SIZE		EQU	2000h		; bytes per AT24C64
PSIZE		EQU	32		; bytes per page for AT24C64
FILL		EQU	55h		; example fill value

; Register definitions.

index		EQU	r0		; buffer pointer
kount		EQU	r1		; byte count register
zdata		EQU	r1		; data register
addr_lo		EQU	r2		; 2-byte address register
addr_hi		EQU	r3		;

; Microcontroller connections to AT24Cxx serial bus lines.

SCL		BIT	p1.2		; serial clock
SDA		BIT	p1.3		; serial data


		DSEG AT 20H

		ORG	40H
buffer:		DS	PSIZE		; storage for read/write data

		ORG	60H		; stack origin
stack:		DS	20H		; stack depth


		CSEG

		ORG	0000H		; power on/reset vector
		jmp	on_reset

		ORG	0003H		; external interrupt 0 vector
		reti			; undefined

		ORG	000BH		; timer 0 overflow vector
		reti			; undefined

		ORG	0013H		; external interrupt 1 vector
		reti			; undefined

		ORG	001BH		; timer 1 overflow vector
		reti			; undefined

		ORG	0023H		; serial I/O interrupt vector
		reti			; undefined

		ORG	0080H		; begin code space
		USING	0		; register bank zero
on_reset:
		mov	sp, #(stack-1)	; initialize stack pointer

		; Initialize AT24Cxx serial bus lines.

		setb	SDA		; high
		setb	SCL		; high


		call	byte_fill
		jc	fault

		call	verify_byte_fill
		jc	fault

		call	page_fill
		jc	fault

		call	verify_page_fill
		jc	fault

	fault:
		jmp	$


byte_fill:

	; Fill every byte in an AT24Cxx with the same value.
	; Writes one address at a time (page mode is not used).
	; Returns CY set to indicate write timeout.
	; Destroys A, B, DPTR, XDATA, ADDR_HI:ADDR_LO.

		mov	zdata, #FILL	; set up fill data
		mov	dptr, #0	; initialize address pointer
	x51:
		mov	addr_lo, dpl	; set up address
		mov	addr_hi, dph	;

		mov	b, #120		; retry counter
	x52:
		mov	a, #PADDR	; programmable address
		call	write_byte	; try to write
		jnc	x53		; jump if write OK

		djnz	b, x52		; try again
		setb	c		; set timeout error flag
		jmp	x54		; exit
	x53:
		inc	dptr			; advance address pointer
;		mov	a, dpl			; check low byte
;		cjne	a, #(LOW SIZE), x51	; jump if not last
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x51	; jump if not last
		clr	c			; clear error flag
	x54:
		ret


verify_byte_fill:

	; Verify that all bytes in an AT24Cxx match a fill value.
	; Reads and verifies one byte at a time (page mode is not used).
	; Performs a Random Read function to initialize the internal
	; address counter and checks the contents of the first address.
	; Then performs multiple Current Address Read functions to step
	; through the remaining addressess.
	; Returns CY set to indicate read timeout or compare fail.
	; Destroys A, B, DPTR.

		mov	dptr, #0	; initialize address pointer/counter
		mov	addr_lo, dpl	; set up address
		mov	addr_hi, dph	;

		mov	b, #120		; retry counter
	x81:
		mov	a, #PADDR	; programmable address
		call	read_random	; try to read
		jnc	x82		; jump if read OK

		djnz	b, x81		; try again
		jmp	x86		; set error flag and exit
	x82:
		cjne	a, #FILL, x86	; jump if compare error
		jmp	x85		; do remaining addresses
	x83:
		mov	a, #PADDR
		call	read_current
		jc	x87		; jump if read fails

		cjne	a, #FILL, x86	; jump if compare error
	x85:
		inc	dptr			; advance address pointer
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x83	; jump if not last
		clr	c			; clear error flag
		jmp	x87		; exit
	x86:
		setb	c		; set error flag
	x87:
		ret


page_fill:

	; Fill every byte in an AT24Cxx with the same value.
	; Writes one page at a time.
	; Returns CY set to indicate write timeout.
	; Destroys A, B, DPTR, KOUNT, INDEX, ADDR_HI:ADDR_LO.

		; First fill buffer.

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	x61:
		mov	@index, #FILL	; put fill value in buffer
		inc	index		; advance pointer
		djnz	b, x61		; next byte

		; Copy buffer to device, one page at a time.

		mov	dptr, #0	; initialize address pointer
	x62:
		mov	addr_lo, dpl	; set up address
		mov	addr_hi, dph	;
		mov	kount, #PSIZE	; bytes per page

		mov	b, #120		; retry counter
	x63:
		mov	a, #PADDR	; programmable address
		call	write_block	; try to write
		jnc	x64		; jump if write OK

		djnz	b, x63		; try again
		setb	c		; set timeout error flag
		jmp	x66		; exit
	x64:
		; Add page size to address pointer.

		mov	a, dpl		; get low byte
		add	a, #PSIZE	; add page size
		mov	dpl, a		; save low byte
		jnc	x65		; jump if high byte not affected
		inc	dph		; increment high byte
	x65:
;		cjne	a, #(LOW SIZE), x62	; jump if low byte not last
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x62	; jump if not last
		clr	c			; clear error flag
	x66:
		ret


verify_page_fill:

	; Verify that all bytes in an AT24Cxx match a fill value.
	; Reads and verifies one page at a time.
	; Returns CY set to indicate read timeout or compare fail.
	; Destroys A, B, DPTR, KOUNT, INDEX, ADDR_HI:ADDR_LO.

		; Copy device page to buffer.

		mov	dptr, #0	; initialize address pointer
	x71:
		mov	addr_lo, dpl	; set up address
		mov	addr_hi, dph	;
		mov	kount, #PSIZE	; bytes per page

		mov	b, #120		; retry counter
	x72:
		mov	a, #PADDR	; programmable address
		call	read_block	; try to read
		jnc	x74		; jump if read OK

		djnz	b, x72		; try again
	x73:
		setb	c		; set error flag
		jmp	x77		; exit
	x74:
		; Verify buffer contents.

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	x75:
		cjne	@index, #FILL, x73	; jump if compare fails
		inc	index		; advance pointer
		djnz	b, x75		; next byte

		; Add page size to address pointer.

		mov	a, dpl		; get low byte
		add	a, #PSIZE	; add page size
		mov	dpl, a		; save low byte
		jnc	x76		; jump if high byte not affected
		inc	dph		; increment high byte
	x76:
;		cjne	a, #(LOW SIZE), x71	; jump if low byte not last
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x71	; jump if not last
		clr	c			; clear error flag
	x77:
		ret


write_block:

	; Write from one byte to one page of data to an AT24Cxx.
	; Called with programmable address in A, address of first byte
	; in register pair ADDR_HI:ADDR_LO, data in BUFFER, byte count
	; in register KOUNT.
	; Does not wait for write cycle to complete.
	; Returns CY set to indicate that the bus is not available
	; or that the addressed device failed to acknowledge.
	; Destroys A, KOUNT, INDEX.

		call	start
		jc	x38		; abort if bus not available

		rl	a		; programmable address to bits 3:1
		orl	a, #FADDR	; add fixed address
		clr	acc.0		; specify write operation
		call	shout		; send device address
		jc	x37		; abort if no acknowledge

		mov	a, addr_hi	; send high byte of address
		call	shout		;
		jc	x37		; abort if no acknowledge

		mov	a, addr_lo	; send low byte of address
		call	shout		;
		jc	x37		; abort if no acknowledge

		mov	index, #buffer	; point to buffer
	x36:
		mov	a, @index	; get data
		call	shout		; send data
		jc	x37		; abort if no acknowledge

		inc	index		; advance buffer pointer
		djnz	kount, x36	; next byte
		clr	c		; clear error flag
	x37:
		call	stop
	x38:
		ret


read_block:

	; Read from one byte to one page of data from an AT24Cxx.
	; Performs a Random Read which is extended into a Sequential Read
	; when more than one byte is read. Called with programmable address
	; in A, address of first byte in register pair ADDR_HI:ADDR_LO,
	; byte count in register KOUNT.
	; Returns data in BUFFER. Returns CY set to indicate that the bus is
	; not available or that the addressed device failed to acknowledge.
	; Destroys A, KOUNT, INDEX.

		; Send dummy write command to address first byte.

		call	start
		jc	x35		; abort if bus not available

		rl	a		; programmable address to bits 3:1
		orl	a, #FADDR	; add fixed address
		mov	index, a	; save copy of device address
		clr	acc.0		; specify write operation
		call	shout		; send device address
		jc	x34		; abort if no acknowledge

		mov	a, addr_hi	; send high byte of address
		call	shout		;
		jc	x34		; abort if no acknowledge

		mov	a, addr_lo	; send low byte of address
		call	shout		;
		jc	x34		; abort if no acknowledge

		; Send read command and receive data.

		call	start		; second start for read
		jc	x34		; abort if bus not available

		mov	a, index	; get device address
		setb	acc.0		; specify read operation
		call	shout		; send device address
		jc	x34		; abort if no acknowledge

		mov	index, #buffer	; point to buffer
	x31:
		call	shin		; receive data byte
		mov	@index, a	; save data

		cjne	kount, #1, x32	; jump if not last byte
		call	NAK		; do not acknowledge last byte
		jmp	x33		; done
	x32:
		call	ACK		; acknowledge byte
		inc	index		; advance buffer pointer
		djnz	kount, x31	; next byte
	x33:
		clr	c		; clear error flag
	x34:
		call	stop
	x35:
		ret


write_byte:

	; AT24Cxx Byte Write function.
	; Called with programmable address in A, byte address in
	; register pair ADDR_HI:ADDR_LO, data in register XDATA.
	; Does not wait for write cycle to complete.
	; Returns CY set to indicate that the bus is not available
	; or that the addressed device failed to acknowledge.
	; Destroys A.

		call	start
		jc	x49		; abort if bus not available

		rl	a		; programmable address to bits 3:1
		orl	a, #FADDR	; add fixed address
		clr	acc.0		; specify write operation
		call	shout		; send device address
		jc	x48		; abort if no acknowledge

		mov	a, addr_hi	; send high byte of address
		call	shout		;
		jc	x48		; abort if no acknowledge

		mov	a, addr_lo	; send low byte of address
		call	shout		;
		jc	x48		; abort if no acknowledge

		mov	a, zdata	; get data
		call	shout		; send data
		jc	x48		; abort if no acknowledge

		clr	c		; clear error flag
	x48:
		call	stop
	x49:
		ret


read_current:

	; AT24Cxx Current Address Read function.
	; Called with programmable address in A. Returns data in A.
	; Returns CY set to indicate that the bus is not available
	; or that the addressed device failed to acknowledge.

		call	start
		jc	x45		; abort if bus not available

		rl	a		; programmable address to bits 3:1
		orl	a, #FADDR	; add fixed address
		setb	acc.0		; specify read operation
		call	shout		; send device address
		jc	x44		; abort if no acknowledge

		call	shin		; receive data byte
		call	NAK		; do not acknowledge byte
		clr	c		; clear error flag
	x44:
		call	stop
	x45:
		ret


read_random:

	; AT24Cxx Random Read function.
	; Called with programmable address in A, byte address in
	; register pair ADDR_HI:ADDR_LO. Returns data in A.
	; Returns CY set to indicate that the bus is not available
	; or that the addressed device failed to acknowledge.

		push	b
		mov	b, a		; save copy of programmable address

		; Send dummy write command to set internal address.

		call	start
		jc	x47		; abort if bus not available

		rl	a		; programmable address to bits 3:1
		orl	a, #FADDR	; add fixed address
		clr	acc.0		; specify write operation
		call	shout		; send device address
		jc	x46		; abort if no acknowledge

		mov	a, addr_hi	; send high byte of address
		call	shout		;
		jc	x46		; abort if no acknowledge

		mov	a, addr_lo	; send low byte of address
		call	shout		;
		jc	x46		; abort if no acknowledge

		; Call Current Address Read function.

		mov	a, b		; get programmable address
		call	read_current
		jmp	x47		; exit
	x46:
		call	stop
	x47:
		pop	b
		ret


start:

	; Send START, defined as high-to-low SDA with SCL high.
	; Return with SCL, SDA low.
	; Returns CY set if bus is not available.

		setb	SDA
		setb	SCL

		; Verify bus available.

		jnb	SDA, x40	; jump if not high
		jnb	SCL, x40	; jump if not high

		nop			; enforce setup delay and cycle delay
		clr	SDA
		nop			; enforce hold delay
		nop			;
		nop			;
		nop			;
		nop			;
		clr	SCL

		clr	c		; clear error flag
		jmp	x41
	x40:
		setb	c		; set error flag
	x41:
		ret


stop:

	; Send STOP, defined as low-to-high SDA with SCL high.
	; SCL expected low on entry. Return with SCL, SDA high.

		clr	SDA
		nop			; enforce SCL low and data setup
		nop
		setb	SCL
		nop			; enforce setup delay
		nop			;
		nop			;
		nop			;
		nop			;
		setb	SDA
		ret


shout:

	; Shift out a byte to the AT24Cxx, most significant bit first.
	; SCL, SDA expected low on entry. Return with SCL low.
	; Called with data to send in A.
	; Returns CY set to indicate failure by slave to acknowledge.
	; Destroys A.

		push	b
		mov	b, #8		; bit counter
	x42:
		rlc	a		; move bit into CY
		mov	SDA, c		; output bit
		nop			; enforce SCL low and data setup
		setb	SCL		; raise clock
		nop			; enforce SCL high
		nop			;
		nop			;
		nop			;
		clr	SCL		; drop clock
		djnz	b, x42		; next bit

		setb	SDA		; release SDA for ACK
		nop			; enforce SCL low and tAA
		nop			;
		setb	SCL		; raise ACK clock
		nop			; enforce SCL high
		nop			;
		nop			;
		nop			;
		mov	c, SDA		; get ACK bit
		clr	SCL		; drop ACK clock

		pop	b
		ret


shin:

	; Shift in a byte from the AT24Cxx, most significant bit first.
	; SCL expected low on entry. Return with SCL low.
	; Returns received data byte in A.

		setb	SDA		; make SDA an input

		push	b
		mov	b, #8		; bit count
	x43:
		nop			; enforce SCL low and data setup
		nop			;
		nop			;
		setb	SCL		; raise clock
		nop			; enforce SCL high
		nop			;
		mov	c, SDA		; input bit
		rlc	a		; move bit into byte
		clr	SCL		; drop clock
		djnz	b, x43		; next bit

		pop	b
		ret


ACK:

	; Clock out an acknowledge bit (low).
	; SCL expected low on entry. Return with SCL, SDA low.

		clr	SDA		; ACK bit
		nop			; enforce SCL low and data setup
		nop			;
		setb	SCL		; raise clock
		nop			; enforce SCL high
		nop			;
		nop			;
		nop			;
		clr	SCL		; drop clock
		ret


NAK:

	; Clock out a negative acknowledge bit (high).
	; SCL expected low on entry. Return with SCL low, SDA high.

		setb	SDA		; NAK bit
		nop			; enforce SCL low and data setup
		nop			;
		setb	SCL		; raise clock
		nop			; enforce SCL high
		nop			;
		nop			;
		nop			;
		clr	SCL		; drop clock
		ret


		END
