OpenWrt Forum Archive

Topic: I2C bus with only 2 GPIOs

The content of this topic has been archived on 21 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

I finally managed to get it I2C working! In contrast to the other mod this one just

    * uses 2 GPIOs of the router,
    * does not need any external circuit,
    * it works with the 2.4 kernel (for broadcom),
    * uses kamikaze 7.09.


Hardware
-----------

I2C is bi-directional and needs pull-up resistors on both lines (10k is recommended). As the router uses its GPIOs for LEDs they act as some uncontrolled pullup or down. As the router uses its GPIOs for LEDs they act as some uncontrolled pullup or down. To avoid any influence from the old components I disconnected the LEDs from those GPIOs (by removing the resistors, in the example below R58 and R60) and removed the switches/buttons (SW2 and the pullup/debouncing circuit C13 and R43).
http://www.ratnet.stw.uni-erlangen.de/~simischo/openwrt/removed_parts_sm.jpg

For I2C to work I just need 10 kOhm pull-ups for both lines. Here you can see some of my sensors (TMP100) and a 24C02 EEPROM that I took from an old SD-RAM module.

http://www.ratnet.stw.uni-erlangen.de/~simischo/openwrt/tmp100_sensors_sm.jpg http://www.ratnet.stw.uni-erlangen.de/~simischo/openwrt/tmp100_sm.jpg http://www.ratnet.stw.uni-erlangen.de/~simischo/openwrt/24c02_sm.jpg

My I2C devices are 3.3V compatible so I could just connect them directly (that makes 2 wires). The module below uses GPIOs 5 and 6 as a default (but there are module parameters if you want to change this).

And here you can find larger images: removed parts tmp100_sensors tmp100 24c02

(Last edited by schobi on 19 Jan 2008, 16:38)

Software
----------

As I'm a hardware guy I had some more trouble with the software. This is why the software part is more detailed.
We will need

    * the low-level kernel module i2c-mips-gpio
    * ready made i2c kernel modules from the standard kernel (i2c-core, i2c-dev, i2c-algo-bit)
    * the userspace i2c-tools from lm-sensors

The next steps will describe how to compile all that. For this I set up a vmware compile environment and checked out the most recent kamikaze 7.09 sources.

Software - i2c-mips-gpio
----------------------------

This is a kernel module that handles the lowest part of the I2C stack. It just turns on/off the line drivers and reads the line state. This file is based on many previous mods. For properly working with the i2c bus with just 2 wires I needed to make some adjustments.

To create a kernel module package I just created a new directory within my /package containing these files:

package/broadcom-i2c/Makefile

package/broadcom-i2c/src/Makefile

package/broadcom-i2c/src/i2c-mips-gpio.c


The default pinout uses GPIO 5 and 6 (and this can be changed in the source file). There is also a module option to supply different pins:

insmod i2c-mips-gpio gpio_scl=6 gpio_sda=7

For enabling this package you need to call

make menuconfig

and select the package from Kernel modules > I2C Bus > kmod-broadcom-i2c. If you just want to build the package and package index you can call:

package/broadcom-i2c-compile
package/broadcom-i2c-install
package/index

Your new kernel module package can be found in

bin/packages/kmod-broadcom-i2c_xxxxxxxxxxx.ipk

If you don't want to compile for yourself, you can just download my package:

kmod-broadcom-i2c_2.4.34+0.3-1_mipsel.ipk

Note on Controlling scl and sda
------------------------------------

Remember: both signal lines have a pullup (10 kOhm).

For setting a line to low, we need to actively drive the line to a low state. Any of the connected devices can do that as well! For getting a line to a high state we need to release the line and let it float. The pullup will then get it to a high state. From the protocol we must not drive it to high permanently.

I included a small tweak for improving the signal quality. When we release the line it will go to high (by the pull-up charging the cable capacity). I inserted a short pulse where I drive the line to hight - this improves the signal quality.

