PIC Microcontoller Input / Output Method

for I2C

by Andrew D. Vassallo [snurple@hotmail.com]

Here are some bit-banged I^2C routines that should be easy to understand. They are fairly optimized, with near-minimum instruction count allowable for 4MHz operation.

Tested/bulletproof at 4MHz. Bank switching is provided. Routines exit with Bank 0 selected.

;------------------------------------------------------------------------
;
; I^2C master send/receive routines for 24Cxx EEPROM memory chips
; by Andrew D. Vassallo
;
; email: snurple@hotmail.com
;
; Timing set for 4MHz clock, 4.7K pullup resistors to SDA and SCL lines.
; Checks provided for failed ACK during EEPROM write access.
; Optimized for speed and clarity of function.
; Full comments provided.
; Bank switching correct.
;
;------------------------------------------------------------------------

;------ NOTE: Locate all variables in Bank 1!!
CBLOCK
	GenCount	; generic counter/temp register (use with caution)
	Mem_Loc		; EEPROM memory address to be accessed
	Data_Buf	; byte read from EEPROM is stored in this register
	OutputByte	; used for holding byte to be output to EEPROM
	flags		; flag bit register
ENDC


;------ Define port pins: RB2=SDA=data, RB1=SCL=clock
;------ Pins are connected via 4.7K pullup resistors for passive control
;------ Any port pins can be used.
#define	SDA	TRISB, 2
#define	SCL	TRISB, 1


;------ The memory address is predefined as Mem_Loc coming in to this routine to read from EPROM.
;------ Note that using the 24Cxx requires an address for bits <1:3> for control bytes.  For a one-
;------ memory circuit used here, just use address 000.  This routine uses "random addressing."
;------ This code has been optimized for speed at 4MHz, assuming Temperature is between -40 and +85 deg. C.
;
; Call with: EEPROM address in Mem_Loc
; Returns with: byte in Data_Buf
ReadEPROM
		bcf	STATUS, RP0
		movf	PORTB, 0		; for EEPROM operation,
		andlw	0xF9			; load zero into RB1 and RB2
		movwf	PORTB			; for passive control of bus
		bsf	STATUS, RP0		; select Bank 1 for TRISB access (passive SCL/SDA control)
		bsf	SDA			; let SDA line get pulled high
		bsf	SCL			; let SCL line get pulled high
		bcf	SDA			; START - data line low
		movlw	0xA0			; send "dummy" write (10100000) to set address
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine		; NOTE: MUST USE "RETURN" FROM THERE
		movf	Mem_Loc, 0
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		bcf	SCL			; pull clock line low in preparation for 2nd START bit
		nop
		bsf	SDA			; pull data line high - data transition during clock low
		bsf	SCL			; pull clock line high to begin generating START
		bcf	SDA			; 2nd START - data line low
		movlw	0xA1			; request data read from EPROM (read=10100001)
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
;------ Note that Byte_Out leaves with SDA line freed to allow slave to send data in to master.
		call	Byte_In
		movf	Data_Buf, 0		; put result into W register for returning to CALL
		bcf	SCL			; extra cycle for SDA line to be freed from EPROM
		nop
		bcf	SDA			; ensure SDA line low before generating STOP
		bsf	SCL			; pull clock high for STOP
		bsf	SDA			; STOP - data line high
		bcf	STATUS, RP0		; leave with Bank 0 active as default
		return

;------ Save each byte as it's written (not page write mode).
;------ We can speed this up to ~1.5ms for fast page write capable EEPROMs, but in case we want to
;------ use another slower memory chip, the default is 10ms delay per write.
;
; Call with:  EEPROM address in Mem_Loc, byte to be sent in Data_Buf
; Returns with:  nothing returned
WriteEPROM
		bcf	STATUS, RP0
		movf	PORTB, 0		; for EEPROM operation,
		andlw	0xF9			; load zero into RB1 and RB2
		movwf	PORTB			; for passive control of bus
		bsf	STATUS, RP0		; select Bank 1 for TRISB access (passive SCL/SDA control)
		bsf	SDA			; ensure SDA line is high
		bsf	SCL			; pull clock high
		bcf	SDA			; START - data line low
		movlw	0xA0			; send write (10100000) to set address
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine		; NOTE: MUST USE "RETURN" FROM THERE
		movf	Mem_Loc, 0
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		movf	Data_Buf, 0		; move data to be sent to W
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		bcf	SCL			; extra cycle for SDA line to be freed from EPROM
		nop
		bcf	SDA			; ensure SDA line low before generating STOP
		bsf	SCL			; pull clock high for STOP
		bsf	SDA			; STOP - data line high
		call	Delay10ms		; 10ms delay max. required for EPROM write cycle
		bcf	STATUS, RP0		; leave with Bank 0 active by default
		return

