Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
split address and data buffers. Address buffer inside Accessor, data …
…buffer inside Bit(s) classes.
  • Loading branch information
FoamyGuy committed Sep 29, 2025
commit b42f50ea350089de7929cdd8df70056bbc216290
109 changes: 62 additions & 47 deletions adafruit_register/register_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,31 @@


class RegisterAccessor:
"""
Subclasses of this class will be used to provide read/write interface to registers
over different bus types.

:param int address_width: The width of the register addresses in bytes. Defaults to 1.
:param bool lsb_first: Is the first byte we read from the bus the LSB? Defaults to true
"""

address_width = None

def _pack_address_into_buffer(self, address, lsb_first, buffer):
# Pack address into the buffer
for i in range(self.address_width):
if lsb_first:
# Little-endian: least significant byte first
buffer[i] = (address >> (i * 8)) & 0xFF
else:
# Big-endian: most significant byte first
big_endian_address = address.to_bytes(self.address_width, byteorder="big")
for address_byte_i in range(self.address_width):
buffer[address_byte_i] = big_endian_address[address_byte_i]
def __init__(self, address_width=1, lsb_first=True):
self.address_width = address_width
self.address_buffer = bytearray(address_width)
self.lsb_first = lsb_first

def _pack_address_into_buffer(self, address):
if self.lsb_first:
# Little-endian: least significant byte first
for address_byte_i in range(self.address_width):
self.address_buffer[address_byte_i] = (address >> (address_byte_i * 8)) & 0xFF
else:
# Big-endian: most significant byte first
big_endian_address = address.to_bytes(self.address_width, byteorder="big")
for address_byte_i in range(self.address_width):
self.address_buffer[address_byte_i] = big_endian_address[address_byte_i]


class SPIRegisterAccessor(RegisterAccessor):
Expand All @@ -49,58 +61,51 @@ class SPIRegisterAccessor(RegisterAccessor):
:param int address_width: The number of bytes in the address
"""

def __init__(self, spi_device: SPIDevice, address_width: int = 1):
self.address_width = address_width
def __init__(self, spi_device: SPIDevice, address_width: int = 1, lsb_first=True):
super().__init__(address_width, lsb_first)
self.spi_device = spi_device

def _shift_rw_cmd_bit_into_address_byte(self, buffer, bit_value):
def _shift_rw_cmd_bit_into_first_byte(self, bit_value):
if bit_value not in {0, 1}:
raise ValueError("bit_value must be 0 or 1")

# Clear the MSB (set bit 7 to 0)
cleared_byte = buffer[0] & 0x7F
cleared_byte = self.address_buffer[0] & 0x7F
# Set the MSB to the desired bit value
buffer[0] = cleared_byte | (bit_value << 7)
self.address_buffer[0] = cleared_byte | (bit_value << 7)

def read_register(self, address: int, lsb_first: bool, buffer: bytearray):
def read_register(self, address: int, buffer: bytearray):
"""
Read register value over SPIDevice.

:param int address: The register address to read.
:param bool lsb_first: Is the first byte we read from the bus the LSB?
:param bytearray buffer: Buffer that will be used to write/read register data.
`address` will be put into the first `address_width` bytes of the buffer, data
will be read into the buffer following the address.
:param bytearray buffer: Buffer that will be used to read register data into.
Buffer must be long enough to be read all data sent by the device.
:return: None
"""

self._pack_address_into_buffer(address, lsb_first, buffer)
self._shift_rw_cmd_bit_into_address_byte(buffer, 1)
self._pack_address_into_buffer(address)
self._shift_rw_cmd_bit_into_first_byte(1)
with self.spi_device as spi:
spi.write(buffer, end=1)
spi.readinto(buffer, start=1)
spi.write(self.address_buffer)
spi.readinto(buffer)

def write_register(
self,
address: int,
lsb_first: bool,
buffer: bytearray,
):
"""
Write register value over SPIDevice.

