;*****************************************************************************************
; I2C Slave background task
; ----------------------------------------
; identification (major-minor) $02-$01
;
; version 1.0
; may 28, 2008
;
; I2C code adapted from Ubicom VP sources
;
; Implements i2c slave, uart
;
; Author: Peter Verkaik
; Email: verkaik6@zonnet.nl
;
;*****************************************************************************************
;
; SPECIAL NOTE !!!
;
; - acts only as I2C slave
;
; - this device accepts a broadcast (id $18)
;   command and generates the ACK signals
;   but the received command is simply ignored
;
;-----------------------------------------------------------------------------------------
;
; Program Description:
;
;	This program has mainline code with a mainloop that accepts
;	a command via the i2c slave, and performs the task as indicated by the
;	command.
;
;-----------------------------------------------------------------------------------------
;
;-----------------------------------------------------------------------------------------
; program modules
;-----------------------------------------------------------------------------------------
device		SX18L,oschs1,turbo,stackx,optionx	;sx device options
irc_cal		IRC_SLOW
id		'SYNOPCOM'				;SYNOP rev.D COM device
reset		reset_entry				;set reset vector
;select one of the following
;RESONATOR	equ	20				;20 MHz
RESONATOR	equ	50				;50 MHz
;RESONATOR	equ	75				;75 MHz
freq		RESONATOR * 1000000			;resonator frequency

; uncomment any of the following lines to enable the module

I2C_SLAVE			;must be enabled
I2C_BROADCAST			;must be enabled

; set static slave id (used if TOKEN_BUS not selected)
SLAVE_STATIC_ID	= $34		;in use if no tokenbus

; broadcast id
BROADCAST_ID	= $18		;special SYNOP board i2c broadcast id	** DO NOT CHANGE

; set devicetype values for this device
SYNOPDEVICE_MAJOR	=	$02		;identifies COM device
SYNOPDEVICE_MINOR	=	$01		;identifies this specific code

;-----------------------------------------------------------------------------------------
; uart constants
;-----------------------------------------------------------------------------------------

if RESONATOR=75
;uartfs=288*1200=345600
;int_period=75000000/345600=217
;288*1200=3*96*1200=3*48*2400=3*24*4800=3*12*9600=3*6*19200=3*3*38400=3*2*57600

uartfs			= 345600	; uart's basic frequency 288 * 1200 = 345600
int_period		= 217		; used as seed for uart baud rate.

endif

if RESONATOR=50
;uartfs=192*1200=230400
;int_period=50000000/230400=217
;192*1200=3*64*1200=3*32*2400=3*16*4800=3*8*9600=3*4*19200=3*2*38400

uartfs			= 230400	; uart's basic frequency 192 * 1200 = 230400
int_period		= 217		; used as seed for uart baud rate.

endif

if RESONATOR=20
;uartfs=96*1200=115200
;int_period=20000000/115200=174
;96*1200=3*32*1200=3*16*2400=3*8*4800=3*4*9600=3*2*19200

uartfs			= 115200	; uart's basic frequency 96 * 1200 = 115200
int_period		= 174		; used as seed for uart baud rate.

endif

THREADS			equ	3	; DO NOT CHANGE. Each thread is executed once every 3 interrupts

;-----------------------------------------------------------------------------------------
; Pin definitions & Port assignments
;-----------------------------------------------------------------------------------------

; port A definitions/assignments *** DO NOT CHANGE ***

sda		equ	0
scl		equ	1
i2c_port	equ	ra
  i2c_scl_pin	equ	i2c_port.scl	; i2c clock
  i2c_sda_pin	equ	i2c_port.sda	; i2c data I/O

; i2c changes output level by changing direction register!! Never drive output high!!
RA_latch	equ	%00000000	;port A output latch init	1=high	0=low
RA_tris		equ	%11111111	;port A direction register	1=input	0=output
RA_plp		equ	%00000000	;port A pull up register	1=off	0=on
RA_lvl		equ	%11111111	;port A level register		1=ttl	0=cmos

RB_latch	equ	%00000010	;port B output latch			1=high	0=low
RB_tris		equ	%11111111	;port B direction register		1=input	0=output
RB_plp		equ	%00000000	;port B pull up register		1=off	0=on
RB_lvl		equ	%11111111	;port B level register			1=ttl	0=cmos
RB_st		equ	%11111111	;port B schmitt trigger register	1=off	0=on
RB_wken		equ	%11111111	;port B wake up enable register		1=off	0=on	** KEEP OFF
RB_wked		equ	%11111111	;port B wake up edge register		1=neg	0=pos

;-----------------------------------------------------------------------------------------
; Code Locations
;-----------------------------------------------------------------------------------------
CODEPAGE_0	equ	$0
CODEPAGE_1	equ	$200
CODEPAGE_2	equ	$400
CODEPAGE_3	equ	$600

