;USE BACK BUTTOM TO RETURN TO PREVIOUS PAGE
;Published in 2003 by Richard Cappels, www.projects.cappels.org  Consulting Available.
;No liability assumed by the publisher. See the disclaimer at www.projects.cappels.org for details.
;Original EEPROM Drver written by Sean Ellis (see below). He did the hard part.
.nolist
.include "2313def.inc"
.list

;This program containsI2C Routines from the Temperature Logger application
;written by sellis (Sean Ellis), Registered at AVRfreaks.net (www.avrfreaks.net)
;on January 10, 2002. ProjectID: 49. The block write routine was omitted, and remaining 
;code slightly modified to be more compact in a processor with a RAM stack.

;CUSTOMIZATION AND USE:

;Pull-up resistors are required on SDA and SCL lines. See the I2C spec. for details.

;The subroutine, I2CInit, needs to be called during initialization, preferable after the I/O
;ports are initialized. I2CInit will set up the I2C I/O pins and initialize the bus.

;The EEPROM code needs to be customized to deal with the I/O pins being used and the 
;processor clock speed. All constants and registers that need to be modified are found 
;in the I2C Memory Driver section. Check and if necessary, change usage of I2CPORT, I2CDDR, 
;and I2CPIN in the I2C routines if necessary. Also change the values of bSDA and bSCL to 
;agree with the I/O bits used for SDA and SCL respectively.

;Delays to accommodate various bus speed and processor clock rates are controlled by
;the constant I2CDelayConstant.

;The device address constant (ADDR24LC64 in this case) needs to match that of the 
;EEPROM being used. The 24LC64 and 24LC256 use address A0 when their address pins are tied low.



;There are two main subroutines of interest.

;WriteI2Mem writes the byte contained in register "I2WData" into the EEPROM location
;pointed to by ZH,ZL.

;ReadI2Mem reads the byte at the EEPROM location pointed to by ZH,ZL and returns the byte in
;register "temp".




;**************Start I2C Memory Driver Constants and Registers
;I2C routines written  by sellin (Sean Ellis)
;temp and I2WDtata must be high registers
.def	DCounter	= r2	; Used for counting loops. Can be used for other things in other routines
.def	temp		= r16	; Return value from functions. Can be used for other things in other rotuines
.def	I2WData		= r17	; Write data for EEPROM reoutines


; I2C Device addressing
.equ	ADDR24LC64	= 0xA0	; 'LC64, 'LC128, and 'LC256 address byte (1010 000R)
.equ	I2CREAD		= 0x01	; read/write bit = read
.equ	I2CWRITE	= 0x00	; read/write bit = write
.equ	I2CDelayConstant=$04	; Constant for delay loop- change as function of uC clock frequency

; IO Port Bits
.equ	bSDA		= 6	;* SET THIS The bit on the I/O Port used for SDA
.equ	bSCL		= 5	;* SET THIS The bit on the I/O Port used for SCL
.equ	mSDA		= (1<<bSDA);Calculate binary value corresponding to I/O port bit for masking
.equ	mSCL		= (1<<bSCL);Calculate binary value corresponding to I/O port bit for masking

.equ	I2CPORT 	= PORTD	;*Set to correspond to I/O Port used for I2C
.equ	I2CDDR  	= DDRD	;*Set to correspond to I/O Port used for I2C
.equ	I2CPIN  	= PIND	;*Set to correspond to I/O Port used for I2C

;**************End I2C Memory Driver Constants and Registers


.equ	clock = 4000000	;* Set for uC clock frequency
.equ	baudrate = 9600		;choose a baudrate
.equ	baudconstant = (clock/(16*baudrate))-1




;D0	USED FOR UART RECEIVE
;D1	USED FOR UART TRANSMIT
;D2
;D3	
;D4	
;D5	SCA via firmware
;D6	SDA via firmware
;D7	The AT90S2313 does not have a D7 I/O port.

	;UART baud rate calculation

 
.cseg		
.ORG $0000				;Reset
	rjmp start	
	
start:
   
	ldi	r16,RAMEND		;Init Stack Pointer
	out	spl,r16
	
	ldi	temp,$FF		;All I/O inputs with pasive pullups
	out	PORTB,temp
	out	PORTD,temp
	ldi	temp,$00
	out	DDRB,temp
	out	DDRD,temp

	
 	ldi     temp,baudconstant     
 	out     ubrr,temp     		;Load baud rate for UART.
 	sbi	ucr,rxen		;Enable WAUT receive 
 	sbi	ucr,txen		;Enable UART Transmit


	rcall	I2CInit			; Initialize I2C bus


rjmp	main