:param int address: The register address to read.
:param bool lsb_first: Is the first byte we read from the bus the LSB?
:param bytearray buffer: Buffer that will be written to the register.
`address` will be put into the first `address_width` bytes of the buffer
:return: None
"""
self._pack_address_into_buffer(address, lsb_first, buffer)
self._shift_rw_cmd_bit_into_address_byte(buffer, 0)
self._pack_address_into_buffer(address)
self._shift_rw_cmd_bit_into_first_byte(0)
with self.spi_device as spi:
self._shift_rw_cmd_bit_into_address_byte(buffer, 0)
spi.write(buffer)
spi.write(self.address_buffer + buffer)


class I2CRegisterAccessor(RegisterAccessor):
Expand All @@ -112,40 +117,50 @@ class I2CRegisterAccessor(RegisterAccessor):
:param int address_width: The number of bytes in the address
"""

def __init__(self, i2c_device: I2CDevice, address_width: int = 1):
def __init__(self, i2c_device: I2CDevice, address_width: int = 1, lsb_first=True):
super().__init__(address_width, lsb_first)
self.i2c_device = i2c_device
self.address_width = address_width

def read_register(self, address: int, lsb_first: bool, buffer: bytearray):
# buffer that will hold address + data for write_then_readinto operations
# will grow as needed
self._full_buffer = bytearray(address_width + 1)

def read_register(self, address: int, buffer: bytearray):
"""
Read register value over I2CDevice.

:param int address: The register address to read.
:param bool lsb_first: Is the first byte we read from the bus the LSB? Defaults to true
:param bytearray buffer: Buffer that will be used to write/read register data.
address will be put into the first `address_width` bytes of the buffer, data
will be read into the buffer following the address.
:param bytearray buffer: Buffer that will be used to read register data into.
Buffer must be long enough to be read all data sent by the device.
:return: None
"""
if self.address_width + len(buffer) > len(self._full_buffer):
self._full_buffer = bytearray(self.address_width + len(buffer))

self._pack_address_into_buffer(address)
for i in range(self.address_width):
self._full_buffer[i] = self.address_buffer[i]

self._pack_address_into_buffer(address, lsb_first, buffer)
with self.i2c_device as i2c:
i2c.write_then_readinto(
buffer, buffer, out_end=self.address_width, in_start=self.address_width
self._full_buffer,
self._full_buffer,
out_end=self.address_width,
in_start=self.address_width,
)
# copy data from _full_buffer into buffer
for i in range(len(buffer)):
buffer[i] = self._full_buffer[self.address_width + i]

def write_register(self, address: int, lsb_first: bool, buffer: bytearray):
def write_register(self, address: int, buffer: bytearray):
"""
Write register value over I2CDevice.

:param int address: The register address to read.
:param bool lsb_first: Is the first byte we read from the bus the LSB? Defaults to true
:param bytearray buffer: Buffer must have register address value at index 0.
Must be long enough to be read all data send by the device for specified register.
:param bytearray buffer: Buffer of data that will be written to the register.

:return: None
"""
self._pack_address_into_buffer(address, lsb_first, buffer)
self._pack_address_into_buffer(address)
with self.i2c_device as i2c:
i2c.write(buffer)
i2c.write(self.address_buffer + buffer)
22 changes: 7 additions & 15 deletions adafruit_register/register_bit.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,31 @@ class RWBit:
"""

def __init__(
self,
register_address: int,
bit: int,
register_width: int = 1,
lsb_first: bool = True,
address_width: int = 1,
self, register_address: int, bit: int, register_width: int = 1, lsb_first: bool = True
):
self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte!

self.address = register_address

self.address_width = address_width
self.buffer = bytearray(address_width + register_width)
self.buffer = bytearray(register_width)

self.lsb_first = lsb_first
self.bit_index = bit
if lsb_first:
self.byte = address_width + (bit // 8) # Little-endian: bit 0 in first register byte
self.byte = bit // 8 # Little-endian: bit 0 in first register byte
else:
self.byte = (
address_width + register_width - 1 - (bit // 8)
) # Big-endian: bit 0 in last register byte
self.byte = register_width - 1 - (bit // 8) # Big-endian: bit 0 in last register byte

def __get__(self, obj, objtype=None):
# read data from register
obj.register_accessor.read_register(self.address, self.lsb_first, self.buffer)
obj.register_accessor.read_register(self.address, self.buffer)

# check specified bit and return boolean
return bool(self.buffer[self.byte] & self.bit_mask)

def __set__(self, obj, value):
# read current data from register
obj.register_accessor.read_register(self.address, self.lsb_first, self.buffer)
obj.register_accessor.read_register(self.address, self.buffer)

# update current data with new value
if value:
Expand All @@ -70,7 +62,7 @@ def __set__(self, obj, value):
self.buffer[self.byte] &= ~self.bit_mask

# write updated data to register
obj.register_accessor.write_register(self.address, self.lsb_first, self.buffer)
obj.register_accessor.write_register(self.address, self.buffer)


class ROBit(RWBit):
Expand Down
31 changes: 15 additions & 16 deletions adafruit_register/register_bits.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

class RWBits:
"""
Multibit register (less than a full byte) that is readable and writeable.
This must be within a byte register.
Multibit register that is readable and writeable.

