Version 3 of Uli Ender bitbang I2C with Arduino

Updated 2019-11-23 19:03:04 by Jorge

JM 23 Nov 2019 - Uli Ender presented "Using Tcl for simple hardware-interfaces" back in 2014 (EuroTcl)

bitbangImage

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 not yet processing the data from the slave, i.e:

  • ACK is being ignored in this demo
  • you cannot use it (yet) to read an I2C temperature sensor for example, as you can only write at this point

From the 13 digital pins 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:

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

### i2C.demo                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 100

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
pack .i2c.c1 -expand yes -fill both
### 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]}
if 0 {
        button .i2c.c1.b2 -text "a
        u
        t
        o"
        button .i2c.c1.b3 -text "s
        t
        e
        p"
        place .i2c.c1.b2 -x 735 -y 0   -height 190 -width 52
        place .i2c.c1.b3 -x 735 -y 190 -height 190 -width 52
}
.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
        update
}

bind .i2c.c1 <ButtonRelease-3> {
        global sda2 rechteck2
        set sda2 30
        .i2c.c1 itemconfigure $rechteck2 -fill yellow
        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
global 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
global 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
global 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
}

proc sclL {} {
global bd
global 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
.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
}

proc s {w} {
global iks sda2 x ser
seitwaerts
sclL
seitwaerts
if {$w == "1" || $w == "A"} { sdaH }
#after 100
tfirmata::sleep 100
update
if {$w == "A" && $sda2 == 30} {set w "N"}
if {$w == "1" && $sda2 == 100} {set w "0"}
if {$w == "0" || $w == "a" || $w == "A"} { sdaL }
if {$w == "1" || $w == "N"} { sdaHH }
seitwaerts
sclH
.i2c.c1 create text $iks 130 -text $w
seitwaerts
}

proc adresse {a} {
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
.i2c.c1 create text $iks 300 -anchor nw -text " sende Chipadresse $a"
adresse $a
s A
}

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

init
stop

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


proc sendData {byte} {
 set byte [string range $byte end-1 end]
 puts "byte: $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 78 $part1 $part2 $part3 $part4
}

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 78 69 65 21 17 

foreach letter [split "Tcl/Tk & Arduino" ""] {
  puts "Sending letter: $letter ==="
        binary scan $letter H2 valor
  sendData $valor
}


if 0 {
start
chipadresse 78
sende 4
sende 0
sende 20
sende 16
stop

start
chipadresse 78
sende 69
sende 65
sende 21
sende 17
stop
}
$bd close