;**** A P P L I C A T I O N   N O T E   A V R 3 0 2 ************************
;*
;* Title		: I2C Slave Implementation
;* Version		: 1.20
;* Last updated		: 97.07.17
;* Target		: All AVR Device
;*			  Fast Mode: AT90S1200 only
;*
;* Support email	: avr@atmel.com
;*
;* Code Size		: 160 words
;* Low Register Usage	: 0
;* High Register Usage	: 5
;* Interrupt Usage	: External Interrupt0,
;*			  Timer/Counter0 overflow interrupt
;*
;* DESCRIPTION
;* 	This application note shows how to implement an AVR AT90S1200 device
;*	as an I2C slave peripheral. For use with other AT90S AVR devices, other
;*	I/O pins might have to be used, and the stack pointer might have to be
;*	initialized.
;*	Received data is stored in r0 and r0 is transmitted during a master
;*	read transfer. This simple protocol is implemented for demonstration
;*	purposes only.
;*
;*	Some features :
;*	* Interrupt based (using INT0 and TIM0).
;*	* The device can be given any 7-bit address. (Expandable to 10bit).
;*	* Supports normal and fast mode.
;*	* Easy insertion of "wait states".
;*	* Size and speed optimized.
;*	* Supports wake-up from idle mode.
;*
;*	Refers to the application note AVR302 documentation for more
;*	detailed description.
;*
;* USAGE
;*	Insert user code in the two locations marked "insert user code here".
;*
;* NOTES
;*	Minimum one instruction will be executed between each interrupt.
;*
;* STATISTICS
;*	Code Size	: 160
;*	Register Usage	: 5 High, 0 Low
;*	Interrupt Usage	: EXT_INT0 and TIM0_OVF
;*	Port Usage	: PD4(T0) and PD2(INT0)
;*	XTAL		: - I2C Fast Mode     : min 16.0MHz
;*			  - I2C Standard Mode : min  3.0MHz
;*
;***************************************************************************

;**** Includes ****

.include "1200def.inc"

;**** Global I2C Constants ****

.equ	devadr	= 0x50		; Slave device address
.equ	devadrm	= 0x7F		; Slave multi address mask

.equ	pinmask = 0x14		; <=> (1<<PD4)+(1<<PD2)

;**** Global Register Variables ****

.def	temp	= r16		; Temporary variable
.def	etemp	= r17		; Extra temporary variable
.def	i2cdata	= r18		; I2C data register
.def	i2cadr	= r19		; I2C address and direction register
.def	i2cstat	= r20		; I2C temporary SREG storage

;**** Interrupt Vectors ****

	rjmp	RESET		; Reset handle
	rjmp	EXT_INT0	; INT0 handle
	rjmp	TIM0_OVF	; Timer 0 overflow handle
;	( rjmp	ANA_COMP )	; ( Analog comparator handle )


;***************************************************************************
;*
;* INTERRUPT
;*	EXT_INT0 / i2c_wakeup
;*
;* DESCRIPTION
;*	Detects the start condition and handles the data transfer on 
;*	the I2C bus.
;*
;*	Received data is stored in the r0 register and r0 is transmitted 
;*	during a master read transfer.
;*
;* NOTE
;*	This interrupt code, in some cases, jumps to code parts in the 
;*	TIM0_OVF / i2c_skip section to reduce code size.
;*
;***************************************************************************

EXT_INT0:			; INT0 handle
	in	i2cstat,SREG	; store SREG


;*************************
;* Get I2C Slave Address *
;*************************

i2c_get_adr:
	ldi	i2cadr,1	; initialize address register
wlo_ga0:sbic	PIND,PIND4	; 	wait for SCL low
	rjmp	wlo_ga0
	rjmp	first_ga
do_ga:				; do
wlo_ga:	sbic	PIND,PIND4	; 	wait for SCL low
	rjmp	wlo_ga

	mov	temp,i2cadr	;	test address
	andi	temp,devadrm	; 	mask "register full" floating bit
	cpi	temp,devadr	; 	if device addressed
	in	temp,SREG	;		set T flag ("T = Z")
	bst	temp,1

first_ga:
	sec			; 	set carry
whi_ga:	sbis	PIND,PIND4	; 	wait for SCL high
	rjmp	whi_ga
	sbis	PIND,PIND2	; 	if SDA low
	clc			;		clear carry
	rol	i2cadr		; 	shift carry into address register

	brcc	do_ga		; while register not full

wlo_ca:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_ca

;**** Check Address ****
				; if T flag set (device addressed)
	brts	i2c_adr_ack	;	acknowledge address
				; else
	rjmp	i2c_adr_miss	; 	goto address miss handle