recvchar:     	;Receive a byte from the terminal into temp        
	sbis	usr,rxc			;Wait for byte to be received
	rjmp 	recvchar
	in	temp,udr		;Read byte
	ret	

emitchar:	;Send character contained in temp 
 	sbis	usr,udre  		;wait until the register is cleared.
 	rjmp	emitchar     
 	out	udr,temp		;send the character.
 	ret

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;-----------------------------------------------------------------
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;		START I2C ROUTINES
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;-----------------------------------------------------------------
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

I2CDelay:
	push	temp
	ldi	temp,I2CDelayConstant
delaymore:
	dec	temp
	brne	delaymore
	pop	temp
	ret
;-----------------------------------------------------------------	
; I2CInit
; Initialize I2C interface, ports, etc.
I2CInit:
	; Set up port assignments
	ldi	temp,mSCL		; SCL only output by default
	out	I2CDDR,temp		
	out	I2CPORT,temp		; All outputs high, input pullups disabled
	
	cbi	I2CPORT,bSDA	; Data line always drives low if driven
	cbi	I2CDDR,bSDA	; SDA as input to start with - floats high
	cbi	I2CPORT,bSCL	; Set SCL low to try and avoid...
	sbi	I2CDDR,bSCL	; ...false start transitions
	rcall	I2CDelay
	rjmp	I2CStop		; Stop any erroneous transfer
;-----------------------------------------------------------------
; I2CStart
; Send I2C start condition
; Assumes SCL and SDA are high to start with.
; Leaves SCL and SDA low
I2CStart:
	rcall	I2CDelay	
	sbi	I2CDDR,bSDA	; Drive data line low (start bit)
	rcall	I2CDelay
	cbi	I2CPORT,bSCL	; Clock line low (ready to start)
	ret	
;-----------------------------------------------------------------
; I2CStop
; Send I2C stop condition
; Assumes SCL is low to start with, SDA may be in either state
; Leaves SCL and SDA high
I2CStop:
	sbi	I2CDDR,bSDA	; SDA driven low
	rcall	I2CDelay	
	sbi	I2CPORT,bSCL	; Clock line high (ready to stop)
	rcall	I2CDelay	
	cbi	I2CDDR,bSDA	; Data line floats high (stop bit)
	rcall	I2CDelay
	ret
;-----------------------------------------------------------------
ReadI2Mem:	; Reads external memory at Z to temp
	rcall	I2CStart
	ldi	I2WData,(ADDR24LC64|I2CWRITE)
	rcall	I2CSendAddress
	brne	ReadI2MemError	; No ack!
	mov	I2WData,ZH
	rcall	I2CSendByte
	mov	I2WData,ZL
	rcall	I2CSendByte
	rcall	I2CStop
	rcall	I2CDelay
	rcall	I2CStart
	ldi	I2WData,(ADDR24LC64|I2CREAD)
	rcall	I2CSendAddress
	brne	ReadI2MemError	; No ack!
	ldi	I2WData,1	; Don't send ack
	rcall	I2CReadByte
	ret			
ReadI2MemError:
	rcall	I2CStop
	clr	DCounter
ReadI2MemErrorDelay:
	dec	DCounter
	brne	ReadI2MemErrorDelay
	rjmp	ReadI2Mem	; and try again
;-----------------------------------------------------------------
WriteI2Mem:;	 Writes I2WData to external memory at Z
	mov	temp,I2WData	; save value
	rcall	I2CStart
	ldi	I2WData,(ADDR24LC64|I2CWRITE)
	rcall	I2CSendAddress
	brne	WriteI2MemError	; No ack!
	mov	I2WData,ZH
	rcall	I2CSendByte
	mov	I2WData,ZL
	rcall	I2CSendByte
	mov	I2WData,temp
	rcall	I2CSendByte
	rcall	I2CStop
	ret

WriteI2MemError:
	rcall	I2CStop
	clr	DCounter
WriteI2MemErrorDelay:
	dec	DCounter
	brne	WriteI2MemErrorDelay
	mov	I2WData,temp 	; restore data value
	rjmp	WriteI2Mem	; and try again
;-----------------------------------------------------------------
; I2CReadByte	 Read a byte from the I2C bus and acknowledge
; If I2WData is zero, acknowledge is sent, otherwise not.
; Byte is returned in temp
; Leaves SCL low, I2C bus not driven.
I2CReadByte:
	ldi	temp,8
	mov	DCounter,temp
	clr	temp
	cbi	I2CDDR,bSDA	; release bus (floats high)	