For reading a line we just need to read its state. There is no reason to explicitly switch to input. If we are by any chance driving it to low, then we will just read a low signal (but this will not happen from a higher level of the protocol interface).
If the line is released then it will go to high. As long as no client is pulling it to low, then we will be able to read the signal form the client.

(Last edited by schobi on 19 Jan 2008, 16:46)

Software - building i2c-core, i2c-dev, i2c-algo-bit
--------------------------------------------------------

These modules can be used from the standard kernel that is used from the openwrt build environment. For enabling them we need to get to the basic kernel configuration with

make kernel_menuconfig

After some waiting we need to go to Character Devices > I2C support and enable

<M> I2C support
<M> I2Cbit-banging interfaces
<M> I2C device interface
<M> I2C /proc interface

Then you can build all of that with

make menuconfig
make world

Your new kernel module packages can be found in

bin/packages/kmod-i2c-algos_xxxxxxxxxxx.ipk
bin/packages/kmod-i2c-core_xxxxxxxxxxx.ipk

If you don't want to compile for yourself, you can just download my packages:

kmod-i2c-algos_2.4.34-brcm-1_mipsel.ipk

kmod-i2c-core_2.4.34-brcm-1_mipsel.ipk


Note: there were problems with make world
--------------------------------------------------

In my case something broke during make world. Some script wanted to copy the newly generated i2c*.o module files from