;-----------------------------------------------------------------------------------------
; Data Memory address definitions
;-----------------------------------------------------------------------------------------
global_org	=	$08
bank0_org	=	$10
bank1_org	=	$30
bank2_org	=	$50
bank3_org	=	$70
bank4_org	=	$90
bank5_org	=	$B0
bank6_org	=	$D0
bank7_org	=	$F0

;-----------------------------------------------------------------------------------------
; Global Register definitions
;-----------------------------------------------------------------------------------------
		org     global_org

isr_temp		equ	global_org-1	; rc is available as general purpose register in SX18
w_save			equ	global_org+0	; to save w during ram bank switch

IFDEF I2C_SLAVE
i2c_flags		equ	global_org+2	; global i2c flags
  i2cs_stop_found	equ	i2c_flags.0	; set by stop and cleared by start condition
  i2cs_rx_flag		equ	i2c_flags.1	; set when this slave has received data
  i2cs_broadcast	equ	i2c_flags.2	; set when this slave is addressed by broadcast id
  i2cs_written_again	equ	i2c_flags.3	; set when this slave receives 2nd or later databyte
  i2cs_data_needed	equ	i2c_flags.4	; set when this slave requires data
ENDIF

command			equ	global_org+7	;used by mainline command menu

;-----------------------------------------------------------------------------------------
; RAM Bank Register definitions
;-----------------------------------------------------------------------------------------

	; Bank 0 -
	;---------------------------------------------------------------------------------
		org     bank0_org

bank0		=	$

	; Bank 1 - I2C Slave variables and timer ram bank
	;---------------------------------------------------------------------------------
		org     bank1_org

bank1		=	$

IFDEF I2C_SLAVE
i2cs		=	$	; i2cs Bank (I2C slave)
i2cs_state	ds	1	; This indicates the state that the I2C slave is currently in.
i2cs_sub_state	ds	1	; This indicates the substate that the I2C slave is currently in.
i2cs_port_buf	ds	1	; This buffer holds the current state of the I2C port direction reg's
i2cs_bit_count	ds	1	; Indicates the number of bits left to process in read/write
i2cs_byte	ds	1	; The byte currently being written/read by the I2C slave
i2cs_past_pres	ds	1	; The last_state of the I2C port (to be compared with the present state)
  i2cs_pres_sda		equ	i2cs_past_pres.0 ; The present state of the SDA line
  i2cs_pres_scl		equ	i2cs_past_pres.1 ; The present state of the SCL line
  i2cs_past_sda		equ	i2cs_past_pres.2 ; The state of the SDA line on last interrupt
  i2cs_past_scl		equ	i2cs_past_pres.3 ; The state of the SCL line on last interrupt
i2cs_id		ds	1	; This byte holds the id which the I2C slave will respond to.
i2cs_in_index	ds	1	;argument count
i2cs_out_index	ds	1	;number of bytes in i2cs_outbuffer
i2cs_out_tail	ds	1	;index of next data to send from i2cs_outbuffer
i2cs_inbuf	ds	1	;one byte input buffer
i2cs_outbuffer	=	$
i2cs_outbuf0	ds	1	;four byte output buffer
i2cs_outbuf1	ds	1
i2cs_outbuf2	ds	1
i2cs_outbuf3	ds	1
ENDIF

	; Bank 2 - uart1,uart2,uart3 transmit/receive variables ram bank
	;---------------------------------------------------------------------------------
		org     bank2_org

bank2		=	$

	; Bank 3 -
	;---------------------------------------------------------------------------------
		org     bank3_org

bank3		=	$

	; Bank 4 -
	;---------------------------------------------------------------------------------
		org     bank4_org

bank4		=	$

	; Bank 5 -
	;---------------------------------------------------------------------------------
		org     bank5_org

bank5		=	$

	; Bank 6 -
	;---------------------------------------------------------------------------------
		org     bank6_org

bank6		=	$

	; Bank 7 -
	;---------------------------------------------------------------------------------
		org     bank7_org

bank7		=	$

;-------------- end of ram variables -----------------------------------------------------


;*****************************************************************************************
		org	CODEPAGE_0
;*****************************************************************************************


;-----------------------------------------------------------------------------------------
; Interrupt Service Routine
;-----------------------------------------------------------------------------------------
; Note: The interrupt code must always originate at address $0.
; Interrupt Frequency = (Cycle Frequency / -(retiw value)) 

interrupt				;3
		jmp	Isr_Start

;-----------------------------------------------------------------------------------------
; Initialization routines for tokenbus, i2c slave and i2c master
;-----------------------------------------------------------------------------------------

IFDEF I2C_SLAVE

	; Function: i2cs_init
	;	This subroutine should be called on initialization of the program.  It
	;	initializes the variables which are critical to the operation of the 
	;	i2cs slave state machine.
	;---------------------------------------------------------------------------------
