The devices we’re building at Quick2Wire will connect to the Raspberry Pi over its I2C bus. Linux lets user-level programs control I2C devices via the device file /dev/i2c-0. However, the I2C bus protocol doesn’t fit well to the Linux file-like device model because the protocol is transactional. The Linux API to perform I2C transactions is awkward to use from Python so we are writing APIs for communicating with our devices, and any other device that connects to the Pi by I2C. You can download the API from our GitHub repository.

I2C Transactions

An I2C master – the Raspberry Pi in our case – communicates in transactions. In each transaction it takes control of the bus, performs a number of reads and writes, and then relinquishes control of the bus allowing other masters to use it. Reading or writing to /dev/i2c-0 will perform a transaction containing only a single read or write. This is fine for some simple devices but most I2C devices have more complex control protocols. For example, to read the register contents from an ITG3200 gyro, the I2C master must write the id of a register to the device and then read one or more bytes, all in one transaction.

To perform multiple I2C reads and/or writes in a single transaction you can’t use the read and write system calls. You must use ioctl.

You Low-Down, Dirty System Call You

The ioctl syscall (short for input/output control) is used to control I/O devices that do not fit into the Linux file I/O abstraction. It’s a very low-level “get out of jail free” card for the Linux API. Being so low-level, ioctl is not easy to use from a high-level language like Python.

The ioctl call can be passed any type of C data structure. The correct type depends on the device being controlled and the operation to be performed. In Python, this means constructing a C data structure with the ctypes module and then passing the numerical address of that structure to the ioctl function. It’s really fiddly to get right and if when you get it wrong you can crash your program or lock up your I2C hardware. Or both.

The Quick2Wire I2C API

To make it easier to use our I2C devices from Python we are writing high-level APIs that take care of the ioctl calls and provide a convenient, “pythonic” programming interface.

The quick2wire.i2c module will make it easy to perform I2C transactions, as shown below:

import quick2wire.i2c as i2c
from quick2wire.i2c import I2CMaster, writing_bytes, reading
import time

address = 0x20
iodir_register = 0x00
gpio_register = 0x09

with I2CMaster() as master:
    master.transaction(
        writing_bytes(address, iodir_register, 0xFF))

    while True:
        read_results = master.transaction(
            writing_bytes(address, gpio_register),
            reading(address, 1))

        gpio_state = read_results[0][0]

        print("%02x" % gpio_state)

        time.sleep(1)

This program creates an I2CBus object to communicate with an MCP23008 port expander chip at address 0x20. It first puts all the pins into input mode by writing to the chip’s IODIR register. It then repeatedly reads the level of the chip’s pins as a single byte from its GPIO register and prints the result. The I2CBus.transaction method returns a list of byte buffers, one for each read operation performed in the transaction. In this case, we’ve only performed one read and only read one byte, so the GPIO state is the first and only byte of the first and only byte buffer returned from the transaction.

Our I2C bus API is included in our Quick2Wire Python API, which you can download from our GitHub repository.

Device Specific APIs

Now we have released our I2C bus API, we are writing classes that make it easy to control specific I2C devices. We’re working on a class to control the MCP23008 chip used in the example above. The following program controls 8 LEDs with an MCP23008 using this API instead of issuing I2C transactions directly. As you can see, although still a work in progress, the chip-specific API makes for simpler code.

import quick2wire.i2c as i2c
from quick2wire.mcp23008 import MCP23008
import time

with i2c.I2CMaster() as master:
    ports = MCP23008(master)

    ports.set_io_direction(0x00)

    n = 0
    while True:
        ports.set_gpio(n)
        n = (n + 1) % 256
        time.sleep(0.25)

Where it makes sense, our device abstractions have a common programming interface so that it is easy to modify a hardware design without having to rewrite a lot of the control software. For instance, in the example above we are setting the state of all the GPIO pins of the MCP23008 port expander in a single write for efficiency. But we can also treat an MCP23008 object as an array of pins that have the same programming interface as our GPIO Pin class if it makes sense to do so.

Concurrency

Unfortunately the I2C ioctl is a blocking call: the operating system pauses the program until the transaction has finished. This is a problem if you need to react to events from devices on different connectors, service network connections, or use I2C in a graphical application. While performing I2C transactions, the program won’t be able to react to those other events. A network server will be unable to serve many clients and interactive apps will feel unresponsive & jittery.

The solution is for the program to start multiple processes or multiple threads of control in a single process, so that while one thread is blocked waiting for an I2C transaction to complete, others can process other events.

Multiprocessing and multithreading introduces a lot of complexity of its own. But we’ve ideas about how to address that, which we’ll write about in a future article.

About the author: Nat Pryce

15 Comments

  1. Thanks! Been waiting for this, to take out the pain of using the ioctl..looking forward to your updates.

    Reply

  2. Do you have any example code for reading all of the byte buffers which are returned on each transaction ? I am trying to get a MCP9800 temperature sensor to read which returns 2 bytes via i2c

    Reply

  3. Michel Lavoie

    Just wanted to say thanks for the awesome work! I was planning on developing my custom I2C module but yours already fills my needs, so thanks for sharing!

    Reply

  4. This library is great, now I won’t have to rig something up for i2c. As of this post, the API seems to have changed so the example given doesn’t work. I had to change “i2c.I2CBus” to “i2c.I2CMaster” and all calls to “write_bytes” to “writing_bytes”.

    Reply

  5. Hi,

    Presumably this API would work with the mcp23017 as supplied with this kit

    http://www.hobbytronics.co.uk/raspberry-pi/mcp23017-port-expander-board

    What I don’t understand is how it address the 2 8bit ports, the examples you have given seem to address a single 8bit port only.

    Sorry if this is a stupid question.

    Andy

    Reply

    • It’s not a stupid question.

      The example relates to the MCP23017′s baby brother, the MCP23008, which has only one 8-bit port.

      We’re currently developing a library for the MCP23017, which is a more powerful chip with a more complex API. There’s some code up on github already but this is test code which is not designed as a tutorial example. We’ll be working on the library over this coming week.

      In the meantime, here’s a brief explanation of how to use the MCP23017.

      The I2C code needs to read and write values using the appropriate registers on the MCP23017. These are described in the manufacturer’s data sheet ( http://ww1.microchip.com/downloads/en/devicedoc/21952b.pdf ) but that’s not the easiest document to understand.

      Let’s assume you want to write a byte of data (0xAA, for example) on Pins 0-7 of the GPIOB pins on the chip. You’d need to

      1. Set the I/O direction on Port B to output, by writing zero to the register that controls the direction on port A. That register is called IODIRB, and it is referred to in the I2C code as register 0×01.
      2. Write the byte you want to by writing 0xAA to the GPIOB register (register 0×13).

      Here’s the code:

      
      import quick2wire.i2c as i2c
      
      iodir_register_b = 0x01 # IODIRB
      gpio_register_b = 0x13 # GPIOB
      
      address = 0x20 # base I2C address for MCP23017
      
      with i2c.I2CMaster(1) as bus:
          bus.transaction(
              i2c.writing_bytes(address, iodir_register_b, 0x00)) # all PORT B pins are now outputs
          bus.transaction(
              i2c.writing_bytes(address, gpio_register_b, 0xAA)) # PORT B now set to output hex AA
      

      Reply

  6. how is the status off the mcp23017 libary ?

    Reply

    • The quick2wire-python-api project on GitHub includes support for the MCP23017 in the quick2wire.parts.mcp23017 module. There are examples of its use in the examples/ subdirectory and the module itself is documented with docstrings.

      Reply

  7. thanks completely spitted the repository except the parts part :$

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>