[Return to top]

I2C Bus interface Driver assembly source file
[Back]


Download this file [Download]

; The following is 6502 code for an I2C driver. It acts only as master.
; I2C uses two bi-directional (OC) lines: clock and data. The maximum
; bit rate for slow mode is 100kbits/sec (fast mode is not supported)
; but with a 1.8MHz 6502 the best you will get is about 43kbits/sec.
; As the master controls the clock line, the processor speed should not
; be a problem.

; To work as a master the 6502 would need extra hardware to detect the
; start condition, the stop condition and extra code to handle collisions.
; This is saved for a later project.

; Devices are accessed using the following four subroutines.
;
; SendAddr
;		This routine sends the slave address to the I2C bus.
;		It can also send any required register address bytes
;		by setting them up as you would data to be sent.
;		No stop is sent so you can either read or write after
;		calling this routine.
; SendData
;		Send data byte(s) to an addressed device. Set the count
;		in I2cCountL/H and point to the data with TxBuffL/H
;		No stop is sent after calling this routine.
; ReadData
;		Read data byte(s) from an addressed device. Set the count
;		in I2cCountL/H and point to the buffer with RxBuffL/H
;		No stop is sent after calling this routine.
; StopI2c
;		generates a stop condition on the i2c bus

; Each device has an 8 bit address, the lowest bit of which is a read/write
; bit.

I2CPort		=	$F121		; i2c bus port, o.c. outputs, tristate inputs
					; bit 0 is data  [SDA]
					; bit 1 is clock [SLC]
					; bits 2 to 7 are unused
