Uli Ender bitbang I2C with Arduino

JM 23 Nov 2019 - Uli Ender presented "Using Tcl for simple hardware-interfaces" back in the 12th European Tcl/Tk Users Meeting 2014

bitbangImage2

The key element from his presentation was to hack commonly available PC accessories in such a way that they can be converted into I/O interfaces for electronic projects (this look very valuable as a tool to be used to teach electronics)

Nowadays the Arduino is commonly available and relatively low-cost, so I made this minor adaptation to use Ender's code to use Arduino instead of the proposed hack to a mouse he presented back then.

What you need:

  1. Arduino UNO or similar (loaded with Standard Firmata)
  2. PC with a Tcl interpreter (preferable undroidwish, which already includes the tfirmata library)
  3. The LCD with I2C "backpack" module

This code is processing the response from the slave during write operations, i.e If ACK is received, it is shown with and "A" on the specific bit slot. On the contrary "N" is shown.

But this code is not yet able to process read transactions, i.e: you cannot use it (yet) to read an I2C temperature sensor for example, as you can only write at this point.

From the 14 digital pins (0 to 13) on Arduino UNO, I used (hard coded):

digital pin 2 for SCL

digital pin 3 for SDA

Also, the Arduino is powering the LCD modules with +5V and GND


See Also: Example #2 from the Firmata page in this wiki


The LCD with I2C "backpack" module is pre-wired like this:

                                     +
         1   2  3                    |
         +   +  +                    |
         |   |  |            +-------+-------+
         |   |  |            |       2       |
       +-+---+--+---+        |               |
     16| A0 A1 A2   |        |               |
+-------+V        P0+--------+4 RS          3+-----+
       |            |        |               |
       |          P1+--------+5 RW           |
     15|            |        |               |
+------+SDA       P2+--------+6 EN           |
       |            |        |               |
       | PCF8574  P3|        |    LCD        |
     14|            |        |               |
+------+SCL         |     DB4|               |
       |          P4+--------+11             |
       |            |     DB5|               |
     13|          P5+--------+12             |
+------+INT         |     DB6|               |
       |          P6+--------+13             |
       |            |     DB7|               |
       |          P7+--------+14             |
       |            |     DB3|               |
       |  8         |     +--+10             |
       +--+---------+     DB2|               |
          |               +--+9              |
          v               DB1|               |
                          +--+8              |
                          DB0|               |
                          +--+7      1       |
                             +-------+-------+
                                     |
                                     v

and the Tcl code:

# Saturday 11/23/2019 Adaptation to use Arduino instead of mouse/light sensors
# Monday   11/25/2019 Adding ACK bit check from slave
#                     Added gridlines
#

package require tfirmata
set bd [tfirmata::open COM4]
set ctr 1

global rechteck rechteck2 iks scl sda sda2 abstand dauer ser
set scl 180
set sda 30
set sda2 30
set abstand 4
set dauer 10

$bd dstream 0 on

proc init {} {
global rechteck rechteck2 iks scl sda abstand dauer 
set iks 80
destroy .i2c
toplevel .i2c
wm geometry .i2c 1020x510+0+0
# wm geometry .i2c 785x380+0+0
destroy .i2c.c1
canvas .i2c.c1 -xscrollcommand {.i2c.x set}
scrollbar .i2c.x -ori hori -command {.i2c.c1 xview}

pack .i2c.c1 -expand yes -fill both
#grid .i2c.c1 -sticky ew
pack .i2c.x -expand yes -fill x

.i2c.c1 create line 0 30 700 30 -fill red -dash .
.i2c.c1 create line 0 100 700 100 -fill red -dash .

.i2c.c1 create line 0 180 700 180 -fill red -dash .
.i2c.c1 create line 0 250 700 250 -fill red -dash .

for {set i 167} {$i <= 700} {incr i 16} {
        .i2c.c1 create line $i 0 $i 300 -fill red -dash .
}

### scl H
if {$scl == 180} {set rechteck [.i2c.c1 create rectangle 0 190 50 380 -fill black]}
### scl L
if {$scl == 250} {set rechteck [.i2c.c1 create rectangle 0 190 50 380 -fill white]}

### sda H
if {$sda == 30}  {set rechteck2 [.i2c.c1 create rectangle 0 0 50 188 -fill black]}
if {$sda == 100} {set rechteck2 [.i2c.c1 create rectangle 0 0 50 180 -fill white]}

.i2c.c1 create text 65 30  -text H
.i2c.c1 create text 65 100 -text L
.i2c.c1 create text 65 180 -text H
.i2c.c1 create text 65 250 -text L
.i2c.c1 create text 80 120 -text SDA
.i2c.c1 create text 80 270 -text SCL

bind .i2c.c1 <ButtonPress-3> {
        global sda2 rechteck2
        set sda2 100
        .i2c.c1 itemconfigure $rechteck2 -fill blue;# SDA pulled low by slave
        update
}

bind .i2c.c1 <ButtonRelease-3> {
        global sda2 rechteck2
        set sda2 30
        .i2c.c1 itemconfigure $rechteck2 -fill yellow;# SDA release by slave
        update
}
seitwaerts
seitwaerts
seitwaerts
seitwaerts
}

proc seitwaerts {} {
global iks sda scl abstand dauer
set iks2 $iks
incr iks $abstand
.i2c.c1 create line $iks2 $sda $iks $sda
.i2c.c1 create line $iks2 $scl $iks $scl 
after $dauer
update
}