build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/algos/*.o

to somewhere else. In my case these files were generated in

build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/i2c-algo-bit.o
build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/i2c-core.o
build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/i2c-dev.o
build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/i2c-proc.o

As a workaround I could just

mkdir build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/algos/
cp build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/*.o build_mipsel/linux-2.4-brcm/linux-2.4.34/drivers/i2c/algos/
make world

I guess someone should have a look at that problem!


Software - I2C-tools from lm-sensors
-------------------------------------------

For using our I2C bus we can use the official I2C tools package from lm-sensors. These tools are most useful:

    * i2cdetect: scans the bus and lists device adresses
    * i2cdump: scans a device and displays its data
    * i2cget: gets a sigle data byte/word from a device
    * i2cset: sets a value to a device

Again we need to build a package. Luckily there is a Makefile in the openwrt repository Makefile which needs to be placed in.

package/i2c-tools/Makefile

As the lm-sensors tools are valid for 2.4 and 2.6 kernel versions we need to edit this Makefile and remove the line

DEPENDS:=@LINUX_2_6

Then we can

make menuconfig

and select the package Utilities > I2C-tools. This package can be compiled with

package/i2c-tools-compile
package/i2c-tools-install
package/index

Your new kernel module package can be found in

bin/packages/i2c-tools_xxxxxxxxxxx.ipk

If you don't want to compile for yourself, you can just download my package:

i2c-tools_3.0.0-1_mipsel.ipk

(Last edited by schobi on 19 Jan 2008, 16:48)

Testing
--------

Now you can install and test these packages. First you have to point your /etc/ikg.conf to your repository. Then you can call:

ipkg update
ipkg install kmod-i2c-algos
ipkg install kmod-i2c-core
ipkg install kmod-broadcom-i2c
ipkg install i2c-tools

If everything went right, you should find your modules:

root@OpenWrt:~# lsmod
Module                  Size  Used by    Tainted: P
i2c-mips-gpio           1132   0
i2c-algo-bit            8860   1 [i2c-mips-gpio]
i2c-dev                 4252   0
i2c-core               16000   0 [i2c-algo-bit i2c-dev]
[...]

There is a special i2c-algo-bit testmode where you can find out if any of your lines is stuck. This can be done by

rmmod i2c-mips-gpio
rmmod i2c-algo-bit
insmod i2c-algo-bit bit_test=1
insmod i2c-mips-gpio

Your dmesg should show something like this. The scl and sda numbers may vary depending on your GPIOs:

i2c-algo-bit.o: i2c bit algorithm module
i2c-mpis-gpio.o: i2c WRT54G GPIO module version 2.6.1 (20010830)
i2c-algo-bit.o: Adapter: WRT54G GPIO scl: 32  sda: 64 -- testing...
i2c-algo-bit.o:1 scl: 32  sda: 0
i2c-algo-bit.o:2 scl: 32  sda: 64
i2c-algo-bit.o:3 scl: 0  sda: 64
i2c-algo-bit.o:4 scl: 32  sda: 64
i2c-algo-bit.o: WRT54G GPIO passed test.
i2c-dev.o: Registered 'WRT54G GPIO' as minor 0
i2c-core.o: adapter WRT54G GPIO registered as adapter 0.

For further testing you can use i2cdetect, i2cdump, i2cget and i2cset.

Sounds great.

Can you explain how reading the lines works? My experience is that reading does always return the level you have SET, no matter what a slave device tries to do, so setting the lines to 'input' was mandatory.

Have you actively tested your I2C with a device attached? Testing the bus without a slave device is not enough of course.

The I2C protocol relies on the pullup. It is built that master and slave can both drive the bus (to a low state). They will only read the bus when they released it (high). This needs to be done:
setting to low: actively drive it to low
setting to high: release the bus
reading: just reading the state (not changing to input!)

I tested with three different devices:
- a PCF8574 port expander
- a 24C02 EEPROM
- a few different TMP100 temperature sensors.

The longest cable I had was a 12 m cable. The temp sensor at the end works just fine.
My circuit has no active components.


As I forgot the pullups on one of my temperature sensors I tweaked the protocol a little:
setting to high: drive the bus to high, then immediately release it.
The 12m cable is even working without pullups, the broadcom chip seems to have a 150kOhm pullup included.

hmm, weird. IIRC the slave devices were incapable of pulling the bus low when it was set to high by the master.

which version of the wrt54 do you use?

have you considered writing a driver for the 2.6 kernels?

hmm... where's the link to your source files?

You must not set a line it to high from the master! The line needs to go high just from the pullup. Only then a device can pull it to low.

I have a buffalo WHR-G54S router. I can only use the 2.4 kernel right now and tomorrow I'll have to go back to work again

indeed, the actual files would be useful for this, I find myself in the position of writing a similar driver

Sorry for taking that long - I had to go back to my regular work...

I updated the above posts and included source code as well as precompiled packages.
Please let me know if those files work for you (or if they don't)!

Using i2c bitbanging with the 2.6 kernel should work just as described above. You might need to edit the broadcom-i2c/Makefile and remove the 2.4 dependency. I checked with the i2c kernel developer and all of that should work together! I could not test this as I just have one broadcom router.


Todo list for integrating into the repository:

- i2c-mips-gpio: test with 2.6 kernel
- i2c-mips-gpio: put it to the repository
- problem when building i2c-core, i2c-dev, i2c-algo-bit (see above), I have no clue where to seach or fix this
- in the repository there is a package/i2c-tools/Makefile, this also works with 2.4 (but the dependency=2.6kernel)

(Last edited by schobi on 19 Jan 2008, 17:09)

Hi,

Not 100% sure if this is the right place to post, but basically the module (both the one I built with 2.4.30 sources and the one provided) crashes on my router. I'm using kamakize 7.09, kernel 2.4.34 - and below is the copy of the dmesg. When I insmod the module, it always gives me a Segmentation Fault.

The router is a MN-700, but not sure what would make it segmentation fault. I discovered 4 more gpio ports on the MN-700, the diag util can toggle them on/off/tristate just fine. I also tried unloading the diag module and loading the i2c-mips-gpio - and still segmentation faulted.


i2c-algo-bit.o: i2c bit algorithm module
i2c-mpis-gpio.o: I2C GPIO module, SCL 5, SDA 6
Data bus error, epc == c0081250, ra == c0081240
Oops in traps.c::do_be, line 385:
$0 : 00000000 1000fc00 b8000060 00000006 00000005 b800006c 0000001f 8037b420
$8 : 00000000 00001773 00001773 00001741 801b0000 801b0000 801b0000 ffffffff
$16: c0080000 c0081000 c0080000 ffffffea 00000060 8071b4e0 806ff000 004fea90
$24: 00000002 00000000                   80718000 80719e70 0000000d c0081240
Hi : ffffeb42
Lo : 000006ea
epc   : c0081250    Tainted: P
Status: 1000fc03
Cause : 0000001c
PrId  : 00024000
Process insmod (pid: 441, stackpage=80718000)
Stack:    0000000d 00000005 00000006 80194b40 0000000d c0081000 004feef0
80014978 00000001 000001f2 000007df 80194b40 00000060 c01ab000 c0081060
000004dc 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 c01a5000 806ea000 00000000
004fea30 ...
Call Trace:   [<80014978>] [<c0081060>] [<80008a60>] [<8005b9ec>]

Code: 8c4514bc  8e0414ac  8e4314a8 <8ca20000> 24110001  00711804  00912004  00832027  304200ff

old driver for 4-wire logic here btw: http://forum.openwrt.org/viewtopic.php?id=7949

so, the only difference between our approach and yours is that you don't set the lines to INPUT. that's the interesting part. i stopped playing with the I2C bus when the univerity project i needed the bus for stopped, that's quite some time ago, but AFAIK it was not possible to read anything except the output you have set was long as the lines where set to output. maybe that's a difference between your router and the WRT54GS or maybe that's because you removed all possible pullups and pulldowns.

i hope, i will have time to try that out soon. smile

btw, i will send oyu an e-mail, you gave slightly wrong credit in your driver's header.

datenritter wrote:

btw, i will send you an e-mail, you gave slightly wrong credit in your driver's header.

Indeed.

My solution works with the recommended pullups. My solution is according to the bus protocol recommendations and works reliable even with long cables. I was lucky and it even works without pullups (but I would not count on that!).

As explained above: if you actively drive an I2C line, you will only be able to read back the value you are driving onto that line. This is perfectly fine with the I2C bus protocol. The protocol implementation knows this and will only read from a line when it is not actively driving it. The idea behind this is called "wired and/or" and this is why there should be pullups.

I got some inspiration from nekmech as well so that is why there are credits...


I'll try to get some more photos ready soon. I would love to spend more time on this but I'll have to earn some money first. In the meantime there is also some new stuff that needs documentation: I finally implemented a RTC, fixed the housing and added some scripts ... stay tuned!

schobi wrote:

My solution works with the recommended pullups.

hmm, ok, but... that's nothing new.

My solution is according to the bus protocol recommendations and works reliable even with long cables. I was lucky and it even works without pullups (but I would not count on that!).

wait, you're implying that the 4-wire-version does not meet the I2C specs? what is wrong about the pullup-resistors and the transistors to pull down the line?

concerning wire length: we have not made any tests, but i doubt there's much difference.

(by the way - the 4-wire-version is of course not desirable, but it's fancy: you don't have to remove the LEDs and get a nice i2c activity light. wink )

As explained above: if you actively drive an I2C line, you will only be able to read back the value you are driving onto that line.

i was talking about the outputs of the broadcom-chip. there are registers for... data, input/output-functionality, "control" and "enabled"... right? i don't remember what "enabled" was supposed to be, but looking at your driver code, i guess "enable" means the line is driven actively, is that right?

so, you put a line to "output", "disabled" and "0", the pullup raises the level to "1", and (if no slave device pulls the line down) that can be read from the registers without setting the line to input?

This is perfectly fine with the I2C bus protocol. The protocol implementation knows this and will only read from a line when it is not actively driving it.

so you have to know when to drive the line to 1 and when to only "let it float" to 1, right?

(because IIRC there are situations, when the slave MUST be able to pull down a line even though the master "wants" it to be high.)

i am really surprised at this this, because i remember writing myself a bit table with all possible register states and tried to pull a line down... and the line had to be set to input... hmm... then there was this "control" register... - i must have forgotten to remove some interferring parts. or maybe the old WRTs are just different from your router.

The idea behind this is called "wired and/or" and this is why there should be pullups.

hmm, do you have a link explaining this "wired and/or" thing? i would like to read about that but couldn't find anything with g**gle.

I got some inspiration from nekmech as well so that is why there are credits...

you wrote "nekmech aka datenritter", but we're two different persons. smile

looking at your driver... maybe i oversaw something, but it looks like a bit oldfashioned to me:

1. what are those static addresses doing there:
static volatile uint32 *gpioaddr_input = (uint32 *)0xb8000060; (...) ?
we removed the static addresses for a reason. the driver is supposed to make use of the sb_gpio interface.

2. you should also do some (software-)cleanup so the router can safely be rebooted. what if the user overrides the GPIO-lines and uses the reset-line as SDA or SCL? you have to make sure, reset is high on reboot, otherwise you will wipe the nvram. (also - what about older routers with an external ADMTEK switch..?)

hmm, i think it is better to change the driver posted in http://forum.openwrt.org/viewtopic.php?id=7949 back to two-wire logic, instead of re-implementing the old mistakes.

ok, i just had a look into some documentation i wrote two years ago.

1. accessing registers like you do is not atomic. you have to use the MMIO-functions (sb_gpio interface).
2. during the first tests with john newbigin's driver ( http://www.byteclub.net/wiki/index.php?title=Wrt54g ), we saw an error when we set i2c-algo-bit's bit_test option. the reason was that it wanted to read a line after it had set it and expected to see the same state. old i2c-mips-gpio changed the line from active low to input, causing a very short positive edge (or spike):

static int bit_gpio_get(int mask) {
        unsigned char port_state;
        port_state = *gpioaddr_enable; /* Read current config */
        port_state &= ~mask; /* Set to input */
        *gpioaddr_enable = port_state;
 
        port_state = *gpioaddr_input;
 
        return port_state & mask;
}

