;******************************************************************************
;       Virtual Peripheral: I2C Subroutines
;
;       Length: 133 bytes (total)
;       Authors: Chip Gracey, President, Parallax Inc.
;		   modified by Craig Webb, Consultant to Scenix Semiconductor, Inc.
;       Written: 97/03/10 to 98/6/03
;
;******************************************************************************
;
;****** Assembler directives
;
; uses: SX28AC, 2 pages of program memory, 8 banks of RAM, high speed osc.
;       operating in turbo mode, with 8-level stack & extended option reg.
;                
		DEVICE  pins28,pages2,banks8,oschs
		DEVICE  turbo,stackx,optionx
		ID      'I2C'			;program ID label
		RESET   reset_entry             ;set reset/boot address
;
;******************************* Program Variables ***************************
;
; Port Assignment: Bit variables
;
scl             EQU     RA.0                    ;I2C clock
sda             EQU     RA.1                    ;I2C data I/O
;
;
;****** Register definitions (bank 0)
;
		org     8                       ;start of program registers
main            =       $                       ;main bank
;
temp            ds      1                       ;temporary storage
byte            ds      1                       ;temporary UART/I2C shift reg.
flags           DS      1                       ;program flags register
;
seq_flag        EQU     flags.1                 ;I2C: R/W mode (if sequential=1)
got_ack         EQU     flags.2                 ;     if we got ack signal
;
		org     90H                     ;bank4 variables
I2C             EQU     $                       ;I2C bank
;
data            DS      1                       ;data byte from/for R/W
address         DS      1                       ;byte address
count           DS      1                       ;bit count for R/W
delay           DS      1                       ;timing delay for write cycle
byte_count      DS      1                       ;number of bytes in R/W
num_bytes       DS      1                       ;number of byte to view at once
save_addr       DS      1                       ;backup location for address
;
in_bit          EQU     byte.0                  ;bit to receive on I2C
out_bit         EQU     byte.7                  ;bit to transmit on I2C 
;
control_r       =       10100001b               ;control byte: read E2PROM
control_w       =       10100000b               ;control byte: write E2PROM
portsetup_r     =       00000110b               ;Port A config: read bit
portsetup_w     =       00000100b               ;Port A config: write bit
eeprom_size     =       128                     ;storage space of EEPROM
;
t_all           =       31                      ;bit cycle delay (62=5 usec)
;
;
		ORG	0h			;Program Start adress
;
;******************************** I2C Subroutines *****************************
;
; These routines write/read data to/from the 24LCxx EEPROM at a rate of approx.
; 200kHz. For faster* reads (up to 400 kHz max), read, write, start amd stop
; bit cycles and time between each bus access must be individually tailored
; using the CALL Bus_delay:custom entry point with appropriate values in the W
; register - in turbo mode: delay[usec] = 1/xtal[MHz] * (6 + 4 * (W-1)).
; Acknowledge polling is used to reduce delays between successive operations 
; where the first of the two is a write operation. In this case, the speed
; is limited by the EEPROM's storage time.
;
;****** Subroutine(s) : Write to I2C EEPROM
; These routines write a byte to the 24LCxxB EEPROM. Before calling this
; subroutine, the address and data registers should be loaded accordingly. The
; sequential mode flag should be clear for normal byte writing operation.
; To write in sequential/page mode, please see application note. 
;
;       Input variable(s) : data, address, seq_flag
;       Output variable(s) : none
;       Variable(s) affected : byte, temp, count, delay
;       Flag(s) affected : none
;       Timing (turbo) : approx. 200 Kbps write rate
;                      : approx. 10 msec between successive writes
;
I2C_write       CALL    Set_address             ;write address to slave
:page_mode      MOV     W,data                  ;get byte to be sent
		CALL    Write_byte              ;Send data byte
		JB      seq_flag,:done          ;is this a page write?
		CALL    Send_stop               ;no, signal stop condition