;------ This routine reads one byte of data from the EPROM into Data_Buf
Byte_In
		clrf	Data_Buf
		movlw	0x08			; 8 bits to receive
		movwf	GenCount
ControlIn
		rlf	Data_Buf, 1		; shift bits into buffer
		bcf	SCL			; pull clock line low
		nop
		bsf	SCL			; pull clock high to read bit
		bcf	STATUS, RP0		; select Bank 0 to read PORTB bits directly!
		btfss	SDA			; test bit from EPROM (if bit=clear, skip because Data_Buf is clear)
		goto	$+3
		bsf	STATUS, RP0		; select Bank 1 to access variables
		bsf	Data_Buf, 0		; read bit into 0 first, then eventually shift to 7
		bsf	STATUS, RP0		; select Bank 1 to access variables
		decfsz	GenCount, 1
		goto	ControlIn
		return

;------ This routine sends out the byte in the W register and then waits for ACK from EPROM (256us timeout period)
Byte_Out
		movwf	OutputByte
		movlw	0x08			; 8 bits to send
		movwf	GenCount
		rrf	OutputByte, 1		; shift right in preparation for next loop
ControlOut
		rlf	OutputByte, 1		; shift bits out of buffer
		bcf	SCL			; pull clock line low
		nop
		btfsc	OutputByte, 7		; send current "bit 7"
		goto	BitHigh
		bcf	SDA
		goto	ClockOut
BitHigh		
		bsf	SDA
ClockOut	
		bsf	SCL			; pull clock high after sending bit
		decfsz	GenCount, 1
		goto	ControlOut
		bcf	SCL			; pull clock low for ACK change
		bsf	SDA			; free up SDA line for slave to generate ACK
		nop
		nop
		nop				; wait for slave to pull down ACK
		bsf	SCL			; pull clock high for ACK read
		clrf	GenCount		; reuse this register as a timeout counter (to 256us) to test for ACK
WaitForACK
		bsf	STATUS, RP0		; select Bank1 for GenCount access
		incf	GenCount, 1		; increase timeout counter each time ACK is not received
		btfsc	STATUS, Z
		goto	No_ACK_Rec
		bcf	STATUS, RP0		; select Bank0 to test SDA PORTB input directly!
		btfsc	SDA			; test pin. If clear, EEPROM is pulling SDA low for ACK
		goto	WaitForACK		; ...otherwise, continue to wait
		bsf	STATUS, RP0		; select Bank1 as default during these routines
		bcf	flags, 0		; clear flag bit (ACK received)
		return


;------ No ACK received from slave (must use "return" from here)
;; Typically, set a flag bit to indicate failed write and check for it upon return.
No_ACK_Rec
		bsf	flags, 0		; set flag bit
		return				; returns to Byte_Out routine (Bank 1 selected)


;------ No ACK received from slave.  This is the error handler.
Error_Routine
; Output error message, etc. here
		bcf	STATUS, RP0		; select Bank0 as default before returning home
		return				; returns to INITIAL calling routine


Delay10ms
	movlw	0x0A
	movwf	GenCount
Delay_Start
	nop
	movlw 0x07			; 249 cycles * 4us per cycle + 5us = 1.000ms
Delay
	addlw 0x01
	btfss STATUS, Z
	goto Delay
	decfsz GenCount, 1
	goto Delay_Start
	return

  

PICList post "IIC Routines - giving, not needing"

Questions:

Interested:

Comments: