;****************************************************************************
;* The following code enhanced MCS-51-BASIC with four commands for I2C
;* communication as bus master and can be an example for creating custom
;* statements for MCS-51-BASIC. The four statements and the syntax to use
;* are:
;*
;*     I2CSTART            Sends a start condition to I2C bus.
;*                         - Returns with busy, time out or status clear.  
;*     I2CSTOP             Sends a stop condition to I2C bus.
;*                         - Returns with time out or status clear.
;*     I2CPUT [byte]       Sends a byte to the I2C bus.
;*                         - Returns with time out, no acknowledge or clear.
;*     I2CGET [variable]   Reads a byte from I2C to a BASIC variable.
;*                         - Set 180H (status) to 1 to send no acknowledge:
;*                            DBY(18H) = 1 : I2CGET B : I2CSTOP : PRINT B
;*                         - Returns with time out or status clear. 
;*   
;* Register 18H is the I2C communication status register; useage is:
;*
;*     STATUS=DBY(18H)
;*      IF STATUS.AND.2=2 PRINT "Time out error!"
;*      IF STATUS.AND.4=4 PRINT "Busy error!"
;*      IF STATUS.AND.8=8 PRINT "No acknowlege error!"
;*
;* (C) H.-J. Boehling 09.15.99 
;*     www.germany.net/teilnehmer/101.107378
;*     www.isis.de/members/~boehling
;*
;****************************************************************************

;----- Definitions ----------------------------------------------------------

SDA		equ	P1.6			;I2C serial data line.
SCL		equ	P1.5			;I2C serial clock line.

status		equ	018H			;Communication status.

; the following bits will be set in the status byte:

tout		equ	00000010B		;I2C time out status.
busy		equ	00000100B		;I2C bus busy status.
nack		equ	00001000B		;Slave sends no acknowledge.

value2var	equ	0A80H			;Address of MCS-51-BASIC
						;routine to give a value on
						;argument stack to a variable.
						
;----------------------------------------------------------------------------
; The following code is necessary to notify the new statements to BASIC.

		org	2002H			;5Ah at 2002h tells BASIC-52
		db	5AH			;to call 2048h (see below).
		org	2048H			;Set bit 45 to tell BASIC-52
						;that custom commands or
		setb	45			;instructions have been
		ret				;added.
		org	2070H			;Store starting address of
		mov	dptr,#vectortable	;vector table.
		ret
		org	2078H			;Store starting address of
		mov	dptr,#tokentable	;token table.
		ret
vectortable:					;Vector table starts here.
						;Label to branch on:
		dw	i2cstart		;I2CSTART command
		dw	i2cstop			;I2CSTOP command
		dw	i2cput			;I2CPUT command
		dw	i2cget			;I2CGET command
tokentable:					;Token table starts here.
		db	10H			;1. user defined token for
		db	"I2CSTART"		;command name.
		db	0			;End of token indicator
		db	11H			;2. user defined token for
		db	"I2CSTOP"		;command name.
		db	0			;End of token indicator.
		db	12H			;3. user defined token for
		db	"I2CPUT"		;command name.
		db	0			;End of token indicator.
		db	13H			;4. user defined token for
		db	"I2CGET"		;command name.
		db	0FFH			;End of list indicator.

;----------------------------------------------------------------------------
; Here starts the code for the new BASIC statements.

		org	3000h			;use any available address

;===== i2cstart - sends an I2C start condition to beginn communication ======

i2cstart:	call	SCLhigh			;Set SCL to high.
		mov	R7,#4			;Load time out counter.
setSDA:		setb	SDA			;Set SDA to high.
		jb	SDA,ishigh		;If not high bus is busy.
		djnz	R7,setSDA		;If not try until R7 is zero.
		orl	status,#busy		;Set busy status.
		ret				;return to BASIC.

ishigh:		clr	SDA			;Set start condition.
		anl     status,#0		;Clear I2C status.
 		ret				;return to BASIC.

;===== i2cstop - sends an I2C stop condition to end communication ===========