:done           RETP                            ;leave and fix page bits
;
Set_address     CALL    Send_start              ;send start bit
		MOV     W,#control_w            ;get write control byte
		CALL    Write_byte              ;Write it & use ack polling
		JNB     got_ack,Set_address     ; until EEPROM ready
		MOV     W,address               ;get EEPROM address pointer
		CALL    Write_byte              ; and send it
		RETP                            ;leave and fix page bits
;
Write_byte      MOV     byte,W                  ;store byte to send
		MOV     count,#8                ;set up to write 8 bits
:next_bit       CALL    Write_bit               ;write next bit
		RL      byte                    ;shift over to next bit
		DJNZ    count,:next_bit         ;whole byte written yet?
		CALL    Read_bit                ;yes, get acknowledge bit
		SETB    got_ack                 ;assume we got it
		SNB     in_bit                  ;did we get ack (low=yes)?
		CLRB    got_ack                 ;if not, flag it
;
; to use the LED as a 'no_ack' signal, the ':toggle_led' line in the interrupt
;  section must be commented out, and the next 3 instructions uncommented.
;               CLRB    led_pin                 ;default: LED off
;               SNB     in_bit                  ;did we get ack (low=yes)?
;               SETB    led_pin                 ; if not, flag it with LED
;
		RETP                            ;leave and fix page bits
;
Write_bit       MOVB    sda,out_bit             ;put tx bit on data line
		MOV     !ra,#portsetup_w        ;set Port A up to write
		JMP     :delay1                 ;100ns data setup delay
:delay1         JMP     :delay2                 ; (note: 250ns at low power)
:delay2         SETB    scl                     ;flip I2C clock to high
;               MOV     W,#t_high                       ;get write cycle timing*
		CALL    Bus_delay               ;do delay while bus settles
		CLRB    scl                     ;return I2C clock low
		MOV     !ra,#portsetup_r        ;set sda->input in case ack
;               MOV     W,#t_low                ;get clock=low cycle timing*
		CALL    Bus_delay               ;allow for clock=low cycle
		RETP                            ;leave and fix page bits
;
Send_start      SETB    sda                     ;pull data line high
		MOV     !ra,#portsetup_w        ;setup I2C to write bit
		JMP     :delay1                 ;100ns data setup delay
:delay1         JMP     :delay2                 ; (note: 250ns at low power)
:delay2         SETB    scl                     ;pull I2C clock high
;               MOV     W,#t_su_sta             ;get setup cycle timing*
		CALL    Bus_delay               ;allow start setup time
:new            CLRB    sda                     ;data line goes high->low
;               MOV     W,#t_hd_sta             ;get start hold cycle timing*
		CALL    Bus_delay               ;allow start hold time          
		CLRB    scl                     ;pull I2C clock low
;               MOV     W,#t_buf                ;get bus=free cycle timing*
		CALL    Bus_delay               ;pause before next function             
		RETP                            ;leave and fix page bits
;
Send_stop       CLRB    sda                     ;pull data line low
		MOV     !ra,#portsetup_w        ;setup I2C to write bit
		JMP     :delay1                 ;100ns data setup delay
:delay1         JMP     :delay2                 ; (note: 250ns at low power)
:delay2         SETB    scl                     ;pull I2C clock high
;               MOV     W,#t_su_sto             ;get setup cycle timing*
		CALL    Bus_delay               ;allow stop setup time
		SETB    sda                     ;data line goes low->high
;               MOV     W,#t_low                ;get stop cycle timing*
		CALL    Bus_delay               ;allow start/stop hold time             
		RETP                            ;leave and fix page bits
;
Bus_delay       MOV     W,#t_all                ;get timing for delay loop
:custom         MOV     temp,W                  ;save it
:loop           DJNZ    temp,:loop              ;do delay
		RETP                            ;leave and fix page bits
