Intel 8085 I2C library
Written by K. Jiang
The BB85 (Bit-Banged '85) is a ready-to-use program template to implement I2C with the Intel 8085. If used as configured, input port 00H must be equipped with a buffer; connect bit 2 to SCL (pulled-up via resistor to 5V) and bit 3 to SDA (also pulled-up to 5V). In addition, output port 01H should also be equipped with a buffer; buffer bit 2 should be fed into the gate of a transistor with emitter tied to 0V and collector connected to SCL, while buffer bit 3 fed into the gate of another transistor with emitter also tied to 0V and collector connected to SDA. Otherwise, the desired ports to be modified in Section 1 are INPUTPORT (input) and LEDCTRLPORT (output). However, it is best to stick with bits 2 and 3 associated with SCL and SDA, respectively. Notice that for output using a non-inverting buffer, pulling a line low means setting the corresponding bit, which may cause some confusion. The code is implemented assuming all non-inverting buffers.
The described layout is provided below. The data lines (D0 - D7) are the demultiplexed AD0 - AD7 from the 8085. IO00 and IO01 are signals that represent activated I/O ports 00H and 01H, respectively.
BB85 contains full functionality for the Intel 8085 to serve as an I2C master. No slave functionality is included. Full consideration has been given to features such as loss of arbitration in multi-master implmentations, slave clock stretching, and restarts. Simply CALL a desired function, placing appropriate arguments (if required) onto the stack or in the CY flag. All pertinent information can be found in the library. The library supports operations at the sub-bit, bit, byte, and bytestream layers, but higher-level functionality can be implemented by creating your own extensions.
- Set / Clear SCL (): i2cSetSCL / i2cClearSCL
- Set / Clear SDA (): i2cSetSDA / i2cClearSDA
- Read SCL (): i2cReadSCL
- Read SDA (): i2cReadSDA
- Start Condition (): i2cStart
- Stop Condition (): i2cStop
- Send Bit (CY): i2cSendBit
- Read Bit (): i2cReadBit
- Send Byte (byte): i2cSendByte
- Read Byte (): i2cReadByte
- Send Byte Stream (argc, *bytesToSend): i2cSendByteStream
- Read Byte Stream (argc, *bytesToStore): i2cReadByteStream
All of the included functions, upon reaching an error in communication, will immediately quit the communication, set the most significant bit of the STATE global variable, and write an error code to bits 6 and 5 of STATE. The possible combinations of STATE[7:5] after an operation are:
- 000: OK (action was successful)
- 100: TimeoutError (waited too long for slave to let go of SCL)
- 101: ArbLostError (8085 was beaten by another master for control of the bus)
- 110: NACKError (slave did not understand or was unable to process the data)
Writing extensions on top of BB85 allows for simpler interfacing with specific I2C devices, but does require some knowledge of Intel assembly programming. To create effective extensions, use i2cSendByte to send 7-bit mode slave addresses and i2cSendByteStream to send 10-bit slave addresses and device-specific addresses and data. As an example, let us create a function that sequentially reads bytes from a random address of the 24AA64 64-KBit EEPROM and stores it somewhere in its own RAM.
First create the header:
;reads a byte of data from the 24AA64 EEPROM (1010XXXR)
;input: data store address (1 byte on the stack), number of bytes to read (1 byte on the stack), data read address (1 byte on the stack, big-endian), EEPROM hardwired address (last 3 bits of 1 byte on the stack)
;output: none
;returns: errorcode (accumulator)
;size: ### bytes
We first save the registers we will be using (usually the last step, after the procedure has been written):
EEPROMrread:
PUSH H
PUSH D
PUSH PSW
Next, we set up HL as a secondary stack pointer, pointing to our first argument (the EEPROM hardwired address):
LXI H, 0009H
DAD SP
We begin communication by sending a Start condition and checking for errors:
LXI D, state
CALL i2cStart
LDAX D
RAL
JNC EEPROMrread1
;(error handling here)
MVI E, 10000000B ;errorcode for failed at Start
JMP EEPROMrread8
Now we form the I2C address of the slave and send that as a byte:
EEPROMrread1:
MOV A, M
RLC
ANI 10101110B ;set bitmasks
ORI 10100000B
PUSH PSW
CALL i2cSendByte
LDAX D ;we don't POP PSW yet because we will reuse
RAL
JNC EEPROMrread2
;(error handling here)
MVI E, 10000001B ;errorcode for failed at address send
POP PSW
JMP EEPROMrread8
Next we send the address of the memory we want to access as a bytestream:
EEPROMrread2:
INX H
INX H
PUSH H
MVI A, 02H
PUSH PSW
CALL i2cSendByteStream
POP PSW
POP H
LDAX D
RAL
JNC EEPROMrread3
;(error handling here)
MVI E, 10000010B ;errorcode for failed at memory address send
POP PSW
JMP EEPROMrread8
We send a repeated Start:
EEPROMrread3:
CALL i2cStart
LDAX D
RAL
JNC EEPROMrread4
;(error handling here)
MVI E, 10000000B ;errorcode for failed at repeated Start
POP PSW
JMP EEPROMrread8
Resend I2C address of slave as a byte, this time in Read mode:
EEPROMrread4:
POP PSW
ANI 11111110B ;reading now
PUSH PSW
CALL i2cSendByte
POP PSW
LDAX D
RAL
JNC EEPROMrread5
;(error handling here)
MVI E, 10000001B ;errorcode for failed at address send
JMP EEPROMrread8
Then read the bytestream:
EEPROMrread5:
INX H
INX H
MOV A, M
INX H
INX H
MOV E, M
INX H
MOV D, M
PUSH D
PUSH PSW
CALL i2cReadByteStream
POP PSW
POP D
LXI D, state
LDAX D
RAL
JNC EEPROMrread6
;(error handling here)
MVI E, 10000011B ;errorcode for failed at data read
JMP EEPROMrread8
Finally, we stop the communication:
EEPROMrread6:
CALL i2cStop
LDAX D
RAL
JNC EEPROMrread7
;(error handling here)
MVI E, 10000100B ;errorcode for failed at stop
JMP EEPROMrread8
And if everything was good, we return 0:
EEPROMrread7:
MVI E, 00000000B ;no error
EEPROMrread8:
POP PSW
MOV A, E
POP D
POP H
RET
- Mr. Hassman - thank you for all the hardware and support.
- Understanding the I2C Bus (Texas Instruments)
- 8080 / 8085 Assembly Language Programming (Intel)