I2CReadBits:
	sbi	I2CPORT,bSCL	; clock high
	rcall	I2CDelay	
	add	temp,temp	
	sbic	I2CPIN,bSDA	; if SDA clear, skip increment
	inc	temp	
	cbi	I2CPORT,bSCL	; clock low
	rcall	I2CDelay
	dec	DCounter
	brne	I2CReadBits
	tst	I2WData		; Send an ACK...?
	brne	I2CReadBitsNack	
	sbi	I2CDDR,bSDA	; yes... drive data bus low
I2CReadBitsNack:
	sbi	I2CPORT,bSCL	; clock high
	rcall	I2CDelay
	cbi	I2CPORT,bSCL	; clock low
	rcall	I2CDelay
	cbi	I2CDDR,bSDA	; release bus (floats high)
	ret
;-----------------------------------------------------------------------------------------------------------------
; I2CSendByte
; Sends 8 bits of data from I2WData and listens for an ACK at the
; end. Returns Z if ack received, else NZ.
; Assumes a start condition has already been sent, and SDA and SCL
; are low Leaves SCL low, SDA not driven. Uses DCounter and I2WData.
I2CSendAddress:
I2CSendByte:
	push	temp
	ldi	temp,8
	mov	DCounter,temp
	pop	temp
I2CSendBits:
	add	I2WData,I2WData
	brcc	I2CSendBits0
	cbi	I2CDDR,bSDA	; send 1 (floats high)
	rjmp	I2CSendBits1
I2CSendBits0:
	sbi	I2CDDR,bSDA	; send 0 (drives low)
I2CSendBits1:	
	rcall	I2CDelay
	sbi	I2CPORT,bSCL	; clock high
	rcall	I2CDelay
	cbi	I2CPORT,bSCL	; clock low
	dec	DCounter
	brne	I2CSendBits
	cbi	I2CDDR,bSDA	; stop driving data bus
	rcall	I2CDelay
	sbi	I2CPORT,bSCL	; clock high
	rcall	I2CDelay
	in	I2WData,I2CPIN	; sample data bus
	cbi	I2CPORT,bSCL	; clock low
	rcall	I2CDelay
	andi	I2WData,mSDA	; check ack bit (z set if OK)
	ret
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;-----------------------------------------------------------------
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;		END I2C ROUTINES
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;-----------------------------------------------------------------
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx



;DEMONSTRATION PROGRAM
;Writes carriage return, linefeed, and asterisk to terminal,
;Then echos characters back to terminal as it writes each character to the
;EEPROM. When a dollar sign is encountered, it is treated as an end of file 
;indicator and stored in memory, then the contents of the memory is sent to the 
;terminal until the dollar sign is reached, at which time the program startrs over.
;If the first character received from the terminal at the start of the program is 
;a dollar sign, the contents of the EEPROM will be dumped up to the first dollar sign
;stored in EEPROM. 


Main: 	
	ldi	temp,$0A		;Send return, linefeed, asterisk
	rcall	emitchar
	ldi	temp,$0D
	rcall	emitchar
	ldi	temp,$2A
	rcall	emitchar
	
	clr	ZH			;Clear Z register (EEPROM pointer)
	clr	ZL
	
MainLoop:
	
	rcall	recvchar		;Get char from terminal

	cpi	temp,$24  		;dump if its a dollar sign $
	breq	dumpsome

	push	temp			;Save char on return stack
	
	mov	I2WData,temp		;Write char to EEPROM
	rcall	WriteI2Mem
	adiw	ZL,1			;Increment ZH,ZL for chips that support adiw instruction

	pop	temp			;Retrieve char
	rcall	emitchar		;Echo char to the terminal

	rjmp	MainLoop		;Go back and do it again
	
dumpsome:
	cpi	ZH,0			;If ZH,ZL = 00 00, then dont' write dollar sign
	brne	zwrite
	cpi	ZL,0
	breq	nowriteds		;Don't write a dollar sign if this is EEPROM location 00 00
	
zwrite:
	mov	I2WData,temp		;Write the dollar sign $ in temp that brought us here
	rcall	WriteI2Mem
	adiw	ZL,1			;Increment ZH,ZL for chips that support adiw instruction	
nowriteds:

	clr	ZH			;Dump EEPROM contents until dollar sigsn is encountered
	clr	ZL
	
dumpsomemore:	
	rcall	ReadI2Mem
	adiw	ZL,1			;Increment ZH,ZL for chips that support adiw instruction
	rcall	emitchar
	cpi	temp,$24
	breq	Main	
	rjmp	dumpsomemore


.exit	;No assembly below this point.

;USE YOUR BACK BUTTON TO RETURN TO THE PREVIOUS PAGE


