Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved I2C read API to reduce memory allocation and copying. #32

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions doc/getting-started-with-i2c.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ Getting Started With I2C
Warning:
-------

[Revision 2.0](http://www.raspberrypi.org/archives/1929) of the Raspberry Pi swaps the connections to I2C buses 0 and 1.
[Revision 2.0](http://www.raspberrypi.org/archives/1929) of the
Raspberry Pi swaps the connections to I2C buses 0 and 1.

With a revision 2.0 board, if you connect an I2C device to the appropriate header,
you will see it when you run `i2cdetect 1` instead of `i2cdetect 0`.
With a revision 2.0 board, if you connect an I2C device to the
appropriate header, you will see it when you run `i2cdetect 1` instead
of `i2cdetect 0`.

The library now auto-detects whether you are running version 1.0 or 2.0 of the board, so the same code will work on
either.
The library now auto-detects whether you are running version 1.0 or
2.0 of the board, so the same code will work on either.

The example:
------------
Expand Down Expand Up @@ -99,22 +101,23 @@ write to and the value of the register.
Then we'll read the value of the chip's GPIO register by performing a
transaction containing two operations: a write operation that tells
the chip which register we want to read, and a read operation that
reads a single byte from that register.
reads a single byte from that register. To issue a read operation we
must allocate a buffer (of type bytearray) for the data to be read
into and use that buffer to create a i2c.reading_into operation. We
only want to read a single byte, so we'll create a bytearray of size
1.

buf = bytearray(1)

read_results = bus.transaction(
i2c.writing_bytes(address, gpio_register),
i2c.reading(address, 1))

The I2CMaster' transaction method returns a list of byte sequences, one
for each read operation performed. Each result is an array of bytes
read from the device. So the state of the GPIO pins is the first and
only byte of the first and only byte sequence returned.
i2c.reading_into(address, buf))

gpio_state = read_results[0][0]
After the transaction has completed, we print out the state of the
gpio_register, which has been read into the first and only byte in our
byte buffer.

We finally print that in hexadecimal:

print("%02x" % gpio_state)
print("%02x" % buf[0])

Putting it all together:

Expand All @@ -129,11 +132,11 @@ Putting it all together:
with i2c.I2CMaster() as bus:
bus.transaction(
i2c.writing_bytes(address, iodir_register, 0xFF))


buf = bytearray(1)

read_results = bus.transaction(
i2c.writing_bytes(address, gpio_register),
i2c.reading(address, 1))

gpio_state = read_results[0][0]
i2c.reading_into(address, buf))

print("%02x" % gpio_state)
print("%02x" % buf[0])
38 changes: 0 additions & 38 deletions examples/i2c-counter

This file was deleted.

16 changes: 8 additions & 8 deletions examples/i2c-example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

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

address = 0x20
Expand All @@ -10,14 +10,14 @@ gpio_register = 0x09
with I2CMaster() as master:
master.transaction(
writing_bytes(address, iodir_register, 0xFF))

while True:
read_results = master.transaction(

buf = bytearray(1)

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

gpio_state = read_results[0][0]
reading_into(address, buf))

print("%02x" % gpio_state)
print("%02x" % buf[0])

time.sleep(1)
16 changes: 9 additions & 7 deletions examples/i2c-multibyte-read
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ with i2c.I2CMaster() as bus:
i2c.writing_bytes(address, iopol_register, 0b10101010))

# Read a single byte (the GPIO register)
gpio_state = bus.transaction(
gpio_buf = bytearray(1)
bus.transaction(
i2c.writing_bytes(address, gpio_register),
i2c.reading(address, 1))[0][0]
i2c.reading_into(address, gpio_buf))

# Read two bytes, the IODIR register and, thanks to sequential
# addressing mode, the next register, which is IOPOL
iodir_state, iopol_state = bus.transaction(
iodir_iopol_buf = bytearray(2)
bus.transaction(
i2c.writing_bytes(address, iodir_register),
i2c.reading(address, 2))[0]
i2c.reading_into(address, iodir_iopol_buf))

print("GPIO: ", bin(gpio_state)[2:])
print("IODIR:", bin(iodir_state)[2:])
print("IOPOL:", bin(iopol_state)[2:])
print("GPIO: ", bin(gpio_buf[0])[2:])
print("IODIR:", bin(iodir_iopol_buf[0])[2:])
print("IOPOL:", bin(iodir_iopol_buf[1])[2:])
22 changes: 10 additions & 12 deletions quick2wire/i2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import posix
from fcntl import ioctl
from quick2wire.i2c_ctypes import *
from ctypes import create_string_buffer, sizeof, c_int, byref, pointer, addressof, string_at
from ctypes import create_string_buffer, sizeof, c_int, byref, string_at
from quick2wire.board_revision import revision