well, i spent some time on comparing john's crazy supposedly non-atomic bit operations with your crazy supposedly non-atomic bit operations:

static int bit_gpio_get(int mask) {
        unsigned char port_state;
        port_state = *gpioaddr_input;
        return port_state & mask;
}

obviously this doesn't intefere with anything, it just expects the line to be an input line.

        // signal quality tweak: we drive it high for a short
        // time and then release it - this improves rise time
        port_state = *gpioaddr_output;
        port_state |= mask;
        *gpioaddr_output = port_state;

        port_state = *gpioaddr_enable;
        port_state &= ~mask;
        *gpioaddr_enable = port_state;

(this "tweak" doesn't sound like a good idea to me. we're talking about a modern broadcom chip dealing with a boring old 400kHz bus here, not about a TFT screen. even with 4MHz you don't need "tweaks" like that... unless... there are some evil line capacities you have to fight. anyway, how do you know, these operations are executed consecutively? the addresses might as well be written in one operation. so i don't see no "tweak" here.)

what i see is that you set all lines to input whenever they're set high. that's pretty neat, as it does not cause the edge mentioned above. smile

static void bit_gpio_clear(unsigned int mask) {
        unsigned int port_state;

    // set pin to output and drive it to low
        port_state = *gpioaddr_enable;
        port_state |= mask;
        *gpioaddr_enable = port_state;

        port_state = *gpioaddr_output;
        port_state &= ~mask;
        *gpioaddr_output = port_state;
}