i2cs_init	bank	i2cs
		mov	w,#%11111111		;Set i2c port in/out directions
		mov	i2cs_port_buf,w
		mov	w,#$ff			;Let part know last states have all been highs
		mov	i2cs_past_pres,w
		mov	w,#SLAVE_STATIC_ID
		mov	i2cs_id,w
		ret

i2cs_input_task
		jmp	@i2cs_input_handler	;handles incoming data from i2c bus

i2cs_output_task
		jmp	@i2cs_output_handler	;handles outgoing data to i2c bus

ENDIF

Isr_start:

IFDEF I2C_SLAVE

	; I2C Slave Stuff...	Runs every ISR to enable high sampling rate
	;---------------------------------------------------------------------------------
slave_i2c
		bank	i2cs			;1
		clc				;1 ;Save the current state and past state of the I2C bus
		snb	i2c_scl_pin			;1
		stc				;1
		rl	i2cs_past_pres		;1
		clc				;1
		snb	i2c_sda_pin			;1
		stc				;1
		rl	i2cs_past_pres		;1
		call	@i2cs_get_start_stop	;3 + 9/12 ;Check the new states to see if we've received a start or a stop
		call	@i2cs_isr		;3 + 11/30(I2C_isr length) = 37/59 ;Call I2C slave ISR
ENDIF
I2C_done

	; Update i2c_token_port and user_port with buffered port data
	;---------------------------------------------------------------------------------
		mov	isr_temp,m		;2 ; Save the m register.

		mov	m,#$0f			;1
		mov	w,#$ff

IFDEF I2C_SLAVE
		bank	i2cs			;1
		and	w,i2cs_port_buf		;1 ;update the i2c port with the buffered port data
ENDIF

		mov	!i2c_port,w	;1
		mov	m,isr_temp		;2 ; Restore the m register.
						;=13
isr_thread_end

	; Set Interrupt Rate
	;---------------------------------------------------------------------------------
isr_end
		mov	w,#(-int_period)&255		;1 ; refresh RTCC on return
		retiw				;3 ; return from the interrupt

;-------------- end of the Interrupt Service Routines ------------------------------------

	;RESET VECTOR	Program execution begins here on power-up or after a reset
	;---------------------------------------------------------------------------------
reset_entry

	; Initialise all port configuration
	;---------------------------------------------------------------------------------
		mov	m,#$0A			; point mode to WKED
		mov	!rb,#RB_wked
		mov	m,#$0B			; point mode to WKEN
		mov	!rb,#RB_wken
		mov	m,#$0C			; point mode to ST
		mov	!rb,#RB_st
		mov	m,#$0D			; point mode to LVL
		mov	!rb,#RB_lvl
		mov	!ra,#RA_lvl
		mov	m,#$0E			; point mode to PLP
		mov	!rb,#RB_plp
		mov	!ra,#RA_plp
		mov	rb,#RB_latch		; init latches
		mov	ra,#RA_latch
		mov	m,#$0F			; point mode to TRIS
		mov	!rb,#RB_tris
		mov	!ra,#RA_tris

	; Clear all Data RAM locations
	;---------------------------------------------------------------------------------
		mov	fsr,#$07
:zero_global
		clr	indf
		inc	fsr
		cjne	fsr,#$10,:zero_global
:zero_ram
		clr	indf
		inc	fsr
		jz	:zero_end
		setb	fsr.4
		jmp	:zero_ram
:zero_end

	; Initialize I2C program registers
	;---------------------------------------------------------------------------------

IFDEF I2C_SLAVE
		call	i2cs_init
ENDIF

	; Setup option register. 
	;---------------------------------------------------------------------------------

RTCC_ON		=	%10000000	;Enables RTCC at address $01 (RTW hi)
					;*WREG at address $01 (RTW lo) by default
RTCC_ID		=	%01000000	;Disables RTCC edge interrupt (RTE_IE hi)
					;*RTCC edge interrupt (RTE_IE lo) enabled by default
RTCC_INC_EXT	=	%00100000	;Sets RTCC increment on RTCC pin transition (RTS hi)
					;*RTCC increment on internal instruction (RTS lo) is defalut
RTCC_FE		=	%00010000	;Sets RTCC to increment on falling edge (RTE_ES hi)
					;*RTCC to increment on rising edge (RTE_ES lo) is default
RTCC_PS_ON	=	%00000000	;Assigns prescaler to RTCC (PSA lo)
RTCC_PS_OFF	=	%00001000	;Assigns prescaler to RTCC (PSA lo)
PS_000		=	%00000000	;RTCC = 1:2, WDT = 1:1
PS_001		=	%00000001	;RTCC = 1:4, WDT = 1:2
PS_010		=	%00000010	;RTCC = 1:8, WDT = 1:4
PS_011		=	%00000011	;RTCC = 1:16, WDT = 1:8
PS_100		=	%00000100	;RTCC = 1:32, WDT = 1:16
PS_101		=	%00000101	;RTCC = 1:64, WDT = 1:32
PS_110		=	%00000110	;RTCC = 1:128, WDT = 1:64
PS_111		=	%00000111	;RTCC = 1:256, WDT = 1:128

		mov	w,#RTCC_PS_OFF	;setup option register
		mov	!option,w

	; Main
	;---------------------------------------------------------------------------------

