		NAME	Control_AT25xxx

; This collection of routines allows the Atmel AT89C5x and AT89Cx051 micro-
; controllers to read and write the AT25xxx family of serial CMOS EEPROMS.
; All AT25xxx family timing requirements are met when the code is run on a
; processor with a 24 MHz (or slower) clock and the EEPROM is powered from
; a supply voltage of from 4.5 to 5.5 Volts.
;
; All six AT25xxx device operations are supported. Read Status Register,
; Write Status Register (set write protection levels), Write Enable and
; Write Disable are implemented by the functions READ_STATUS, WRITE_STATUS,
; ENABLE_WRITE and DISABLE_WRITE, respectively. The operations Read Data
; from Memory, and Write Data to Memory are supported by READ_BYTE and
; WRITE_BYTE, respectively. These functions read and write a single byte,
; while the functions READ_PAGE and WRITE_PAGE utilize the page mode of the
; AT25xxx to read and write an eight-byte page. The page data is transferred
; between the AT25xxx and a RAM buffer.
;
; None of the functions which initiates a write operation (WRITE_STATUS,
; WRITE_BYTE, WRITE_PAGE) waits for the operation to complete. It is the
; responsibility of the calling routine to absorb the time delay required
; for the completion of the write operation. The status of the device may
; be determined utilizing the READ_STATUS function. Note that all write
; operations (including WRITE_STATUS) must be preceded by a call to
; ENABLE_WRITE.
;
; 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 AT25040. To modify the code for
; a different member of the AT25xxx family, simply redefine the values of
; SIZE (the number of bytes per device) and PSIZE (the number of bytes per
; page).
;
; Primitive functions SHIN (SHift IN) and SHOUT (SHift OUT) manage the
; movement of data on the serial bus. The serial clock is generated and
; timed by software.
;
; The function COMMANDER replaces READ_STATUS, WRITE_STATUS, ENABLE_WRITE,
; DISABLE_WRITE, READ_BYTE and WRITE_BYTE and provides a single entry point
; for all operations. COMMANDER is called with the command code in the
; accumulator and address and data (if required) in designated registers.
; The functions COMMANDER, SEND_ADDR, SHIN and SHOUT require a total of
; 128 bytes, including initialization code. The functions READ_STATUS,
; WRITE_STATUS, ENABLE_WRITE, DISABLE_WRITE, READ_BYTE, WRITE_BYTE, SHIN
; and SHOUT require a total of 160 bytes, including initialization code.
;
; AT25xxx control lines are mapped to microcontroller I/O port pins in the
; definitions of CS, SCK, SI and SO. To specify the three-wire configuration,
; assign SI and SO to the same pin. To specify the four-wire configuration,
; assign SI and SO to different pins. No other code changes are required.
; Code to initialize the AT25xxx control lines should be included in the
; user's application in the reset or initialization area.


PSIZE		EQU	8		; bytes per page for AT25xxx
SIZE		EQU	200h		; bytes per AT25040
NPAGES		EQU	SIZE/PSIZE	; pages per device
FILL		EQU	55h		; example fill value

; AT25xxx device command definitions.

RDSR		EQU	05h		; Read Status Register
WRSR		EQU	01h		; Write Status Register
READ		EQU	03h		; Read Data from Memory
WRITE		EQU	02h		; Write Data to Memory
WREN		EQU	06h		; Write Enable
WRDI		EQU	04h		; Write Disable

; Microcontroller connections to AT25xxx control lines.
; For a three-wire interface, define SI and SO to be the same pin.
; For a four-wire interface, define SI and SO to be different pins.
; No other code changes are required.

CS		BIT	p1.2		; AT25xxx chip select
SCK		BIT	p1.3		;       serial data clock
SI		BIT	p1.4		;       serial data input
SO		BIT	p1.5		;       serial data output

; Bit definitions.

A8		BIT	acc.3		; MSB of address
NRDY		BIT	acc.0		; high = write cycle in progress
WEN		BIT	acc.1		; high = memory write enabled
BP0		BIT	acc.2		; block write protection levels
BP1		BIT	acc.3		;

; Register utilization.

index		EQU	r0		; buffer pointer
zdata		EQU	r2		; data register
addr_lo		EQU	r4		; 2-byte address register
addr_hi		EQU	r5		;
kount		EQU	r7		; page counter


		DSEG AT 20H

		ORG	20H
buffer:		DS	PSIZE		; storage for read/write data

		ORG	30H		; stack origin
