From 24c6ce59141e6aa38286d1232527e3e6e9bd014c Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Wed, 22 May 2024 21:28:15 -0400 Subject: [PATCH 1/6] Add files via upload Folder containing register.py for consideration into micropython-lib --- micropython/register/register.py | 999 +++++++++++++++++++++++++++++++ 1 file changed, 999 insertions(+) create mode 100644 micropython/register/register.py diff --git a/micropython/register/register.py b/micropython/register/register.py new file mode 100644 index 000000000..e1b09794e --- /dev/null +++ b/micropython/register/register.py @@ -0,0 +1,999 @@ +""" + * Author(s): SquirtleSquadLeader + + * License: MIT + + * Purpose: + * The purpose of this module is to provide easy I2C Register + * access. It is inspired by the CircuitPython Register + * module maintained by Adafruit. + + * RORegBit - Single bit Read Only + * RWRegBit - Single bit Read/Write + + * RORegBits - Multi-bit Read Only + * RWRegBits - Multi-bit Read/Write + + * ROReg - Single/Multi Read Only + * RWReg - Single/Multi Read/Write + + + * Notes: + 1) Reference format strings below: + Format C Type Standard size + c char 1 + b signed char 1 + B unsigned char 1 + h short 2 + H unsigned short 2 + i integer 4 + I unsigned int 4 + l long 4 + L unsigned long 4 + q long long 8 + Q unsigned long long 8 + f float 4 + d double 8 + + +""" + +from machine import I2C +from struct import pack, unpack + +class RORegBit: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian='', fmt='B'): + """ + Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. + + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RORegBit object + :rtype: RORegBit() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RORegBit` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RORegBit + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RORegBit` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RORegBit(i2c, 68, 5, 1, 5) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # 0 or 1 + + Alternatively, a :class:`RORegBit` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RORegBit + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RORegBit(i2c_bus, 68, 5, 1, 5) + self._my_reg_2 = RORegBit(i2c_bus, 68, 6, 1, 5) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def get_my_reg2(self): + return self._my_reg_1.__get__() + + # invoke class object + device = FooDevice(i2c) + + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._bit_location = bit_location + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + del(i2c, dev_addr, reg_addr, num_bytes, bit_location, fmt) + + def __get__(self): + """ + :return: Returns the value of the bit located at :param bit_location: + :rtype: int() + """ + return __getbit(self) + +class RWRegBit: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian='', fmt='B'): + """ + Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RWRegBit object + :rtype: RWRegBit() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWRegBit` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWRegBit + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWRegBit` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWRegBit(i2c, 68, 5, 1, 5) + + 'my_reg' can now provide access to the :method:`__get__() and :method:`__set__(). + Using these methods will get/set the value of the bit found at :param bit_location:. + + .. code-block:: python + my_reg.__set__(1) + + print(my_reg.__get__()) # prints 1 + + Alternatively, a :class:`RWRegBit` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWRegBit + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg = RORegBit(i2c_bus, 68, 5, 1, 5) + + def get_my_reg(self): + return self._my_reg.__get__() + + def get_my_reg2(self, n): + return self._my_reg.__set__(n) + + # invoke class object + device = FooDevice(i2c) + + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._bit_location = bit_location + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._postmask = __calc_mask(bit_location, bit_location, num_bytes) + + del(i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) + + def __get__(self): + """ + :return: Returns the value of the bit located at :param bit_location: + :rtype: int() + """ + return __getbit(self) + + def __set__(self, setting): + """ + :return: Returns 'True' if operation successful + :rtype: bool() + """ + return __setbit(self, setting) + +class RORegBits: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian='', fmt='B'): + """ + Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield + :type lsb: int() + :param msb: Location of most significant bit within bitfield + :type msb: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RORegBits object + :rtype: RORegBits() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RORegBits` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RORegBits + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RORegBits` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RORegBits(i2c_bus, 68, 5, 1, 0, 2) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # Returns some value from 0b000 to 0b111 + + Alternatively, a :class:`RORegBits` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RORegBits + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RORegBits(i2c_bus, 68, 5, 1, 0, 2) + self._my_reg_2 = RORegBits(i2c_bus, 68, 6, 1, 3, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + @property + def my_reg2(self): + return self._my_reg_2.__get__() + + # invoke class object + device = FooDevice(i2c) + + n1 = device.get_my_reg() + n2 = device.my_reg2 + + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del(i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian, fmt) + + def __get__(self): + """ + :return: Returns the value of the bitfield located between :param lsb: and :param msb: + :rtype: int() + """ + return __getbits(self) + +class RWRegBits: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian='', fmt='B'): + """ + Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield + :type lsb: int() + :param msb: Location of most significant bit within bitfield + :type msb: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RWRegBits object + :rtype: RWRegBits() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWRegBits` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWRegBits + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWRegBits` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) + + 'my_reg' can now provide access to :method:`__get__() and :method:`__set__(). + + .. code-block:: python + + my_reg.__set__(0b110) # Returns some value from 0b000 to 0b111 + value = my_reg.__get__() # Returns 0b110, assuming nothing changes + + Alternatively, a :class:`RWRegBits` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWRegBits + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) + self._my_reg_2 = RWRegBits(i2c_bus, 68, 6, 1, 3, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def set_my_reg1(self, n): + return self._my_reg_1.__set__(n) + + @property + def my_reg2(self): + return self._my_reg_2.__get__() + + @my_reg2.setter + def my_reg2(self, n): + return self._my_reg_2.__set__(n) + + # invoke class object + device = FooDevice(i2c) + + device.set_my_reg(0b110) + print(device.get_my_reg()) # prints 6 + + device.my_reg2 = 0b110 + print(device.my_reg2) # prints 6 + + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del(i2c, dev_addr, reg_addr, num_bytes, lsb, msb, fmt, endian) + + def __get__(self): + """ + :return: Returns the value of the bitfield located between :param lsb: and :param msb: + :rtype: int() + """ + return __getbits(self) + + def __set__(self, setting): + """ + :return: True if successful + :rtype: bool() + """ + return __setbits(self, setting) + +class ROReg: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes=1, endian='', fmt='B'): + """ + Creates a :class:`ROReg` object which allows read only access to n number of sequential registers, + where n is specified by :param num_bytes:. + + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read. Defaults to 1. + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + + :return: An initialized ROReg object + :rtype: ROReg() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`ROReg` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import ROReg + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`ROReg` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = ROReg(i2c, 68, 5) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # some value between 0b0 and 0b1111_1111 + + Alternatively, a :class:`ROReg` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import ROReg + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = ROReg(i2c_bus, 68, 5) + self._my_reg_2 = ROReg(i2c_bus, 68, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + @property + def my_reg2(self): + return self._my_reg_1.__get__() + + # invoke class object + device = FooDevice(i2c) + + print(device.get_my_reg1()) + print(device.my_reg2) + + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._fmt = fmt + self._endian = endian + + __check_reg(self) + + del(i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + + def __get__(self): + """ + :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: + :rtype: tuple() + """ + return __getreg(self) + +class RWReg: + """ + Creates a :class:`RWReg` object which allows read and write access to n number of sequential registers, + where n is specified by :param num_bytes:. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read. Defaults to 1. + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + + :return: An initialized RWReg object + :rtype: RWReg() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWReg` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWReg + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWReg` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWReg(i2c, 68, 5) + + 'my_reg' can now provide access to the :method:`__get__() and __set__(). + + .. code-block:: python + my_reg.__set__(0b0) + value = my_reg.__get__() # 0b0 if nothing changed + + Alternatively, a :class:`RWReg` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWReg + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RWReg(i2c_bus, 68, 5) + self._my_reg_2 = RWReg(i2c_bus, 68, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def set_my_reg1(self, n): + return self._my_reg_1.__set__(n) + + @property + def my_reg2(self): + return self._my_reg_1.__get__() + + @my_reg2.setter + def my_reg2(self, n): + return self._my_reg_1.__set__(n) + + + # invoke class object + device = FooDevice(i2c) + + device.set_my_reg1(0b110) + print(device.get_my_reg1()) # prints 6, assuming nothing changed + + device.my_reg2 = 0b1111_0000 + print(device.my_reg2) # prints 240 + + """ + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, endian='', fmt='B'): + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._fmt = fmt + self._endian = endian + + __check_reg(self) + + del(i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + + def __get__(self): + """ + :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: + :rtype: tuple() + """ + return __getreg(self) + + def __set__(self, setting): + """ + :param setting: Value(s) to be written to register(s). Order must match :param fmt:. + :type setting: int(), bytes(), bytearray(), or list/tuple containing those values in order + :return: Returns True if operation successful + :rtype: tuple() + """ + return __setreg(self, setting) + +""" +* +* GLOBAL HELPER FUNCTIONS +* +* +""" +def __getbit(reg_object): + if isinstance(reg_object, (RORegBit, RWRegBit)): + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) + + # Unpack byte + value = unpack(reg_object._endian+reg_object._fmt, value)[0] + + # Perform shift followed by _AND_ operation to determine bit state + return (value >> reg_object._bit_location)&0b1 + + else: + raise TypeError("incorrect object type - must be RORegBit, RWRegBit") + +def __setbit(reg_object, setting): + if isinstance(reg_object, RWRegBit): + if setting in (0,1): + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) + + # Unpack byte + value = unpack(reg_object._endian+reg_object._fmt, value)[0] + + # Assemble byte + value = (value®_object._postmask) + (setting<>reg_object._lsb + + else: + raise TypeError("incorrect object type - must be RORegBits, RWRegBits") + +def __setbits(reg_object, setting): + if isinstance(reg_object, RWRegBits): + if isinstance(setting, int) and setting <= reg_object._mask: + + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) + + # Unpack bytes + value = unpack(reg_object._endian+reg_object._fmt, value)[0] + + # Assemble + value = (value®_object._postmask) + (setting<= 0: + if msb.__class__() == int() and msb >= 0: + if numbytes.__class__() == int() and numbytes >= 0: + + # Check for detectable errors + if msb>=lsb: + + # Single bit mask + if msb == lsb: + pre, post = 0b0, 0b0 + + # Calc post masking + for bit in range(msb+1, numbytes*8): + post = (post<<1) + 0b1 + + # Calc pre masking + for bit in range(0, lsb): + pre = (pre<<1) + 0b1 + + return pre, post + + # Multibit mask + else: + + # Values to return + pre, mask, post = 0b0, 0b0, 0b0 + + # Calc post masking + for bit in range(msb+1, numbytes*8): + post = (post<<1) + 0b1 + + # Calc bitfield masking + for bit in range(lsb, msb+1): + mask = (mask<<1) + 0b1 + + # No bits lower than 0 + if lsb == 0: + return 0b0, mask, post + + else: + for bit in range(0, lsb): + pre = (pre<<1) + 0b1 + + return pre, mask, post + else: + raise ValueError("msb must be greater than or equal to lsb") + else: + raise ValueError("numbytes must be of type int() and 0 or greater") + else: + raise ValueError("msb must be of type int() and 0 or greater") + else: + raise ValueError("lsb must be of type int() and 0 or greater") + +def __check_reg(reg_object): + + # Alowable struct.pack/unpack formats to check for + fmts = {'b':1, 'B':1, 'h':2, 'H':2, 'f':4, 'i':4, 'I':4, 'l':4, 'L':4, 'q':8, 'Q':8} + endians = '@><' + byte_count = 0 + + # Take in only register objects + if isinstance(reg_object, (RORegBit, RWRegBit, RORegBits, RWRegBits, ROReg, RWReg)): + + # Make sure they are strings + if type(reg_object._fmt) == str and type(reg_object._endian) == str: + + # Check each letter in format string, To see if allowable + for n in range(0, len(reg_object._fmt)): + if reg_object._fmt[n] in fmts: + # Add corresonding byte length to verify _num_bytes and format string agree + byte_count = byte_count + fmts[reg_object._fmt[n]] + + else: + raise ValueError(f"unsupported format code of '{reg_object._fmt[n]}'") + + if byte_count != reg_object._num_bytes: + raise ValueError(f"format string accounts for {byte_count} bytes, _num_bytes value of {reg_object._num_bytes} does not match") + + else: + raise TypeError("format and endian must be of type str()") + else: + raise TypeError("incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg") + +class Transaction: + """ + The user can supply a transaction object with a list of any number of + Register objects. The Transaction object will then perform one I2C + transaction and return all data as a list OR perform all write operations. + + 1) The Register objects should all be from one physical I2C device + 2) True = Read, False = Write (Default is read) + 3) Reads can be from non-sequential registers + 4) Writes can be made only to sequential registers OR more than one + transaction will be generated + + i.e. + + # Define Register objects + register1 = ROBits() + register2 = ROBits() + register3 = ROBits() + + # Create list object containing only Register objects + list_of_registers = [register1, register2, register3] + + # Instantiate Transaction object + data_from_device = Transaction(list_of_registers) + + # Retrieve data + data = data_from_device.__get__() + + # Use data as desired + datapoint_1 = data_from_device[0] + datapoint_2 = data_from_device[1] + datapoint_3 = data_from_device[2] + """ + + + def __init__(self, read_or_write:bool = True, list_of_registers:list() =[]): + # Data + self.__list_of_registers = list_of_registers + + # Check if it is a list + if self.__list_of_registers.__class__() == list(): + + for reg in self.__list_of_registers: + # Check each element against all possible Register types + if self.__list_of_registers[reg].__class__() in [RORegBit, RORegBits, RWRegBit, RWRegBits, RORegStruct]: + pass + + else: + # Error - list_element[reg] not a register object + pass + + else: + # Error - list_of_registers object must be list() + pass + + def add_reg(self, reg_object): + """ + This function allows for register objects to be added to an already + instantiated Transaction object + """ + if reg_object.__class__() in [RORegBit, RORegBits, RWRegBit, RWRegBits, RORegStruct]: + self.__list_of_registers.append(reg_object) + + self._order_list() + + else: + # Error - reg object of incorrect type() + pass + + def rem_reg(self, index): + """ + This function allows for a register object to be removed from an + already instantiated transaction object + """ + if index in range(0, len(self.__list_of_registers)): + # Remove element + self.__list_of_registers.remove(index) + else: + # Error - index out of bounds + pass + + def order_list(self): + """ + Sorts list of registers by register address + """ + self.__list_of_registers = sorted(self.__list_of_registers, key=lambda reg:reg.reg_addr) + + def data(self): + """ + Performs 1 i2c transaction and returns data as list + """ + + + + + + + + + + + From 9dcdd0606bc24aceda7c5ab2a05301362b256615 Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Wed, 22 May 2024 21:31:32 -0400 Subject: [PATCH 2/6] Create manifest.py Add manifest. --- micropython/register/manifest.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 micropython/register/manifest.py diff --git a/micropython/register/manifest.py b/micropython/register/manifest.py new file mode 100644 index 000000000..67ec04c5e --- /dev/null +++ b/micropython/register/manifest.py @@ -0,0 +1,7 @@ +metadata( + description="I2C register library for MicroPython.", + version="0.0.1", + pypi_publish="micropython-i2c_reg", +) + +require("struct") From ce4a579b6ea9554dea45d61e125f42fb58977e1d Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Wed, 22 May 2024 21:38:41 -0400 Subject: [PATCH 3/6] Update and rename register.py to i2c_register.py Edit prior to PR --- .../register/{register.py => i2c_register.py} | 114 +----------------- 1 file changed, 3 insertions(+), 111 deletions(-) rename micropython/register/{register.py => i2c_register.py} (90%) diff --git a/micropython/register/register.py b/micropython/register/i2c_register.py similarity index 90% rename from micropython/register/register.py rename to micropython/register/i2c_register.py index e1b09794e..6c32bd92a 100644 --- a/micropython/register/register.py +++ b/micropython/register/i2c_register.py @@ -34,8 +34,6 @@ Q unsigned long long 8 f float 4 d double 8 - - """ from machine import I2C @@ -44,8 +42,7 @@ class RORegBit: def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian='', fmt='B'): """ - Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. - + Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() @@ -780,8 +777,7 @@ def __setreg(reg_object, settings): if isinstance(reg_object, RWReg): if isinstance(settings, (bytes, bytearray)): # Write to device - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, settings) - + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, settings) elif isinstance(settings, (tuple, list)): # Where our data will go @@ -809,8 +805,7 @@ def __calc_mask(lsb, msb, numbytes): Takes in full description of bitfield that needs masking returns ints() pre, mask, post - """ - + """ # Check input types if lsb.__class__() == int() and lsb >= 0: if msb.__class__() == int() and msb >= 0: @@ -894,106 +889,3 @@ def __check_reg(reg_object): raise TypeError("format and endian must be of type str()") else: raise TypeError("incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg") - -class Transaction: - """ - The user can supply a transaction object with a list of any number of - Register objects. The Transaction object will then perform one I2C - transaction and return all data as a list OR perform all write operations. - - 1) The Register objects should all be from one physical I2C device - 2) True = Read, False = Write (Default is read) - 3) Reads can be from non-sequential registers - 4) Writes can be made only to sequential registers OR more than one - transaction will be generated - - i.e. - - # Define Register objects - register1 = ROBits() - register2 = ROBits() - register3 = ROBits() - - # Create list object containing only Register objects - list_of_registers = [register1, register2, register3] - - # Instantiate Transaction object - data_from_device = Transaction(list_of_registers) - - # Retrieve data - data = data_from_device.__get__() - - # Use data as desired - datapoint_1 = data_from_device[0] - datapoint_2 = data_from_device[1] - datapoint_3 = data_from_device[2] - """ - - - def __init__(self, read_or_write:bool = True, list_of_registers:list() =[]): - # Data - self.__list_of_registers = list_of_registers - - # Check if it is a list - if self.__list_of_registers.__class__() == list(): - - for reg in self.__list_of_registers: - # Check each element against all possible Register types - if self.__list_of_registers[reg].__class__() in [RORegBit, RORegBits, RWRegBit, RWRegBits, RORegStruct]: - pass - - else: - # Error - list_element[reg] not a register object - pass - - else: - # Error - list_of_registers object must be list() - pass - - def add_reg(self, reg_object): - """ - This function allows for register objects to be added to an already - instantiated Transaction object - """ - if reg_object.__class__() in [RORegBit, RORegBits, RWRegBit, RWRegBits, RORegStruct]: - self.__list_of_registers.append(reg_object) - - self._order_list() - - else: - # Error - reg object of incorrect type() - pass - - def rem_reg(self, index): - """ - This function allows for a register object to be removed from an - already instantiated transaction object - """ - if index in range(0, len(self.__list_of_registers)): - # Remove element - self.__list_of_registers.remove(index) - else: - # Error - index out of bounds - pass - - def order_list(self): - """ - Sorts list of registers by register address - """ - self.__list_of_registers = sorted(self.__list_of_registers, key=lambda reg:reg.reg_addr) - - def data(self): - """ - Performs 1 i2c transaction and returns data as list - """ - - - - - - - - - - - From 1f37df5588c13af1b976a4642fa99e64cd60e70c Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Wed, 22 May 2024 21:50:01 -0400 Subject: [PATCH 4/6] re-commit after black --- micropython/register/i2c_register.py | 820 +++++++++++++++------------ 1 file changed, 448 insertions(+), 372 deletions(-) diff --git a/micropython/register/i2c_register.py b/micropython/register/i2c_register.py index 6c32bd92a..e7de93a5d 100644 --- a/micropython/register/i2c_register.py +++ b/micropython/register/i2c_register.py @@ -34,85 +34,91 @@ Q unsigned long long 8 f float 4 d double 8 + + """ from machine import I2C from struct import pack, unpack + class RORegBit: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian='', fmt='B'): + def __init__( + self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B" + ): """ - Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. - + Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. + + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param bit_location: Location of bit within bitfield - :type bit_locatin: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + :return: An initialized RORegBit object :rtype: RORegBit() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`RORegBit` class. + Here is an example of using the :class:`RORegBit` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import RORegBit Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`RORegBit` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = RORegBit(i2c, 68, 5, 1, 5) - + 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. + will return the value of the bit found at :param bit_location:. .. code-block:: python - + value = my_reg.__get__() # 0 or 1 - + Alternatively, a :class:`RORegBit` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import RORegBit - - # define I2C - i2c = I2C(0) - + from register.register import RORegBit + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg_1 = RORegBit(i2c_bus, 68, 5, 1, 5) self._my_reg_2 = RORegBit(i2c_bus, 68, 6, 1, 5) - + def get_my_reg1(self): return self._my_reg_1.__get__() - + def get_my_reg2(self): - return self._my_reg_1.__get__() - - # invoke class object + return self._my_reg_1.__get__() + + # invoke class object device = FooDevice(i2c) """ @@ -123,92 +129,95 @@ def get_my_reg2(self): self._bit_location = bit_location self._endian = endian self._fmt = fmt - + __check_reg(self) - - del(i2c, dev_addr, reg_addr, num_bytes, bit_location, fmt) - + + del (i2c, dev_addr, reg_addr, num_bytes, bit_location, fmt) + def __get__(self): """ :return: Returns the value of the bit located at :param bit_location: :rtype: int() """ return __getbit(self) - + + class RWRegBit: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian='', fmt='B'): + def __init__( + self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B" + ): """ - Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. - + Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param bit_location: Location of bit within bitfield - :type bit_locatin: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + :return: An initialized RWRegBit object :rtype: RWRegBit() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`RWRegBit` class. + Here is an example of using the :class:`RWRegBit` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import RWRegBit Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`RWRegBit` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = RWRegBit(i2c, 68, 5, 1, 5) - + 'my_reg' can now provide access to the :method:`__get__() and :method:`__set__(). - Using these methods will get/set the value of the bit found at :param bit_location:. + Using these methods will get/set the value of the bit found at :param bit_location:. .. code-block:: python - my_reg.__set__(1) - + my_reg.__set__(1) + print(my_reg.__get__()) # prints 1 - + Alternatively, a :class:`RWRegBit` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import RWRegBit - - # define I2C - i2c = I2C(0) - + from register.register import RWRegBit + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg = RORegBit(i2c_bus, 68, 5, 1, 5) def get_my_reg(self): return self._my_reg.__get__() - + def get_my_reg2(self, n): return self._my_reg.__set__(n) - - # invoke class object + + # invoke class object device = FooDevice(i2c) """ @@ -219,106 +228,111 @@ def get_my_reg2(self, n): self._bit_location = bit_location self._endian = endian self._fmt = fmt - + __check_reg(self) - - self._premask, self._postmask = __calc_mask(bit_location, bit_location, num_bytes) - - del(i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) - + + self._premask, self._postmask = __calc_mask( + bit_location, bit_location, num_bytes + ) + + del (i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) + def __get__(self): """ :return: Returns the value of the bit located at :param bit_location: :rtype: int() """ return __getbit(self) - + def __set__(self, setting): """ - :return: Returns 'True' if operation successful + :return: Returns 'True' if operation successful :rtype: bool() """ return __setbit(self, setting) - + + class RORegBits: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian='', fmt='B'): + def __init__( + self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B" + ): """ - Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. - + Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param lsb: Location of least significant bit within bitfield + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield :type lsb: int() - :param msb: Location of most significant bit within bitfield + :param msb: Location of most significant bit within bitfield :type msb: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + :return: An initialized RORegBits object :rtype: RORegBits() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`RORegBits` class. + Here is an example of using the :class:`RORegBits` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import RORegBits Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`RORegBits` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = RORegBits(i2c_bus, 68, 5, 1, 0, 2) - + 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. + will return the value of the bit found at :param bit_location:. .. code-block:: python - + value = my_reg.__get__() # Returns some value from 0b000 to 0b111 - + Alternatively, a :class:`RORegBits` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import RORegBits - - # define I2C - i2c = I2C(0) - + from register.register import RORegBits + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg_1 = RORegBits(i2c_bus, 68, 5, 1, 0, 2) self._my_reg_2 = RORegBits(i2c_bus, 68, 6, 1, 3, 6) - + def get_my_reg1(self): return self._my_reg_1.__get__() - + @property def my_reg2(self): return self._my_reg_2.__get__() - - # invoke class object + + # invoke class object device = FooDevice(i2c) - + n1 = device.get_my_reg() n2 = device.my_reg2 @@ -329,109 +343,112 @@ def my_reg2(self): self._num_bytes = num_bytes self._endian = endian self._fmt = fmt - + __check_reg(self) - - self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) - - del(i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian, fmt) - + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian, fmt) + def __get__(self): """ :return: Returns the value of the bitfield located between :param lsb: and :param msb: :rtype: int() """ return __getbits(self) - + + class RWRegBits: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian='', fmt='B'): + def __init__( + self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B" + ): """ - Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. - + Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param lsb: Location of least significant bit within bitfield + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield :type lsb: int() - :param msb: Location of most significant bit within bitfield + :param msb: Location of most significant bit within bitfield :type msb: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + :return: An initialized RWRegBits object :rtype: RWRegBits() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`RWRegBits` class. + Here is an example of using the :class:`RWRegBits` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import RWRegBits Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`RWRegBits` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) - - 'my_reg' can now provide access to :method:`__get__() and :method:`__set__(). + + 'my_reg' can now provide access to :method:`__get__() and :method:`__set__(). .. code-block:: python - + my_reg.__set__(0b110) # Returns some value from 0b000 to 0b111 value = my_reg.__get__() # Returns 0b110, assuming nothing changes - + Alternatively, a :class:`RWRegBits` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import RWRegBits - - # define I2C - i2c = I2C(0) - + from register.register import RWRegBits + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg_1 = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) self._my_reg_2 = RWRegBits(i2c_bus, 68, 6, 1, 3, 6) - + def get_my_reg1(self): return self._my_reg_1.__get__() - + def set_my_reg1(self, n): return self._my_reg_1.__set__(n) - + @property def my_reg2(self): return self._my_reg_2.__get__() - + @my_reg2.setter def my_reg2(self, n): return self._my_reg_2.__set__(n) - - # invoke class object + + # invoke class object device = FooDevice(i2c) - + device.set_my_reg(0b110) print(device.get_my_reg()) # prints 6 - + device.my_reg2 = 0b110 print(device.my_reg2) # prints 6 @@ -442,102 +459,103 @@ def my_reg2(self, n): self._num_bytes = num_bytes self._endian = endian self._fmt = fmt - + __check_reg(self) - - self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) - - del(i2c, dev_addr, reg_addr, num_bytes, lsb, msb, fmt, endian) - + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, fmt, endian) + def __get__(self): """ :return: Returns the value of the bitfield located between :param lsb: and :param msb: :rtype: int() """ return __getbits(self) - + def __set__(self, setting): """ :return: True if successful :rtype: bool() """ - return __setbits(self, setting) - + return __setbits(self, setting) + + class ROReg: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes=1, endian='', fmt='B'): + def __init__(self, i2c, dev_addr, reg_addr, num_bytes=1, endian="", fmt="B"): """ Creates a :class:`ROReg` object which allows read only access to n number of sequential registers, - where n is specified by :param num_bytes:. - - + where n is specified by :param num_bytes:. + + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() :param num_bytes: Number of bytes to read. Defaults to 1. - :type num_bytes: int() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. - :type fmt: int() - + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + :return: An initialized ROReg object :rtype: ROReg() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`ROReg` class. + Here is an example of using the :class:`ROReg` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import ROReg Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`ROReg` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = ROReg(i2c, 68, 5) - + 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. + will return the value of the bit found at :param bit_location:. .. code-block:: python - + value = my_reg.__get__() # some value between 0b0 and 0b1111_1111 - + Alternatively, a :class:`ROReg` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import ROReg - - # define I2C - i2c = I2C(0) - + from register.register import ROReg + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg_1 = ROReg(i2c_bus, 68, 5) self._my_reg_2 = ROReg(i2c_bus, 68, 6) - + def get_my_reg1(self): return self._my_reg_1.__get__() - + @property def my_reg2(self): - return self._my_reg_1.__get__() - - # invoke class object + return self._my_reg_1.__get__() + + # invoke class object device = FooDevice(i2c) - + print(device.get_my_reg1()) print(device.my_reg2) @@ -548,309 +566,348 @@ def my_reg2(self): self._num_bytes = num_bytes self._fmt = fmt self._endian = endian - + __check_reg(self) - - del(i2c, dev_addr, reg_addr, num_bytes, fmt, endian) - + + del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + def __get__(self): """ :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: :rtype: tuple() """ return __getreg(self) - + + class RWReg: - """ + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, endian="", fmt="B"): + """ Creates a :class:`RWReg` object which allows read and write access to n number of sequential registers, - where n is specified by :param num_bytes:. - + where n is specified by :param num_bytes:. + :param i2c: I2C bus which connects the host system to the peripheral device :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() :param num_bytes: Number of bytes to read. Defaults to 1. - :type num_bytes: int() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. - :type fmt: int() - + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + :return: An initialized RWReg object :rtype: RWReg() - - + + **Quickstart: Importing and using the device** - Here is an example of using the :class:`RWReg` class. + Here is an example of using the :class:`RWReg` class. First you will need to import the following libraries: .. code-block:: python - + from machine import I2C from register.register import RWReg Once this is done you must define a :class:`machine.I2C` object and then pass that to the :class:`RWReg` object to instantiate it. - + .. code-block:: python i2c = I2C(0) # I2C details are project specific - + my_reg = RWReg(i2c, 68, 5) - - 'my_reg' can now provide access to the :method:`__get__() and __set__(). + + 'my_reg' can now provide access to the :method:`__get__() and __set__(). .. code-block:: python my_reg.__set__(0b0) value = my_reg.__get__() # 0b0 if nothing changed - + Alternatively, a :class:`RWReg` object(s) can be placed within another class. .. code-block:: python - # import + # import from machine import I2C - from register.register import RWReg - - # define I2C - i2c = I2C(0) - + from register.register import RWReg + + # define I2C + i2c = I2C(0) + # create class with desired functionality class FooDevice: - + def __init__(self, i2c_bus): self._my_reg_1 = RWReg(i2c_bus, 68, 5) self._my_reg_2 = RWReg(i2c_bus, 68, 6) - + def get_my_reg1(self): return self._my_reg_1.__get__() - + def set_my_reg1(self, n): return self._my_reg_1.__set__(n) - + @property def my_reg2(self): return self._my_reg_1.__get__() - + @my_reg2.setter def my_reg2(self, n): return self._my_reg_1.__set__(n) - - - # invoke class object + + + # invoke class object device = FooDevice(i2c) - + device.set_my_reg1(0b110) print(device.get_my_reg1()) # prints 6, assuming nothing changed - + device.my_reg2 = 0b1111_0000 print(device.my_reg2) # prints 240 """ - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, endian='', fmt='B'): self._i2c = i2c self._dev_addr = dev_addr self._reg_addr = reg_addr self._num_bytes = num_bytes self._fmt = fmt self._endian = endian - + __check_reg(self) - - del(i2c, dev_addr, reg_addr, num_bytes, fmt, endian) - + + del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + def __get__(self): """ :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: :rtype: tuple() """ return __getreg(self) - + def __set__(self, setting): """ :param setting: Value(s) to be written to register(s). Order must match :param fmt:. - :type setting: int(), bytes(), bytearray(), or list/tuple containing those values in order + :type setting: int(), bytes(), bytearray(), or list/tuple containing those values in order :return: Returns True if operation successful :rtype: tuple() """ return __setreg(self, setting) - + + """ * * GLOBAL HELPER FUNCTIONS * * """ + + def __getbit(reg_object): if isinstance(reg_object, (RORegBit, RWRegBit)): # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) - + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + # Unpack byte - value = unpack(reg_object._endian+reg_object._fmt, value)[0] - - # Perform shift followed by _AND_ operation to determine bit state - return (value >> reg_object._bit_location)&0b1 - + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + + # Perform shift followed by _AND_ operation to determine bit state + return (value >> reg_object._bit_location) & 0b1 + else: - raise TypeError("incorrect object type - must be RORegBit, RWRegBit") - + raise TypeError("incorrect object type - must be RORegBit, RWRegBit") + + def __setbit(reg_object, setting): if isinstance(reg_object, RWRegBit): - if setting in (0,1): + if setting in (0, 1): # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) - + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + # Unpack byte - value = unpack(reg_object._endian+reg_object._fmt, value)[0] - + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + # Assemble byte - value = (value®_object._postmask) + (setting<>reg_object._lsb - + return (value & reg_object._mask) >> reg_object._lsb + else: - raise TypeError("incorrect object type - must be RORegBits, RWRegBits") - + raise TypeError("incorrect object type - must be RORegBits, RWRegBits") + + def __setbits(reg_object, setting): - if isinstance(reg_object, RWRegBits): + if isinstance(reg_object, RWRegBits): if isinstance(setting, int) and setting <= reg_object._mask: - + # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem(reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes) - + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + # Unpack bytes - value = unpack(reg_object._endian+reg_object._fmt, value)[0] - - # Assemble - value = (value®_object._postmask) + (setting<= 0: if msb.__class__() == int() and msb >= 0: - if numbytes.__class__() == int() and numbytes >= 0: - + if numbytes.__class__() == int() and numbytes >= 0: + # Check for detectable errors - if msb>=lsb: - + if msb >= lsb: + # Single bit mask if msb == lsb: pre, post = 0b0, 0b0 - + # Calc post masking - for bit in range(msb+1, numbytes*8): - post = (post<<1) + 0b1 - + for bit in range(msb + 1, numbytes * 8): + post = (post << 1) + 0b1 + # Calc pre masking for bit in range(0, lsb): - pre = (pre<<1) + 0b1 - + pre = (pre << 1) + 0b1 + return pre, post - + # Multibit mask else: - + # Values to return pre, mask, post = 0b0, 0b0, 0b0 - + # Calc post masking - for bit in range(msb+1, numbytes*8): - post = (post<<1) + 0b1 - + for bit in range(msb + 1, numbytes * 8): + post = (post << 1) + 0b1 + # Calc bitfield masking - for bit in range(lsb, msb+1): - mask = (mask<<1) + 0b1 - + for bit in range(lsb, msb + 1): + mask = (mask << 1) + 0b1 + # No bits lower than 0 if lsb == 0: return 0b0, mask, post - + else: for bit in range(0, lsb): - pre = (pre<<1) + 0b1 - - return pre, mask, post + pre = (pre << 1) + 0b1 + + return pre, mask, post else: raise ValueError("msb must be greater than or equal to lsb") else: @@ -860,32 +917,51 @@ def __calc_mask(lsb, msb, numbytes): else: raise ValueError("lsb must be of type int() and 0 or greater") + def __check_reg(reg_object): - + # Alowable struct.pack/unpack formats to check for - fmts = {'b':1, 'B':1, 'h':2, 'H':2, 'f':4, 'i':4, 'I':4, 'l':4, 'L':4, 'q':8, 'Q':8} - endians = '@><' + fmts = { + "b": 1, + "B": 1, + "h": 2, + "H": 2, + "f": 4, + "i": 4, + "I": 4, + "l": 4, + "L": 4, + "q": 8, + "Q": 8, + } + endians = "@><" byte_count = 0 - + # Take in only register objects if isinstance(reg_object, (RORegBit, RWRegBit, RORegBits, RWRegBits, ROReg, RWReg)): - + # Make sure they are strings if type(reg_object._fmt) == str and type(reg_object._endian) == str: - + # Check each letter in format string, To see if allowable for n in range(0, len(reg_object._fmt)): - if reg_object._fmt[n] in fmts: + if reg_object._fmt[n] in fmts: # Add corresonding byte length to verify _num_bytes and format string agree - byte_count = byte_count + fmts[reg_object._fmt[n]] - + byte_count = byte_count + fmts[reg_object._fmt[n]] + else: - raise ValueError(f"unsupported format code of '{reg_object._fmt[n]}'") - + raise ValueError( + f"unsupported format code of '{reg_object._fmt[n]}'" + ) + if byte_count != reg_object._num_bytes: - raise ValueError(f"format string accounts for {byte_count} bytes, _num_bytes value of {reg_object._num_bytes} does not match") - + raise ValueError( + f"format string accounts for {byte_count} bytes, _num_bytes value of {reg_object._num_bytes} does not match" + ) + else: raise TypeError("format and endian must be of type str()") else: - raise TypeError("incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg") + raise TypeError( + "incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg" + ) \ No newline at end of file From 3b717f135384dc28cf85d39201ac4242d274b3db Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Thu, 23 May 2024 11:45:44 -0400 Subject: [PATCH 5/6] Ran pre-commit --- micropython/register/i2c_register.py | 50 ++++++---------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/micropython/register/i2c_register.py b/micropython/register/i2c_register.py index e7de93a5d..4e0907bf7 100644 --- a/micropython/register/i2c_register.py +++ b/micropython/register/i2c_register.py @@ -43,9 +43,7 @@ class RORegBit: - def __init__( - self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B" - ): + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): """ Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. @@ -143,9 +141,7 @@ def __get__(self): class RWRegBit: - def __init__( - self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B" - ): + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): """ Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. @@ -231,9 +227,7 @@ def get_my_reg2(self, n): __check_reg(self) - self._premask, self._postmask = __calc_mask( - bit_location, bit_location, num_bytes - ) + self._premask, self._postmask = __calc_mask(bit_location, bit_location, num_bytes) del (i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) @@ -253,9 +247,7 @@ def __set__(self, setting): class RORegBits: - def __init__( - self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B" - ): + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): """ Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. @@ -359,9 +351,7 @@ def __get__(self): class RWRegBits: - def __init__( - self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B" - ): + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): """ Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. @@ -742,9 +732,7 @@ def __setbit(reg_object, setting): value = pack(reg_object._endian + reg_object._fmt, value) # Write to I2C - reg_object._i2c.writeto_mem( - reg_object._dev_addr, reg_object._reg_addr, value - ) + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) # Return True for success return True @@ -775,7 +763,6 @@ def __getbits(reg_object): def __setbits(reg_object, setting): if isinstance(reg_object, RWRegBits): if isinstance(setting, int) and setting <= reg_object._mask: - # Retrieve register value and unpack to int value = reg_object._i2c.readfrom_mem( reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes @@ -795,16 +782,12 @@ def __setbits(reg_object, setting): value = struct.pack(reg_object._endian + reg_object._fmt, value) # Write to device - reg_object._i2c.writeto_mem( - reg_object._dev_addr, reg_object._reg_addr, value - ) + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) return True else: - raise ValueError( - f"value of setting exceeds max value of bitfield: {reg_object._mask}" - ) + raise ValueError(f"value of setting exceeds max value of bitfield: {reg_object._mask}") else: raise TypeError("incorrect object type - must be RWRegBits") @@ -824,13 +807,10 @@ def __getreg(reg_object): def __setreg(reg_object, settings): - if isinstance(reg_object, RWReg): if isinstance(settings, (bytes, bytearray)): # Write to device - reg_object._i2c.writeto_mem( - reg_object._dev_addr, reg_object._reg_addr, settings - ) + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, settings) elif isinstance(settings, (tuple, list)): # Where our data will go @@ -867,10 +847,8 @@ def __calc_mask(lsb, msb, numbytes): if lsb.__class__() == int() and lsb >= 0: if msb.__class__() == int() and msb >= 0: if numbytes.__class__() == int() and numbytes >= 0: - # Check for detectable errors if msb >= lsb: - # Single bit mask if msb == lsb: pre, post = 0b0, 0b0 @@ -887,7 +865,6 @@ def __calc_mask(lsb, msb, numbytes): # Multibit mask else: - # Values to return pre, mask, post = 0b0, 0b0, 0b0 @@ -919,7 +896,6 @@ def __calc_mask(lsb, msb, numbytes): def __check_reg(reg_object): - # Alowable struct.pack/unpack formats to check for fmts = { "b": 1, @@ -939,10 +915,8 @@ def __check_reg(reg_object): # Take in only register objects if isinstance(reg_object, (RORegBit, RWRegBit, RORegBits, RWRegBits, ROReg, RWReg)): - # Make sure they are strings if type(reg_object._fmt) == str and type(reg_object._endian) == str: - # Check each letter in format string, To see if allowable for n in range(0, len(reg_object._fmt)): if reg_object._fmt[n] in fmts: @@ -950,9 +924,7 @@ def __check_reg(reg_object): byte_count = byte_count + fmts[reg_object._fmt[n]] else: - raise ValueError( - f"unsupported format code of '{reg_object._fmt[n]}'" - ) + raise ValueError(f"unsupported format code of '{reg_object._fmt[n]}'") if byte_count != reg_object._num_bytes: raise ValueError( @@ -964,4 +936,4 @@ def __check_reg(reg_object): else: raise TypeError( "incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg" - ) \ No newline at end of file + ) From 728839d526c24e989c2fc4cc56fbc8dac082214c Mon Sep 17 00:00:00 2001 From: SquirtleSquadLeader <112285567+SquirtleSquadLeader@users.noreply.github.com> Date: Thu, 23 May 2024 12:28:59 -0400 Subject: [PATCH 6/6] Add i2c register object Path: micropython-lib/micropython/register Allows for Object Oriented access to I2C registers. --- micropython/register/i2c_register.py | 1870 +++++++++++++------------- 1 file changed, 931 insertions(+), 939 deletions(-) diff --git a/micropython/register/i2c_register.py b/micropython/register/i2c_register.py index 4e0907bf7..cea18fd46 100644 --- a/micropython/register/i2c_register.py +++ b/micropython/register/i2c_register.py @@ -1,939 +1,931 @@ -""" - * Author(s): SquirtleSquadLeader - - * License: MIT - - * Purpose: - * The purpose of this module is to provide easy I2C Register - * access. It is inspired by the CircuitPython Register - * module maintained by Adafruit. - - * RORegBit - Single bit Read Only - * RWRegBit - Single bit Read/Write - - * RORegBits - Multi-bit Read Only - * RWRegBits - Multi-bit Read/Write - - * ROReg - Single/Multi Read Only - * RWReg - Single/Multi Read/Write - - - * Notes: - 1) Reference format strings below: - Format C Type Standard size - c char 1 - b signed char 1 - B unsigned char 1 - h short 2 - H unsigned short 2 - i integer 4 - I unsigned int 4 - l long 4 - L unsigned long 4 - q long long 8 - Q unsigned long long 8 - f float 4 - d double 8 - - -""" - -from machine import I2C -from struct import pack, unpack - - -class RORegBit: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): - """ - Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. - - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param bit_location: Location of bit within bitfield - :type bit_locatin: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. - :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - - :return: An initialized RORegBit object - :rtype: RORegBit() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`RORegBit` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import RORegBit - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`RORegBit` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = RORegBit(i2c, 68, 5, 1, 5) - - 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. - - .. code-block:: python - - value = my_reg.__get__() # 0 or 1 - - Alternatively, a :class:`RORegBit` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import RORegBit - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg_1 = RORegBit(i2c_bus, 68, 5, 1, 5) - self._my_reg_2 = RORegBit(i2c_bus, 68, 6, 1, 5) - - def get_my_reg1(self): - return self._my_reg_1.__get__() - - def get_my_reg2(self): - return self._my_reg_1.__get__() - - # invoke class object - device = FooDevice(i2c) - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._bit_location = bit_location - self._endian = endian - self._fmt = fmt - - __check_reg(self) - - del (i2c, dev_addr, reg_addr, num_bytes, bit_location, fmt) - - def __get__(self): - """ - :return: Returns the value of the bit located at :param bit_location: - :rtype: int() - """ - return __getbit(self) - - -class RWRegBit: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): - """ - Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param bit_location: Location of bit within bitfield - :type bit_locatin: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. - :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - - :return: An initialized RWRegBit object - :rtype: RWRegBit() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`RWRegBit` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import RWRegBit - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`RWRegBit` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = RWRegBit(i2c, 68, 5, 1, 5) - - 'my_reg' can now provide access to the :method:`__get__() and :method:`__set__(). - Using these methods will get/set the value of the bit found at :param bit_location:. - - .. code-block:: python - my_reg.__set__(1) - - print(my_reg.__get__()) # prints 1 - - Alternatively, a :class:`RWRegBit` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import RWRegBit - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg = RORegBit(i2c_bus, 68, 5, 1, 5) - - def get_my_reg(self): - return self._my_reg.__get__() - - def get_my_reg2(self, n): - return self._my_reg.__set__(n) - - # invoke class object - device = FooDevice(i2c) - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._bit_location = bit_location - self._endian = endian - self._fmt = fmt - - __check_reg(self) - - self._premask, self._postmask = __calc_mask(bit_location, bit_location, num_bytes) - - del (i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) - - def __get__(self): - """ - :return: Returns the value of the bit located at :param bit_location: - :rtype: int() - """ - return __getbit(self) - - def __set__(self, setting): - """ - :return: Returns 'True' if operation successful - :rtype: bool() - """ - return __setbit(self, setting) - - -class RORegBits: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): - """ - Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param lsb: Location of least significant bit within bitfield - :type lsb: int() - :param msb: Location of most significant bit within bitfield - :type msb: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. - :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - - :return: An initialized RORegBits object - :rtype: RORegBits() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`RORegBits` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import RORegBits - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`RORegBits` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = RORegBits(i2c_bus, 68, 5, 1, 0, 2) - - 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. - - .. code-block:: python - - value = my_reg.__get__() # Returns some value from 0b000 to 0b111 - - Alternatively, a :class:`RORegBits` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import RORegBits - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg_1 = RORegBits(i2c_bus, 68, 5, 1, 0, 2) - self._my_reg_2 = RORegBits(i2c_bus, 68, 6, 1, 3, 6) - - def get_my_reg1(self): - return self._my_reg_1.__get__() - - @property - def my_reg2(self): - return self._my_reg_2.__get__() - - # invoke class object - device = FooDevice(i2c) - - n1 = device.get_my_reg() - n2 = device.my_reg2 - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._endian = endian - self._fmt = fmt - - __check_reg(self) - - self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) - - del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian, fmt) - - def __get__(self): - """ - :return: Returns the value of the bitfield located between :param lsb: and :param msb: - :rtype: int() - """ - return __getbits(self) - - -class RWRegBits: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): - """ - Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read - :type num_bytes: int() - :param lsb: Location of least significant bit within bitfield - :type lsb: int() - :param msb: Location of most significant bit within bitfield - :type msb: int() - :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. - :type endian: str() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register - :type fmt: int() - - :return: An initialized RWRegBits object - :rtype: RWRegBits() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`RWRegBits` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import RWRegBits - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`RWRegBits` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) - - 'my_reg' can now provide access to :method:`__get__() and :method:`__set__(). - - .. code-block:: python - - my_reg.__set__(0b110) # Returns some value from 0b000 to 0b111 - value = my_reg.__get__() # Returns 0b110, assuming nothing changes - - Alternatively, a :class:`RWRegBits` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import RWRegBits - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg_1 = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) - self._my_reg_2 = RWRegBits(i2c_bus, 68, 6, 1, 3, 6) - - def get_my_reg1(self): - return self._my_reg_1.__get__() - - def set_my_reg1(self, n): - return self._my_reg_1.__set__(n) - - @property - def my_reg2(self): - return self._my_reg_2.__get__() - - @my_reg2.setter - def my_reg2(self, n): - return self._my_reg_2.__set__(n) - - # invoke class object - device = FooDevice(i2c) - - device.set_my_reg(0b110) - print(device.get_my_reg()) # prints 6 - - device.my_reg2 = 0b110 - print(device.my_reg2) # prints 6 - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._endian = endian - self._fmt = fmt - - __check_reg(self) - - self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) - - del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, fmt, endian) - - def __get__(self): - """ - :return: Returns the value of the bitfield located between :param lsb: and :param msb: - :rtype: int() - """ - return __getbits(self) - - def __set__(self, setting): - """ - :return: True if successful - :rtype: bool() - """ - return __setbits(self, setting) - - -class ROReg: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes=1, endian="", fmt="B"): - """ - Creates a :class:`ROReg` object which allows read only access to n number of sequential registers, - where n is specified by :param num_bytes:. - - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read. Defaults to 1. - :type num_bytes: int() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. - :type fmt: int() - - :return: An initialized ROReg object - :rtype: ROReg() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`ROReg` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import ROReg - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`ROReg` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = ROReg(i2c, 68, 5) - - 'my_reg' can now provide access to the :method:`__get__(). Using this method - will return the value of the bit found at :param bit_location:. - - .. code-block:: python - - value = my_reg.__get__() # some value between 0b0 and 0b1111_1111 - - Alternatively, a :class:`ROReg` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import ROReg - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg_1 = ROReg(i2c_bus, 68, 5) - self._my_reg_2 = ROReg(i2c_bus, 68, 6) - - def get_my_reg1(self): - return self._my_reg_1.__get__() - - @property - def my_reg2(self): - return self._my_reg_1.__get__() - - # invoke class object - device = FooDevice(i2c) - - print(device.get_my_reg1()) - print(device.my_reg2) - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._fmt = fmt - self._endian = endian - - __check_reg(self) - - del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) - - def __get__(self): - """ - :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: - :rtype: tuple() - """ - return __getreg(self) - - -class RWReg: - def __init__(self, i2c, dev_addr, reg_addr, num_bytes, endian="", fmt="B"): - """ - Creates a :class:`RWReg` object which allows read and write access to n number of sequential registers, - where n is specified by :param num_bytes:. - - :param i2c: I2C bus which connects the host system to the peripheral device - :type kind: machine.I2C() - :param dev_addr: I2C address of the device which - :type dev_addr: int() - :param reg_addr: Physical register address which contains the bit of interest - :type reg_addr: int() - :param num_bytes: Number of bytes to read. Defaults to 1. - :type num_bytes: int() - :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. - :type fmt: int() - - :return: An initialized RWReg object - :rtype: RWReg() - - - **Quickstart: Importing and using the device** - - Here is an example of using the :class:`RWReg` class. - First you will need to import the following libraries: - - .. code-block:: python - - from machine import I2C - from register.register import RWReg - - Once this is done you must define a :class:`machine.I2C` object and then pass that - to the :class:`RWReg` object to instantiate it. - - .. code-block:: python - - i2c = I2C(0) # I2C details are project specific - - my_reg = RWReg(i2c, 68, 5) - - 'my_reg' can now provide access to the :method:`__get__() and __set__(). - - .. code-block:: python - my_reg.__set__(0b0) - value = my_reg.__get__() # 0b0 if nothing changed - - Alternatively, a :class:`RWReg` object(s) can be placed within another class. - - .. code-block:: python - # import - from machine import I2C - from register.register import RWReg - - # define I2C - i2c = I2C(0) - - # create class with desired functionality - class FooDevice: - - def __init__(self, i2c_bus): - self._my_reg_1 = RWReg(i2c_bus, 68, 5) - self._my_reg_2 = RWReg(i2c_bus, 68, 6) - - def get_my_reg1(self): - return self._my_reg_1.__get__() - - def set_my_reg1(self, n): - return self._my_reg_1.__set__(n) - - @property - def my_reg2(self): - return self._my_reg_1.__get__() - - @my_reg2.setter - def my_reg2(self, n): - return self._my_reg_1.__set__(n) - - - # invoke class object - device = FooDevice(i2c) - - device.set_my_reg1(0b110) - print(device.get_my_reg1()) # prints 6, assuming nothing changed - - device.my_reg2 = 0b1111_0000 - print(device.my_reg2) # prints 240 - - """ - self._i2c = i2c - self._dev_addr = dev_addr - self._reg_addr = reg_addr - self._num_bytes = num_bytes - self._fmt = fmt - self._endian = endian - - __check_reg(self) - - del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) - - def __get__(self): - """ - :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: - :rtype: tuple() - """ - return __getreg(self) - - def __set__(self, setting): - """ - :param setting: Value(s) to be written to register(s). Order must match :param fmt:. - :type setting: int(), bytes(), bytearray(), or list/tuple containing those values in order - :return: Returns True if operation successful - :rtype: tuple() - """ - return __setreg(self, setting) - - -""" -* -* GLOBAL HELPER FUNCTIONS -* -* -""" - - -def __getbit(reg_object): - if isinstance(reg_object, (RORegBit, RWRegBit)): - # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem( - reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes - ) - - # Unpack byte - value = unpack(reg_object._endian + reg_object._fmt, value)[0] - - # Perform shift followed by _AND_ operation to determine bit state - return (value >> reg_object._bit_location) & 0b1 - - else: - raise TypeError("incorrect object type - must be RORegBit, RWRegBit") - - -def __setbit(reg_object, setting): - if isinstance(reg_object, RWRegBit): - if setting in (0, 1): - # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem( - reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes - ) - - # Unpack byte - value = unpack(reg_object._endian + reg_object._fmt, value)[0] - - # Assemble byte - value = ( - (value & reg_object._postmask) - + (setting << reg_object._bit_location) - + (value & reg_object._premask) - ) - - # Pack to bytes - value = pack(reg_object._endian + reg_object._fmt, value) - - # Write to I2C - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) - - # Return True for success - return True - - else: - raise ValueError("setting must be int(0) or int(1)") - else: - raise TypeError("incorrect object type - must be RWRegBit") - - -def __getbits(reg_object): - if isinstance(reg_object, (RORegBits, RWRegBits)): - # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem( - reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes - ) - - # Unpack bytes - value = unpack(reg_object._endian + reg_object._fmt, value)[0] - - # Return value of bit field - return (value & reg_object._mask) >> reg_object._lsb - - else: - raise TypeError("incorrect object type - must be RORegBits, RWRegBits") - - -def __setbits(reg_object, setting): - if isinstance(reg_object, RWRegBits): - if isinstance(setting, int) and setting <= reg_object._mask: - # Retrieve register value and unpack to int - value = reg_object._i2c.readfrom_mem( - reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes - ) - - # Unpack bytes - value = unpack(reg_object._endian + reg_object._fmt, value)[0] - - # Assemble - value = ( - (value & reg_object._postmask) - + (setting << reg_object._lsb) - + (value & reg_object._premask) - ) - - # Pack to bytes object - value = struct.pack(reg_object._endian + reg_object._fmt, value) - - # Write to device - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) - - return True - - else: - raise ValueError(f"value of setting exceeds max value of bitfield: {reg_object._mask}") - else: - raise TypeError("incorrect object type - must be RWRegBits") - - -def __getreg(reg_object): - if isinstance(reg_object, (ROReg, RWReg)): - # Retrieve register value and unpack to int - values = reg_object._i2c.readfrom_mem( - reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes - ) - - # Return Tuple of values - return unpack(reg_object._endian + reg_object._fmt, values) - - else: - raise TypeError("incorrect object type - must be ROReg, RWReg") - - -def __setreg(reg_object, settings): - if isinstance(reg_object, RWReg): - if isinstance(settings, (bytes, bytearray)): - # Write to device - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, settings) - - elif isinstance(settings, (tuple, list)): - # Where our data will go - d = bytearray() - - # Pack and append to d - for n in range(0, len(settings)): - d.extend(pack(reg_object._endian + reg_object._fmt[n], settings[n])) - - # Write to device - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, d) - - # Assumed single int() for single reg-op - elif isinstance(settings, int): - d = pack(reg_object._endian + reg_object._fmt, settings) - reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, d) - - else: - raise TypeError( - "unsupported object type, settings must be int(), bytes(), bytearray(), tuple(), or list()" - ) - else: - raise TypeError("incorrect object type - must be ROReg, RWReg") - - -def __calc_mask(lsb, msb, numbytes): - """ - Takes in full description of bitfield that needs masking - - returns ints() pre, mask, post - """ - - # Check input types - if lsb.__class__() == int() and lsb >= 0: - if msb.__class__() == int() and msb >= 0: - if numbytes.__class__() == int() and numbytes >= 0: - # Check for detectable errors - if msb >= lsb: - # Single bit mask - if msb == lsb: - pre, post = 0b0, 0b0 - - # Calc post masking - for bit in range(msb + 1, numbytes * 8): - post = (post << 1) + 0b1 - - # Calc pre masking - for bit in range(0, lsb): - pre = (pre << 1) + 0b1 - - return pre, post - - # Multibit mask - else: - # Values to return - pre, mask, post = 0b0, 0b0, 0b0 - - # Calc post masking - for bit in range(msb + 1, numbytes * 8): - post = (post << 1) + 0b1 - - # Calc bitfield masking - for bit in range(lsb, msb + 1): - mask = (mask << 1) + 0b1 - - # No bits lower than 0 - if lsb == 0: - return 0b0, mask, post - - else: - for bit in range(0, lsb): - pre = (pre << 1) + 0b1 - - return pre, mask, post - else: - raise ValueError("msb must be greater than or equal to lsb") - else: - raise ValueError("numbytes must be of type int() and 0 or greater") - else: - raise ValueError("msb must be of type int() and 0 or greater") - else: - raise ValueError("lsb must be of type int() and 0 or greater") - - -def __check_reg(reg_object): - # Alowable struct.pack/unpack formats to check for - fmts = { - "b": 1, - "B": 1, - "h": 2, - "H": 2, - "f": 4, - "i": 4, - "I": 4, - "l": 4, - "L": 4, - "q": 8, - "Q": 8, - } - endians = "@><" - byte_count = 0 - - # Take in only register objects - if isinstance(reg_object, (RORegBit, RWRegBit, RORegBits, RWRegBits, ROReg, RWReg)): - # Make sure they are strings - if type(reg_object._fmt) == str and type(reg_object._endian) == str: - # Check each letter in format string, To see if allowable - for n in range(0, len(reg_object._fmt)): - if reg_object._fmt[n] in fmts: - # Add corresonding byte length to verify _num_bytes and format string agree - byte_count = byte_count + fmts[reg_object._fmt[n]] - - else: - raise ValueError(f"unsupported format code of '{reg_object._fmt[n]}'") - - if byte_count != reg_object._num_bytes: - raise ValueError( - f"format string accounts for {byte_count} bytes, _num_bytes value of {reg_object._num_bytes} does not match" - ) - - else: - raise TypeError("format and endian must be of type str()") - else: - raise TypeError( - "incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg" - ) +""" + * Author(s): SquirtleSquadLeader + + * License: MIT + + * Purpose: + * The purpose of this module is to provide easy I2C Register + * access. It is inspired by the CircuitPython Register + * module maintained by Adafruit. + + * RORegBit - Single bit Read Only + * RWRegBit - Single bit Read/Write + + * RORegBits - Multi-bit Read Only + * RWRegBits - Multi-bit Read/Write + + * ROReg - Single/Multi Read Only + * RWReg - Single/Multi Read/Write + + + * Notes: + 1) Reference format strings below: + Format C Type Standard size + c char 1 + b signed char 1 + B unsigned char 1 + h short 2 + H unsigned short 2 + i integer 4 + I unsigned int 4 + l long 4 + L unsigned long 4 + q long long 8 + Q unsigned long long 8 + f float 4 + d double 8 +""" + +from machine import I2C +from struct import pack, unpack + + +class RORegBit: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): + """ + Creates an :class:`RORegBit` object which allows read only access to a single bit within a register. + + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RORegBit object + :rtype: RORegBit() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RORegBit` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RORegBit + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RORegBit` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RORegBit(i2c, 68, 5, 1, 5) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # 0 or 1 + + Alternatively, a :class:`RORegBit` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RORegBit + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RORegBit(i2c_bus, 68, 5, 1, 5) + self._my_reg_2 = RORegBit(i2c_bus, 68, 6, 1, 5) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def get_my_reg2(self): + return self._my_reg_1.__get__() + + # invoke class object + device = FooDevice(i2c) + """ + + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._bit_location = bit_location + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + del (i2c, dev_addr, reg_addr, num_bytes, bit_location, fmt) + + def __get__(self): + """ + :return: Returns the value of the bit located at :param bit_location: + :rtype: int() + """ + return __getbit(self) + + +class RWRegBit: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, bit_location, endian="", fmt="B"): + """ + Creates an :class:`RORegBit` object which allows read and write access to a single bit within a register. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param bit_location: Location of bit within bitfield + :type bit_locatin: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RWRegBit object + :rtype: RWRegBit() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWRegBit` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWRegBit + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWRegBit` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWRegBit(i2c, 68, 5, 1, 5) + + 'my_reg' can now provide access to the :method:`__get__() and :method:`__set__(). + Using these methods will get/set the value of the bit found at :param bit_location:. + + .. code-block:: python + my_reg.__set__(1) + + print(my_reg.__get__()) # prints 1 + + Alternatively, a :class:`RWRegBit` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWRegBit + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg = RORegBit(i2c_bus, 68, 5, 1, 5) + + def get_my_reg(self): + return self._my_reg.__get__() + + def get_my_reg2(self, n): + return self._my_reg.__set__(n) + + # invoke class object + device = FooDevice(i2c) + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._bit_location = bit_location + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._postmask = __calc_mask(bit_location, bit_location, num_bytes) + + del (i2c, dev_addr, reg_addr, num_bytes, bit_location, endian, fmt) + + def __get__(self): + """ + :return: Returns the value of the bit located at :param bit_location: + :rtype: int() + """ + return __getbit(self) + + def __set__(self, setting): + """ + :return: Returns 'True' if operation successful + :rtype: bool() + """ + return __setbit(self, setting) + + +class RORegBits: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): + """ + Creates an :class:`RORegBits` object which allows read only access to a sequential set of bits within a bitfield. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield + :type lsb: int() + :param msb: Location of most significant bit within bitfield + :type msb: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RORegBits object + :rtype: RORegBits() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RORegBits` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RORegBits + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RORegBits` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RORegBits(i2c_bus, 68, 5, 1, 0, 2) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # Returns some value from 0b000 to 0b111 + + Alternatively, a :class:`RORegBits` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RORegBits + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RORegBits(i2c_bus, 68, 5, 1, 0, 2) + self._my_reg_2 = RORegBits(i2c_bus, 68, 6, 1, 3, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + @property + def my_reg2(self): + return self._my_reg_2.__get__() + + # invoke class object + device = FooDevice(i2c) + + n1 = device.get_my_reg() + n2 = device.my_reg2 + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian, fmt) + + def __get__(self): + """ + :return: Returns the value of the bitfield located between :param lsb: and :param msb: + :rtype: int() + """ + return __getbits(self) + + +class RWRegBits: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, lsb, msb, endian="", fmt="B"): + """ + Creates an :class:`RWRegBits` object which allows read and write access to a sequential set of bits within a bitfield. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read + :type num_bytes: int() + :param lsb: Location of least significant bit within bitfield + :type lsb: int() + :param msb: Location of most significant bit within bitfield + :type msb: int() + :param endian: Endian-ness of system for which the code is intended to run on. str('') uses native Endian-ness. + :type endian: str() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B' which is good for a single 8-bit register + :type fmt: int() + + :return: An initialized RWRegBits object + :rtype: RWRegBits() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWRegBits` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWRegBits + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWRegBits` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) + + 'my_reg' can now provide access to :method:`__get__() and :method:`__set__(). + + .. code-block:: python + + my_reg.__set__(0b110) # Returns some value from 0b000 to 0b111 + value = my_reg.__get__() # Returns 0b110, assuming nothing changes + + Alternatively, a :class:`RWRegBits` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWRegBits + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RWRegBits(i2c_bus, 68, 5, 1, 0, 2) + self._my_reg_2 = RWRegBits(i2c_bus, 68, 6, 1, 3, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def set_my_reg1(self, n): + return self._my_reg_1.__set__(n) + + @property + def my_reg2(self): + return self._my_reg_2.__get__() + + @my_reg2.setter + def my_reg2(self, n): + return self._my_reg_2.__set__(n) + + # invoke class object + device = FooDevice(i2c) + + device.set_my_reg(0b110) + print(device.get_my_reg()) # prints 6 + + device.my_reg2 = 0b110 + print(device.my_reg2) # prints 6 + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._endian = endian + self._fmt = fmt + + __check_reg(self) + + self._premask, self._mask, self._postmask = __calc_mask(lsb, msb, num_bytes) + + del (i2c, dev_addr, reg_addr, num_bytes, lsb, msb, fmt, endian) + + def __get__(self): + """ + :return: Returns the value of the bitfield located between :param lsb: and :param msb: + :rtype: int() + """ + return __getbits(self) + + def __set__(self, setting): + """ + :return: True if successful + :rtype: bool() + """ + return __setbits(self, setting) + + +class ROReg: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes=1, endian="", fmt="B"): + """ + Creates a :class:`ROReg` object which allows read only access to n number of sequential registers, + where n is specified by :param num_bytes:. + + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read. Defaults to 1. + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + + :return: An initialized ROReg object + :rtype: ROReg() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`ROReg` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import ROReg + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`ROReg` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = ROReg(i2c, 68, 5) + + 'my_reg' can now provide access to the :method:`__get__(). Using this method + will return the value of the bit found at :param bit_location:. + + .. code-block:: python + + value = my_reg.__get__() # some value between 0b0 and 0b1111_1111 + + Alternatively, a :class:`ROReg` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import ROReg + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = ROReg(i2c_bus, 68, 5) + self._my_reg_2 = ROReg(i2c_bus, 68, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + @property + def my_reg2(self): + return self._my_reg_1.__get__() + + # invoke class object + device = FooDevice(i2c) + + print(device.get_my_reg1()) + print(device.my_reg2) + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._fmt = fmt + self._endian = endian + + __check_reg(self) + + del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + + def __get__(self): + """ + :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: + :rtype: tuple() + """ + return __getreg(self) + + +class RWReg: + def __init__(self, i2c, dev_addr, reg_addr, num_bytes, endian="", fmt="B"): + """ + Creates a :class:`RWReg` object which allows read and write access to n number of sequential registers, + where n is specified by :param num_bytes:. + + :param i2c: I2C bus which connects the host system to the peripheral device + :type kind: machine.I2C() + :param dev_addr: I2C address of the device which + :type dev_addr: int() + :param reg_addr: Physical register address which contains the bit of interest + :type reg_addr: int() + :param num_bytes: Number of bytes to read. Defaults to 1. + :type num_bytes: int() + :param fmt: Format code which is used to unpack data from bytes(). Defaults to 'B'. + :type fmt: int() + + :return: An initialized RWReg object + :rtype: RWReg() + + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`RWReg` class. + First you will need to import the following libraries: + + .. code-block:: python + + from machine import I2C + from register.register import RWReg + + Once this is done you must define a :class:`machine.I2C` object and then pass that + to the :class:`RWReg` object to instantiate it. + + .. code-block:: python + + i2c = I2C(0) # I2C details are project specific + + my_reg = RWReg(i2c, 68, 5) + + 'my_reg' can now provide access to the :method:`__get__() and __set__(). + + .. code-block:: python + my_reg.__set__(0b0) + value = my_reg.__get__() # 0b0 if nothing changed + + Alternatively, a :class:`RWReg` object(s) can be placed within another class. + + .. code-block:: python + # import + from machine import I2C + from register.register import RWReg + + # define I2C + i2c = I2C(0) + + # create class with desired functionality + class FooDevice: + + def __init__(self, i2c_bus): + self._my_reg_1 = RWReg(i2c_bus, 68, 5) + self._my_reg_2 = RWReg(i2c_bus, 68, 6) + + def get_my_reg1(self): + return self._my_reg_1.__get__() + + def set_my_reg1(self, n): + return self._my_reg_1.__set__(n) + + @property + def my_reg2(self): + return self._my_reg_1.__get__() + + @my_reg2.setter + def my_reg2(self, n): + return self._my_reg_1.__set__(n) + + # invoke class object + device = FooDevice(i2c) + + device.set_my_reg1(0b110) + print(device.get_my_reg1()) # prints 6, assuming nothing changed + + device.my_reg2 = 0b1111_0000 + print(device.my_reg2) # prints 240 + """ + self._i2c = i2c + self._dev_addr = dev_addr + self._reg_addr = reg_addr + self._num_bytes = num_bytes + self._fmt = fmt + self._endian = endian + + __check_reg(self) + + del (i2c, dev_addr, reg_addr, num_bytes, fmt, endian) + + def __get__(self): + """ + :return: Returns tuple containing n number of elements, where n is the number of characters in :param fmt: + :rtype: tuple() + """ + return __getreg(self) + + def __set__(self, setting): + """ + :param setting: Value(s) to be written to register(s). Order must match :param fmt:. + :type setting: int(), bytes(), bytearray(), or list/tuple containing those values in order + :return: Returns True if operation successful + :rtype: tuple() + """ + return __setreg(self, setting) + + +""" +* +* GLOBAL HELPER FUNCTIONS +* +* +""" + + +def __getbit(reg_object): + if isinstance(reg_object, (RORegBit, RWRegBit)): + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + + # Unpack byte + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + + # Perform shift followed by _AND_ operation to determine bit state + return (value >> reg_object._bit_location) & 0b1 + + else: + raise TypeError("incorrect object type - must be RORegBit, RWRegBit") + + +def __setbit(reg_object, setting): + if isinstance(reg_object, RWRegBit): + if setting in (0, 1): + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + + # Unpack byte + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + + # Assemble byte + value = ( + (value & reg_object._postmask) + + (setting << reg_object._bit_location) + + (value & reg_object._premask) + ) + + # Pack to bytes + value = pack(reg_object._endian + reg_object._fmt, value) + + # Write to I2C + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) + + # Return True for success + return True + + else: + raise ValueError("setting must be int(0) or int(1)") + else: + raise TypeError("incorrect object type - must be RWRegBit") + + +def __getbits(reg_object): + if isinstance(reg_object, (RORegBits, RWRegBits)): + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + + # Unpack bytes + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + + # Return value of bit field + return (value & reg_object._mask) >> reg_object._lsb + + else: + raise TypeError("incorrect object type - must be RORegBits, RWRegBits") + + +def __setbits(reg_object, setting): + if isinstance(reg_object, RWRegBits): + if isinstance(setting, int) and setting <= reg_object._mask: + # Retrieve register value and unpack to int + value = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + + # Unpack bytes + value = unpack(reg_object._endian + reg_object._fmt, value)[0] + + # Assemble + value = ( + (value & reg_object._postmask) + + (setting << reg_object._lsb) + + (value & reg_object._premask) + ) + + # Pack to bytes object + value = struct.pack(reg_object._endian + reg_object._fmt, value) + + # Write to device + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, value) + + return True + + else: + raise ValueError(f"value of setting exceeds max value of bitfield: {reg_object._mask}") + else: + raise TypeError("incorrect object type - must be RWRegBits") + + +def __getreg(reg_object): + if isinstance(reg_object, (ROReg, RWReg)): + # Retrieve register value and unpack to int + values = reg_object._i2c.readfrom_mem( + reg_object._dev_addr, reg_object._reg_addr, reg_object._num_bytes + ) + + # Return Tuple of values + return unpack(reg_object._endian + reg_object._fmt, values) + + else: + raise TypeError("incorrect object type - must be ROReg, RWReg") + + +def __setreg(reg_object, settings): + if isinstance(reg_object, RWReg): + if isinstance(settings, (bytes, bytearray)): + # Write to device + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, settings) + + elif isinstance(settings, (tuple, list)): + # Where our data will go + d = bytearray() + + # Pack and append to d + for n in range(0, len(settings)): + d.extend(pack(reg_object._endian + reg_object._fmt[n], settings[n])) + + # Write to device + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, d) + + # Assumed single int() for single reg-op + elif isinstance(settings, int): + d = pack(reg_object._endian + reg_object._fmt, settings) + reg_object._i2c.writeto_mem(reg_object._dev_addr, reg_object._reg_addr, d) + + else: + raise TypeError( + "unsupported object type, settings must be int(), bytes(), bytearray(), tuple(), or list()" + ) + else: + raise TypeError("incorrect object type - must be ROReg, RWReg") + + +def __calc_mask(lsb, msb, numbytes): + """ + Takes in full description of bitfield that needs masking + + returns ints() pre, mask, post + """ + + # Check input types + if lsb.__class__() == int() and lsb >= 0: + if msb.__class__() == int() and msb >= 0: + if numbytes.__class__() == int() and numbytes >= 0: + # Check for detectable errors + if msb >= lsb: + # Single bit mask + if msb == lsb: + pre, post = 0b0, 0b0 + + # Calc post masking + for bit in range(msb + 1, numbytes * 8): + post = (post << 1) + 0b1 + + # Calc pre masking + for bit in range(0, lsb): + pre = (pre << 1) + 0b1 + + return pre, post + + # Multibit mask + else: + # Values to return + pre, mask, post = 0b0, 0b0, 0b0 + + # Calc post masking + for bit in range(msb + 1, numbytes * 8): + post = (post << 1) + 0b1 + + # Calc bitfield masking + for bit in range(lsb, msb + 1): + mask = (mask << 1) + 0b1 + + # No bits lower than 0 + if lsb == 0: + return 0b0, mask, post + + else: + for bit in range(0, lsb): + pre = (pre << 1) + 0b1 + + return pre, mask, post + else: + raise ValueError("msb must be greater than or equal to lsb") + else: + raise ValueError("numbytes must be of type int() and 0 or greater") + else: + raise ValueError("msb must be of type int() and 0 or greater") + else: + raise ValueError("lsb must be of type int() and 0 or greater") + + +def __check_reg(reg_object): + # Alowable struct.pack/unpack formats to check for + fmts = { + "b": 1, + "B": 1, + "h": 2, + "H": 2, + "f": 4, + "i": 4, + "I": 4, + "l": 4, + "L": 4, + "q": 8, + "Q": 8, + } + endians = "@><" + byte_count = 0 + + # Take in only register objects + if isinstance(reg_object, (RORegBit, RWRegBit, RORegBits, RWRegBits, ROReg, RWReg)): + # Make sure they are strings + if type(reg_object._fmt) == str and type(reg_object._endian) == str: + # Check each letter in format string, To see if allowable + for n in range(0, len(reg_object._fmt)): + if reg_object._fmt[n] in fmts: + # Add corresonding byte length to verify _num_bytes and format string agree + byte_count = byte_count + fmts[reg_object._fmt[n]] + + else: + raise ValueError(f"unsupported format code of '{reg_object._fmt[n]}'") + + if byte_count != reg_object._num_bytes: + raise ValueError( + f"format string accounts for {byte_count} bytes, _num_bytes value of {reg_object._num_bytes} does not match" + ) + + else: + raise TypeError("format and endian must be of type str()") + else: + raise TypeError( + "incorrect object type - must be ROReg, RWReg, ROBits, RWBits, ROReg, RWReg" + )