/ README.rst
README.rst
  1  
  2  Introduction
  3  ============
  4  
  5  .. image:: https://readthedocs.org/projects/adafruit-micropython-register/badge/?version=latest
  6      :target: https://circuitpython.readthedocs.io/projects/register/en/latest/
  7      :alt: Documentation Status
  8  
  9  .. image :: https://img.shields.io/discord/327254708534116352.svg
 10      :target: https://adafru.it/discord
 11      :alt: Discord
 12  
 13  .. image:: https://github.com/adafruit/Adafruit_CircuitPython_Register/workflows/Build%20CI/badge.svg
 14      :target: https://github.com/adafruit/Adafruit_CircuitPython_Register/actions/
 15      :alt: Build Status
 16  
 17  This library provides a variety of data descriptor class for `Adafruit
 18  CircuitPython <https://github.com/adafruit/circuitpython>`_ that makes it really
 19  simple to write a device drivers for a I2C and SPI register based devices. Data
 20  descriptors act like basic attributes from the outside which makes using them
 21  really easy to use.
 22  
 23  Dependencies
 24  =============
 25  This driver depends on:
 26  
 27  * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
 28  
 29  Please ensure all dependencies are available on the CircuitPython filesystem.
 30  This is easily achieved by downloading
 31  `the Adafruit library and driver bundle <https://github.com/adafruit/Adafruit_CircuitPython_Bundle>`_.
 32  
 33  Installing from PyPI
 34  ====================
 35  
 36  On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
 37  PyPI <https://pypi.org/project/adafruit-circuitpython-register/>`_. To install for current user:
 38  
 39  .. code-block:: shell
 40  
 41      pip3 install adafruit-circuitpython-register
 42      
 43  To install system-wide (this may be required in some cases):
 44  
 45  .. code-block:: shell
 46  
 47      sudo pip3 install adafruit-circuitpython-register
 48      
 49  To install in a virtual environment in your current project:
 50  
 51  .. code-block:: shell
 52  
 53      mkdir project-name && cd project-name
 54      python3 -m venv .env
 55      source .env/bin/activate
 56      pip3 install adafruit-circuitpython-register
 57      
 58  Usage Example
 59  =============
 60  
 61  Creating a driver
 62  -----------------
 63  
 64  Creating a driver with the register library is really easy. First, import the
 65  register modules you need from the `available modules <adafruit_register/index.html>`_:
 66  
 67  .. code-block:: python
 68  
 69      from adafruit_register import i2c_bit
 70      from adafruit_bus_device import i2c_device
 71  
 72  Next, define where the bit is located in the device's memory map:
 73  
 74  .. code-block:: python
 75  
 76      class HelloWorldDevice:
 77          """Device with two bits to control when the words 'hello' and 'world' are lit."""
 78  
 79          hello = i2c_bit.RWBit(0x0, 0x0)
 80          """Bit to indicate if hello is lit."""
 81  
 82          world = i2c_bit.RWBit(0x1, 0x0)
 83          """Bit to indicate if world is lit."""
 84  
 85  Lastly, we need to add an ``i2c_device`` member of type `I2CDevice <https://circuitpython.readthedocs.io/projects/busdevice/en/latest/api.html#adafruit_bus_device.i2c_device.I2CDevice>`_
 86  that manages sharing the I2C bus for us. Make sure the name is exact, otherwise
 87  the registers will not be able to find it. Also, make sure that the i2c device
 88  implements the `busio.I2C` interface.
 89  
 90  .. code-block:: python
 91  
 92          def __init__(self, i2c, device_address=0x0):
 93              self.i2c_device = i2c_device.I2CDevice(i2c, device_address)
 94  
 95  Thats it! Now we have a class we can use to talk to those registers:
 96  
 97  .. code-block:: python
 98  
 99      import busio