;
;****** Subroutine(s) : Read from I2C EEPROM
; These routines read a byte from a 24LCXXB E2PROM either from a new address
; (random access mode), from the current address in the EEPROM's internal
; address pointer (CALL Read_byte:current), or as a sequential read. In either
; the random access or current address mode, seq_flag should be clear. Please
; refer to the application note on how to access the sequential read mode.
;
;       Input variable(s) : address, seq_flag
;       Output variable(s) : data
;       Variable(s) affected : byte, temp, count, delay
;       Flag(s) affected : none
;       Timing (turbo) : reads at approx. 200Kbps 
;
I2C_read        CALL    Set_address             ;write address to slave
:current        CALL    Send_start              ;signal start of read
		MOV     W,#control_r            ; get read control byte
		CALL    Write_byte              ; and send it
:sequential     MOV     count,#8                ;set up for 8 bits
		CLR     byte                    ;zero result holder
:next_bit       RL      byte                    ;shift result for next bit
		CALL    Read_bit                ;get next bit
		DJNZ    count,:next_bit         ;got whole byte yet?
		MOV     data,byte               ;yes, store what was read
		SB      seq_flag                ;is this a sequential read?
:non_seq        JMP     Send_stop               ; no, signal stop & exit
		CLRB    out_bit                 ; yes, setup acknowledge bit
		CALL    Write_bit               ;   and send it
		RETP                            ;leave and fix page bits
;
Read_bit        CLRB    in_bit                  ;assume input bit low
		MOV     !ra,#portsetup_r        ;set Port A up to read
		SETB    scl                     ;flip I2C clock to high
;               MOV     W,#t_high               ;get read cycle timing*
		CALL    Bus_delay               ;Go do delay
		SNB     sda                     ;is data line high?
		SETB    in_bit                  ;yes, switch input bit high
		CLRB    scl                     ;return I2C clock low
;               MOV     W,#t_buf                ;get bus=free cycle timing*
		CALL    Bus_delay               ;Go do delay
		RETP                            ;leave and fix page bits
;
; *****	High level functions: Store byte & Erase
;
Store_W		BANK    I2C                     ;switch to EEPROM bank
		MOV     data,W                  ;save incoming value
		CALL    I2C_Write               ;store it in EEPROM
		INC     address                 ;move to next address
		INC     byte_count              ;adjust # bytes stored
		MOV     W,eeprom_size           ;get memory size
		MOV     W,address-W             ;are we past end?
		SNZ                             ;if not, skip ahead
		CLR     address                 ;if so, reset it
:done           RETP                            ;leave and fix page bits
;
Erase_Mem       CLR     address                 ;restore address pointer
		MOV     num_bytes,#eeprom_size  ;wipe whole mem
:wipeloop       CLR     data                    ;byte to wipe with=0
;               MOV     data,address            ;byte to wipe with=addr
		CALL    I2C_write               ;wipe EEPROM byte
		INC     address                 ;move to next address
		DJNZ    num_bytes,:wipeloop     ;Erased enough yet?
		CLR     byte_count              ;done, reset stored count
		CLR     save_addr               ;reset backup address
		RETP                            ;leave and fix page bits
;
;************************** End of I2C Subroutines ****************************
;
;***************************** MAIN PROGRAM CODE ******************************
;
;		ORG     100h
;
; Program execution begins here on power-up or after a reset
;
reset_entry
		mov      ra,#%1011              ;initialize port RA
		mov     !ra,#%0100              ;Set RA in/out directions
		CLR     FSR                     ;reset all ram starting at 08h
:zero_ram       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,:zero_ram           ;repeat until done

		mov     !option,#%11011111      ;disable rtcc interrupt
;
; Main Program Loop
;
:loop		MOV	W,#55			;example byte
		CALL    Store_W                 ;Store byte in next I2c address
;
; <program code goes here>
;
		JMP     :loop                   ;back to main loop
;
;***************
		END                             ;End of program code

