For my electronics class, my project required communicating with an I2C peripheral (MCP4725 DAC, adafruit breakout board).
I got stuck trying to use the ‘410 SMBus; example code from the application note is very complex and I could not get this to work. I was able to communicate with the DAC using an Arduino. After observing the Arduino’s I2C implementation using the Saleae logic analyzer, and reading a great tutorial from TI:
http://www.ti.com/lit/an/slva704/slva704.pdf?&ts=1589762906285
I was able to write a very simple I2C protocol for the 8051:
| ; simple I2C protocol for C8051F410 used LAoE bitflip.a51 as a starting point | |
| ; https://learningtheartofelectronics.com/program-listings/silabs-code/lab-c1/ | |
| $NOSYMBOLS ; keeps listing short.. | |
| $INCLUDE (C:\MICRO\8051\RAISON\INC\c8051f410.inc) | |
| SCL EQU P0.0 ; clock | |
| SDA EQU P0.1 ; data | |
| BLUE_LED EQU P0.4 ; an LED is always nice | |
| 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 BLUE_LED | |
| SETB SCL ; initialize with SCL, SDA high | |
| SETB SDA | |
| MAIN: CPL BLUE_LED ; flips LED | |
| ACALL SET_VOLTAGE_TO_5V ; Comment/uncomment for testing | |
| //ACALL SET_VOLTAGE_TO_1V | |
| //ACALL SET_VOLTAGE_TO_0V | |
| SJMP MAIN | |
| ; These small set_voltage functions demonstrate a how to send a complete I2C frame | |
| ; Start | |
| ; Load accumulator with value, then send byte | |
| ; repeat if needed | |
| ; Stop | |
| SET_VOLTAGE_TO_5V: ; as advertised, sets the DAC to max 5V | |
| ACALL I2C_START | |
| MOV A, #0C4h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #040h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #0FAh | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #00h | |
| ACALL I2C_SEND_BYTE | |
| LCALL I2C_STOP | |
| LCALL DELAY | |
| RET | |
| SET_VOLTAGE_TO_0V: | |
| ACALL I2C_START | |
| MOV A, #0C4h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #040h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #00h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #00h | |
| ACALL I2C_SEND_BYTE | |
| LCALL I2C_STOP | |
| LCALL DELAY | |
| RET | |
| SET_VOLTAGE_TO_1V: | |
| ACALL I2C_START | |
| MOV A, #0C4h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #040h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #32h | |
| ACALL I2C_SEND_BYTE | |
| MOV A, #00h | |
| 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 | |
| DELAY: | |
| MOV R0, #20h | |
| WAIT: | |
| DJNZ R0, WAIT | |
| 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 | |
| END | |
Here is the code communicating with the DAC:

I wrote up a quick tutorial:







