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
Next Next commit
register accessor layer and SPI support
  • Loading branch information
FoamyGuy committed Sep 25, 2025
commit dc6d6474675ec23ae67ab56b114ac306d2418277
80 changes: 80 additions & 0 deletions adafruit_register/register_accessor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# SPDX-FileCopyrightText: Copyright (c) 2022 Max Holliday
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_register.register_accessor`
====================================================

SPI and I2C Register Accessor classes.

* Author(s): Max Holliday
* Adaptation by Tim Cocks
"""


class SPIRegisterAccessor:
def __init__(self, spi_device):
self.spi_device = spi_device

def _shift_rw_cmd_bit_into_address_byte(self, buffer, 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
# Set the MSB to the desired bit value
buffer[0] = cleared_byte | (bit_value << 7)

def read_register(self, buffer):
with self.spi_device as spi:
self._shift_rw_cmd_bit_into_address_byte(buffer, 1)
spi.write(buffer, end=1)
spi.readinto(buffer, start=1)

def write_register(self, buffer, value, lsb_first, bit_mask, lowest_bit, byte=None):
# read current register data
with self.spi_device as spi:
self._shift_rw_cmd_bit_into_address_byte(buffer, 1)
spi.write(buffer, end=1)
spi.readinto(buffer, start=1)

if isinstance(value, int):
# shift in integer value to register data
reg = 0
order = range(len(buffer) - 1, 0, -1)
if not lsb_first:
order = range(1, len(buffer))
for i in order:
reg = (reg << 8) | buffer[i]

shifted_value = value << lowest_bit
reg &= ~bit_mask # mask off the bits we're about to change
reg |= shifted_value # then or in our new value

# put data from reg back into buffer
for i in reversed(order):
buffer[i] = reg & 0xFF
reg >>= 8
elif isinstance(value, bool):
# shift in single bit value to register data
if value:
buffer[byte] |= bit_mask
else:
buffer[byte] &= ~bit_mask

# write updated register data
with self.spi_device as spi:
self._shift_rw_cmd_bit_into_address_byte(buffer, 0)
spi.write(buffer)


class I2CRegisterAccessor:
def __init__(self, bus):
pass

def read_register(self, address):
pass

def write_register(self, address, value):
pass
63 changes: 63 additions & 0 deletions adafruit_register/register_bit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_register.register_bit`
====================================================

Single bit registers that use RegisterAccessor

* Author(s): Tim Cocks

"""


class RWBit:
"""
Single bit register that is readable and writeable.

Values are `bool`

:param int register_address: The register address to read the bit from
:param int bit: The bit 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 spi the LSB? Defaults to true

"""

def __init__(self, register_address, bit, register_width=1, lsb_first=True):
self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte!
self.buffer = bytearray(1 + register_width)
self.address = register_address
self.buffer[0] = register_address
self.buffer[1] = register_width - 1
self.lsb_first = lsb_first
self.bit_index = bit
if lsb_first:
self.byte = bit // 8 + 1 # the byte number within the buffer
else:
self.byte = register_width - (bit // 8) # the byte number within the buffer

def __get__(self, obj, objtype=None):
obj.register_accessor.read_register(self.buffer)
return bool(self.buffer[self.byte] & self.bit_mask)

def __set__(self, obj, value):
obj.register_accessor.write_register(
self.buffer, value, self.lsb_first, self.bit_mask, self.bit_index, byte=self.byte
)


class ROBit(RWBit):
"""Single bit register that is read only. Subclass of `RWBit`.

Values are `bool`

:param int register_address: The register address to read the bit from
:param type bit: The bit index within the byte at ``register_address``
:param int register_width: The number of bytes in the register. Defaults to 1.

"""

def __set__(self, obj, value):
raise AttributeError()
85 changes: 85 additions & 0 deletions adafruit_register/register_bits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_register.register_bits`
====================================================

Multi bit registers

* Author(s): Tim Cocks

"""

import time


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

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 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 SPI the LSB? Defaults to true
"""

# pylint: disable=too-many-arguments
def __init__(
self,
num_bits,
register_address,
lowest_bit,
register_width=1,
lsb_first=True,
):
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.buffer = bytearray(1 + register_width)
self.buffer[0] = register_address
self.buffer[1] = register_width - 1
self.lsb_first = lsb_first

def __get__(self, obj, objtype=None):
obj.register_accessor.read_register(self.buffer)

# read the bytes into a single variable
reg = 0
order = range(len(self.buffer) - 1, 0, -1)
if not self.lsb_first:
order = reversed(order)
for i in order:
reg = (reg << 8) | self.buffer[i]

# extract integer value from specified bits
result = (reg & self.bit_mask) >> self.lowest_bit
return result

def __set__(self, obj, value):
obj.register_accessor.write_register(
self.buffer, value, self.lsb_first, self.bit_mask, self.lowest_bit
)


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

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 register_width: The number of bytes in the register. Defaults to 1.
"""

def __set__(self, obj, value):
raise AttributeError()