I2C Bus interface Driver assembly source file
[Back]
; 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