correct me if i'm wrong - couldn't this on the other hand cause a positive spike? what if the line is pulled low by a slave and you set it to active low? if these operations ARE executed consecutively, there might be a short moment when the line is actively driven high. :-\

MODULE_DESCRIPTION("I2C-Bus adapter routines for GPIOs in openwrt");

hmm, the driver itself has nothing to do with openwrt actually. openwrt doesn't have GPIO lines. it's a linux driver for the WRT54's GPIO. call me pedantic, but i really wonder why you changed that.

I'm going with 5 wires here, this's off the topic but still applies because of my earlier post.

Diag program works just fine, but the i2c module that schobi wrote still segment-faults every time I load it.
I'm not sure what the i2c module is doing differently that causes it to segment-faults. And if the i2c-algo module isnt loaded, it just says that it cant find some dependencies, doesn't segment fault.

So either the segment fault is coming from the i2c modules or its from the way that the program is driving the lines.
I just wanted to bring it up because the MN-700 is very similiar if not same to the Asus router - which is more popular, so this segment fault may occur more often. Maybe its related to the issues that datenritter brought up.

My project - I have 4 PIC microcontrollers, each drives 6 LED's. The LED's are RGB, and the PIC's basically can "dim" and "brighten" them via PWM. They are connected via I2C bus, so that way I can address any of them and tell them which LED, which color, and the value (0-64). Unfortunately, they use a 5v power supply, while the router is 3.3v.