###################################################################

proc sdaH {} {
global bd iks sda rechteck2 ser
.i2c.c1 itemconfigure $rechteck2 -fill black
$bd mode 3 in
$bd dset 3 1
}

proc sdaHH {} {
global iks sda 
.i2c.c1 create line $iks $sda $iks 30
set sda 30
}

proc sdaL {} {
global bd iks sda rechteck2 ser
.i2c.c1 create line $iks $sda $iks 100
set sda 100
.i2c.c1 itemconfigure $rechteck2 -fill white
$bd mode 3 out
$bd dset 3 0
}

proc sclH {} {
global bd ctr iks scl rechteck ser 

.i2c.c1 create line $iks $scl $iks 180
set scl 180
.i2c.c1  itemconfigure $rechteck -fill black
$bd mode 2 in
$bd dset 2 1
update
incr ctr
}

proc sclL {} {
global bd iks scl rechteck ser
.i2c.c1 create line $iks $scl $iks 250
set scl 250
.i2c.c1 itemconfigure $rechteck -fill white
$bd mode 2 out
$bd dset 2 0
update
}

###################################################################

proc start {} {
global iks
.i2c.c1 create text $iks 315 -anchor nw -text " Start"
seitwaerts
sdaL
.i2c.c1 create text $iks 130 -text S
seitwaerts
}

proc stop  {} {
global iks ctr
.i2c.c1 create text $iks 315 -anchor nw -text " Stop"
s 0
seitwaerts
seitwaerts
seitwaerts
seitwaerts
sdaH
sdaHH
.i2c.c1 create text $iks 130 -text P
seitwaerts
seitwaerts
seitwaerts
seitwaerts
}

# Prepara un bit
proc s {w} {
global iks sda2 x ser ctr bd rechteck2
seitwaerts
#####
sclL
#####
seitwaerts
if {$w == "1" || $w == "A"} { sdaH }
#after 100
tfirmata::sleep 100

set SDA_state1 [$bd state 3]
set SDA_state2 "[lindex [lindex $SDA_state1 1] 0]"

if {$sda2==100} {
  puts "RELEASED"
        set sda2 30
        .i2c.c1 itemconfigure $rechteck2 -fill yellow;# SDA release by slave
}

if {$SDA_state2 == "in" && [$bd dget 3] == 0} {
        puts "ACK"
        set sda2 100
        .i2c.c1 itemconfigure $rechteck2 -fill blue;# SDA pulled low by slave
}

update
if {$w == "A" && $sda2 == 30} {set w "N"};# se recibe un HIGH (no ACK)
if {$w == "1" && $sda2 == 100} {set w "0"};# se recibe o se escribe un sda LOW, slave overrides master
if {$w == "0" || $w == "a" || $w == "A"} {sdaL }
if {$w == "1" || $w == "N"} {sdaHH };# solo traza el HIGH
seitwaerts
#####
sclH
#####
.i2c.c1 create text $iks 130 -text $w
seitwaerts
}

# Envia un byte
proc adresse {a} {
global ctr
set ctr 1
set b [format %c $a]
binary scan $b B8 var
set n 0
while {$n < 8} {
  set bit [string index $var $n]
  if {$bit == 0} { s 0 }
  if {$bit == 1} { s 1 }
        incr n
}
} 

proc chipadresse {a} {
global iks ctr
.i2c.c1 create text $iks 300 -anchor nw -text " sende Chip Addr $a"
adresse $a
s A
}

proc sende {a} {
global iks ctr
.i2c.c1 create text $iks 300 -anchor nw -text " sende $a"
adresse $a
s A
}

proc enviar {addr args} {
start
chipadresse $addr
foreach byte $args {
        sende $byte
        puts "nibble >>> $byte"
}
stop
}

proc sendData {byte} {
 set byte [string range $byte end-1 end]
 puts "hex: $byte"
 foreach {n1 n2} [split $byte ""] break
 scan ${n1}5 %x part1
 scan ${n1}1 %x part2
 scan ${n2}5 %x part3
 scan ${n2}1 %x part4
 enviar 0x4e $part1 $part2 $part3 $part4
}

proc sendData2 {byte} {
 set byte [string range $byte end-1 end]
 puts "hex: $byte"
 foreach {n1 n2} [split $byte ""] break
 enviar 0x4e 0x${n1}5 0x${n1}1 0x${n2}5 0x${n2}1
}

init
stop
if 0 {
enviar 78 52 48
enviar 78 52 48 52 48 36 32 36 32
enviar 78 196 192
enviar 78 4 0 20 16
enviar 78 4 0 196 192
enviar 78 4 0 100 96
}

enviar 0x4e 0x34 0x30

if 1 {
enviar 0x4e 0x34 0x30 0x34 0x30 0x24 0x20 0x24 0x20
enviar 0x4e 0xc4 0xc0
enviar 0x4e 0x04 0x00 0x14 0x10
enviar 0x4e 0x04 0x00 0xc4 0xc0
enviar 0x4e 0x04 0x00 0x64 0x60
}

# enviar 78 69 65 21 17 
if 1 {
        foreach letter [split "Tcl/Tk & Arduino" ""] {
          puts "Sending letter: $letter ==="
                binary scan $letter H2 valor
          sendData2 $valor
        }
}
$bd close