Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 26 additions & 0 deletions internal_filesystem/lib/drivers/fri3d/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import struct

from micropython import const
from machine import I2C

# common registers
_REG_VERSION = const(0x00)


class Device:
"""Fri3d I2C device."""

def __init__(self, i2c_bus: I2C, address: int):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasFarstrike should we use a i2c.I2C.Device() here for thread-safety?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I'm not sure...

"""Read from a sensor on the given I2C bus, at the given address."""
self.i2c = i2c_bus
self.address = address

def _read(self, format, reg, amount):
return struct.unpack(format, self.i2c.readfrom_mem(self.address, reg, amount))

def _write(self, reg, value):
self.i2c.writeto_mem(self.address, reg, value, addrsize=8)

@property
def version(self) -> tuple[int, int, int]:
return self._read("BBB", _REG_VERSION, 3)
89 changes: 89 additions & 0 deletions internal_filesystem/lib/drivers/fri3d/expander.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import struct

from micropython import const
from machine import I2C, Pin

from .device import Device

# registers
_EXPANDER_REG_INPUTS = const(0x04)
_EXPANDER_REG_ANALOG = const(0x06)
_EXPANDER_REG_LCD_BRIGHTNESS = const(0x12)
_EXPANDER_REG_DEBUG_LED = const(0x14)
_EXPANDER_REG_CONFIG = const(0x16)

_EXPANDER_I2CADDR_DEFAULT = const(0x50)


class Expander(Device):
"""Fri3d Badge 2026 expander MCU."""

def __init__(
self,
i2c_bus: I2C,
address: int = _EXPANDER_I2CADDR_DEFAULT,
int_pin: Pin = None,
):
"""Read from a sensor on the given I2C bus, at the given address."""
Device.__init__(self, i2c_bus, address)
self.use_interrupt = False
if int_pin:
self.use_interrupt = True
self._rx_buf = bytearray(2)
self._rx_mv = memoryview(self._rx_buf)
self.int_pin = int_pin
self.i2c.readfrom_mem_into(self.address, _EXPANDER_REG_INPUTS, self._rx_mv)
self.int_pin.irq(trigger=Pin.IRQ_RISING, handler=self.int_callback)

def int_callback(self, p):
self.i2c.readfrom_mem_into(self.address, _EXPANDER_REG_INPUTS, self._rx_mv)

@property
def analog(self) -> tuple[int, int, int, int, int, int]:
"""Read the analog inputs: ain1, ain0, battery_monitor, usb_monitor, joystick_y, joystick_x"""
return self._read("<HHHHHH", _EXPANDER_REG_ANALOG, 12)

@property
def digital(
self,
) -> tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool]:
"""Read the digital inputs: usb_plugged, joy_right, joy_left, joy_down, joy_up, button_menu, button_b, button_a, button_y, button_x, charger_standby, charger_charging"""
if self.use_interrupt:
inputs = struct.unpack("<H", self._rx_buf)[0]
else:
inputs = self._read("<H", _EXPANDER_REG_INPUTS, 2)[0]
return tuple([bool(int(digit)) for digit in "{:016b}".format(inputs)[4:]])

@property
def lcd_brightness(self) -> int:
"""Read the LCD brightness state (0-100)"""
return self._read("<H", _EXPANDER_REG_LCD_BRIGHTNESS, 2)[0]

@lcd_brightness.setter
def lcd_brightness(self, value: int):
"""Set the LCD brightness (0-100)"""
if value >= 0 and value <= 100:
self._write(_EXPANDER_REG_LCD_BRIGHTNESS, struct.pack("<H", value))

@property
def debug_led(self) -> int:
"""Read the Debug LED state (0-100)"""
return self._read("<H", _EXPANDER_REG_DEBUG_LED, 2)[0]

@debug_led.setter
def debug_led(self, value: int):
"""Set the Debug LED (0-100)"""
if value >= 0 and value <= 100:
self._write(_EXPANDER_REG_DEBUG_LED, struct.pack("<H", value))

@property
def config(self) -> tuple[bool, bool, bool]:
"""Read the configuration bits: reboot, lcd_reset, aux_power"""
config = self._read("B", _EXPANDER_REG_CONFIG, 1)[0]
return tuple([bool(int(digit)) for digit in "{:08b}".format(config)[5:]])

@config.setter
def config(self, value: int):
"""set the configuration byte"""
if value >= 0 and value <= 0xFF:
self._write(_EXPANDER_REG_CONFIG, struct.pack("B", value))