;******************************************************************************
; Copyright © [04/11/1999] Scenix Semiconductor, Inc. All rights reserved.
;
; Scenix Semiconductor, Inc. assumes no responsibility or liability for
; the use of this [product, application, software, any of these products].
; Scenix Semiconductor conveys no license, implicitly or otherwise, under
; any intellectual property rights.
; Information contained in this publication regarding (e.g.: application,
; implementation) and the like is intended through suggestion only and may
; be superseded by updates. Scenix Semiconductor makes no representation
; or warranties with respect to the accuracy or use of these information,
; or infringement of patents arising from such use or otherwise.
;******************************************************************************
; SX Interrupt-Driven I2C Master demo.
;
; Filename:	I2C_master_1_3.src
;
; Author:	Chris Fogelklou
;		Applications Engineer
;		Scenix Semiconductor Inc.
;
; Revision:	1.3
;
; Part:		SX28AC rev. 2.5
;
; Freq:		50Mhz
;
; Compiled using Parallax SX-Key software v1.04
;
; Date Written: March 19,1999
;
; Last Revised: April 11,1999
;
; Program Description:
;
;	This program is written to run on the Parallax SX-DEMO board
;	
;	This program requires a PC to interface with.  The communications
;	settings are 19200,N,8,1
;
;	This program demonstrates the use of a Scenix SX 8-bit Microcontroller
;	to as an I2C master-controller.  The I2C communications interface
;	is interrupt-driven, so the majority of the I2C processing is
;	hidden from the mainline code, making the I2C interface a true virtual
;	peripheral.  Most current Scenix use an interrupt rate of 3.26us (retiw
;	value of -163 at 50Mhz), and at this interrupt rate, the I2C master 
;	virtual peripheral will operate at just under 100kb/s, meaning it will 
;	be able to interface with ALL standard mode I2C devices.  Currently, this
;	VP has been tested with Microchip 24LC01B and 24LC16B EEPROMs, as well 
;	as with the SCENIX SX I2C Slave virtual peripheral, and has been found to
;	work in these two applications.  The code will be tested and qualified 
;	with a true I2C analyzer, so keep in touch for more updates.
;
;	On startup, this program initializes itself, and the I2C registers.
;	It then proceeds to send a prompt VIA RS-232 to the screen.
;	It then polls every I2C address from 0 to 256 (neglecting the lowest bit,
;	which is the R/W bit) to find all the devices present on the bus.  If
;	a device is present on the bus, it will respond to the polling with an
;	ACK signal.  If a NACK is received, the SX will assume that the device
;	is not present and will continue on to the next device.  It will output,
;	in HEX, all addresses to which it receives an ACK.
;	Following this, the SX will wait for a command from the user.  These commands
;	are accepted:
;	?	-	Will send a small help window to the screen.
;	A	-	Will change the slave address to read/write from
;	S	-	Will store a null-terminated string to the EEPROM (Assuming the 
;			slave is an EEPROM
;	M	-	Will retrieve the null-terminated string from the EEPROM. 
;			(Assuming the slave is an EEPROM)
;
;	If the user tries to read or write a slave address which is not present on the
;	bus, the SX will receive a NACK.  The main program will try each address 256 tmies
;	before giving up and sending a "NACK." message to the screen.  The user can then
;	try an new address.
;
; Revision History:
;	1.0 Core I2C code with some test code.
;	1.1 Core I2C code with demo program.  
;	1.2 Changed I2C read routine so it sends a NACK before the stop.
;	1.3 Updated with the I2C master code from the multi master demo software,
;	    for more up-to-date subroutines...  Made main demo code more
;	    "user friendly" with addition of slave-finder and changeable slave address.
;
;******************************************************************************
; Device Directives
;******************************************************************************
		device	pins28,pages4,banks8		; 28-pin device, 4 pages, 8 banks of RAM
		device	oschs,turbo,optionx,stackx	; High speed oscillator, turbo mode,
							; option register extend, 8-level stack
		freq	50_000_000			; default run speed = 50MHz
		ID	'i2c_12'			; Version = 1.2

		reset	start				; JUMP to start label on reset


;**************************************************************************
; Watches (For Debug in SX_Key software V.1.0 +)
;**************************************************************************
watch	sda_pin,1,ubin
watch	scl_pin,1,ubin
watch	I2CM_port_buf.sda,1,ubin
watch	I2CM_port_buf.scl,1,ubin

watch	I2CM_num_bytes,8,udec
watch	I2CM_flags,8,ubin
watch	I2CM_state,8,udec
watch	I2CM_sub_state,8,udec
watch	I2CM_port_buf,8,ubin
watch	I2CM_bit_count,8,udec
watch	I2CM_address,8,uhex
watch	I2CM_byte,8,udec
watch	I2CM_byte,8,ubin
watch	I2CM_data_0,8,udec
watch	I2CM_data_0,8,ubin
watch	temp,8,udec
watch	rx_byte,1,fstr

;*************************************************************
; Macros
;*************************************************************

;**************************************************************************
; Equates for certain baud rates: 19200 baud
;**************************************************************************
baud_bit	=       4                       ;for 19200 baud
start_delay	=       16+8+1                  ; "    "     "
int_period	=       163                     ; "    "     "

;*************************************************************
; I2C Defines
;*************************************************************
write		=	1			; The "write" state starts at 1
						; in the jump table.
read		=	13			; The "read" state starts
						; at 13 in the jump table.
eeprom_addr	=	%10100000		; The address for the eeprom
eeprom_size	=       128                     ; storage space of EEPROM

;**************************************************************************
; Pin Definitions
;**************************************************************************
scl		equ	0
sda		equ	1
scl_pin         EQU     ra.scl                  ;I2C clock
sda_pin         EQU     ra.sda                  ;I2C data I/O
rx_pin          EQU     ra.2                    ;UART receive input
tx_pin          EQU     ra.3                    ;UART transmit output
led_pin         EQU     rb.6                    ;LED output
spkr_pin        EQU     rb.7                    ;Speaker output
pwm0_pin        EQU     rc.0                    ;Pulse width mod. PWM0 output
pwm1_pin        EQU     rc.2                    ;Pulse width mod. PWM1 output
adc0_out_pin    EQU     rc.4                    ;ADC0 input pin
adc0_in_pin     EQU     rc.5                    ;ADC0 output/calibrate pin
adc1_out_pin    EQU     rc.6                    ;ADC1 input pin
adc1_in_pin     EQU     rc.7                    ;ADC1 output/calibrate pin
;**************************************************************************
; Global Variables
;**************************************************************************

		org     $8                       ;Global Registers

temp		ds	1
isr_temp	ds	1
flags		ds	1
	timer_flag	equ	flags.0
	rx_flag		equ	flags.1
NACK_counter	ds	1

;*************************************************************
; Bank 1 Variables
;*************************************************************
		org     $10

I2CM		=     $		; I2CM bank


I2CM_state	ds	1	; This indicates the state that the I2CM master is currently in.
I2CM_sub_state	ds	1	; This indicates the substate that the I2C master is currently in.
I2CM_port_buf	ds	1	; This buffer holds the current state of the I2C port direction reg's
I2CM_bit_count	ds	1	; Indicates the number of bits left to process in read/write
I2CM_byte	ds	1	; The byte currently being written/read by the I2C master
I2CM_flags	ds	1
	I2CM_nack	equ	I2CM_flags.0	; This bit is set if the I2C master has received a NACK from the slave
	I2CM_rx_flag	equ	I2CM_flags.1 	; Indicates that the number of bytes requested have been received
I2CM_index 	ds	1	; The index into the I2CM buffer, used for writing	
I2CM_num_bytes 	ds	1	; The index into the I2CM buffer, used for reading
I2CM_buffer	 =	$	; The buffer uses the last 8 registers of this bank (pre-increments, so put I2CM buffer here.)
  I2CM_address	ds	1	; The address to read/write to.
  I2CM_data_0 	ds 	1	; Data buffer 
  I2CM_data_1	ds	1	; Data buffer
  I2CM_data_2	ds	1	; Data buffer
  I2CM_data_3	ds	1	; Data buffer
  I2CM_data_4	ds	1	; Data buffer
  I2CM_data_5	ds	1	; Data buffer
  I2CM_data_6	ds	1	; Data buffer

;*************************************************************
; Bank 2 Variables
;*************************************************************
		org     $30                     ;bank2 variables
serial		=       $                       ;UART bank

tx_high		ds      1                       ;hi byte to transmit
tx_low		ds      1                       ;low byte to transmit
tx_count	ds      1                       ;number of bits sent
tx_divide	ds      1                       ;xmit timing (/16) counter
rx_count	ds      1                       ;number of bits received
rx_divide	ds      1                       ;receive timing counter
rx_byte		ds      1                       ;buffer for incoming byte
string		ds	1
byte		ds	1
hex		ds	1
timers		=	$
timer_l	ds	1
timer_h	ds	1	
;*************************************************************
; Bank 3 Variables
;*************************************************************
		org     $50                     ;bank3 variables
;*************************************************************
; Bank 4 Variables
;*************************************************************
		org     $70                     ;bank4 variables
;*************************************************************
; Bank 5 Variables
;*************************************************************
		org     $90                     ;bank5 variables
;*************************************************************
; Bank 6 Variables
;*************************************************************
		org     $b0                     ;bank6 variables
;*************************************************************
; Bank 7 Variables
;*************************************************************
		org     $d0                     ;bank7 variables
;*************************************************************
; Bank 8 Variables
;*************************************************************
		org     $f0                     ;bank8 variables

;**************************** INTERRUPT CODE *******************************
;
; Note: The interrupt code must always originate at 0h.
;
; Interrupt Frequency = (Cycle Frequency / -(retiw value))  For example:
; 
; With a retiw value of -163 and an oscillator frequency of 50MHz, this
; code runs every 3.26us.
;******************************************************************************
interrupt	org     $0                       ;interrupt starts at 0h

		mov	fsr,#I2CM_buffer	; Preload FSR with location of I2C
		sb	I2CM_port_buf.scl	; buffer
		call	@I2CM_ISR		; Call the I2C interrupt service routine
		snb	scl_pin
		call	@I2CM_ISR
		mov	isr_temp,m
		mov	m,#$f			; Update the I2C port with the buffered
		mov	!ra,I2CM_port_buf	; values.
		mov	m,isr_temp
		mov	w,ra
		and	w,#%11111100
		mov	ra,w

;**************************************************************************
:transmit
; This is an asynchronous transmitter for RS-232 transmission
; INPUTS:
;	divider.divider_bit -	Transmitter/receiver only executes when this bit is = 1
;	tx_divide.baud_bit  -	Transmitter only executes when this bit is = 1
;	tx_high		    -	Part of the data to be transmitted
;	tx_low		    -	Some more of the data to be transmitted
;	tx_count	    -	Counter which counts the number of bits transmitted.
; OUTPUTS:
;	tx_pin		    -	Sets/Clears this pin to accomplish the transmission.
;**************************************************************************
		bank	serial
		clrb    tx_divide.baud_bit      ;clear xmit timing count flag
		inc     tx_divide               ;only execute the transmit routine
		STZ                             ;set zero flag for test
		SNB     tx_divide.baud_bit      ; every 2^baud_bit interrupt
		test    tx_count                ;are we sending?
		JZ      :receive                ;if not, go to :receive
		clc                             ;yes, ready stop bit
		rr      tx_high                 ; and shift to next bit
		rr      tx_low                  ;
		dec     tx_count                ;decrement bit counter
		movb    tx_pin,/tx_low.6        ;output next bit

;**************************************************************************
:receive
; This is an asynchronous receiver for RS-232 reception
; INPUTS:
;	rx_pin		   -	Pin which RS-232 is received on.
; OUTPUTS:
;	rx_byte		   -	The byte received
;	rx_flag		   -	Set when a byte is received.
;**************************************************************************
		movb    c,rx_pin                ;get current rx bit
		test    rx_count                ;currently receiving byte?
		jnz     :rxbit                  ;if so, jump ahead
		mov     w,#9                    ;in case start, ready 9 bits
		sc                              ;skip ahead if not start bit
		mov     rx_count,w              ;it is, so renew bit count
		mov     rx_divide,#start_delay  ;ready 1.5 bit periods
:rxbit		djnz    rx_divide,:rxdone       ;middle of next bit?
		setb    rx_divide.baud_bit      ;yes, ready 1 bit period
		dec     rx_count                ;last bit?
		sz                              ;if not
		rr      rx_byte                 ;  then save bit
		snz                             ;if so
		setb    rx_flag                 ;  then set flag
:rxdone
;**************************************************************************
do_timers	
; The timer will tick at the interrupt rate (3.26us for 50MHz.)  To set up
; the timers, move in FFFFh - (value that corresponds to the time.)  Example:
; for 1ms = 1ms/3.26us = 306 dec = 132 hex so move in $FFFF - $0132 = $FECD
;**************************************************************************

		bank	timers			; Switch to the timer bank
		mov	w,#1
		add	timer_l,w		; add 1 to timer_l
		jnc	:timer_out		; if it's not zero, then 
		add	timer_h,w		; don't increment timer_h
		snc
		setb	timer_flag		
		movb	led_pin,timer_h.5	; once timer_h is changed, update the LED
:timer_out
;******************************************************************************
:ISR_DONE
; This is the end of the interrupt service routine.  Now load 163 into w and
; perform a retiw to interrupt 163 cycles from the start of this one.  
; (3.26us@50MHz)
;******************************************************************************
		mov	w,#-163		;1	; interrupt 163 cycles after this interrupt
		retiw			;3	; return from the interrupt
;******************************************************************************
; End of the Interrupt Service Routine
;******************************************************************************



;******************************************************************************
;******************************************************************************
;******************************************************************************
;******************************************************************************
;******************************************************************************
start		
; Program Starts Here on Power Up
;******************************************************************************
;******************************************************************************
;******************************************************************************
;******************************************************************************
;******************************************************************************
;******************************************************************************

		call	@init

	;*********************************************************************************
	; Main Program:
	; Loops and does I2C stuff...
	;*********************************************************************************


	;*********************************************************************************
	; Send Prompts
	;*********************************************************************************
		mov	w,#_hello
		call	@send_string
		mov	w,#_finding_devices
		call	@send_string

	;*********************************************************************************
	; First, check every 7-bit address from 0 - 256 for which slaves are present...
	; (Lowest bit is R/W bit).  Put address on bus.  If slave returns with ACK, it is
	; present, so output the address.
	;*********************************************************************************
_start
		bank	I2CM
		clr	temp
_loopstart	
		bank	I2CM
		clr	I2CM_num_bytes

_find_loop
		bank	I2CM
		mov	w,temp
		mov	I2CM_address,w
		call	@I2CM_send_bytes
		call	@I2CM_wait_not_busy
		and	w,#$ff
		jz	:device_found
:next_device
		setb	temp.0
		inc	temp
		jz	device_search_done
		jmp	_find_loop
:device_found
		mov	w,temp
		call	@send_hex
		mov	w,#' '
		call	@send_byte
		setb	temp.0
		inc	temp
		jz	device_search_done
		jmp	_loopstart

device_search_done

	;*********************************************************************************
	; 	Done searching for devices, so now load I2CM_address with default of
	;	$A0 (microchip EEPROM) and send out the instructions.
	;*********************************************************************************
		bank	I2CM
		mov	I2CM_address,#eeprom_addr ; Set the address of the EEPROM

		mov	w,#_instructions1
		call	@send_string
		mov	w,#_help
		call	@send_string
prompt		mov	w,#_prompt
		call	@send_string
	;*********************************************************************************
	; Wait for input character.
	; -S stores an input string
	; -M dumps the contents of the EEPROM
	; -A changes the address of the slave to write to.
	; -? Gets help
	;*********************************************************************************
_cmd_loop	

		clr	NACK_counter
		jnb	rx_flag,_cmd_loop		; wait for an input character
		clrb	rx_flag				; from terminal
		bank	serial
		mov	byte,rx_byte
		call	@uppercase			; convert incoming byte to uppercase
		cje	byte,#'?',:help			; send help
		cje	byte,#'S',:store_string		; store incoming string to EEPROM
		cje	byte,#'M',:mem_dump		; dump memory of eeprom
		cje	byte,#'A',:set_address		; change address of slave to write to/ read from
		jmp	_cmd_loop			; and come back for more.

	;*********************************************************************************
	; Send Help
	;*********************************************************************************
:help
		mov	w,#_help
		call	@send_string
		jmp	prompt

	;*********************************************************************************
	; Store a string of input characters, byte by byte.  Quit when user presses 
	; enter.  Store a zero to indicate the termination of the string.
	;*********************************************************************************
:store_string
		mov	w,#_type_string		; Prompt for string
		call	@send_string
		mov	w,#_storing		; Send "Storing @" message
		call	@send_string
		bank	I2CM
		mov	w,I2CM_address		; Send hex address of slave
		call	@send_hex
		mov	w,#_prompt		; Send Prompt
		call	@send_string

		bank	I2CM
		clr	temp			; temp is used to change the internal address
						; pointer of the EEPROM
:error_1	call	@I2CM_send_byte		; Set the starting address in EEPROM to 0		
		call	@I2CM_wait_not_busy	; Wait until I2C master is done
		and	w,#$ff			; if there was an error, try again.
		jz	:get_loop		
		incsz	NACK_counter		; If we still fail after 256 attempts, send "NACK."
		jmp	:error_1		; message
		jmp	:NACK		

:get_loop
		bank	serial
		jnb	rx_flag,:get_loop		; wait for an input character
		clrb	rx_flag				; from terminal
		mov	byte,rx_byte
		cje	byte,#$0d,:enter2		; if it equals a carriage return, parse the string.
		mov	w,byte				; if it does not resemble the above characters, echo it.
		call	@send_byte
:error_2	bank	serial
		mov	w,byte
:I2C_send_zero	bank	I2CM
		mov	I2CM_data_1,w
		mov	I2CM_data_0,temp
		call	@I2CM_send_2_bytes		; send the byte of data
		call	@I2CM_wait_not_busy		; wait until I2C master is done
		and	w,#$ff				; If there was an error, try again
		jz	:next_byte			; with the same byte
		incsz	NACK_counter
		jmp	:error_2
		jmp	:NACK
:next_byte	inc	temp				; Increment the EEPROM memory location
		test	I2CM_data_1			; check if the retrieved byte is zero...
		sz	
		jmp	:get_loop
		jmp	:storage_done
		
		
:enter2		bank	I2CM				; User pressed enter, so null terminate the
		clr	wreg				; string in the EEPROM
		jmp	:I2C_send_zero			; and send it.

:storage_done
		mov	w,#_finished			; send "finished" string
		call	@send_string
		jmp	prompt				; and send prompt

	;*********************************************************************************
	; Output the contents of the EEPROM.  Quit when the retrieved character is a 
	; NULL
	;*********************************************************************************
:mem_dump

		mov	w,#_retrieving		; Send "retrieving" string
		call	@send_string
		bank	I2CM
		mov	w,I2CM_address		; Send hex address of slave
		call	@send_hex
		mov	w,#_prompt		; Send the prompt
		call	@send_string

:error_3	bank	I2CM
		clr	I2CM_data_0		; Set the starting address of EEPROM
		call	@I2CM_send_byte
		call	@I2CM_wait_not_busy
		and	w,#$ff			; If ACK, move on.
		jz	:ret_loop
		incsz	NACK_counter		; Try 256 times to get an ACK
		jmp	:error_3		; If it fails, send "NACK." message.
		jmp	:NACK
		

:ret_loop	bank	I2CM
		call	@I2CM_get_byte		; If we are here, slave responded with ACK
		test	wreg
		jz	prompt
		call	@send_byte
		jmp	:ret_loop
		jmp	prompt

	;*********************************************************************************
	; Set a new slave address to read from/write to.
	;*********************************************************************************
:set_address
		call	@get_hex		; Get HEX value from user
		bank	I2CM
		mov	I2CM_address,w
		jmp	prompt			; and go back to prompt
		
	;*********************************************************************************
	; Send out "NACK." message and go back to the prompt.
	;*********************************************************************************
:NACK
		mov	w,#_NACK
		call	@send_string
		jmp	prompt
;*********************************************************************************
org	$200	; Miscellaneous Subroutines are on this page.

;**************************************************************************
delay_n_ms
; This subroutine delays 'w' milliseconds. 
; This subroutine uses the TEMP register
; INPUT		w	-	# of milliseconds to delay for.
; OUTPUT	Returns after n milliseconds.
;**************************************************************************
	mov	temp,w
	bank	timers
:loop	clrb	timer_flag	; This loop delays for 1ms
	mov	timer_h,#$0fe
	mov	timer_l,#$0cd
	jnb	timer_flag,$
	dec	temp		; do it w-1 times.
	jnz	:loop
	clrb	timer_flag
	retp
;*********************************************************************
; Initialization Code...
;*********************************************************************
init
		mov	m,#$0e			; Initialize pullups on port A
		mov	!ra,#%1100		; for I2C
		mov	m,#$0f
		mov     ra,#%1000              	; Initialize data latches for
		mov	!ra,#%0111		; port A.
		mov     rb,#%11111111          	; Initialize data latches for
		mov	!rb,#%10111111		; port B.
		call	@zero_ram
		bank	I2CM
		call	@I2CM_init		; Initialize I2C
		mov     !option,#%00011111      ; enable rtcc interrupt and wreg
		retp
;*********************************************************************
zero_ram
; Subroutine - Zero all ram.
; INPUTS:	None
; OUTPUTS:	All ram locations (except special function registers) are = 0
;**************************************************************************
		CLR	FSR
:loop	    	SB      FSR.4                   ;are we on low half of bank?
		SETB    FSR.3                   ;If so, don't touch regs 0-7
		CLR     IND                     ;clear using indirect addressing
		IJNZ    FSR,:loop	        ;repeat until done
		retp
;**************************************************************************
; Subroutine - Get byte via serial port and echo it back to the serial port
; INPUTS:
;	-NONE
; OUTPUTS:
;	-received byte in rx_byte
;**************************************************************************
get_byte     	jnb     rx_flag,$		;wait till byte is received
		clrb    rx_flag		;reset the receive flag
		bank	serial
		mov     byte,rx_byte		;store byte (copy using W)
						; & fall through to echo char back
		retp
;**************************************************************************
; Subroutine - Send byte via serial port
; INPUTS:
;	w 	-	The byte to be sent via RS-232
;**************************************************************************
send_byte    	bank    serial

:wait        	test    tx_count                ;wait for not busy
		jnz     :wait                   ;

		not     w                       ;ready bits (inverse logic)
		mov     tx_high,w               ; store data byte
		setb    tx_low.7                ; set up start bit
		mov     tx_count,#10            ;1 start + 8 data + 1 stop bit
		RETP                            ;leave and fix page bits
;**************************************************************************
; Subroutine - Send string pointed to by address in W register
; INPUTS:
;	w	-	The address of a null-terminated string in program
;			memory
; OUTPUTS:
; 	outputs the string via. RS-232
;**************************************************************************
send_string	bank	serial
 		mov     string,w                ;store string address
:loop        	mov     w,string                ;read next string character
		mov     m,#3                    ; with indirect addressing
		iread                           ; using the mode register
		mov     m,#$F                   ;reset the mode register
		test    w                       ;are we at the last char?
		snz                             ;if not=0, skip ahead
		RETP                            ;yes, leave & fix page bits
		call    send_byte               ;not 0, so send character
		inc     string                  ;point to next character
		jmp     :loop                   ;loop until done

;**************************************************************************
; Subroutine - Make byte uppercase
; INPUTS:
;	byte	-	The byte to be converted
;**************************************************************************
uppercase    	csae	  byte,#'a'            	;if byte is lowercase, then skip ahead
		RETP

		sub     byte,#'a'-'A'           ;change byte to uppercase
		RETP                            ;leave and fix page bits
;**************************************************************************
;**************************************************************************
; Subroutine - Output a hex number
; INPUTS:
;	w	-	The byte to be output
;**************************************************************************
send_hex
	mov	temp,w
	swap	wreg
	and	w,#$0f
	call	hex_table
	call	send_byte
	mov	w,temp
	and	w,#$0f
	call	hex_table
	call	send_byte
	retp
;**************************************************************************
get_hex
;	This routine returns with an 8-bit value in the W and in the hex 
;	register.  It accepts a hex number from the terminal screen and
;	returns.
;**************************************************************************
	bank	serial

	mov	w,#_enter_hex
	call	@send_string
	call	:get_valid_hex
	mov	w,byte			; send the received (good) byte
	call	send_byte
	swap	temp			; put the nybble in the upper nybble of
	mov	w,temp
	mov	hex,w			; hex register

	call	:get_valid_hex
	mov	w,byte			; send the second received byte
	call	send_byte
	mov	w,temp			
	and	w,#$0f
	or	w,hex
	mov	hex,w
	retp

	;*******
	:get_valid_hex	; Part of get_hex subroutine.
	;*******
:gh1
	clr	temp
	jnb	rx_flag,$		; get a byte from the terminal
	clrb	rx_flag
	mov	byte,rx_byte
	call	uppercase		; uppercase it.
:loop
	mov	w,temp			; get the value at temp (index)
	call	hex_table
	xor	w,byte
	snz				; compare it to the received byte
	ret
	inc	temp			; if they are equal, we have the
	jb	temp.4,:gh1		; upper nybble.  Continue if not.
	jmp	:loop			
ret
;**************************************************************************
hex_table
;**************************************************************************
	add	pc,w
	retw	'0'
	retw	'1'
	retw	'2'
	retw	'3'
	retw	'4'
	retw	'5'
	retw	'6'
	retw	'7'
	retw	'8'
	retw	'9'
	retw	'A'
	retw	'B'
	retw	'C'
	retw	'D'
	retw	'E'
	retw	'F'
;**************************************************************************
org	$300
;**************************************************************************
; String data (for RS-232 output) and tables needs to be in page $300
;**************************************************************************
_hello          dw      13,10,'SX I2C Master Demo V.1.3',0
_instructions1  dw      13,10,' Press ? for help',0
_help           dw      13,10,' S = Store String',13,10,' M = Memory Dump',13,10,' A = Change Address',0
_prompt		dw	13,10,'>',0
_finding_devices dw	13,10,'Finding Devices...',0
_type_string	dw	13,10,'Type string & ENTER',0
_storing	dw	13,10,'Storing @ $',0
_finished	dw	13,10,'Finished.',0
_retrieving	dw	13,10,'Retrieving @ $',0
_enter_hex	dw	13,10,'Enter hex address $',0
_NACK		dw	13,10,'NACK.',0

;*********************************************************************************
org	$400	; I2C routines start at $400

I2CM_send_byte		jmp	_I2CM_send_byte
I2CM_send_bytes		jmp	_I2CM_send_bytes
I2CM_send_2_bytes	jmp	_I2CM_send_2_bytes
I2CM_get_byte		jmp	_I2CM_get_byte
I2CM_get_bytes		jmp	_I2CM_get_bytes
I2CM_wait_not_busy	jmp	_I2CM_wait_not_busy
I2CM_init		jmp	_I2CM_init
;*********************************************************************************
I2CM_ISR; -- I2C Master Interrupt-Driven State Machine
;	    -------------------------------------------
;	This is the I2C Master Interrupt Service Routine.  It is an interrupt-
;	driven state machine which allows all of the actions of the I2C Master
;	controller to be carried out, virtual peripheral style, with virtually
;	no interaction from the mainline program.  It
;I2CM_idle
; 	This is the state that the I2C Master is usually in when it is not in use.
; 	It just ensures that the I2C_port_buf SCL and SDA are both set high
;I2CM_start
; 	When any mainline program wants to use the I2C master it puts the master into
; 	start mode.  This mode creates a start condition on the I2C bus.  A start 
;	condition is created when SDA goes from high to low while SCL stays high.
;I2CM_start_write
;	This state performs some pre-processing which allows the I2CM_write state to
;	do its work.  It sets up the bit count, gets the next piece of data from the
;	buffer and prepares to send it.
;I2CM_write
;	This state writes the data in I2CM_byte to the I2C bus
;I2CM_get_ack
;	This state gets an ACK from the slave device.  If no ACK is received, the I2C
;	Master state machine puts a stop condition on the bus and the I2CM_flags 
;	register is loaded to indicate that a NACK has occurred.
;I2CM_write_repeat
;	This state determines, after one byte of data is sent, whether or not
; 	there is another byte to be sent.  If so, this state goes back to I2CM_start_write
;	and sends the next byte.
;I2CM_stop
;	This state puts a stop condition on the I2C bus and resets the state machine back
;	to its idle state.  A stop condition is when SDA goes from low to high while SCL
;	is high.
;I2CM_start_read
;	This state simply loads the contents of the I2CM_address register into the I2CM_byte
;	register and sets up the I2CM_write state to output the address of the slave to read.
;I2CM_read_data	
; 	This state prepares the I2CM read routine so it can read from the slave device.  It
;	initializes the bit count, etc.
;I2CM_read
; 	This state read 8 bits of data from the slave device.
;I2CM_store_byte
; 	This state stores the byte just read into the buffer.
;I2CM_send_ack
; 	This state sends an ack if there is data left to write, and a NACK if there is
;	no data left to write.
;*********************************************************************************

	mov	w,I2CM_state
	add	PC,w			;  Add the state to the program counter
					;  and go to the state in the jump table.

I2CM_idle_loc		=	$
	jmp	I2CM_idle		; If I2C_state = 0, I2C is idle

I2CM_write_loc	=	$
	;***************
	:I2CM_write
	;***************
	jmp	I2CM_start		; Make SDA go low while SCL is high
	jmp	I2CM_start_write	; Load a byte from the buffer and prepare to send. 
	jmp	I2CM_write		; Write it.
	jmp	I2CM_get_ack		; Get an ACK signal
	jmp	I2CM_write_repeat	; Check to see if we have finished sending (if buffer
					;  read index = buffer write index)
I2CM_stop_loc	=	$
	jmp	I2CM_stop		; If write_repeat determines we're finished, then send stop
I2CM_read_loc	=	$		; Load this state into state machine if we are starting to read
	;***************
	:I2CM_read
	;***************
	jmp	I2CM_start		;
	jmp	I2CM_start_read		; Write address used for reading
	jmp	I2CM_write 		; (writing address)
	jmp	I2CM_get_ack		;
I2CM_read_rpt_loc =	$
	jmp	I2CM_read_data		;
	jmp	I2CM_read		; Keep doing this until done
	jmp	I2CM_store_byte		;
	jmp	I2CM_send_ack		;
	jmp	I2CM_stop		;
				
;*********************************************************************************
I2CM_idle
; 	This is the state that the I2C Master is usually in when it is not in use.
; 	It just ensures that the I2C_port_buf SCL and SDA are both set high
;*********************************************************************************
	setb	I2CM_port_buf.sda
	setb	I2CM_port_buf.scl
	retp

;*********************************************************************************
I2CM_start
; 	When any mainline program wants to use the I2C master it puts the master into
; 	start mode.  This mode creates a start condition on the I2C bus.  A start 
;	condition is created when SDA goes from high to low while SCL stays high.
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3
	jmp	:state4
	jmp	:state5

:state1
	setb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state2
	setb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp

:state3
	clrb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state4
	clrb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state5
	setb	I2CM_port_buf.sda
	clr	I2CM_sub_state
	inc	I2CM_state
	retp
;*********************************************************************************
I2CM_start_write	
;	This state performs some pre-processing which allows the I2CM_write state to
;	do its work.  It sets up the bit count, gets the next piece of data from the
;	buffer and prepares to send it.
;*********************************************************************************
	inc	I2CM_index
	add	fsr,I2CM_index
	mov	w,indf

	mov	I2CM_byte,w
	mov	I2CM_bit_count,#8
	inc	I2CM_state
	retp
;*********************************************************************************
I2CM_write
;	This state writes the data in I2CM_byte to the I2C bus
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3
:state1
	setb	I2CM_port_buf.sda
	rl	I2CM_byte
	sc
	clrb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state2
	setb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state3
	clrb	I2CM_port_buf.scl
	dec	I2CM_bit_count
	jz	:done
	clr	I2CM_sub_state
	retp
:done
	clr	I2CM_sub_state
	inc	I2CM_state
	retp	
;*********************************************************************************
I2CM_get_ack
;	This state gets an ACK from the slave device.  If no ACK is received, the I2C
;	Master state machine puts a stop condition on the bus and the I2CM_flags 
;	register is loaded to indicate that a NACK has occurred.
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3
	jmp	:state4
:state1
	setb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state2
	setb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state3
	sb	sda_pin
	inc	I2CM_sub_state
	sb	sda_pin
	retp			; if no ack,
	mov	I2CM_state,#(I2CM_stop_loc-I2CM_idle_loc)
	clr	I2CM_sub_state	; send a stop and indicate that this didn't work.
	setb	I2CM_NACK	; set I2CM_NACK to show that this did not go through.
	retp
:state4
	clrb	I2CM_port_buf.scl
	clr	I2CM_sub_state
	inc	I2CM_state	; move on to next state
	retp

;*********************************************************************************
I2CM_write_repeat
;	This state determines, after one byte of data is sent, whether or not
; 	there is another byte to be sent.  If so, this state goes back to I2CM_start_write
;	and sends the next byte.
;*********************************************************************************

	mov	w,I2CM_num_bytes	; test the read index to see if it is = to write index.
	xor	w,I2CM_index	; if it is, then we have finished writing the buffer via. I2C.
	jz	:I2CM_write_done
	dec	I2CM_state	; back to get_ack
	dec	I2CM_state	; back to write
	dec	I2CM_state	; back to write_data
	retp

:I2CM_write_done
	
	inc	I2CM_state	; move on to next state (stop)
	retp			; and start the stop bit	

;*********************************************************************************
I2CM_stop
;	This state puts a stop condition on the I2C bus and resets the state machine back
;	to its idle state.  A stop condition is when SDA goes from low to high while SCL
;	is high.
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3
	jmp	:state4
	jmp	:state5
:state1
	clrb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state2
	setb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state3					; Delay so that another master can transmit, if needed.
	inc	I2CM_sub_state
	retp
:state4
	inc	I2CM_sub_state
	retp
:state5
	setb	I2CM_port_buf.sda
	clr	I2CM_sub_state		; put the state machine in Idle
	clr	I2CM_state
	clr	I2CM_num_bytes
	mov	w,#$ff
	mov	I2CM_index,w
	retp

;*********************************************************************************
; I2CM_read subroutines
;*********************************************************************************
;*********************************************************************************
I2CM_start_read
;	This state simply loads the contents of the I2CM_address register into the I2CM_byte
;	register and sets up the I2CM_write state to output the address of the slave to read.
;*********************************************************************************
	mov	I2CM_byte,I2CM_address
	inc	I2CM_index
	mov	I2CM_bit_count,#8
	inc	I2CM_state
	retp
;*********************************************************************************
I2CM_read_data	
; 	This state prepares the I2CM read routine so it can read from the slave device.  It
;	initializes the bit count, etc.
;*********************************************************************************
	mov	I2CM_bit_count,#8
	inc	I2CM_state
	jmp	I2CM_read
;*********************************************************************************
I2CM_read
; 	This state read 8 bits of data from the slave device.
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3

:state1
	setb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state2
	stc
	sb	sda_pin
	clc
	rl	I2CM_byte
	inc	I2CM_sub_state
	retp
:state3
	clrb	I2CM_port_buf.scl
	clr	I2CM_sub_state
	dec	I2CM_bit_count
	sz	
	retp
	inc	I2CM_state
	retp

;*********************************************************************************
I2CM_store_byte
; 	This state stores the byte just read into the buffer.
;*********************************************************************************
	mov	isr_temp,I2CM_byte
	inc	I2CM_index
	add	fsr,I2CM_index
	mov	indf,isr_temp
	inc	I2CM_state
	setb	I2CM_rx_flag
	retp
;*********************************************************************************
I2CM_send_ack
; 	This state sends an ack if there is data left to write, and a NACK if there is
;	no data left to write.
;*********************************************************************************
	mov	w,I2CM_sub_state
	add	pc,w
	jmp	:state1
	jmp	:state2
	jmp	:state3
	jmp	:state4	
:state1
	clrb	I2CM_port_buf.sda
	mov	w,I2CM_index
	xor	w,I2CM_num_bytes
	snz
	setb	I2CM_port_buf.sda
	inc	I2CM_sub_state
	retp
:state2
	setb	I2CM_port_buf.scl	; Pulse the clock
	inc	I2CM_sub_state
	retp
:state3
	clrb	I2CM_port_buf.scl
	inc	I2CM_sub_state
	retp
:state4
	setb	I2CM_port_buf.sda
	clr	I2CM_sub_state
	mov	w,I2CM_index
	xor	w,I2CM_num_bytes
	jz	:done
	mov	I2CM_state,#(I2CM_read_rpt_loc - I2CM_idle_loc) ;back to read 
	retp
:done	inc	I2CM_state	; If this was the last byte to be read, send a stop
	retp

org	$500
;*********************************************************************************
; I2CM subroutines:  Mainline Access routines (call from the mainline)
;*********************************************************************************
;*********************************************************************************
_I2CM_send_2_bytes
; 	This routine is useful for writing to an EEPROM, where the address is written,
;	then the starting address in memory, and then the data to write.  It actually
;	only writes one byte of data to the EEPROM (the byte in I2CM_data_1)
;*********************************************************************************
		mov	I2CM_num_bytes,#2 ; There is one byte of data in the buffer
		jmp	I2CM_send_bytes
;*********************************************************************************
_I2CM_send_byte
; 	This routine sets up the I2CM state machine to write the byte of data in
; 	the I2CM_data_0 register.  Before entering this routine, make sure that
;	the I2CM state machine is in its idle state (use the I2CM_wait_not_busy 
;	subroutine) and that the I2CM_address is loaded with the address of the
;	slave that this byte is going to, and that I2CM_data_0 is loaded with the
;	data to send.
;*********************************************************************************
		mov	I2CM_num_bytes,#1 ; There is one byte of data in the buffer
;*********************************************************************************
_I2CM_send_bytes
; 	This routine sets up the I2CM state machine to write the bytes of data in
; 	the I2CM_data_0 - I2CM_data_n registers.  Before entering this routine, 
;	make sure that the I2CM state machine is in its idle state (use the 
;	I2CM_wait_not_busy subroutine) and that I2CM_address is loaded with the 
;	address of the slave that this byte is going to, that I2CM_data_0 - I2CM_data_n
;	are loaded loaded with the data to send, and that the I2CM_num_bytes register
;	is loaded with the number of data bytes to send.
;*********************************************************************************
		mov	w,#%11111110
		and	I2CM_address,w
		clrb	I2CM_nack
		mov	I2CM_state,#(I2CM_write_loc-I2CM_idle_loc)
		retp
;*********************************************************************************
_I2CM_wait_not_busy
; 	This routine polls the I2CM_state register until it is not busy.  It returns 
;	when the I2C master state machine becomes idle.  It returns a (0) in the w
;	register if the transfer appeared successful (ie.  The slave returned an ACK
;	when addressed), and a (1) in the w register if the slave did not return
;	an ack when addressed/written.
;*********************************************************************************
:wait_loop	test	I2CM_state
		jnz	:wait_loop	; wait until I2C is idle
		mov	w,#1		; return (1) if we are idle because a NACK was
		snb	I2CM_nack	; received.  Return (0) if we are idle because
		retp			; this was successful
		clr	w		
		retp
;*********************************************************************************
_I2CM_get_byte
;	This routine gets one byte of data from the slave at address I2CM_address.
;	before calling this routine, ensure that the I2C Master State Machine is
;	in its idle state (use the I2CM_wait_not_busy subroutine) and that the
;	I2CM_address register is loaded with a valid address.  The routine returns
;	with the byte received in the w register and in the I2CM_data_0 register.
;*********************************************************************************
:nack		mov	w,#%00000001
		or	I2CM_address,w
		mov	I2CM_num_bytes,#1
		clrb	I2CM_nack
		mov	I2CM_state,#(I2CM_read_loc-I2CM_idle_loc)
		call	I2CM_wait_not_busy
		and	w,$ff
		jnz	:nack
		clrb	I2CM_rx_flag
		mov	w,I2CM_data_0
		retp
;*********************************************************************************
_I2CM_get_bytes
;	This routine gets I2CM_num_bytes of data from the slave at address
;	I2CM_address.  Before calling this routine, ensure that the I2CM state
;	machine is idle by using the I2CM_wait_not_busy subroutine, that I2CM_address
;	register contains the address of the slave to be read from, and that the
;	I2CM_num_bytes register is loaded with the number of bytes of data to receive.
;	The received bytes will be contained in registers I2CM_data_0 to I2CM_data_n.
;*********************************************************************************
		mov	w,#%00000001
		or	I2CM_address,w
		clrb	I2CM_nack
		clrb	I2CM_rx_flag
		mov	I2CM_state,#(I2CM_read_loc-I2CM_idle_loc)
		retp
;*********************************************************************************
_I2CM_init
;	This subroutine should be called on startup.  It initializes the registers
;	which are critical to the operation of the I2CM state machine.
;*********************************************************************************
		mov     I2CM_port_buf,#%0111     ;Set RA in/out directions
		mov	I2CM_num_bytes,#$ff
		mov	I2CM_index,#$ff
		retp		

;**************************************************************************