100      from board import *
101  
102      with busio.I2C(SCL, SDA) as i2c:
103          device = HelloWorldDevice(i2c)
104          device.hello = True
105          device.world = True
106  
107  Adding register types
108  --------------------------
109  
110  Adding a new register type is a little more complicated because you need to be
111  careful and minimize the amount of memory the class will take. If you don't,
112  then a driver with five registers of your type could take up five times more
113  extra memory.
114  
115  First, determine whether the new register class should go in an existing module
116  or not. When in doubt choose a new module. The more finer grained the modules
117  are, the fewer extra classes a driver needs to load in.
118  
119  Here is the start of the `RWBit` class:
120  
121  .. code-block:: python
122  
123      class RWBit:
124          """
125          Single bit register that is readable and writeable.
126  
127          Values are `bool`
128  
129          :param int register_address: The register address to read the bit from
130          :param type bit: The bit index within the byte at ``register_address``
131          """
132          def __init__(self, register_address, bit):
133              self.bit_mask = 1 << bit
134              self.buffer = bytearray(2)
135              self.buffer[0] = register_address
136  
137  The first thing done is writing an RST formatted class comment that explains the
138  functionality of the register class and any requirements of the register layout.
139  It also documents the parameters passed into the constructor (``__init__``) which
140  configure the register location in the device map. It does not include the
141  device address or the i2c object because its shared on the device class instance
142  instead. That way if you have multiple of the same device on the same bus, the
143  register classes will be shared.
144  
145  In ``__init__`` we only use two member variable because each costs 8 bytes of
146  memory plus the memory for the value. And remember this gets multiplied by the
147  number of registers of this type in a driver! Thats why we pack both the
148  register address and data byte into one bytearray. We could use two byte arrays
149  of size one but each MicroPython object is 16 bytes minimum due to the garbage
150  collector. So, by sharing a byte array we keep it to the 16 byte minimum instead
151  of 32 bytes. Each `memoryview` also costs 16 bytes minimum so we avoid them too.
152  
153  Another thing we could do is allocate the `bytearray` only when we need it. This
154  has the advantage of taking less memory up front but the cost of allocating it
155  every access and risking it failing. If you want to add a version of ``Foo`` that
156  lazily allocates the underlying buffer call it ``FooLazy``.
157  
158  Ok, onward. To make a `data descriptor <https://docs.python.org/3/howto/descriptor.html>`_
159  we must implement ``__get__`` and ``__set__``.
160  
161  .. code-block:: python
162  
163      def __get__(self, obj, objtype=None):
164          with obj.i2c_device as i2c:
165              i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
166          return bool(self.buffer[1] & self.bit_mask)
167  
168      def __set__(self, obj, value):
169          with obj.i2c_device as i2c:
170              i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
171              if value:
172                  self.buffer[1] |= self.bit_mask
173              else:
174                  self.buffer[1] &= ~self.bit_mask
175              obj.i2c_device.write(self.buffer)
176  
177  As you can see, we have two places to get state from. First, ``self`` stores the
178  register class members which locate the register within the device memory map.
179  Second, ``obj`` is the driver class that uses the register class which must by
180  definition provide a `I2CDevice <https://circuitpython.readthedocs.io/projects/busdevice/en/latest/api.html#adafruit_bus_device.i2c_device.I2CDevice>`_ compatible
181  object as ``i2c_device``. This object does two thing for us:
182  
183    1. Waits for the bus to free, locks it as we use it and frees it after.
184    2. Saves the device address and other settings so we don't have to.
185  
186  Note that we take heavy advantage of the ``start`` and ``end`` parameters to the
187  i2c functions to slice the buffer without actually allocating anything extra.
188  They function just like ``self.buffer[start:end]`` without the extra allocation.
189  
190  Thats it! Now you can use your new register class like the example above. Just
191  remember to keep the number of members to a minimum because the class may be
192  used a bunch of times.
193  
194  
195  Contributing
196  ============
197  
198  Contributions are welcome! Please read our `Code of Conduct
199  <https://github.com/adafruit/Adafruit_CircuitPython_Register/blob/master/CODE_OF_CONDUCT.md>`_
200  before contributing to help this project stay welcoming.
201  
202  Documentation
203  =============
204  
205  For information on building library documentation, please check out `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.