main
IFDEF I2C_SLAVE
		snb	i2cs_rx_flag
		call	i2cs_input_task			;handle data written to this device
		snb	i2cs_data_needed
		call	i2cs_output_task		;supply data read from this device
ENDIF
		jmp	main

;-------------- codepage 0 end -----------------------------------------------------------


;*****************************************************************************************
		org	CODEPAGE_1
;*****************************************************************************************
		jmp	$	;generates error if code above crosses page boundary

IFDEF I2C_SLAVE
;-----------------------------------------------------------------------------------------
; I2C Slave Interrupt Service Routines
;-----------------------------------------------------------------------------------------

	; Function: i2cs_get_start_stop
	; This subroutine checks the i2cs_past_pres register for the presence of a start
	; or stop condition.  It takes the values present in the i2cs_past_pres register
	; and checks them against what would be in the register if a start or stop condition
	; had occured.  This subroutine should be called on every pass of the ISR, because
	; it runs independantly to the rest of the slave code and re-initializes the state
	; machine, regardless of its last state, on the receipt of these conditions.
	;---------------------------------------------------------------------------------
i2cs_get_start_stop
		bank	i2cs
		mov	w,i2cs_past_pres	;1
		and	w,#$0f			;1
		xor	w,#%00001110		;1 ;Compare with a start condition.
		snz				;1
		jmp	:start			;3
		xor	w,#%00000101		;1 ;Compare XOR of start and stop with
		sz				;1 ;what would be present if a stop was present.
		retp				;3 = 9

:stop		clr	i2cs_state		;1 ;If stop, then make the I2C slave idle
		clr	i2cs_sub_state		;1
		setb	i2cs_stop_found		;1
		retp				;3 = 12

:start		mov	w,#1			;1 ;If start, then start receiving the address.
		mov	i2cs_state,w		;1
		clr	i2cs_sub_state
		clrb	i2cs_stop_found
		clrb	i2cs_written_again
		retp				;3 = 12

	; Function: i2cs_isr
	; I2C Slave Interrupt-Driven State Machine
	;
	;	This is the main interrupt service routine for the I2C slave interrupt
	;	service routine.  It is a state machine, allowing semi-straightline 
	;	code to operate on successive calls.  The current state of this state
	;	machine is stored in the registers i2cs_state and i2cs_sub_state.  These
	;	are the discrete states of this state machine...
	;
	;  i2cs_idle
	;	i2cs_idle is the state used when no start condition has occurred
	;	and the device has not been addressed.  The I2C slave will sit in this
	;	state until the state is changed by the independant subroutine 
	;	i2cs_get_start_stop.  
	;
	;  i2cs_wait_for_scl_low
	;	This state simply waits until SCL goes low.  It is entered once
	;	i2cs_get_start_stop encounters a start condition.  Once SCL goes low,
	;	the state is incremented
	;
	;  i2cs_get_id
	;	This state prepares the i2cs_read_byte routine to get 8-bits of data. 
	;	It increments the state to i2cs_read_byte before exiting.
	;
	;  i2cs_read_byte
	;	This state reads a byte of data.  i2cs_bit_count needs to be loaded with
	;	#8 before entering this state, or it won't know to increment to the next
	;	state after 8 bits of data have been loaded.
	;
	;  i2cs_process_id
	;	This state simply performs a quick calculation to figure out if this slave
	;	was just addressed.  If it was not, then it changes the state back to 
	;	i2cs_idle.  If this slave was addressed, then this state prepares to either
	;	read data or write data, depending on the bit 1 of the first byte received.
	;
	;  i2cs_send_ack
	;	This state outputs an ACK pulse, to tell the master that data was received
	;	correctly.  It pulls SDA low while SCL is pulsed high and low by the master.
	;
	;  i2cs_waiting_for_data
	;	This state waits for data to be put in i2cs_byte.
	;
	;  i2cs_send_data
	;	This state prepares the i2cs_write_byte state to send the 8-bits of data
	;	in the i2cs_data_out register.
	;
	;  i2cs_write_byte
	;	This state outputs a byte of data, clocked out by the SCL pin.  It must be
	;	pre-prepared to send out a byte by having the i2cs_byte register loaded with
	;	valid data and having the i2cs_bit_count register loaded with #8.
	;
	;  i2cs_get_ack
	;	This state gets an ACK from the I2C master.  If an ACK is received, this state
	;	will return to waiting_for_data. If no ACK is received, the slave will be put
	;	back into its idle state.
	;
	;  i2cs_read_data
	;	This state prepares i2cs_read_byte to receive 8 bits of data.  It increments
	;	the state before exiting.
	;
	;  i2cs_read_again
	;	This state waits for more data to receive.
	;
	;  i2cs_process_data
	;	This state processes a byte of data which was just received.
	;	It sets the i2cs_rx_flag to indicate that a byte of data was received by the slave.
	;
	;---------------------------------------------------------------------------------
