Expand your mind, and I/O ports! MCP23017 IO Port Expander! Presto, Expando!

This tutorial shows how to use the MCP23017 with an 8051 chip (C8051F410 Silicon Labs) with I2C protocol.

I was wondering how the adafruit I2C LCD (https://www.adafruit.com/product/772) worked with only 2 pins.  They use an MCP23017 chip.  Looked it up, its an IO port expander.

Looked pretty useful so I ordered a couple.  Works great with pin hungry devices!  I liked this project, gave a chance to learn:

1) I2C

2) MCP23017 port expander

3) 7-segment display

4) Special indexed addressing mode on ‘8051: MOVC A, @A+PC for pulling data from table!

; simple I2C protocol for C8051F410 with MCP23017 I/O Port expander
; I used LAoE bitflip.a51 as a starting point from Tom C. Hayes, Learning the art of electronics
; https://learningtheartofelectronics.com/program-listings/silabs-code/lab-c1/
; demonstrates simple read and write
$NOSYMBOLS ; keeps listing short..
$INCLUDE (C:\MICRO\8051\RAISON\INC\c8051f410.inc)
SCL EQU P2.0 ; clock
SDA EQU P2.1 ; data
ORG 0 ; tells assembler the address at which to place this code
SJMP STARTUP ; here code begins--with just a jump to start of
; real program. ALL our programs will start thus
ORG 80h ; ...and here the program starts
STARTUP:
ACALL USUAL_SETUP
ACALL IO_SETUP
SETB SCL ; initialize with SCL, SDA high
SETB SDA
MAIN:
MOV R1, #00h ; R1 holds value
MOV R2, #00h ; R2 holds the address (DIRA)
ACALL EXPANDER_WRITE
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #12h ; R2 holds the address (GPIOA)
ACALL EXPANDER_WRITE ; Call the write function
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #01h ; R2 holds the address (DIRB)
ACALL EXPANDER_WRITE
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #13h ; R2 holds the address (GPIOB)
ACALL EXPANDER_WRITE ; Call the write function
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #00h ; R2 holds the address (DIRA)
ACALL EXPANDER_WRITE_2
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #12h ; R2 holds the address (GPIOA)
ACALL EXPANDER_WRITE_2 ; Call the write function
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #01h ; R2 holds the address (DIRB)
ACALL EXPANDER_WRITE_2
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV R1, #00h ; R1 holds value
MOV R2, #13h ; R2 holds the address (GPIOB)
ACALL EXPANDER_WRITE_2 ; Call the write function
LCALL DELAY
LCALL DELAY
LCALL DELAY
CLR A
MOV R3, #00h
MAIN_LOOP:
ACALL GETCODE
MOV R1, A;//#01011110b
MOV R2, #12h
ACALL EXPANDER_WRITE
LCALL DELAY
LCALL DELAY
ACALL GETCODE
MOV R1, A;//#01011110b
MOV R2, #12h
ACALL EXPANDER_WRITE_2
LCALL DELAY
LCALL DELAY
ACALL GETCODE
MOV R1, A;//#01011110b
MOV R2, #13h
ACALL EXPANDER_WRITE
LCALL DELAY
LCALL DELAY
ACALL GETCODE
MOV R1, A;//#01011110b
MOV R2, #13h
ACALL EXPANDER_WRITE_2
LCALL DELAY
LCALL DELAY
LCALL LONG_DELAY
INC R3
CJNE R3, #10h, MAIN_LOOP
MOV R3, #00h
SJMP MAIN_LOOP
; EEPROM WRITE AND READ FUNCTIONS
EXPANDER_WRITE:
ACALL I2C_START
MOV A, #40h
ACALL I2C_SEND_BYTE
MOV A, R2
ACALL I2C_SEND_BYTE
MOV A, R1
ACALL I2C_SEND_BYTE
LCALL I2C_STOP
LCALL DELAY
RET
EXPANDER_WRITE_2:
ACALL I2C_START
MOV A, #42h
ACALL I2C_SEND_BYTE
MOV A, R2
ACALL I2C_SEND_BYTE
MOV A, R1
ACALL I2C_SEND_BYTE
LCALL I2C_STOP
LCALL DELAY
RET
I2C_START: ; start condition
CLR SDA
CLR SCL
RET
I2C_STOP: ; stop condition
CLR SDA
CLR SCL
SETB SCL
SETB SDA
RET
;Send Byte assumess accumulator loaded with byte you want to send.
;Basically, just reads bit 7 and pulses SDA appropriately, along with a clock pulse.
;Then, all bits are shifted and the process is repeated until all 8 bits are sent.
;After the 8 bits, an acknowledgement bit is sent.
I2C_SEND_BYTE:
MOV R0, #08
I2C_SEND_LOOP:
JB ACC.7, GO_HIGH
GO_LOW:
ACALL I2C_LOW
SJMP CONTINUE
GO_HIGH:
ACALL I2C_HIGH
SJMP CONTINUE
CONTINUE:
RL A
DJNZ R0, I2C_SEND_LOOP
I2C_SEND_BYTE_DONE:
ACALL I2C_HIGH //ACK
RET
I2C_HIGH:
SETB SDA ; data high
SETB SCL ; Clock hi edge
SETB SDA ;stay high
CLR SCL ; clear clock
RET
I2C_LOW:
CLR SDA ; data low
SETB SCL ; clock edge hi
CLR SDA ; stay low
CLR SCL ; clear clock
RET
; I2C READ BYTE
; Assumes slave is ready to send data.
; Basically, sends clock pulses and polls SDA and sets/clears ACC.0 depending on value.
; Then, the ACC is left shifted until the byte is read.
; Read byte is left in accumulator upon return from subroutine
I2C_READ_BYTE:
MOV R0, #08
MOV ACC, #00h
I2C_READ_LOOP:
RL A
SETB SCL; clock edge hi
JB SDA, READ_HIGH
READ_LOW:
CLR ACC.0
SJMP CONTINUE_READ
READ_HIGH:
SETB ACC.0
SJMP CONTINUE_READ
CONTINUE_READ:
CLR SCL; clock low
DJNZ R0, I2C_READ_LOOP
I2C_SEND_BYTE_DONE_READ:
ACALL I2C_HIGH //ACK
RET
USUAL_SETUP: ; Disable the WDT.
anl PCA0MD, #NOT(040h) ; Clear Watchdog Enable bit
; Enable the Port I/O Crossbar
mov XBR1, #40h ; Enable Crossbar
ret
IO_SETUP: orl P0MDOUT, #01h ; enable P0.0 as push-pull output
RET
DELAY:
MOV R7, #00h
WAIT:
DJNZ R7, WAIT
RET
LONG_DELAY:
MOV R6, #50h
INNER:
MOV R7, #00
LONG_WAIT:
DJNZ R7, LONG_WAIT
DJNZ R6, INNER
RET
GETCODE: MOV A, R3
ADD A, #01
MOVC A, @A+PC ; that we must read beyond
RET
DB 00111111b ; 3F
DB 00000110b ; 06
DB 01011011b ; 5B
DB 01001111b ; 4F
DB 01100110b
DB 01101101b
DB 01111101b
DB 00000111b
DB 01111111b
DB 01101111b
DB 01110111b
DB 01111100b
DB 00111001b
DB 01011110b
DB 01111001b
DB 01110001b
END