assert sys.version_info.major >= 3, __name__ + " is only supported on Python 3"


default_bus = 1 if revision() > 1 else 0


class I2CMaster(object):
"""Performs I2C I/O transactions on an I2C bus.

Expand Down Expand Up @@ -76,18 +76,13 @@ def transaction(self, *msgs):
ioctl_arg = i2c_rdwr_ioctl_data(msgs=msg_array, nmsgs=msg_count)

ioctl(self.fd, I2C_RDWR, ioctl_arg)

return [i2c_msg_to_bytes(m) for m in msgs if (m.flags & I2C_M_RD)]



def reading(addr, n_bytes):
"""An I2C I/O message that reads n_bytes bytes of data"""
return reading_into(addr, create_string_buffer(n_bytes))

def reading_into(addr, buf):
"""An I2C I/O message that reads into an existing ctypes string buffer."""
return _new_i2c_msg(addr, I2C_M_RD, buf)
"""An I2C I/O message that reads into a mutable byte buffer, such as a bytearray or a ctypes struct."""
ptr = (c_char*len(buf)).from_buffer(buf)
return _new_i2c_msg(addr, I2C_M_RD, ptr)

def writing_bytes(addr, *bytes):
"""An I2C I/O message that writes one or more bytes of data.
Expand All @@ -101,8 +96,11 @@ def writing(addr, byte_seq):

The bytes are passed to this function as a sequence.
"""
buf = bytes(byte_seq)
return _new_i2c_msg(addr, 0, create_string_buffer(buf, len(buf)))
buf = bytearray(byte_seq)
ptr = (c_char*len(buf)).from_buffer(buf)
return _new_i2c_msg(addr, 0, ptr)

writing_from = writing


def _new_i2c_msg(addr, flags, buf):
Expand Down
8 changes: 5 additions & 3 deletions quick2wire/parts/mcp23017.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
interface for the MCP23017 I2C GPIO expander.
"""

from quick2wire.i2c import writing_bytes, reading
from quick2wire.i2c import writing_bytes, reading_into
import quick2wire.parts.mcp23x17 as mcp23x17
from quick2wire.parts.mcp23x17 import deferred_read, immediate_read, deferred_write, immediate_write, In, Out

Expand Down Expand Up @@ -56,8 +56,10 @@ def read_register(self, register_id):

Returns: the value of the register.
"""
return self.master.transaction(
buf = bytearray(1)
self.master.transaction(
writing_bytes(self.address, register_id),
reading(self.address, 1))[0][0]
reading_into(self.address, buf))
return buf[0]


9 changes: 5 additions & 4 deletions quick2wire/parts/pcf8591.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

"""

from quick2wire.i2c import writing_bytes, reading
from quick2wire.i2c import writing_bytes, reading_into
from quick2wire.gpio import Out, In

BASE_ADDRESS = 0x48
Expand Down Expand Up @@ -191,14 +191,15 @@ def read_differential(self, channel):
return (unsigned & 127) - (unsigned & 128)

def read_raw(self, channel):
buf = bytearray(2)
if channel != self._last_channel_read:
self.master.transaction(writing_bytes(self.address, self._control_flags|channel),
reading(self.address, 2))
reading_into(self.address, buf))
self._last_channel_read = channel

results = self.master.transaction(
reading(self.address, 2))
return results[0][-1]
reading_into(self.address, buf))
return buf[-1]


class _OutputChannel(object):
Expand Down
25 changes: 16 additions & 9 deletions quick2wire/parts/test_pcf8591.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

from itertools import repeat
from quick2wire.i2c_ctypes import I2C_M_RD
from quick2wire.gpio import In
from quick2wire.parts.pcf8591 import PCF8591, FOUR_SINGLE_ENDED, THREE_DIFFERENTIAL, SINGLE_ENDED_AND_DIFFERENTIAL, TWO_DIFFERENTIAL
Expand All @@ -24,15 +25,21 @@ def transaction(self, *messages):

self._requests.append(messages)

read_count = sum(bool(m.flags & I2C_M_RD) for m in messages)
if read_count == 0:
return []
elif self._next_response < len(self._responses):
response = self._responses[self._next_response]
self._next_response += 1
return response
else:
return [(0x00,)]*read_count
read_requests = [m for m in messages if bool(m.flags & I2C_M_RD)]

if len(read_requests) > 0:
if self._next_response < len(self._responses):
responses = self._responses[self._next_response]
self._next_response += 1
assert len(responses) == len(read_requests), \
"expected " + str(len(responses)) + " read requests, got " + str(len(read_requests))
else:
responses = repeat(repeat(0))

for request, response in zip(read_requests, responses):
for i, b in zip(range(request.len), response):
request.buf[i] = b


def add_response(self, *messages):
self._responses.append(messages)
Expand Down