i2cs_isr
		bank	i2cs
		mov	w,i2cs_state	;1 
		add	PC,w		;3 ;Add the state to the program counter
					   ;and go to the state in the jump table.

		; States for I2C slave while idle
		;-------------------------------------------------------------------------
	i2cs_idle_loc		=	$
		jmp	i2cs_idle		;3 ;If I2C_state = 0, I2C is idle
		jmp	i2cs_wait_for_scl_low	;3 ;Wait for SCL line to go low after start condition
		jmp	i2cs_get_id		;3 ;Get the id
		jmp	i2cs_read_byte		;3 
		jmp	i2cs_process_id		;3 ;Check to see if this device was addressed, and set
						   ;up for read or write if it was.

		; States for I2C slave being read
		;-------------------------------------------------------------------------
	i2cs_being_read		=	$	; Go to this state if the slave is being read.
		jmp	i2cs_send_ack		;3 
	i2cs_being_read_again	=	$
		jmp	i2cs_request_for_data	;3 ;tell mainline code to supply data.
		jmp	i2cs_waiting_for_data	;3 ;wait for data.
		jmp	i2cs_send_data		;3 ;Set up to send out a byte.
		jmp	i2cs_write_byte		;3 ;Send the byte, clocked by SCL
		jmp	i2cs_get_ack		;3 ;If we receive an ACK, then we are being read again.
	
		; States for I2C slave being written
		;-------------------------------------------------------------------------
	i2cs_being_written	=	$	; Go to this state if the slave is being written
		jmp	i2cs_send_ack		;3 ;Send an ACK
	i2cs_being_written_again	=	$
		jmp	i2cs_read_data		;3 
		jmp	i2cs_read_byte		;3 
		jmp	i2cs_process_data	;3 
		jmp	i2cs_send_ack		;3 
		jmp	i2cs_read_again
ENDIF

IFDEF I2C_SLAVE

	; State: i2cs_idle
	;	i2cs_idle is the state used when no start condition has occurred
	;	and the device has not been addressed.  The I2C slave will sit in this
	;	state until the state is changed by the independant subroutine 
	;	i2cs_get_start_stop.  
	;---------------------------------------------------------------------------------
i2cs_idle	setb	i2cs_port_buf.scl	;1
		setb	i2cs_port_buf.sda	;1
		retp				;3 = 5 + 7 = 12

	; State: i2cs_wait_for_scl_low
	;	This state simply waits until SCL goes low.  It is entered once
	;	i2cs_get_start_stop encounters a start condition.  Once SCL goes low,
	;	the state is incremented
	;---------------------------------------------------------------------------------
i2cs_wait_for_scl_low
		snb	i2cs_pres_scl		;1
		retp				;3 = 4 + 7 = 11
		clrb	i2cs_port_buf.scl	;1
		inc	i2cs_state		;1
		retp				;3 = 6 + 7 = 13

	; State: i2cs_get_id
	;	This state prepares the i2cs_read_byte routine to get 8-bits of data. 
	;	It increments the state to i2cs_read_byte before exiting.
	;---------------------------------------------------------------------------------
i2cs_get_id
		mov	w,#8			;1
		mov	i2cs_bit_count,w	;1
		inc	i2cs_state		;1
		retp				;3 = 6 + 7 = 13

	; State: i2cs_read_byte
	;	This state reads a byte of data.  i2cs_bit_count needs to be loaded with
	;	#8 before entering this state, or it won't know to increment to the next
	;	state after 8 bits of data have been loaded.
	;---------------------------------------------------------------------------------
i2cs_read_byte
		mov	w,i2cs_sub_state	;1
		add	PC,w			;3
		skip	; goto state1		;1
		jmp	:state2			;3

:state1						; wait until SCL goes high
		setb	i2cs_port_buf.scl	;1
		sb	i2cs_pres_scl		;1
		retp				;3 = 10 + 7 = 17		
		inc	i2cs_sub_state		;1 ;If SCL is high, rotate in the bit
		clc				;1
		snb	i2cs_pres_sda		;1
		stc				;1
		rl	i2cs_byte		;1
		retp				;3 = 15/17 + 7 = 22/24
