Skip to main content

Python wrapper library around the common I2C controller register pattern.

Project description

# Python I2C Register [![Build Status](https://travis-ci.org/Noah-Huppert/py-i2c-register.svg?branch=master)](https://travis-ci.org/Noah-Huppert/py-i2c-register) [![Test Coverage](https://codeclimate.com/github/Noah-Huppert/py-i2c-register/badges/coverage.svg)](https://codeclimate.com/github/Noah-Huppert/py-i2c-register/coverage)
Python wrapper library around the common I2C controller register pattern.

I2C Register is a python library which aims to make communicating with registers on I2C devices dead simple. It is meant
to directly transfer the Register Definitions pages of a data sheet into your program.

# Table Of Contents
- [Quick Example](#quick-example)
- [Systems Overview](#systems-overview)
- [Creating a RegisterList](#creating-a-registerlist)
- [Defining Registers](#defining-registers)
- [Adding RegisterSegments](#adding-registersegments)
- [Reading from RegisterSegments](#reading-from-registersegments)
- [Writing to RegisterSegments](#writing-to-registersegments)
- [Writting Wrapper Classes](#writing-wrapper-classes)
- [Development](#development)
- [Running Tests](#running-tests)

# Quick Example
Take these control register definitions from a data sheet:
![Example Hardware Data Sheet Register Definitions page 1](/docs/img/example-register-defs-p1.png)
![Example Hardware Data Sheet Register Definitions page 2](/docs/img/example-register-defs-p2.png)

With the help of the I2C Register library they can easily be represented and manipulated.
```python
# Create RegisterList instance to hold registers, device's i2c address is 0x62
controls = RegisterList(0x62, i2c, {})

# Add a definition for an ACQ_COMMAND (Acquisition Command) register, address 0x00 with WRITE permissions
controls.add("ACQ_COMMAND", 0x00, Register.WRITE, {})\
.add("ACQ_COMMAND", 0, 7, [0] * 8) # Define the segment of bits to read with LSB index of 0 and MSB index of 7

# Add a definition for a STATUS register, address 0x01 with READ permissions
controls.add("STATUS", 0x01, Register.READ, {}) \
# Define various individual Register Segments which each signify different parts of the status
.add("PROC_ERROR_FLAG", 6, 6, [0]) \
.add("HEALTH_FLAG", 5, 5, [0]) \
.add("SECONDARY_RET_FLAG", 4, 4, [0]) \
.add("INVALID_SIGNAL_FLAG", 3, 3, [0]) \
.add("SIGNAL_OVERFLOW_FLAG", 2, 2, [0]) \
.add("REFERENCE_OVERFLOW_FLAG", 1, 1, [0]) \
.add("BUSY_FLAG", 0, 0, [0])

# Add a definition for a VELOCITY register, address 0x09 with READ permissions
controls.add("VELOCITY", 0x09, Register.READ, {})\
.add("VELOCITY", 0, 7, [0] * 8) # Define the segment of bits to read for velocity value with LSB index of 0 and MSB index of 7


# Super simple to read and write values
# Set ACQ_COMMAND Register bits to value of 0x04, then write to register
controls.set_bits_from_int("ACQ_COMMAND", "ACQ_COMMAND", 0x04, write_after=True)

# Read STATUS register for BUSY_FLAG value and convert to an integer
busy = controls.to_int("STATUS", "BUSY_FLAG", read_first=True)

# Read VELOCITY register and convert to two's compliment integer
velocity = controls.to_twos_comp_int("VELOCITY", "VELOCITY", read_first=True)
```

# Systems Overview
The main class this library provides is the `RegisterList` class. This class manages a list of
`Register` definitions. It also provides some useful helper methods to make performing certain common actions quick and
easy.

## Creating a RegisterList
To create a `RegisterList` import the `register_list.RegisterList` class. Then call the constructor giving it a I2C device
address, an [I2C Object](/docs/i2c-object.md), and any `Register`s you have already defined:
```python
from py_i2c_register.register_list import RegisterList
controls = RegisterList(0x62, i2c, {})
```
The provided I2C Device address will be used to contact the device which holds the registers over I2C. The [I2C Object](/docs/i2c-object.md)
depends on your platform, see the [documentation](/docs/i2c-object.md) for more information. In most cases you can provide
an empty `Register` map as well.

## Defining Registers
After you create a `RegisterList` you must define some `Register`s to control. A `Register` is defined by a name (for
easy programmatic access), an I2C address, and a string containing IO operation permissions. The `RegisterList` class
provides a useful `add(reg_name, reg_addr, reg_permissions, reg_segments)` method for adding `Register`s.
```python
from py_i2c_register.register import Register
controls.add("REGISTER_NAME", 0x00, Registers.READ + Register.WRITE, {})
```
This would define a `Register` with the name `REGISTER_NAME`, the address `0x00` and the permission to read and write to/from it.

## Adding RegisterSegments
To actually read or write to/from a `Register` you need to define at least one `RegisterSegment`. These describe how bits
read from registers map to sub values. This could be useful if a device for example: provides a health register and each
bit represents a different system's health. You define `RegisterSegment`s by giving a name (for easy programmatic access)
and the index of the segment's least and most significant bits. The previously mentioned `RegisterList.add()` method
returns the `Register` that it just created. You can then in turn use a similar helper method that `Register` provides
called `add(seg_name, lsb_i, msb_i, default_bits)`:
```python
controls.add("HEALTH", 0x00, Registers.READ, {})\
.add("LEFT_MOTOR_FLAG", 2, 2, [0])\
.add("RIGHT_MOTOR_FLAG", 1, 1, [0])\
.add("NETWORK_FLAG", 0, 0, [0])
```
This would define a `Register` named `HEALTH` at address `0x00` with read permissions. This `Register` would have 3
`RegisterSegment`s. These 3 register segments would look at bits 0, 1, and 2 for the status of the left and right motors as
well as some made up network module.

## Reading from RegisterSegments
The `RegisterList` provides some useful helper methods for reading `RegisterSegment`s as integer values. They are called
`to_int` and `to_twos_comp_int`. They both take the name of a `Register` and `RegisterSegment` to read. Optionally you can
pass a `read_first` value. When `True` these methods will read the `Register` off the I2C device before returning the
`RegisterSegment` value:
```python
network_status = controls.to_int("HEALTH", "NETWORK_FLAG", read_first=True)
velocity = controls.to_twos_comp_int("VELOCITY", "VELOCITY", read_first=True)
```
This would read the `NETWORK_FLAG` segment of the `HEALTH` register and the `VELOCITY` segment of the `VELOCITY` register.

Ontop of using `RegisterList`s helper methods one can access raw `RegisterSegment` values via the `RegisterSegment.bits`
array. This array contains the raw `0` or `1` values of the register. Just be sure to call `Register.read` before accessing
the `RegisterSegment.bits` array:
```python
controls.get("VELOCITY").read()
velocity_bits = controls.get("VELOCITY").get("VELOCITY").bits
```

## Writing to RegisterSegments
The `RegisterList` class provides the `set_bits` and `set_bits_from_int` helper methods. Similar to the reading helper
methods mentioned above `set_bits` and `set_bits_from_int` both also take a `Register` and `RegisterSegment` name as
their first two parameters. The third value of both functions is the value to set. In the case of the `set_bits` method
it is expected to be an array of bits to set. In the case of the `set_bits_from_int` method it is expected to be an integer
value to set. The `set_bits` and `set_bits_from_int` methods also offer an optional `write_after` flag. If `True` they will
write the value of the `Register` to the I2C device after the value has been set.
```python
controls.set_bits("ACQ_COMMAND", "ACQ_COMMAND", [0, 0, 0, 0, 0, 1, 0, 0], write_after=True)
controls.set_bits_from_int("ACQ_COMMAND", "ACQ_COMMAND", 0x04, write_after=True)
```
This would set the `ACQ_COMMAND` segment of the `ACQ_COMMAND` register to the value `0x04` using the `set_bits` and
`set_bits_from_int` methods.

# Writing Wrapper Classes
I2C Register's simple architecture lends itself well to being used in hardware wrapper classes. All one must do is
create a class with its own `RegisterList` instance. Then add `Register` and `RegisterSegment` definitions in the `__init__()`
method:
```python
from py_i2c_register.register_list import RegisterList
from py_i2c_register.register import Register

class LidarLiteV3():
# Register and Segment name constants
REG_ACQ_COMMAND = "ACQ_COMMAND"
SEG_ACQ_COMMAND = REG_ACQ_COMMAND

REG_STATUS = "STATUS"
SEG_PROC_ERROR_FLAG = "PROC_ERROR_FLAG"
SEG_HEALTH_FLAG = "HEALTH_FLAG"
SEG_SECONDARY_RET_FLAG = "SECONDARY_RET_FLAG"
SEG_INVALID_SIGNAL_FLAG = "INVALID_SIGNAL_FLAG"
SEG_SIGNAL_OVERFLOW_FLAG = "SIGNAL_OVERFLOW_FLAG"
SEG_REFERENCE_OVERFLOW_FLAG = "REFERENCE_OVERFLOW_FLAG"
SEG_BUSY_FLAG = "BUSY_FLAG"

REG_VELOCITY = "VELOCITY"
SEG_VELOCITY= REG_VELOCITY

REG_DISTANCE = "DISTANCE"
SEG_DISTANCE = REG_DISTANCE

def __init__(self):
# Create some device specific I2C Object
self.i2c = ...

# Configure control registers
self.controls = RegisterList(0x62, self.i2c, {})
self.controls.add(LightLiteV3.REG_ACQ_COMMAND, 0x00, Register.WRITE, {}) \
.add(LightLiteV3.SEG_ACQ_COMMAND, 0, 7, [0] * 8)

self.controls.add(LightLiteV3.REG_STATUS, 0x01, Register.READ, {}) \
.add(LightLiteV3.SEG_PROC_ERROR_FLAG, 6, 6, [0]) \
.add(LightLiteV3.SEG_HEALTH_FLAG, 5, 5, [0]) \
.add(LightLiteV3.SEG_SECONDARY_RET_FLAG, 4, 4, [0]) \
.add(LightLiteV3.SEG_INVALID_SIGNAL_FLAG, 3, 3, [0]) \
.add(LightLiteV3.SEG_SIGNAL_OVERFLOW_FLAG, 2, 2, [0]) \
.add(LightLiteV3.SEG_REFERENCE_OVERFLOW_FLAG, 1, 1, [0]) \
.add(LightLiteV3.SEG_BUSY_FLAG, 0, 0, [0])

self.controls.add(LightLiteV3.REG_VELOCITY, 0x09, Register.READ, {})\
.add(LightLiteV3.SEG_VELOCITY, 0, 7, [0] * 8)

self.controls.add(LightLiteV3.REG_DISTANCE, 0x8f, Register.READ, {})\
.add(LightLiteV3.SEG_DISTANCE, 0, 15, [0] * 16)

# Provide useful helper methods
def measure(self):
self.controls.set_bits_from_int(LidarLiteV3.REG_ACQ_COMMAND, LidarLiteV3.SEG_ACQ_COMMAND, 0x04, write_after=True)

def distance(self):
return self.controls.to_int(LidarLiteV3.REG_DISTANCE, LidarLiteV3.SEG_DISTANCE, read_first=True)

def velocity(self):
return self.controls.to_int(LidarLiteV3.REG_VELOCITY, LidarLiteV3.SEG_VELOCITY, read_first=True)

# Now using your hardware has never been easier
lidar = LidarLiteV3()

while True:
lidar.measure()
print("Car is going {} m/s when it was {} m away".format(lidar.velocity(), lidar.distance()))
```

# Development
The code for I2C Register is located in the `py_i2c_register` directory. Feel free to contribute by opening a pull
request. I try to test and document as much as I can.

Supported Python Versions: 2.7, 3.6

## Running Tests
To run tests a couple python packages are required. To install them you can run the `test-install` Make target:
```bash
make test-install
```

You can then run test by executing the `test` Make target:
```bash
make test
```

To see a more detailed HTML report you can run the `test-html` Make target. The results will then be saved to `htmlcov/index.html`.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py-i2c-register-0.0.1.tar.gz (13.5 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page