;**** A P P L I C A T I O N   N O T E   A V R 3 0 0 ************************
;*
;* Title		: TWI (Single) Master Implementation
;* Version		: 1.0
;* Last updated		: 2002.05.03
;* Target		: AT90Sxxxx (any AVR device)
;*
;* Support email	: avr@atmel.com
;*
;* DESCRIPTION
;* 	Basic routines for communicating with TWI slave devices. This
;*	"single" master implementation is limited to one bus master on the
;*	TWI bus. Most applications do not need the multimaster ability
;*	the TWI bus provides. A single master implementation uses, by far,
;*	less resources and is less XTAL frequency dependent.
;*
;*	Some features :
;*	* All interrupts are free, and can be used for other activities.
;*	* Supports normal and fast mode.
;*	* Supports both 7-bit and 10-bit addressing.
;*	* Supports the entire AVR microcontroller family.
;*
;*	Main TWI functions :
;*	'TWI_start' -		Issues a start condition and sends address
;*				and transfer direction.
;*	'TWI_rep_start' -	Issues a repeated start condition and sends
;*				address and transfer direction.
;*	'TWI_do_transfer' -	Sends or receives data depending on
;*				direction given in address/dir byte.
;*	'TWI_stop' -		Terminates the data transfer by issue a
;*				stop condition.
;*
;* USAGE
;*	Transfer formats is described in the AVR300 documentation.
;*	(An example is shown in the 'main' code).	
;*
;* NOTES
;*	The TWI routines can be called either from non-interrupt or
;*	interrupt routines, not both.
;*
;* STATISTICS
;*	Code Size	: 81 words (maximum)
;*	Register Usage	: 4 High, 0 Low
;*	Interrupt Usage	: None
;*	Other Usage	: Uses two I/O pins on port D
;*	XTAL Range	: N/A
;*
;***************************************************************************

;**** Includes ****

.include "1200def.inc"			; change if an other device is used

;**** Global TWI Constants ****

.equ	SCLP	= 1			; SCL Pin number (port D)
.equ	SDAP	= 0			; SDA Pin number (port D)

.equ	b_dir	= 0			; transfer direction bit in TWIadr

.equ	TWIrd	= 1
.equ	TWIwr	= 0

;**** Global Register Variables ****

.def	TWIdelay= r16			; Delay loop variable
.def	TWIdata	= r17			; TWI data transfer register
.def	TWIadr	= r18			; TWI address and direction register
.def	TWIstat	= r19			; TWI bus status register

;**** Interrupt Vectors ****

	rjmp	RESET			; Reset handle
;	( rjmp	EXT_INT0 )		; ( IRQ0 handle )
;	( rjmp	TIM0_OVF )		; ( Timer 0 overflow handle )
;	( rjmp	ANA_COMP )		; ( Analog comparator handle )


;***************************************************************************
;*
;* FUNCTION
;*	TWI_hp_delay
;*	TWI_qp_delay
;*
;* DESCRIPTION
;*	hp - half TWI clock period delay (normal: 5.0us / fast: 1.3us)
;*	qp - quarter TWI clock period delay (normal: 2.5us / fast: 0.6us)
;*
;*	SEE DOCUMENTATION !!!
;*
;* USAGE
;*	no parameters
;*
;* RETURN
;*	none
;*
;***************************************************************************

TWI_hp_delay:
	ldi	TWIdelay,2
TWI_hp_delay_loop:
	dec	TWIdelay
	brne	TWI_hp_delay_loop
	ret

TWI_qp_delay:
	ldi	TWIdelay,1	
TWI_qp_delay_loop:
	dec	TWIdelay
	brne	TWI_qp_delay_loop
	ret


;***************************************************************************
;*
;* FUNCTION
;*	TWI_rep_start
;*
;* DESCRIPTION
;*	Assert repeated start condition and sends slave address.
;*
;* USAGE
;*	TWIadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by TWI_start.
;*
;***************************************************************************

TWI_rep_start:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	TWI_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	TWI_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	TWI_start
;*
;* DESCRIPTION
;*	Generates start condition and sends slave address.
;*
;* USAGE
;*	TWIadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by TWI_write.
;*
;***************************************************************************

TWI_start:				
	mov	TWIdata,TWIadr		; copy address to transmitt register
	sbi	DDRD,SDAP		; force SDA low
	rcall	TWI_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	TWI_write
;*
;* DESCRIPTION
;*	Writes data (one byte) to the TWI bus. Also used for sending
;*	the address.
;*
;* USAGE
;*	TWIdata - Contains data to be transmitted.
;*
;* RETURN
;*	Carry flag - Set if the slave respond transfer.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by TWI_get_ack.
;*
;***************************************************************************

TWI_write:
	sec				; set carry flag
	rol	TWIdata			; shift in carry and out bit one
	rjmp	TWI_write_first
TWI_write_bit:
	lsl	TWIdata			; if transmit register empty
TWI_write_first:
	breq	TWI_get_ack		;	goto get acknowledge
	sbi	DDRD,SCLP		; force SCL low

	brcc	TWI_write_low		; if bit high
	nop				;	(equalize number of cycles)
	cbi	DDRD,SDAP		;	release SDA
	rjmp	TWI_write_high
TWI_write_low:				; else
	sbi	DDRD,SDAP		;	force SDA low
	rjmp	TWI_write_high		;	(equalize number of cycles)
TWI_write_high:
	rcall	TWI_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	TWI_hp_delay		; half period delay

	rjmp	TWI_write_bit


;***************************************************************************
;*
;* FUNCTION
;*	TWI_get_ack
;*
;* DESCRIPTION
;*	Get slave acknowledge response.
;*
;* USAGE
;*	(used only by TWI_write in this version)
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to a request.
;*
;***************************************************************************