:state2						; wait until SCL goes low
		snb	i2cs_pres_scl		;1
		retp				;3 = 11 + 7 = 18
		clrb	i2cs_port_buf.scl	;1
		clr	i2cs_sub_state		;1
		dec	i2cs_bit_count		;1
		sz				;1
		retp				;3 = 15 + 7 = 22
		inc	i2cs_state		;1
		retp				;3 = 16 + 7 = 23
						; Stay in this i2cs_state until all 8 bits
						; are received	

	; State: i2cs_process_id
	;	This state simply performs a quick calculation to figure out if this slave
	;	was just addressed.  If it was not, then it changes the state back to 
	;	i2cs_idle.  If this slave was addressed, then this state prepares to either
	;	read data or write data, depending on the bit 1 of the first byte received.
	;---------------------------------------------------------------------------------
i2cs_process_id
		mov	w,i2cs_byte		;1 ;Get the received I2C byte
		xor	w,i2cs_id		;1 ;Compare with the id of this slave
		and	w,#$FE			;1 ;And out the R/!W bit
		clrb	i2cs_broadcast
		jz	:slave_addressed
  IFDEF I2C_BROADCAST
		mov	w,#BROADCAST_ID		;compare broadcast id
		xor	w,i2cs_byte
		and	w,#$FE
		setb	i2cs_broadcast		;identify broadcast
		jz	:slave_addressed
		clrb	i2cs_broadcast
  ENDIF
:broadcast_read					;if addressed for read using broadcast id or
		clr	i2cs_state		;1 if the device was not addressed, then
		clr	i2cs_sub_state		;1 go back to the idle state
		retp				;3 = 12 + 7 = 19
:slave_addressed
		clrb	i2cs_port_buf.scl	;1	
						; If bit zero of the incoming byte is a 0,
		snb	i2cs_byte.0		;1 ;then go to the being written state, otherwise
		jmp	:being_read		;3 ;go to the being read state
		mov	w,#(i2cs_being_written-i2cs_idle_loc) ;1
		mov	i2cs_state,w		;1
		retp				;3 = 14 + 7 = 21
:being_read
		jb	i2cs_broadcast,:broadcast_read
		mov	w,#(i2cs_being_read-i2cs_idle_loc);1
		mov	i2cs_state,w		;1
		retp				;3 = 18 + 7 = 25

	; State: i2cs_send_ack
	;	This state outputs an ACK pulse, to tell the master that data was received
	;	correctly.  It pulls SDA low while SCL is pulsed high and low by the master.
	;---------------------------------------------------------------------------------
i2cs_send_ack	
		mov	w,i2cs_sub_state	;1
		add	pc,w			;3
		jmp	:state1			;3
		jmp	:state2			;3
		jmp	:state3			;3

:state1
		;sb	i2cs_broadcast		;this COM device does generate ACK for broadcast
		clrb	i2cs_port_buf.sda	;1 ;Clear SDA to set up ACK
		inc	i2cs_sub_state		;1
		retp				;3 = 12 + 7 = 19
:state2					
		setb	i2cs_port_buf.scl	;1 ;Allow the clock to go high
		sb	i2cs_pres_scl		;1 ;Check to see if the clock is high.
		retp				;3 = 12 + 7 = 19
		inc	i2cs_sub_state		;1
		retp				;3 = 13 + 7 = 20
:state3
		snb	i2cs_pres_scl		;1
		retp				;3 = 11 + 7 = 18
		clrb	i2cs_port_buf.scl	;1
		setb	i2cs_port_buf.sda	;1
		clr	i2cs_sub_state		;1
		inc	i2cs_state		;1
		retp				;3 = 15 + 7 = 22

	; State: i2cs_read_data
	;	This state prepares i2cs_read_byte to receive 8 bits of data.  It increments
	;	the state before exiting.
	;---------------------------------------------------------------------------------
i2cs_read_data
		mov	w,#8			;1
		mov	i2cs_bit_count,w	;1
		inc	i2cs_state		;1
		retp				;3 = 13 + 7 = 20

	; State: i2cs_read_again
	;	This state prepares to re-enter i2cs_read_data to receive another 8 bits of data.
	;	It stays in this state until i2cs_rx_flag is cleared.
	;---------------------------------------------------------------------------------
i2cs_read_again
		snb	i2cs_rx_flag		;wait till mainline code has grabbed data
		retp
		setb	i2cs_written_again	; identifies 2nd or later databyte, cleared by stop condition
		mov	w,#(i2cs_being_written_again-i2cs_idle_loc) ;1
		mov	i2cs_state,w		;  ;we may receive another byte
		retp				;3 = 13 + 7 = 20

	; State: i2cs_process_data
	;	This state processes a byte of data which was just received.  It moves the 
	;	byte that was just received into the i2cs_data_in register, and sets the 
	;	i2cs_event_flag to indicate an i2cs event and sets the i2cs_rx_flag to 
	;	indicate that a byte of data was received by the slave.
	;---------------------------------------------------------------------------------
