
#include	<pic.h>
// demo of Mike's optimised IIC code for eeprom access under hitech C
// (C) Mike Harrison 2001 email mike -at- whitewing.co.uk	
// Note that this doesn't seem to work when  local optimisation is enabled, as it
// appears to 'optimise' the asm code by simply ignoring it! Global optimisation seems to be OK.
// this is intended for inclusion within C source, not as a seperate 'extern' module
// It could  be converted to the latter form easily, and this would avoid the above optimisation
// problem. (If anyone does this please let me have a copy!)
// see notes on original assembler version for more details. 

// this version works under hitech C V7.86, and was tested on a PIC16C74
// if anyone who knows C better can suggest any improvements to the C side of this please let me know!


//asm macros
#define c 0
#define z 2
#define skpnc btfsc _STATUS,c
#define skpc btfss _STATUS,c
#define sec bsf _STATUS,c
#define clc bcf _STATUS,c
#define skpz btfss _STATUS,z
#define skpnz btfsc _STATUS,z
#define tris dw 0x60+
#define rp0 5
// define indf - missing from hitech 
static volatile unsigned char INDF	@ 0x00;

// eeprom port bits (PORT A)
#define sclbit 2 
#define sdabit 3
#define ee_scl _PORTA,sclbit
#define ee_sda _PORTA,sdabit
#define hitris  0x1a // PORT A TRIS for SDA high
#define lotris hitris-(1<<sdabit) 

#define iiport _PORTA
#define iiadr 0x0a0


void do_iic(char cnt,char eeadr,char fsrval)

// flags 
#define rden 2
#define addr 1
#define read 0