Values are `int` between 0 and 2 ** ``num_bits`` - 1.

Expand All @@ -29,7 +28,7 @@ class RWBits:
:param bool lsb_first: Is the first byte we read from the bus the LSB? Defaults to true
:param bool signed: If True, the value is a "two's complement" signed value.
If False, it is unsigned.
:param int address_width: The width of the register address in bytes. Defaults to 1.

"""

# pylint: disable=too-many-arguments
Expand All @@ -41,28 +40,26 @@ def __init__(
register_width: int = 1,
lsb_first: bool = True,
signed: bool = False,
address_width: int = 1,
):
self.bit_mask = ((1 << num_bits) - 1) << lowest_bit

if self.bit_mask >= 1 << (register_width * 8):
raise ValueError("Cannot have more bits than register size")
self.lowest_bit = lowest_bit

self.address_width = address_width
self.address = register_address
self.buffer = bytearray(address_width + register_width)
self.buffer = bytearray(register_width)

self.lsb_first = lsb_first
self.sign_bit = (1 << (num_bits - 1)) if signed else 0

def __get__(self, obj, objtype=None):
# read data from register
obj.register_accessor.read_register(self.address, self.lsb_first, self.buffer)
obj.register_accessor.read_register(self.address, self.buffer)

# read the bytes into a single variable
reg = 0
order = range(len(self.buffer) - 1, self.address_width - 1, -1)
order = range(len(self.buffer) - 1, -1, -1)
if not self.lsb_first:
order = reversed(order)
for i in order:
Expand All @@ -79,13 +76,13 @@ def __get__(self, obj, objtype=None):

def __set__(self, obj, value):
# read current data from register
obj.register_accessor.read_register(self.address, self.lsb_first, self.buffer)
obj.register_accessor.read_register(self.address, self.buffer)

# shift in integer value to register data
reg = 0
order = range(len(self.buffer) - 1, self.address_width - 1, -1)
order = range(len(self.buffer) - 1, -1, -1)
if not self.lsb_first:
order = range(1, len(self.buffer))
order = reversed(order)
for i in order:
reg = (reg << 8) | self.buffer[i]
shifted_value = value << self.lowest_bit
Expand All @@ -97,21 +94,23 @@ def __set__(self, obj, value):
self.buffer[i] = reg & 0xFF
reg >>= 8

# write updated data into the register
obj.register_accessor.write_register(self.address, self.lsb_first, self.buffer)
# write updated data buffer to the register
obj.register_accessor.write_register(self.address, self.buffer)


class ROBits(RWBits):
"""
Multibit register (less than a full byte) that is read-only. This must be
within a byte register.
Multibit register that is read-only.

Values are `int` between 0 and 2 ** ``num_bits`` - 1.

:param int num_bits: The number of bits in the field.
:param int register_address: The register address to read the bit from
:param type lowest_bit: The lowest bits index within the byte at ``register_address``
:param int lowest_bit: The lowest bits index within the byte at ``register_address``
:param int register_width: The number of bytes in the register. Defaults to 1.
:param bool lsb_first: Is the first byte we read from the bus the LSB? Defaults to true
:param bool signed: If True, the value is a "two's complement" signed value.
If False, it is unsigned.
"""

def __set__(self, obj, value):
Expand Down