i2cstop:	anl 	status,#0		;Clear I2C status.
		clr	SDA			;Get SDA ready for stop.
		acall 	SCLhigh			;Set clock for stop.
           	acall	delay			;Delay 4 machine cycles.
		setb	SDA			;Set stop condition.
		ret				;Return to BASIC.

;===== i2cput - sends a byte from a BASIC value out to the I2C bus ==========

;----- Get value and test for 8 bit only ------------------------------------

i2cput:		mov	A,#39H			;Put value to send on
		lcall	30H			;argument stack.
		mov	A,#1			;change value to 16 bit
		lcall	30H			;integer and write to R3:R1
		cjne	R3,#0,error		;If R3 not zero	then 
						;value bigger than 0FFH.
						;8 bit integer only error.

;----- Send byte to I2C bus -------------------------------------------------		

		mov	A,R1			;Load byte to send.
		mov	R6,#8			;Load bit counter
send:		clr	SCL			;Make clock low
           	acall	delay			;Delay 4 machine cycles.
		rlc	A			;Rotate data bit to C.
		mov	SDA,C			;Put data bit on pin.
		acall	SCLhigh		        ;Send clock.
           	acall	delay			;Delay 4 machine cycles.
		djnz	R6,send			;Repeat until all bits sent.

;----- Read acknowledge from slave ------------------------------------------
	
		clr	SCL			;Make clock low.
           	acall	delay			;Delay 4 machine cycles.
		setb	SDA			;Release line for acknowledge.
		acall	SCLhigh		        ;Send clock for acknowlege.
	     	acall	delay			;Delay 4 machine cycles.
		jnb	SDA,ackok		;Check for valid acknowledge.
		orl	status,#nack		;Set no acknowledge status.
ackok:		clr	SCL			;Finish acknowledge bit.
		ret				;Return to BASIC.

;===== i2cget - Reads one byte from I2C bus to the argument stack ===========

i2cget:		mov	R6,#8			;Load bit counter
read:		clr	SCL			;Make clock low.
              	acall	delay			;Delay 4 machine cycles.
		acall	SCLhigh		        ;Send clock.
	   	acall	delay			;Delay 4 machine cycles.
		mov	C,SDA			;Get data bit from pin.
		rlc	A			;Rotate bit into result byte.
		djnz	R6,read			;Repeat until all received.

;----- Put received byte on argument stack ----------------------------------

		mov	R0,A			;Load R0 with reseived byte.
		mov	R2,#0			;Set high byte to zero.
		mov	A,#9AH			;Put byte on argument stack
		lcall	30H			;with BASIC funktion.

;----- Send acknowledge to slave --------------------------------------------

		clr	SCL			;Set clock low.
           	acall	delay			;Delay 4 machine cycles.
		mov	A,status		;Load acknowledge bit
		rrc	A			;into C and
		mov	SDA,C			;send acknowledge bit.
		acall	SCLhigh		        ;Send acknowledge clock.
		ljmp	value2var		;Set next variable to value
						;on argument stack and
						;return to BASIC.

;----- delay - generates a delay of 4 machine cycles ------------------------
; (This routine is tuned for a oscilator frequency of 12MHz).

delay:		ret				;4 cycles for CALL and RET.

;----- SCLhigh - sends SCL pin high and waits for any clock stretching ------

SCLhigh:	mov	R7,#4			;Load time out counter.
setSCL:	   	setb	SCL			;Set SCL to high.
	       	jb	SCL,quit		;If SCL actually high return.
		djnz	R7,setSCL		;If not try until R7 is zero.
		orl	status,#tout		;Set status time out.
quit:		ret

;----- error - sends an error message to the terminal -----------------------

error:		mov	A,#7			;Send CR/LF
		lcall	30H
		mov	R3,#high errmsg		;Set string address.
		mov	R1,#low errmsg
		setb	34H			;Read string from code memory.
		mov	A,#6			;Send String to
		lcall	30H			;terminal.
		clr	A
		ljmp	30H			;Back to command mode.

errmsg:		db "ERROR: BAD I2CPUT ARGUMENT"
		db	22H			;End of text.  	

		end
