Skip to content

Commit 03ee42f

Browse files
committed
Add docs and fix i2c_bit.
1 parent 2812898 commit 03ee42f

File tree

7 files changed

+485
-28
lines changed

7 files changed

+485
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
*.egg-info/
33
dist
44
*.pyc
5+
_build/

README.md

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,113 @@
1-
# Adafruit_MicroPython_Register
2-
Python data descriptor classes to represent hardware registers on I2C and SPI devices.
1+
# Adafruit's Register library
2+
This library provides a variety of data descriptor class for Adafruit's
3+
MicroPython that makes it really simple to write a device driver for an I2C or
4+
SPI register based device. Data descriptors act like basic attributes from the
5+
outside which makes using them really easy.
6+
7+
## Creating a driver
8+
Creating a driver with the register library is really easy. First, import the register modules you need from the [available modules](adafruit_register/index.html):
9+
10+
from adafruit_register import i2c_bit
11+
12+
Next, define where the bit is located in the device's memory map:
13+
14+
class HelloWorldDevice:
15+
"""Device with two bits to control when the words 'hello' and 'world' are lit."""
16+
17+
hello = i2c_bit.RWBit(0x0, 0x0)
18+
"""Bit to indicate if hello is lit."""
19+
20+
world = i2c_bit.RWBit(0x1, 0x0)
21+
"""Bit to indicate if world is lit."""
22+
23+
Lastly, we need to add two instance members `i2c` and `device_address` so that
24+
the register classes know how to talk to the device. Make sure their names are
25+
exact, otherwise the registers will not be able to find them. Also, make sure
26+
that the i2c device implements the `nativeio.I2C` interface.
27+
28+
def __init__(self, i2c, device_address=0x0):
29+
self.i2c = i2c
30+
self.device_address = device_address
31+
32+
Thats it! Now we have a class we can use to talk to those registers:
33+
34+
import nativeio
35+
from board import *
36+
37+
with nativeio.I2C(SCL, SDA) as i2c:
38+
device = HelloWorldDevice(i2c)
39+
device.hello = True
40+
device.world = True
41+
42+
## Adding register types
43+
Adding a new register type is a little more complicated because you need to be careful and minimize the amount of memory the class will take. If you don't, then a driver with five registers of your type could take up five times more extra memory.
44+
45+
First, determine whether the new register class should go in an existing module or not. When in doubt choose a new module. The more finer grained the modules are, the fewer extra classes a driver needs to load in.
46+
47+
Here is the start of the `RWBit` class:
48+
49+
class RWBit:
50+
"""
51+
Single bit register that is readable and writeable.
52+
53+
Values are `bool`
54+
55+
:param int register_address: The register address to read the bit from
56+
:param type bit: The bit index within the byte at ``register_address``
57+
"""
58+
def __init__(self, register_address, bit):
59+
self.bit_mask = 1 << bit
60+
self.buffer = bytearray(2)
61+
self.buffer[0] = register_address
62+
63+
The first thing done is writing an RST formatted class comment that explains the
64+
functionality of the register class and any requirements of the register layout.
65+
It also documents the parameters passed into the constructor (`__init__`) which
66+
configure the register location in the device map. It does not include the
67+
device address or the i2c object because its shared on the device class instance
68+
instead. That way if you have multiple of the same device on the same bus, the
69+
register classes will be shared.
70+
71+
In `__init__` we only use two member variable because each costs 8 bytes of
72+
memory plus the memory for the value. And remember this gets multiplied by the
73+
number of registers of this type in a device! Thats why we pack both the
74+
register address and data byte into one bytearray. We could use two byte arrays
75+
of size one but each MicroPython object is 16 bytes minimum due to the garbage
76+
collector. So, by sharing a byte array we keep it to the 16 byte minimum instead
77+
of 32 bytes. `memoryview`s also cost 16 bytes minimum so we avoid them too.
78+
79+
Another thing we could do is allocate the `bytearray` only when we need it. This
80+
has the advantage of taking less memory up front but the cost of allocating it
81+
every access and risking it failing. If you want to add a version of `Foo` that
82+
lazily allocates the underlying buffer call it `FooLazy`.
83+
84+
Ok, onward. To make a [data descriptor](https://docs.python.org/3/howto/descriptor.html)
85+
we must implement `__get__` and `__set__`.
86+
87+
def __get__(self, obj, objtype=None):
88+
obj.i2c.writeto(obj.device_address, self.buffer, end=1, stop=False)
89+
obj.i2c.readfrom_into(obj.device_address, self.buffer, start=1)
90+
return bool(self.buffer[1] & self.bit_mask)
91+
92+
def __set__(self, obj, value):
93+
obj.i2c.writeto(obj.device_address, self.buffer, end=1, stop=False)
94+
obj.i2c.readfrom_into(obj.device_address, self.buffer, start=1)
95+
if value:
96+
self.buffer[1] |= self.bit_mask
97+
else:
98+
self.buffer[1] &= ~self.bit_mask
99+
obj.i2c.writeto(obj.device_address, self.buffer)
100+
101+
As you can see, we have two places to get state from. First, `self` stores the
102+
register class members which locate the register within the device memory map.
103+
Second, `obj` is the device class that uses the register class which must by
104+
definition provide a `nativeio.I2C` compatible object as ``i2c`` and the 7-bit
105+
device address as ``device_address``.
106+
107+
Note that we take heavy advantage of the ``start`` and ``end`` parameters to the
108+
i2c functions to slice the buffer without actually allocating anything extra.
109+
They function just like ``self.buffer[start:end]`` without the extra allocation.
110+
111+
Thats it! Now you can use your new register class like the example above. Just
112+
remember to keep the number of members to a minimum because the class may be
113+
used a bunch of times.

adafruit_register/i2c_bcd_datetime.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,24 @@
33
except:
44
import collections
55

6-
7-
##############################################################################
8-
# Globals and constants
9-
##############################################################################
10-
6+
# TODO(tannewt): Split out the datetime tuple stuff so it can be shared more widely.
117
DateTimeTuple = collections.namedtuple("DateTimeTuple", ["year", "month",
128
"day", "weekday", "hour", "minute", "second", "millisecond"])
139

14-
15-
##############################################################################
16-
# Functions
17-
##############################################################################
18-
1910
def datetime_tuple(year, month, day, weekday=0, hour=0, minute=0,
2011
second=0, millisecond=0):
21-
"""Return individual values converted into a data structure (a tuple).
22-
23-
Arguments:
24-
year - The year (four digits, required, no default).
25-
month - The month (two digits, required, no default).
26-
day - The day (two digits, required, no default).
27-
weekday - The day of the week (one digit, not required, default zero).
28-
hour - The hour (two digits, 24-hour format, not required, default zero).
29-
minute - The minute (two digits, not required, default zero).
30-
second - The second (two digits, not required, default zero).
31-
millisecond - Milliseconds (not supported, default zero).
12+
"""Converts individual values into a `DateTimeTuple` with defaults.
13+
14+
:param int year: The year
15+
:param int month: The month
16+
:param int day: The day
17+
:param int weekday: The day of the week (0-6)
18+
:param int hour: The hour
19+
:param int minute: The minute
20+
:param int second: The second
21+
:param int millisecond: not supported
22+
:return: The date and time
23+
:rtype: DateTimeTuple
3224
"""
3325
return DateTimeTuple(year, month, day, weekday, hour, minute,second,
3426
millisecond)
@@ -51,6 +43,15 @@ def _bin2bcd(value):
5143
return value + 6 * (value // 10)
5244

5345
class BCDDateTimeRegister:
46+
"""
47+
Date and time register using binary coded decimal structure.
48+
49+
The byte order of the register must be: second, minute, hour, weekday, day, month, year (in years after 2000).
50+
51+
Values are `DateTimeTuple`
52+
53+
:param int register_address: The register address to start the read
54+
"""
5455
def __init__(self, register_address):
5556
self.buffer = bytearray(8)
5657
self.buffer[0] = register_address
@@ -82,6 +83,15 @@ def __set__(self, obj, value):
8283
obj.i2c.writeto(obj.device_address, self.buffer)
8384

8485
class BCDAlarmTimeRegister:
86+
"""
87+
Date and time register using binary coded decimal structure.
88+
89+
The byte order of the register must be: minute, hour, day, weekday.
90+
91+
Values are `DateTimeTuple` with year, month and seconds ignored.
92+
93+
:param int register_address: The register address to start the read
94+
"""
8595
def __init__(self, register_address):
8696
self.buffer = bytearray(5)
8797
self.buffer[0] = register_address

adafruit_register/i2c_bit.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
class RWBit:
2+
"""
3+
Single bit register that is readable and writeable.
4+
5+
Values are `bool`
6+
7+
:param int register_address: The register address to read the bit from
8+
:param type bit: The bit index within the byte at ``register_address``
9+
"""
210
def __init__(self, register_address, bit):
311
self.bit_mask = 1 << bit
412
self.buffer = bytearray(2)
513
self.buffer[0] = register_address
614

715
def __get__(self, obj, objtype=None):
8-
obj.i2c.writeto(obj.device_address, self.buffer, length=1, stop=False)
9-
obj.i2c.readfrom_into(obj.device_address, self.buffer, offset=1)
10-
return bool(self.buffer[1] & mask)
16+
obj.i2c.writeto(obj.device_address, self.buffer, end=1, stop=False)
17+
obj.i2c.readfrom_into(obj.device_address, self.buffer, start=1)
18+
return bool(self.buffer[1] & self.bit_mask)
1119

1220
def __set__(self, obj, value):
13-
obj.i2c.writeto(obj.device_address, self.buffer, length=1, stop=False)
14-
obj.i2c.readfrom_into(obj.device_address, self.buffer, offset=1)
21+
obj.i2c.writeto(obj.device_address, self.buffer, end=1, stop=False)
22+
obj.i2c.readfrom_into(obj.device_address, self.buffer, start=1)
1523
if value:
1624
self.buffer[1] |= self.bit_mask
1725
else:
1826
self.buffer[1] &= ~self.bit_mask
1927
obj.i2c.writeto(obj.device_address, self.buffer)
2028

2129
class ROBit(RWBit):
30+
"""Single bit register that is read only. Subclass of `RWBit`.
31+
32+
Values are `bool`
33+
34+
:param int register_address: The register address to read the bit from
35+
:param type bit: The bit index within the byte at ``register_address``"""
2236
def __set__(self, obj, value):
2337
raise AttributeError()

adafruit_register/index.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Module Reference
2+
================
3+
4+
I2C
5+
---
6+
7+
`i2c_bit` - Single bit registers
8+
++++++++++++++++++++++++++++++++
9+
10+
.. automodule:: adafruit_register.i2c_bit
11+
:members:
12+
13+
`i2c_bcd_datetime` - Binary Coded Decimal date and time registers
14+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15+
16+
.. automodule:: adafruit_register.i2c_bcd_datetime
17+
:members:
18+
19+
SPI
20+
---
21+
Coming soon!

0 commit comments

Comments
 (0)