{
char flags;
char temp;
// the following assignments seem to stop the compiler optimising 
// out the temp vars because it thinks they are unused.
// This is required when global optimisation is enabled.
// if anyone knows a better way (i.e. one that doesn't waste ROM)
// of telling the compiler that these vars are used please let me know!
// one alternative would be to use global temp vars for temp & flags

flags=flags;
eeadr=eeadr; 
cnt=cnt;
temp=temp;
FSR=fsrval; // pointer to read/write data
#asm
do_iic ; read/write byte(s) to I2C EEPROM
       ; W & FSR to be setup as follows : 
       ; read  : cnt=EF - nbytes      FSR = RAM address-1 
       ; write : cnt=E0 - (nbytes<<4) FSR = RAM address-3 
       ; eeadr holds eeprom address (preserved on exit)
       ; on exit, FSR points to the byte after the last one read/written
       ; nbytes can be up to 14, but eeprom write cache may limit this

       
retry_iic
 clrf _do_iic$flags ; initialise flags and bit count
phaseloop
  movlw hitris 
  tris iiport 		; tris iiportensure SDA high
  bsf ee_scl  	; SCL high
  bcf ee_sda 	; ensure SDA o/p reg low
  movlw lotris
  goto $+1			; ensure Tsu:sta - can be omitted in fast mode
  tris iiport 			; sda low - start condition


  movlw iiadr 			; IIC control byte (write)
  btfsc _do_iic$flags,rden
  movlw iiadr+1 		; .. or read control byte if read pending
  movwf _do_iic$temp    		; IIC control byte 
  bcf ee_scl     ; scl low - code above ensures Thd:sta


byteloop        		;
								; start of byte read/write section
    movlw lotris  
    btfss _do_iic$flags,read 			; set SDA high (tri-state) if reading
    btfsc _do_iic$temp,7     			; set SDA low only if writing and data bit = 0
    movlw hitris     			; (sda o/p register bit will be low)
    tris iiport     
    goto $+1          					; wait set-up time          
    bsf ee_scl 					; clock high (may set SDA o/p reg bit high)
    clc									; used later - done here for timing
    movlw 0xff^(1<<sclbit)^(1<<sdabit)  	; mask to clear SDA and SCL, "   "
    btfsc ee_sda        			; test SDA input for read
    sec					      
    andwf iiport						; SCL low, SDA o/p reg bit low
    rlf _do_iic$temp 							; shift read data in or write data out    
    movlw 0x20
    addwf _do_iic$flags 						; increment bitcount in b5-7
    skpc
    goto byteloop						; do 8 bits

    movlw 0xf0
    xorwf _do_iic$cnt,w 		; =f0 (last byte of read), result used later
   
   movlw lotris				; ack low if reading to send ack to eeprom
   skpz 					; no ack on read of last byte
   btfss _do_iic$flags,read
   movlw hitris				; ack high for write to test ack from eeprom, or on last byte read
   
   tris iiport
   
   bsf ee_scl   		    ; clock high to get or send ack bit
   goto $+1 				; wait ack pull-up time
   movlw 0xff^(1<<sclbit)^(1<<sdabit) 	; SDA/SCL low mask, done here to add delay
   skpz 					; last byte of read - skip retry
   btfss ee_sda  		; read ack bit state 
   goto no_retry_iic	; no retry if ack low, or on last byte of read		
   goto retry_iic		; retry if ack high (will be forced low on reads, except last byte)
no_retry_iic

   andwf iiport				; set scl and sda o/p register bit low
  ;.....................  end of byte read/write section
   movf _do_iic$temp,w
   btfsc _do_iic$flags,read
   movwf indf 			; store data if reading
   movf indf,w			; get write data
   incf fsr 			; increment RAM pointer
   
   btfss _do_iic$flags,addr
   movf _do_iic$eeadr,w			; load eeprom address if not disabled 
   movwf _do_iic$temp			; byte to send next loop - address or data
   bsf _do_iic$flags,addr       ; disable address flag
   btfsc _do_iic$flags,rden		; read mode pending?
   bsf _do_iic$flags,read		; set read mode
   
   movlw 0x10
   addwf _do_iic$cnt			; increment byte counter in B4..7
   skpnz               
   goto done  			; both nibbles zero - all done
   skpc				; c set if b7-4 now clear - write phase done
   goto byteloop
   bsf _do_iic$flags,rden       ; set 'read pending' flag
   swapf _do_iic$cnt            ; load byte counter with read byte count
   goto phaseloop		; do second phase of command

done 
 						; do stop condition				
  movlw lotris			; (SDA o/p bit will be low)
  tris iiport 			; set SDA low
  bsf ee_scl			; scl high
  goto $+1				; ensure Tsu:sto
  goto $+1				; both these can be omitted for fast mode
  movlw hitris			
  tris iiport			; sda high
  

#endasm
 
}


//eeprom read/write routines. If only called once it would be more efficient to use macros instead
// note upper limit of 14 bytes that can be written/read at a time	
// and for writes, #bytes may be limited by eeprom cache size

void write_ee(char eeaddress,char nbytes,char source)
// call with write_ee(eeadr,#bytes,(char)&varname);
{
do_iic(0xe0-(nbytes<<4),eeaddress,source-1);
}


void read_ee(char eeaddress,char nbytes,char dest)
// call with read_ee(eeadr,#bytes,(char)&varname);
{
do_iic(0xef-nbytes,eeaddress,dest-3);
}



main()
{

long test1=0x33221100;
int test2=0x5544;
char test3=0x66;
char buffer[8];

TRISA=hitris;
ADCON1=7; // disable adc
OPTION=0b10110000;


write_ee(0x10,4,(char)&test1); // write long test1 to ee address 0x10
// data will be written LSB first, i.e. 00,11,22,33 in addresses 10..13

write_ee(0x14,2,(char)&test2); // write int test2 to ee address 0x14
write_ee(0x16,1,(char)&test3); // write char test3 to ee address 0x16

read_ee(0x10,4,(char)&test1); // read long test1 from ee address 0x10
read_ee(0x14,2,(char)&test2); // read int test2 from ee address 0x14
read_ee(0x16,1,(char)&test3); // read char test3 from ee address 0x16


read_ee(0x10,8,(char)&buffer[0]); // read 8 bytes from ee addresses 0x10..17 to buffer[0..7]
							     
do
 asm("clrwdt");
 while(1);
}