Close

I2C

A project log for Q2 Computer

A 12-bit single-board discrete transistor computer.

Joe WingbermuehleJoe Wingbermuehle 04/14/2021 at 02:210 Comments

I should have my test board for the 2SK3018 transistors back next week, but in the meantime I've been thinking about other changes.

In the interest of adding more I/O capabilities, I think I've settled on adding an I2C interface to the Q2.  I2C is pretty easy to support, requiring 2 open-drain outputs (SDA for data and SCL for a clock), and an input (only SDA assuming there isn't a need for clock stretching).

To implement the output for I2C is the most complicated, requiring a latch for SDA and SCL. Input is easy, requiring only a single NAND gate. Here's the current proposal:

The idea is that bit 11 of address 0xFFF will select between the LCD (0) and I2C (1), allowing easy access to the LCD just as before.  When bit 11 is set, bit 10 sets SCL and bit 9 sets SDA. The software for I2C is fairly simple. For start/stop, I think something like this should work:

.def I2C_EN   0x800
.def I2C_SCL  0x400
.def I2C_SDA  0x200

; Note I2C signals are inverted.
i2c_zero:
  .dw   I2C_EN | I2C_SDA | I2C_SCL
i2c_zero_clk:
  .dw   I2C_EN | I2C_SDA
i2c_one:
  .dw   I2C_EN | I2C_SCL
i2c_one_clk:
  .dw   I2C_EN
i2c_input_mask:
  .dw   ~I2C_SDA

; Send I2C start
; Take SDA low while SCL stays high.
i2c_start:
  sta   =x1
  lda   i2c_one_clk   ; SDA=1, CLK=1
  sta   @=neg1
  lda   i2c_zero_clk  ; SDA=0, CLK=1
  sta   @=neg1
  jmp   @=x1

; Send I2C stop
; Take SDA high while SCL stays high.
i2c_stop:
  sta   =x1
  lda   i2c_zero_clk    ; SDA=0, CLK=1
  sta   @=neg1
  lda   i2c_one_clk     ; SDA=1, CLK=1
  sta   @=neg1
  jmp   @=x1

 For writing, we just loop over each bit.  Being a 12-bit architecture, we have to shift off 4 bits first. So, something like:

; Write byte in x0.
; Destroys x0-x2
i2c_write:
  sta   =x1

  ; Shift out high 4 bits
  lda   =x0
  add   =x0
  sta   =x0   ; x2
  add   =x0
  sta   =x0   ; x4
  add   =x0
  sta   =x0   ; x8
  add   =x0
  sta   =x0   ; x16

  lea   =8
i2c_write_loop:
  add   =neg1
  sta   =x2

  lda   =x0
  add   =x0
  sta   =x0
  jfc   i2c_write_zero

  ; Write 1
  lda   i2c_one
  sta   @=neg1
  lda   i2c_one_clk
  sta   @=neg1
  lda   i2c_one

  jmp   i2c_write_cont
i2c_write_zero:

  ; Write 0
  lda   i2c_zero
  sta   @=neg1
  lda   i2c_zero_clk
  sta   @=neg1
  lda   i2c_zero

i2c_write_cont:
  sta   @=neg1
  lda   =x2
  jfc   i2c_write_loop

  ; Acknowledge
  lda   i2c_one
  sta   @=neg1
  lda   i2c_one_clk
  sta   @=neg1
  lda   i2c_one
  sta   @=neg1

  jmp   @=x1

Reading is similar. From simulation, this would make reading 256 bytes from an EEPROM take somewhere in the neighborhood of 26 seconds at a 80kHz clock.  It would be nice to get this faster, but that's plenty fast to use some I2C sensors or a real-time clock, etc.

Discussions