i2cs_process_data
		setb	i2cs_rx_flag		;1
		inc	i2cs_state		;1
		retp				;3 = 15 + 7 = 22

	; State: i2cs_send_data
	;	This state prepares the i2cs_write_byte state to send the 8-bits of data
	;	in the i2cs_data_out register.
	;---------------------------------------------------------------------------------
i2cs_send_data
		mov	w,#8			;1
		mov	i2cs_bit_count,w	;1
		inc	i2cs_state		;1
		clr	i2cs_sub_state		;1
		retp				;3 = 17 + 7 = 24

	; State: i2cs_write_byte
	;	This state outputs a byte of data, clocked out by the SCL pin.  It must be
	;	pre-prepared to send out a byte by having the i2cs_byte register loaded with
	;	valid data and having the i2cs_bit_count register loaded with #8.
	;---------------------------------------------------------------------------------
i2cs_write_byte
		mov	w,i2cs_sub_state	;1
		add	pc,w			;3
		jmp	:state1			;3
		jmp	:state2			;3
		jmp	:state3			;3
:state1
		rl	i2cs_byte		;1
		setb	i2cs_port_buf.sda	;1
		sc				;1
		clrb	i2cs_port_buf.sda	;1
		inc	i2cs_sub_state		;1
		retp				;3 = 15 + 7 = 22
:state2
		setb	i2cs_port_buf.scl	;1 ;allow scl to go high, indicating slave is
						; ready
		snb	i2cs_pres_scl		;1 ;If SCL is not yet high, don't proceed to next state.
		inc	i2cs_sub_state		;1
		retp				;3 = 13 + 7 = 20
:state3
		snb	i2cs_pres_scl		;1
		retp				;3 = 11 + 7 = 18
		clr	i2cs_sub_state		;1
		clrb	i2cs_port_buf.scl	;1 ;Put scl low again to indicate slave is processing
		setb	i2cs_port_buf.sda	;1 ;release sda line again
		dec	i2cs_bit_count		;1
		sz				;1 ;once we've done all 8 bits, go to next state	
		retp				;3 = 16 + 7 = 23
		inc	i2cs_state		;1
		retp				;3 = 17 + 7 = 24

	; State: i2cs_get_ack
	;	This state gets an ACK from the I2C master.  If an ACK is received, this state
	;	will return to send more data.  If no ACK is received, the slave will be put back
	;	into its idle state.
	;---------------------------------------------------------------------------------
i2cs_get_ack
		mov	w,i2cs_sub_state	;1
		add	pc,w			;3
		jmp	:state1			;3
		jmp	:state2			;3

:state1		setb	i2cs_port_buf.scl	;1
		sb	i2cs_pres_scl		;1
		retp				;3 = 12 + 7 = 19
		snb	i2cs_pres_sda		;1
		jmp	:NACK			;3 = 13 + 7 = 20
		inc	i2cs_sub_state		;1
		retp				;3 = 14 + 7 = 21

:NACK		clr	i2cs_sub_state		;1
		clr	i2cs_state		;1
		retp				;3 = 18 + 7 = 25

:state2		snb	i2cs_pres_scl		;1
		retp				;3 = 11 + 7 = 18
		clrb	i2cs_port_buf.scl	;1
		clr	i2cs_sub_state		;1
		mov	w,#(i2cs_being_read_again-i2cs_idle_loc);1
		mov	i2cs_state,w		;1
		retp				;3 = 14 + 7 = 21

	; State: i2cs_request_for_data
	;	This state tells the mainline code to supply data in the i2cs_byte register.
	;---------------------------------------------------------------------------------
i2cs_request_for_data
		setb	i2cs_data_needed	;1 mainline code must fill i2cs_byte and clear i2cs_data_needed
		inc	i2cs_state
		retp				;3 = 13 + 7 = 20

	; State: i2cs_waiting_for_data
	;	This state waits for the mainline routine to put some valid data in the
	;	i2cs_byte register.  This state is only reached if the master has
	;	indicated it wants more data by sending an ACK after the last byte.
	;---------------------------------------------------------------------------------
i2cs_waiting_for_data
		snb	i2cs_data_needed	;1 wait till mainline code has cleared i2cs_data_needed
		retp				;3 = 13 + 7 = 20
		inc	i2cs_state
		retp
ENDIF

;-------------- codepage 1 end -----------------------------------------------------------


;*****************************************************************************************
		org	CODEPAGE_2
;*****************************************************************************************
		jmp	$	;generates error if code above crosses page boundary

;-------------- codepage 2 end -----------------------------------------------------------


;*****************************************************************************************
		org	CODEPAGE_3
;*****************************************************************************************
		jmp	$	;generates error if code above crosses page boundary

;-----------------------------------------------------------------------------------------
; MAIN PROGRAM CODE
; This can be changed as you like to suit your own application
;-----------------------------------------------------------------------------------------