;**** Acknowledge Address ****

i2c_adr_ack:
	sbi	DDRD,DDD2	; assert acknowledge on SDA
whi_aa:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_aa

;**** Check Transfer Direction ****

	lsr	i2cadr		; if master write
	brcc	i2c_master_write;	goto master write handle	


;*******************************
;* Transmit Data (master read) *
;*******************************

i2c_master_read:

;**** Load Data (handle outgoing data) ****

;! INSERT USER CODE HERE !
;!NOTE!	If the user code is more than ONE instruction then wait
;	states MUST be inserted as shown in description.

	mov	i2cdata,r0	; insert data in "serial"
				;  register (user code example)

	sec			; shift MSB out and "floating empty flag" in
	rol	i2cdata

wlo_mr:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr


;**** Transmitt data ****

	brcc	fb_low_mr	; if current data bit high
	cbi	DDRD,DDD2	;	release SDA
	rjmp	fb_mr		;	goto read loop
fb_low_mr:			; else
	sbi	DDRD,DDD2	;	force SDA
fb_mr:	lsl	i2cdata		; 	if data register not empty

loop_mr:
whi_mr:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mr
wlo_mr2:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr2

	brcc	b_low_mr	; if current data bit high

	cbi	DDRD,DDD2	;	release SDA
	lsl	i2cdata		; 	if data register not empty
	brne	loop_mr		;		loop
	rjmp	done_mr		;	done
b_low_mr:			; else
	sbi	DDRD,DDD2	;	force SDA
	lsl	i2cdata		; 	if data register not empty
	brne	loop_mr		;		loop

done_mr:
whi_mr2:sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mr2
wlo_mr3:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr3

	cbi	DDRD,DDD2	; release SDA

;**** Read Acknowledge from Master ****

whi_ra:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_ra

	sec			; read acknowledge
	sbis	PIND,PIND2
	clc
	brcc	i2c_master_read	; if ack transfer (next) data

wlo_ra:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_ra

	rjmp	i2c_wait_cond	; goto wait condition (rep. start or stop)


;*******************************
;* Receive Data (master write) *
;*******************************

i2c_master_write:

wlo_mw0:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mw0
	cbi	DDRD,DDD2	; remove acknowledge from SDA

whi_mw:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mw

	in	temp,PIND	; sample SDA (first bit) and SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_mw:				; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_mw		; while no change

	sbrs	etemp,PIND4	; if SCL changed to low
	rjmp	receive_data	;	goto receive data

	sbrs	etemp,PIND2	; if SDA changed to low
	rjmp	i2c_get_adr	;	goto repeated start
				; else
	rjmp	i2c_stop	;	goto transfer stop

receive_data:
	ldi	i2cdata,2	; set i2cdata MSB to zero
	sbrc	temp,PIND2	; if SDA sample is one
	ldi	i2cdata,3	;	set i2cdata MSB to one

do_rd:				; do
wlo_rd:	sbic	PIND,PIND4	;	wait for SCL low
	rjmp	wlo_rd
	sec			; 	set carry
whi_rd:	sbis	PIND,PIND4	;	wait for SCL high
	rjmp	whi_rd

	sbis	PIND,PIND2	; 	if SDA low
	clc			;		clear carry
	rol	i2cdata		; 	shift carry into data register

	brcc	do_rd		; while register not full

;**** Acknowledge Data ****

i2c_dat_ack:
wlo_da:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_da
	sbi	DDRD,DDD2	; assert acknowledge on SDA
whi_da:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_da
				; (acknowledge is removed later)

;**** Store Data (handle incoming data) ****

	; insert wait states here if nessesary
;	sbi	DDRD,DDD4	; (start wait state)

;! INSERT USER CODE HERE !
;!NOTE!	If the user code is more than TWO instruction then wait
;	states MUST be inserted as shown in description.

	mov	r0,i2cdata	; Store data receved in "serial"
				;  register (user code example)

;	cbi	DDRD,DDD4	; (end wait state)

	rjmp	i2c_master_write; Start on next transfer


;***************************************************************************
;*
;* INTERRUPT
;*	TIM0_OVF / i2c_skip
;*
;* DESCRIPTION
;*	This interrupt handles a "address miss". If the slave device is
;*	not addressed by a master, it will not acknowledge the address.
;*
;*	Instead of waiting for a new start condition in a "busy loop"
;*	the slave set up the counter to count 8 falling edges on SCL and 
;*	returns from the current interrupt. This "skipping" of data
;*	do not occupies any processor time. When 8 egdes are counted, a
;*	timer overflow interrupt handling routine (this one) will check
;*	the next condition that accure. If it's a stop condition
;*	the transfer ends, if it's a repeated start condition a jump
;*	to the i2c_wakeup interrupt will read the new address. If a new
;*	transfer is initiated the "skipping" process is repeated.
;*
;***************************************************************************