So what I'm doing is using a 5-wire connection from router to another microcontroller (1 way databus for now) and then the LED's and other I2C devices are controlled from that main microcontroller. I'm going to basically rewrite diag to send commands (4 bits, 1 clock) and use that.

I dont mind trying to fix the code so it'll work, but so far I've been having a hard time compiling the code using the buildroot. Plus, I doubt that I can do I2C directly from the gpio to the PIC without using transistors.. The whole idea is to have a picture frame with lights and so forth thats also wireless - i.e. can be part of security system.

fireether: maybe your version of openwrt does not fit together with the binary modules...? i always compiled OpenWrt myself when i played with I²C.

you say, you're missing 5V and nekmech wrote:

5V power was obtained from a 7805 regulator circuit (added on a separate circuit) which is not described here.

in http://wiki.openwrt.org/OpenWrtDocs/Cus … re/I2C_RTC

don't know about the other routers, but the WRT54 DOES have 5V as well as 3.3V and 1.8V, all stabilized. External voltage is 12V unstabilized. (WRT54s can work with far less than 12V, though.)

datenritter wrote:

fireether: maybe your version of openwrt does not fit together with the binary modules...? i always compiled OpenWrt myself when i played with I²C.

That's what I did. I also used the pre-compiled drivers (look above) - which are all the same version as the os on my router. Same problems. I2C loads fine, but always get segmentation fault with the gpio one. Even when I compile it together from source, same thing.

datenritter wrote:

you say, you're missing 5V and nekmech wrote:

5V power was obtained from a 7805 regulator circuit (added on a separate circuit) which is not described here.

in http://wiki.openwrt.org/OpenWrtDocs/Cus … re/I2C_RTC

don't know about the other routers, but the WRT54 DOES have 5V as well as 3.3V and 1.8V, all stabilized. External voltage is 12V unstabilized. (WRT54s can work with far less than 12V, though.)

The router I'm working with is the MN-700, which is similiar to an asus (forgot model) - just doesnt have USB and board layout is a bit different. As far as I can tell, there is no 5v power supply at all in it - its strictly 3.3v. I'm using a power regulator in my other circuit to obtain 5v from 12v.

datenritter wrote:

you say, you're missing 5V and nekmech wrote:

5V power was obtained from a 7805 regulator circuit (added on a separate circuit) which is not described here.

in http://wiki.openwrt.org/OpenWrtDocs/Cus … re/I2C_RTC

don't know about the other routers, but the WRT54 DOES have 5V as well as 3.3V and 1.8V, all stabilized. External voltage is 12V unstabilized. (WRT54s can work with far less than 12V, though.)

I decided to add a separate 5v regulator since I added additional hardware (and planned to add more) and didn't want to produce a load on the routers regulator.

The discussion might have continued from here.