; This slave provides the following to the mainline code:
;
; i2cs_rx_flag			;set when a databyte has been received by this slave
; i2cs_broadcast		;set when this slave received a broadcast id
; i2cs_written_again		;set for 2nd or later databyte received by this slave
; i2cs_data_needed		;set when this slave needs data to send to the master
; i2cs_stop_found		;set when stop condition detected on i2c bus
; i2cs_byte			;databyte buffer for this slave

; This slave sets the flag i2cs_written_again for the 2nd or later databyte received.
; It is cleared by the stop condition.
;
; The flag i2cs_rx_flag is set for any received databyte. It must be cleared by the mainline code
; directly after the byte has been grabbed from i2cs_byte. When the i2cs_rx_flag is set while
; i2cs_written_again is cleared, then the received byte is the command. More bytes received by this
; slave will have the flag i2cs_written_again set. After receiving a byte, this slave sends
; an ack to the master.
;
; The flag i2cs_data_needed is set when this slave requires data to send to the master.
; When this flag is set, the mainline code must place a databyte into i2cs_byte and clear
; the i2cs_data_needed flag. When the slave needs more data later on, the flag will be set again.
; Do not place a byte in the buffer while i2cs_data_needed is cleared, the buffer may be in use.
;
; The flag i2cs_stop_found is set by the stop condition and cleared by any start condition.
; It can be used to detect the end of the current i2c transfer.


; The mainline code will accept the following commands that are received by the i2c slave:
;
; This device must be addressed as follows:
; I2COUT id,command,[outdata,...]
; I2CIN  id,command,[indata,....]
;
; command	    description				parameters / returns
;
; 0000 0xxx	-   reserved				not specified
; 0000 1xxx	-   read device type and i2c id		major,minor,id
;
; other		-   reserved				not specified
;
; xxx = don't care
; xbb = uart number

	;Because the i2c bus operates in half duplex and the datastream is sequential,
	;incoming data can be processed directly. Just keep argument count.

i2cs_input_handler
		bank	i2cs
		mov	w,i2cs_byte
		clrb	i2cs_rx_flag
		snb	i2cs_broadcast			;broadcast command (using id $10)?
		retp					;yes, then simply ignore
		jb	i2cs_written_again,:i2cs_ih2
		mov	command,w
		clr	i2cs_in_index
		clr	i2cs_out_index
		clr	i2cs_out_tail
		jb	command.3,exec_read_command	;bit3 set -> read command
		jmp	exec_write_command		;else -> write command
:i2cs_ih2
		mov	i2cs_inbuf,w
		inc	i2cs_in_index			;increment argument count
		jmp	exec_write_command

i2cs_output_handler
		bank	i2cs
		mov	w,#$FF
		mov	i2cs_byte,w			;dummy databyte of known value
		cje	i2cs_out_tail,i2cs_out_index,exec_read_command	;read again if outbuf enpty
		mov	w,i2cs_out_tail
		mov	fsr,w
		mov	w,#i2cs_outbuffer
		add	fsr,w
		mov	w,indf
		bank	i2cs
		mov	i2cs_byte,w
		inc	i2cs_out_tail
		clrb	i2cs_data_needed
		retp

	; read command switch

exec_read_command
		clr	i2cs_out_tail			;reset outbuf index
		jb	command.7,rd_cmd_invalid
		jb	command.6,rd_cmd_invalid
		mov	w,<>command
		and	w,#$03
		add	pc,w
		jmp	rd_cmd_0
		jmp	rd_cmd_1
		jmp	rd_cmd_2
		jmp	rd_cmd_invalid			;jmp	rd_cmd_3

rd_cmd_0
; 0000 1xxx	-   read device type and i2c id		major,minor,id

		mov	w,#SYNOPDEVICE_MAJOR
		mov	i2cs_outbuf0,w
		mov	w,#SYNOPDEVICE_MINOR
		mov	i2cs_outbuf1,w
		mov	w,i2cs_id
		mov	i2cs_outbuf2,w
		mov	w,#3
		mov	i2cs_out_index,w
		retp

rd_cmd_1
; 0001 1xbb	-
		retp

rd_cmd_2
; 0010 1xbb	-
		retp

rd_cmd_invalid
		mov	w,#$FF
i2cs_return_one_byte
		bank	i2cs
		mov	i2cs_outbuf0,w
		mov	w,#1
		mov	i2cs_out_index,w
		retp

	;write command switch

exec_write_command
		snb	command.7
		retp
		snb	command.6
		retp
		mov	w,<>command
		and	w,#$03
		add	pc,w
		retp					;jmp	wr_cmd_0
		jmp	wr_cmd_1
		jmp	wr_cmd_2
		retp					;jmp	wr_cmd_3

wr_cmd_1
; 0001 0xbb	-
		retp

wr_cmd_2
; 0010 0xbb	-
		retp

;-------------- codepage 3 end ------------------------------------------------------