stack:		DS	10H		; 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 AT25xxx control lines.

		setb	CS		; high
		clr	SCK		; low
		setb	SI		; high
		setb	SO		; high

;
;	Sample function calls.
;

	; Protect memory regions.

;		call	enable_write	; must precede status register write

;		mov	a, #0		; no protection
;		mov	a, #04h		; protect top 1/4
;		mov	a, #08h		; protect top 1/2
;		mov	a, #0ch		; protect all
;		call	write_status	; write protection level
;	xxx:
;		call	read_status	; check write status
;		jb	NRDY, xxx	; loop until ready


	; Protect memory regions using COMMANDER.

;		mov	a, #WREN	; must precede status register write
;		call	commander

;		mov	zdata, #0	; no protection
;		mov	zdata, #04h	; protect top 1/4
;		mov	zdata, #08h	; protect top 1/2
;		mov	zdata, #0ch	; protect all
;		mov	a, #WRSR	; write protection level
;		call	COMMANDER
;	xxx:
;		mov	a, #RDSR	; check write status
;		call	commander
;		mov	a, zdata	; get status
;		jb	NRDY, xxx	; loop until ready


	; Write and verify entire device, by bytes.

;		call	byte_fill		; write
;		call	verify_byte_fill	; verify
;		jc	fault			; jump on verify error


	; Write and verify entire device, by pages. Utilizes RAM buffer.

;		call	page_fill		; write
;		call	verify_page_fill	; verify
;		jc	fault			; jump on verify error


	; Write and verify one byte.

;		call	enable_write	; must precede each byte write

;		call	disable_write	; cancel write enable (disable writes)

;		mov	a, #FILL	; data
;		mov	dptr, #123h	; address
;		call	write_byte	; write
;	yyy:
;		call	read_status	; check write status
;		jb	NRDY, yyy	; loop until ready

;		mov	dptr, #123h	; address
;		call	read_byte	; read
;		cjne	a, #FILL, fault	; jump on compare error


	; Write and verify one byte using COMMANDER.

;		mov	a, #WREN	; must precede each byte write
;		call	commander

;		mov	a, #WRDI	; cancel write enable (disable writes)
;		call	commander

;		mov	zdata, #FILL	; data
;		mov	addr_lo, #23h	; address
;		mov	addr_hi, #01h	;
;		mov	a, #WRITE
;		call	commander
;	yyy:
;		mov	a, #RDSR	; check write status
;		call	commander
;		mov	a, zdata	; get status
;		jb	NRDY, yyy	; loop until ready

;		mov	zdata, #FILL	; data
;		mov	addr_lo, #23h	; address
;		mov	addr_hi, #01h	;
;		mov	a, #READ
;		call	commander
;		cjne	zdata, #FILL, fault	; jump on compare error

;	fault:
;		jmp	$


byte_fill:

	; Write every byte in an AT25xxx with the same fill value.
	; Writes one address at a time (page mode is not used).
	; Waits until previous write cycle is complete before starting next.
	; Does not wait for last write cycle to complete before returning.
	; Memory write must be re-enabled after each write cycle.
	; Returns nothing. Destroys A, DPTR.

		mov	dptr, #0	; initialize address pointer
	x11:
		call	read_status	; get status in A
		jb	NRDY, x11	; loop until ready

		call	enable_write	; enable memory write

		mov	a, #FILL	; get fill value
		call	write_byte	; write data

		inc	dptr			; advance address pointer
;		mov	a, dpl			; check low byte
;		cjne	a, #(LOW SIZE), x11	; jump if not last
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x11	; jump if not last
		ret


verify_byte_fill:

	; Verify that all bytes in an AT25xxx match a fill value.
	; Reads and verifies one byte at a time (page mode is not used).
	; Waits for device ready before beginning compare.
	; Returns CY set to indicate compare fail.
	; Destroys A, DPTR.

	x21:
		call	read_status	; get status in A
		jb	NRDY, x21	; loop until ready

		mov	dptr, #0	; initialize address pointer
	x22:
		call	read_byte	; get data in A
		cjne	a, #FILL, x23	; jump if compare error

		inc	dptr			; advance address pointer
;		mov	a, dpl			; check low byte
;		cjne	a, #(LOW SIZE), x22	; jump if not last
		mov	a, dph			; check high byte
		cjne	a, #(HIGH SIZE), x22	; jump if not last
		clr	c			; clear error flag
		jmp	x24		; exit
	x23:
		setb	c		; set error flag
	x24:
		ret