TWI_get_ack:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	TWI_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL

TWI_get_ack_wait:
	sbis	PIND,SCLP		; wait SCL high 
					;(In case wait states are inserted)
	rjmp	TWI_get_ack_wait

	clc				; clear carry flag
	sbic	PIND,SDAP		; if SDA is high
	sec				;	set carry flag
	rcall	TWI_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	TWI_do_transfer
;*
;* DESCRIPTION
;*	Executes a transfer on bus. This is only a combination of TWI_read
;*	and TWI_write for convenience.
;*
;* USAGE
;*	TWIadr - Must have the same direction as when TWI_start was called.
;*	see TWI_read and TWI_write for more information.
;*
;* RETURN
;*	(depends on type of transfer, read or write)
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by TWI_read.
;*
;***************************************************************************

TWI_do_transfer:
	sbrs	TWIadr,b_dir		; if dir = write
	rjmp	TWI_write		;	goto write data


;***************************************************************************
;*
;* FUNCTION
;*	TWI_read
;*
;* DESCRIPTION
;*	Reads data (one byte) from the TWI bus.
;*
;* USAGE
;*	Carry flag - 	If set no acknowledge is given to the slave
;*			indicating last read operation before a STOP.
;*			If cleared acknowledge is given to the slave
;*			indicating more data.
;*
;* RETURN
;*	TWIdata - Contains received data.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by TWI_put_ack.
;*
;***************************************************************************

TWI_read:
	rol	TWIstat			; store acknowledge
					; (used by TWI_put_ack)
	ldi	TWIdata,0x01		; data = 0x01
TWI_read_bit:				; do
	sbi	DDRD,SCLP		; 	force SCL low
	rcall	TWI_hp_delay		;	half period delay

	cbi	DDRD,SCLP		;	release SCL
	rcall	TWI_hp_delay		;	half period delay

	clc				;	clear carry flag
	sbic	PIND,SDAP		;	if SDA is high
	sec				;		set carry flag

	rol	TWIdata			; 	store data bit
	brcc	TWI_read_bit		; while receive register not full


;***************************************************************************
;*
;* FUNCTION
;*	TWI_put_ack
;*
;* DESCRIPTION
;*	Put acknowledge.
;*
;* USAGE
;*	(used only by TWI_read in this version)
;*
;* RETURN
;*	none
;*
;***************************************************************************

TWI_put_ack:
	sbi	DDRD,SCLP		; force SCL low

	ror	TWIstat			; get status bit
	brcc	TWI_put_ack_low		; if bit low goto assert low
	cbi	DDRD,SDAP		;	release SDA
	rjmp	TWI_put_ack_high
TWI_put_ack_low:			; else
	sbi	DDRD,SDAP		;	force SDA low
TWI_put_ack_high:

	rcall	TWI_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
TWI_put_ack_wait:
	sbis	PIND,SCLP		; wait SCL high
	rjmp	TWI_put_ack_wait
	rcall	TWI_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	TWI_stop
;*
;* DESCRIPTION
;*	Assert stop condition.
;*
;* USAGE
;*	No parameters.
;*
;* RETURN
;*	None.
;*
;***************************************************************************

TWI_stop:
	sbi	DDRD,SCLP		; force SCL low
	sbi	DDRD,SDAP		; force SDA low
	rcall	TWI_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	TWI_qp_delay		; quarter period delay
	cbi	DDRD,SDAP		; release SDA
	rcall	TWI_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	TWI_init
;*
;* DESCRIPTION
;*	Initialization of the TWI bus interface.
;*
;* USAGE
;*	Call this function once to initialize the TWI bus. No parameters
;*	are required.
;*
;* RETURN
;*	None
;*
;* NOTE
;*	PORTD and DDRD pins not used by the TWI bus interface will be
;*	set to Hi-Z (!).
;*
;* COMMENT
;*	This function can be combined with other PORTD initializations.
;*
;***************************************************************************

TWI_init:
	clr	TWIstat			; clear TWI status register (used
					; as a temporary register)
	out	PORTD,TWIstat		; set TWI pins to open colector
	out	DDRD,TWIstat
	ret


;***************************************************************************
;*
;* PROGRAM
;*	main - Test of TWI master implementation
;*
;* DESCRIPTION
;*	Initializes TWI interface and shows an example of using it.
;*
;***************************************************************************

RESET:
main:	rcall	TWI_init		; initialize TWI interface

;**** Write data => Adr(00) = 0x55 ****

	ldi	TWIadr,$A0+TWIwr	; Set device address and write
	rcall	TWI_start		; Send start condition and address

	ldi	TWIdata,$00		; Write word address (0x00)
	rcall	TWI_do_transfer		; Execute transfer

	ldi	TWIdata,$55		; Set write data to 01010101b
	rcall	TWI_do_transfer		; Execute transfer

	rcall	TWI_stop		; Send stop condition

;**** Read data => TWIdata = Adr(00) ****

	ldi	TWIadr,$A0+TWIwr	; Set device address and write
	rcall	TWI_start		; Send start condition and address

	ldi	TWIdata,$00		; Write word address
	rcall	TWI_do_transfer		; Execute transfer

	ldi	TWIadr,$A0+TWIrd	; Set device address and read
	rcall	TWI_rep_start		; Send repeated start condition and address

	sec				; Set no acknowledge (read is followed by a stop condition)
	rcall	TWI_do_transfer		; Execute transfer (read)

	rcall	TWI_stop		; Send stop condition - releases bus

	rjmp	main			; Loop forewer

;**** End of File ****