TIM0_OVF:			; ( Timer 0 overflow handle )
	in	i2cstat,SREG	; store SREG

i2c_adr_miss:
	
;**** Drop Acknowledge ****

whi_dac:sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_dac
wlo_dac:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_dac

	; disable timer 0 overflow interrupt
	ldi	temp,(0<<TOIE0)
	out	TIMSK,temp
	; enable external interrupt request 0
	ldi	temp,(1<<INT0)
	out	GIMSK,temp


;************************
;* Wait for a Condition *
;************************

i2c_wait_cond:
whi_wc:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_wc

	in	temp,PIND	; sample SDA (first bit) and SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_wc:				; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_wc		; while no change

	sbrs	etemp,PIND4	; if SCL changed to low
	rjmp	i2c_skip_byte	;	goto skip byte

	sbrs	etemp,PIND2	; if SDA changed to low
	rjmp	i2c_get_adr	;	goto repeated start
				; else
				;	goto transfer stop


;*************************
;* Handle Stop Condition *
;*************************

i2c_stop:
	;! Set INT0 to generate an interrupt on low level,
	;! then set INT0 to generate an interrupt on falling edge.
	;! This will clear the EXT_INT0 request flag.
	ldi	temp,(0<<ISC01)+(0<<ISC00)
	out	MCUCR,temp
	ldi	temp,(1<<ISC01)+(0<<ISC00)
	out	MCUCR,temp

	out	SREG,i2cstat	; restore SREG
	reti			; return from interrupt


;****************
;* Skip byte(s) *
;****************

i2c_skip_byte:
	; set counter initial value
	ldi	temp,-7
	out	TCNT0,temp
	; enable timer 0 overflow interrupt
	ldi	temp,(1<<TOIE0)
	out	TIMSK,temp
	; disable external interrupt request 0
	ldi	temp,(0<<INT0)
	out	GIMSK,temp

	out	SREG,i2cstat	; restore SREG
	reti


;***************************************************************************
;*
;* FUNCTION
;*	i2c_init
;*
;* DESCRIPTION
;*	Initialization of interrupts and port used by the I2C interface
;*	and waits for the first start condition.
;*
;* USAGE
;*	Jump to this code directly after a reset.
;*
;* RETURN
;*	none
;*
;* NOTE
;*	Code size can be reduced by 12 instructions (words) if no transfer
;*	is started on the I2C bus (bus free), before the interrupt
;*	initialization is finished. If this can be ensured, remove the
;*	"Wait for I2C Start Condition" part and replace it with a "sei"
;*	instruction.
;*
;***************************************************************************

RESET:
i2c_init:

;**** PORT Initialization ****

	; Initialize PD2 (INT0) for open colector operation (SDA in/out)
	ldi	temp,(0<<DDD4)+(0<<DDD2)
	out	DDRD,temp

	; Initialize PD4 (T0) for open colector operation (SCL in/out)
	ldi	temp,(0<<PD4)+(0<<PD2)
	out	PORTD,temp

;**** Interrupt Initialization ****

	; Set INT0 to generate an interrupt on falling edge
	ldi	temp,(1<<ISC01)+(0<<ISC00)
	out	MCUCR,temp

	; Enable INT0
	ldi	temp,(1<<INT0)
	out	GIMSK,temp

	; Set clock to count on falling edge of T0
	ldi	temp,(1<<CS02)+(1<<CS01)+(0<<CS00)
	out	TCCR0,temp


;***************************************************
;* Wait for I2C Start Condition (13 intstructions) *
;***************************************************

do_start_w:			; do
	in	temp,PIND	; 	sample SDA & SCL
	com	temp		;	invert
	andi	temp,pinmask	;	mask SDA and SCL
	brne	do_start_w	; while (!(SDA && SCL))

	in	temp,PIND	; sample SDA & SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_start_w2:			; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_start_w2	; while no change

	sbrc	etemp,PIND2	; if SDA no changed to low
	rjmp	do_start_w	;	repeat

	rcall	i2c_get_adr	; call(!) interrupt handle (New transfer)


;***************************************************************************
;*
;* PROGRAM
;*	main - Test of I2C slave implementation
;*
;***************************************************************************

main:	rjmp	main		; loop forever


;**** End of File ****

