diff --git a/micropython/register/i2c_register.py b/micropython/register/i2c_register.py index 4e0907bf..cea18fd4 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" + )