/ 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>`_.