page_fill:

	; Write every byte in an AT25xxx with the same fill value.
	; Writes one page at a time.
	; Waits until previous write cycle is complete before starting next.
	; Does not wait for last write cycle to complete before returning.
	; Memory write must be re-enabled after each write cycle.
	; Returns nothing. Destroys A, B, DPTR, KOUNT.

		; First fill buffer.

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	x31:
		mov	@index, #FILL	; put fill value in buffer
		inc	index		; advance pointer
		djnz	b, x31		; next byte

		; Copy buffer to device, one page at a time.

		mov	kount, #NPAGES	; initialize page counter
		mov	dptr, #0	; initialize address pointer
	x32:
		call	read_status	; get status in A
		jb	NRDY, x32	; loop until ready

		call	enable_write	; enable memory write

		call	write_page	; write data from buffer into device

		; 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	x33		; jump if high byte not affected
		inc	dph		; increment high byte
	x33:
		djnz	kount, x32	; next page
		ret


verify_page_fill:

	; Verify that all bytes in an AT25xxx match a fill value.
	; Reads and verifies one page at a time.
	; Waits for device ready before beginning compare.
	; Returns CY set to indicate compare fail.
	; Destroys A, B, DPTR, INDEX, KOUNT.

	x41:
		call	read_status	; get status in A
		jb	NRDY, x41	; loop until ready

		; Copy device page to buffer.

		mov	kount, #NPAGES	; initialize page counter
		mov	dptr, #0	; initialize address pointer
	x42:
		call	read_page	; read data from device into buffer

		; Verify buffer contents.

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	x43:
		cjne	@index, #FILL, x45	; jump if compare fails
		inc	index		; advance pointer
		djnz	b, x43		; 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	x44		; jump if high byte not affected
		inc	dph		; increment high byte
	x44:
		djnz	kount, x42	; next page
		clr	c		; clear error flag
		jmp	x46		; exit
	x45:
		setb	c		; set error flag
	x46:
		ret


read_status:

	; Read device status.
	; Returns status byte in A.

		clr	CS		; select device

		mov	a, #RDSR	; get command
		call	shout		; send command

		call	shin		; get status

		setb	CS		; deselect device
		ret


write_status:

	; Write device status.
	; Does not check for device ready before sending command.
	; Called with status byte to send in A.
	; Returns nothing.

		clr	CS		; select device

		push	acc		; save status
		mov	a, #WRSR	; get command
		call	shout		; send command

		pop	acc		; restore status
		call	shout		; send status

		clr	SCK		; drop clock
		nop			; enforce delay
		setb	CS		; deselect device
		ret


enable_write:

	; Enable write.
	; Does not check for device ready before sending command.
	; Returns nothing. Destroys A.

		clr	CS		; select device

		mov	a, #WREN	; get command
		call	shout		; send command

		clr	SCK		; drop clock
		nop			; enforce delay
		setb	CS		; deselect device
		ret


disable_write:

	; Disable write.
	; Does not check for device ready before sending command.
	; Returns nothing. Destroys A.

		clr	CS		; select device

		mov	a, #WRDI	; get command
		call	shout		; send command

		clr	SCK		; drop clock
		nop			; enforce delay
		setb	CS		; deselect device
		ret


read_byte:

	; Read one byte of data from specified address.
	; Does not check for device ready before sending command.
	; Called with address in DPTR.
	; Returns data in A.

		clr	CS		; select device

		mov	a, dph		; get high byte of address
		rrc	a		; move LSB into carry bit
		mov	a, #READ	; get command
		mov	A8, c		; combine command and high bit of addr
		call	shout		; send command and high bit of address

		mov	a, dpl		; get low byte of address
		call	shout		; send low byte of address

		call	shin		; get data

		setb	CS		; deselect device
		ret


write_byte:

	; Write one byte of data to specified address.
	; Does not check for device ready or write enabled before sending
	; command. Does not wait for write cycle to complete before returning.
	; Called with address in DPTR, data in A.
	; Returns nothing.

		clr	CS		; select device

		push	acc		; save data
		mov	a, dph		; get high byte of address
		rrc	a		; move LSB into carry bit
		mov	a, #WRITE	; get command
		mov	A8, c		; combine command and high bit of addr
		call	shout		; send command and high bit of address

		mov	a, dpl		; get low byte of address
		call	shout		; send low byte of address

		pop	acc		; restore data
		call	shout		; send data

		clr	SCK		; drop clock
		nop			; enforce delay
		setb	CS		; deselect device
		ret