RxBuffL		=	$F1		; receive buffer pointer low byte
TxBuffL		=	RxBuffL		; the same (can't do both at once!)
RxBuffH		=	$F2		; receive buffer pointer high byte
TxBuffH		=	RxBuffH		; the same (can't do both at once!)
ByteBuff	=	$F3		; byte buffer for Tx/Rx routines
I2cAddr		=	$F4		; Tx/Rx address
I2cCountL	=	$F5		; Tx/Rx byte count low byte
I2cCountH	=	$F6		; Tx/Rx byte count high byte


		*=	$2000
	JMP	SendData		; test vector to send data
	JMP	ReadData		; test vector to read data
	JMP	SendAddr		; test vector to send slave address
	JMP	StopI2c			; test vector to send stop

; send the slave address for an i2c device
; if I2cCountL is non zero then that number of bytes will be
; sent after the address (to allow register addressing, required
; on some devices)
; RxBuff is a pointer, in page zero, to the transmit buffer
; exits with the clock low and Cb=0 if all ok
; routine entered with the i2c bus in a stopped state [SDA=SCL=1]

SendAddr
	LDA	I2CPort			; get i2c port state
	ORA	#$01			; release data
	STA	I2CPort			; out to i2c port
	LDA	#$03			; release clock
	STA	I2CPort			; out to i2c port

	LDA	#$01			; set for data test
WaitAD
	BIT	I2CPort			; test the clock line
	BEQ	WaitAD			; wait for the data to rise

	LDA	#$02			; set for clock test
WaitAC
	BIT	I2CPort			; test the clock line
	BEQ	WaitAC			; wait for the clock to rise

	JSR	StartI2c		; generate start condition

	LDA	I2cAddr			; get address (including read/write bit)
	JSR	ByteOut			; send address byte
	BCS	StopI2c			; branch if no ack

	LDA	I2cCountL		; get byte count
	BNE	SendData		; go send if not zero

	RTS				; else exit

; send data to an already addressed i2c device
; I2cCountL/H is the number of bytes to send
; RxBuff is a pointer, in page zero, to the transmit buffer
; exits with Cb=0 if all ok
; it is assumed at least one byte is to be sent
; routine entered with the i2c bus in a held state [SCL=0]

SendData
	INC	I2cCountH		; increment count high byte
	LDY	#$00			; set index to zero
WriteLoop
	LDA	(RxBuffL),Y		; get byte from buffer
	JSR	ByteOut			; send byte to device
	BCS	StopI2c			; branch if no ack

	INY				; increment index
	BNE	NoHiWrInc		; branch if no rollover

	INC	RxBuffH			; else increment pointer high byte
NoHiWrInc
	DEC	I2cCountL		; decrement count low byte
	BNE	WriteLoop		; loop if not all done

	DEC	I2cCountH		; increment count high byte
	BNE	WriteLoop		; loop if not all done

	RET

; get data from already addressed i2c device
; I2cCountL/H is the number of bytes to get
; RxBuff is a pointer, in page zero, to the receive buffer
; exits with Cb=0 if all ok
; it is assumed at least one byte is to be received
; routine entered with the i2c bus in a held state [SCL=0]

ReadData
	INC	I2cCountH		; increment count high byte
	LDY	#$00			; set index to zero
ReadLoop
	JSR	ByteIn			; get byte from device
	CLC				; clear for zero ack
	JSR	DoAck			; send ack bit
	LDA	ByteBuff		; get byte from byte buffer
	STA	(TxBuffL),Y		; save in device buffer
	INY				; increment index
	BNE	NoHiRdInc		; branch if no rollover

	INC	TxBuffH			; else increment pointer high byte
NoHiRdInc
	DEC	I2cCountL		; decrement count low byte
	BNE	ReadLoop		; loop if not all done

	DEC	I2cCountH		; decrement count high byte
	BNE	ReadLoop		; loop if not all done

	RTS

; generate stop condition on i2c bus. it is assumed only that
; the clock is low on entry to this routine.

StopI2c
	LDA	#$00			; now hold the data down
	STA	I2CPort			; out to i2c port

;	NOP				; need this if running >1.9MHz
	LDA	#$02			; release the clock
	STA	I2CPort			; out to i2c port

;	NOP				; need this if running >1.9MHz
	LDA	#$03			; now release the data (stop)
	STA	I2CPort			; out to i2c port
	RTS

; generate start condition on i2c bus. it is assumed that both
; clock and data are high on entry to this routine.
; note, another condition is A=$02 on entry

StartI2c
	STA	I2CPort			; out to i2c port

;	NOP				; need this if running >1.9MHz
	LDA	#$00			; clock low, data low
	STA	I2CPort			; out to i2c port
	RTS

; output byte to 12c bus, byte is in A. returns Cb=0 if ok
; clock should be low after generating a start or a previously
; sent byte

; exits with clock held low

ByteOut
	STA	ByteBuff		; save byte for transmit
	LDX	#$08			; 8 bits to do
OutLoop
	LDA	#$00			; unshifted clock low
	ROL	ByteBuff		; bit into carry
	ROL	A			; get data from carry
	STA	I2CPort			; out to i2c port

;	NOP				; need this if running >1.9MHz
	ORA	#$02			; clock line high
	STA	I2CPort			; out to i2c port

	LDA	#$02			; set for clock test
WaitT1
	BIT	I2CPort			; test the clock line
	BEQ	WaitT1			; wait for the clock to rise

	LDA	I2CPort			; get data bit
	AND	#$01			; set clock low
	STA	I2CPort			; out to i2c port

	DEX				; decrement count
	BNE	OutLoop			; branch if not all done

; clock is low, data needs to be released, then the
; clock needs to be released then we need to wait for
; the clock to rise and get the ack bit.

GetAck
	LDA	#$01			; float data
	STA	I2CPort			; out to i2c port

	LDA	#$03			; float clock, float data
	STA	I2CPort			; out to i2c port

	LDA	#$02			; set for clock test
WaitGA
	BIT	I2CPort			; test the clock line
	BEQ	WaitGA			; wait for the clock to rise

	LDA	I2CPort			; get data
	LSR	A			; data bit to Cb

	LDA	#$01			; clock low, data released
	STA	I2CPort			; out to i2c port
	RTS


; input byte from 12c bus, byte is returned in A. entry should
; be with clock low after generating a start or a previously
; sent byte

; exits with clock held low

ByteIn
	LDX	#$08			; 8 bits to do
	LDA	#$01			; release data
	STA	I2CPort			; out to i2c port
InLoop
	LDA	#$03			; release clock
	STA	I2CPort			; out to i2c port

	LDA	#$02			; set for clock test
WaitR1
	BIT	I2CPort			; test the clock line
	BEQ	WaitR1			; wait for the clock to rise

	LDA	I2CPort			; get data
	ROR	A			; bit into carry
	ROL	ByteBuff		; bit into buffer

	LDA	#$01			; set clock low
	STA	I2CPort			; out to i2c port

	DEX				; decrement count
	BNE	InLoop			; branch if not all done

	RTS

; clock is low, ack needs to be set then the clock released
; then we wait for the clock to rise before pulling it low
; and finishing. Ack bit is in Cb

DoAck
	LDA	#$00			; unshifted clock low
	ROL	A			; get ack from carry
	STA	I2CPort			; out to i2c port

;	NOP				; need this if running >1.9MHz
	ORA	#$02			; release clock
	STA	I2CPort			; out to i2c port

	LDA	#$02			; set for clock test
WaitTA
	BIT	I2CPort			; test the clock line
	BEQ	WaitTA			; wait for the clock to rise

	LDA	I2CPort			; get ack back
	AND	#$01			; hold clock
	STA	I2CPort			; out to i2c port

	RTS

	END


e-mail me [e-mail]
Last page update: 28th April, 2002.