read_page:

	; Read one page of data beginning at specified address.
	; Does not check for device ready before sending command.
	; Called with address in DPTR.
	; Returns data in BUFFER. Destroys A, INDEX.

		push	b

		clr	CS		; select device

		mov	a, dph		; get high byte of address
		rrc	a		; move LSB into carry bit
		mov	a, #READ	; get command
		mov	A8, c		; combine command and high bit of addr
		call	shout		; send command and high bit of address

		mov	a, dpl		; get low byte of address
		call	shout		; send low byte of address

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	ee10:
		call	shin		; get data
		mov	@index, a	; save data

		inc	index		; advance buffer pointer
		djnz	b, ee10		; next byte

		setb	CS		; deselect device

		pop	b
		ret


write_page:

	; Write one page of data beginning at specified address.
	; Does not check for device ready or write enabled before sending
	; command. Does not wait for write cycle to complete before returning.
	; Called with address in DPTR, data in BUFFER.
	; Returns nothing. Destroys A, B, INDEX.

		push	b

		clr	CS		; select device

		mov	a, dph		; get high byte of address
		rrc	a		; move LSB into carry bit
		mov	a, #WRITE	; get command
		mov	A8, c		; combine command and high bit of addr
		call	shout		; send command and high bit of address

		mov	a, dpl		; get low byte of address
		call	shout		; send low byte of address

		mov	b, #PSIZE	; bytes per page
		mov	index, #buffer	; point to buffer
	ee11:
		mov	a, @index	; get data from buffer
		call	shout		; send data

		inc	index		; advance buffer pointer
		djnz	b, ee11		; next byte

		clr	SCK		; drop clock
		nop			; enforce delay
		setb	CS		; deselect device

		pop	b
		ret


commander:

	; Execute AT25xxx command.
	; Called with command code in A, address (if required) in
	; HADDR:LADDR, output data (if required) in XDATA.
	; Data (if any) returned in XDATA.
	; Destroys A.

		clr	CS		; select device

		cjne	a, #RDSR, x1	; check for read status command
		call	shout		; send command
		jmp	x2
	x1:
		cjne	a, #READ, x3	; check for read memory command
		call	send_addr	; send command/high addr and low addr
	x2:
		call	shin		; read data from device
		mov	zdata, a	; save data
		jmp	x10
	x3:
		cjne	a, #WREN, x4	; check for write enable command
		jmp	x5
	x4:
		cjne	a, #WRDI, x6	; check for write disable command
	x5:
		call	shout		; send command
		jmp	x9
	x6:
		cjne	a, #WRITE, x7	; check for write memory command
		call	send_addr	; send command/high addr and low addr
		jmp	x8
	x7:
		cjne	a, #WRSR, x10	; check for write status command
		call	shout		; send command
	x8:
		mov	a, zdata	; get data
		call	shout		; send data
	x9:
		clr	SCK		; drop clock
		nop			; enforce delay
	x10:
		setb	CS		; deselect device
		ret


send_addr:

	; Send command combined with most significant bit of address
	; followed by least significant byte of address.
	; Called with command code in A, address in ADDR_HI:ADDR_LO.
	; Returns nothing. Destroys A.

		push	acc		; save command
		mov	a, addr_hi	; get high byte of address
		rrc	a		; move LSB into carry bit
		pop	acc		; restore command
		mov	A8, c		; combine command and high bit of addr
		call	shout		; send command and high bit of address
		mov	a, addr_lo	; get low byte of address
		call	shout		; send low byte of address
		ret


shin:
	; Shift in a byte from the device, most significant bit first.
	; Expects device already selected (CS low).
	; Expects SCK high on entry. Returns with SCK high.
	; The SO pin is allowed to float before data is read
	; to support three-wire (SI shorted to SO) mode.
	; Returns assembled byte in A.

		setb	SO		; float pin for three-wire mode

		push	b
		mov	b, #8		; bit count
	ee12:
		clr	SCK		; drop clock
		nop			; enforce output delay
		mov	c, SO		; input bit
		rlc	a		; move bit into byte
		setb	SCK		; raise clock
		djnz	b, ee12		; next bit

		pop	b
		ret


shout:
	; Shift out a byte to the device, most significant bit first.
	; Expects device already selected (CS low).
	; Returns with SCK high.
	; Called with data to send in A.
	; Returns nothing. All registers preserved.

		push	b
		mov	b, #8		; bit count

		rlc	a		; move bit into CY
	ee13:
		clr	SCK		; drop clock
		mov	SI, c		; output bit
		rlc	a		; move bit into CY
		setb	SCK		; raise clock
		djnz	b, ee13		; next bit

		pop	b
		ret


		END
