From a0bdeb0d2b0e461c981e8f46c0e3e974d337395e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 21 Mar 2026 18:30:44 +0100 Subject: [PATCH] WIP: Support SQUiXL device by "Unexpected Maker" Display doesn't work: Backlight will be enabled, but doesn't render anything. Init looks like: ``` Starting main.py... sys.version=3.4.0; LVGL (9.3.0) MicroPython (1.25.0) Binding compiled on 2026-03-21 sys.implementation=(name='micropython', version=(1, 25, 0, ''), _machine='Generic ESP32S3 module with Octal-SPIRAM with ESP32S3', _mpy=11014, _build='ESP32_GENERIC_S3-SPIRAM_OCT') Free space on root filesystem: total_space=12517376 / used_space=1712128 / free_space=10805248 bytes RAM: 4650480 free, 1040 allocated, 4651520 total Passing execution over to mpos.main MicroPythonOS 0.9.0 running lib/mpos/main.py unPhone ? (emulated) lilygo_t_display_s3 ? odroid_go ? fri3d_2026 ? SQUiXL ? Detected squixl system, importing mpos.board.squixl squixl.py initialization squixl.py init i2c Bus with: scl=2, sda=1... Scanning I2C bus for devices... Found I2C device at address: 32 ($0X20) Found I2C device at address: 54 ($0X36) Found I2C device at address: 82 ($0X52) Found I2C device at address: 90 ($0X5A) Found I2C device at address: 93 ($0X5D) Create instance of the LCA9555 IO Expander... Writing to TCA9555: reg=0x6, value=0xffff Resetting LCD... Setting LCD backlight ON (BL_EN=0) Screen soft power EN 5V presense sense IO IO MUX - EN is Active LOW, so start it off IO MUX - Set default to I2S - LOW is SD Haptic EN Setting LCD backlight OFF (BL_EN=0) Setting LCD backlight ON (BL_EN=0) squixl.py RGB parallel bus display initialization squixl.py ST7701S() display initialization _st7701s_init.py Send initialization commands to ST7701S... Using custom set_params_func for initialization set_params: cmd=0x11 no params set_params: cmd=0xFF params: 77 01 00 00 10 set_params: cmd=0xC0 params: 3B 00 set_params: cmd=0xC1 params: 0D 02 set_params: cmd=0xC2 params: 21 08 set_params: cmd=0xCD params: 08 set_params: cmd=0xB0 params: 00 11 18 0E 11 06 07 08 07 22 04 12 0F AA 31 18 set_params: cmd=0xB1 params: 00 11 19 0E 12 07 08 08 08 22 04 11 11 A9 32 18 set_params: cmd=0xFF params: 77 01 00 00 11 set_params: cmd=0xB0 params: 60 set_params: cmd=0xB1 params: 30 set_params: cmd=0xB2 params: 87 set_params: cmd=0xB3 params: 80 set_params: cmd=0xB5 params: 49 set_params: cmd=0xB7 params: 85 set_params: cmd=0xB8 params: 21 set_params: cmd=0xC1 params: 78 set_params: cmd=0xC2 params: 78 set_params: cmd=0xE0 params: 00 1B 02 set_params: cmd=0xE1 params: 08 A0 00 00 07 A0 00 00 00 44 44 set_params: cmd=0xE2 params: 11 11 44 44 A0 00 00 EC A0 00 00 set_params: cmd=0xE3 params: 00 00 11 11 set_params: cmd=0xE4 params: 44 44 set_params: cmd=0xE5 params: 0A E9 D8 A0 0C EB D8 A0 0E ED D8 A0 10 EF D8 A0 set_params: cmd=0xE6 params: 00 00 11 11 set_params: cmd=0xE7 params: 44 44 set_params: cmd=0xE8 params: 09 E8 D8 A0 0B EA D8 A0 0D EC D8 A0 0F EE D8 A0 set_params: cmd=0xEB params: 02 00 E4 E4 88 00 40 set_params: cmd=0xEC params: 3C 00 set_params: cmd=0xED params: AB 89 76 54 02 FF FF FF FF FF FF 20 45 67 98 BA set_params: cmd=0xFF params: 77 01 00 00 00 set_params: cmd=0x36 params: 00 set_params: cmd=0x3A params: 66 set_params: cmd=0x21 no params set_params: cmd=0x2A params: 00 00 01 DF set_params: cmd=0x2B params: 00 00 01 DF set_params: cmd=0x29 no params _st7701s_init.py initialization complete squixl.py display.init() squixl.py display.set_rotation() initialization squixl.py lv.init() initialization Detect if VBUS (5V) power source is present: raw_value=0 squixl.get_vbus_present()=False squixl.py initialization complete mounting freezefs_mount_builtin at /builtin. SharedPreferences.load didn't find preferences: [Errno 2] ENOENT [AppearanceManager] Setting primary color: 15769616 [AppearanceManager] Initialized: light_mode=True, primary_color= init_rootscreen set resolution to 480x480 at 130 DPI SharedPreferences.load didn't find preferences: [Errno 2] ENOENT AppManager finding apps... WifiService: Auto-connect thread starting AppManager: handling apps got exception: [Errno 2] ENOENT SharedPreferences.load didn't find preferences: [Errno 2] ENOENT SharedPreferences.load didn't find preferences: [Errno 2] ENOENT WifiService: No access points configured, exiting Found launcher com.micropythonos.launcher Foreground app: com.micropythonos.launcher Thread 1070340860: executing script with cwd: builtin/apps/com.micropythonos.launcher/assets/ Thread 1070340860: reading script from file builtin/apps/com.micropythonos.launcher/assets/launcher.py execute_script: reading script_source took 1ms Thread 1070340860: starting script execute_script: compiling script_source took 43ms apps.py execute_script: exec took 14ms Classes: dict_keys(['DisplayMetrics', 'Launcher', 'Activity', 'AppearanceManager', 'AppManager']) Functions: dict_keys([]) Variables: dict_keys(['time', '__name__', '__file__', 'ubinascii', 'math', 'uhashlib', 'lv']) launcher.py onCreate() ``` --- internal_filesystem/lib/drivers/__init__.py | 0 .../lib/drivers/display/st7701s/__init__.py | 16 + .../drivers/display/st7701s/_st7701s_init.py | 89 ++++ .../lib/drivers/display/st7701s/st7701s.py | 53 ++ .../lib/drivers/io_expander/__init__.py | 0 .../lib/drivers/io_expander/tca9555.py | 81 +++ internal_filesystem/lib/mpos/board/squixl.py | 479 ++++++++++++++++++ internal_filesystem/lib/mpos/board/unphone.py | 82 +-- internal_filesystem/lib/mpos/main.py | 4 + 9 files changed, 725 insertions(+), 79 deletions(-) create mode 100644 internal_filesystem/lib/drivers/__init__.py create mode 100644 internal_filesystem/lib/drivers/display/st7701s/__init__.py create mode 100644 internal_filesystem/lib/drivers/display/st7701s/_st7701s_init.py create mode 100644 internal_filesystem/lib/drivers/display/st7701s/st7701s.py create mode 100644 internal_filesystem/lib/drivers/io_expander/__init__.py create mode 100644 internal_filesystem/lib/drivers/io_expander/tca9555.py create mode 100644 internal_filesystem/lib/mpos/board/squixl.py diff --git a/internal_filesystem/lib/drivers/__init__.py b/internal_filesystem/lib/drivers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/internal_filesystem/lib/drivers/display/st7701s/__init__.py b/internal_filesystem/lib/drivers/display/st7701s/__init__.py new file mode 100644 index 00000000..dd3173fd --- /dev/null +++ b/internal_filesystem/lib/drivers/display/st7701s/__init__.py @@ -0,0 +1,16 @@ +import sys +from . import st7701s +from . import _st7701s_init + +# Register _st7701s_init in sys.modules so __import__('_st7701s_init') can find it +# This is needed because display_driver_framework.py uses __import__('_st7701s_init') +# expecting a top-level module, but _st7701s_init is in the st7701s package subdirectory +sys.modules['_st7701s_init'] = _st7701s_init + +# Explicitly define __all__ and re-export public symbols from st7701s module +__all__ = [ + 'ST7701S', +] + +# Re-export the public symbols +ST7701S = st7701s.ST7701S diff --git a/internal_filesystem/lib/drivers/display/st7701s/_st7701s_init.py b/internal_filesystem/lib/drivers/display/st7701s/_st7701s_init.py new file mode 100644 index 00000000..59bd1571 --- /dev/null +++ b/internal_filesystem/lib/drivers/display/st7701s/_st7701s_init.py @@ -0,0 +1,89 @@ +# ST7701S initialization + +import time +from micropython import const + +_SWRESET = const(0x01) +_SLPOUT = const(0x11) +_DISPON = const(0x29) +_MADCTL = const(0x36) +_COLMOD = const(0x3A) +_LCD_DELAY = const(0xFF) +_CASET = const(0x2A) +_RASET = const(0x2B) +_INVOFF = const(0x20) +_INVON = const(0x21) + + +def init(self): + print("_st7701s_init.py Send initialization commands to ST7701S...") + + # ST7701S init sequence + seq = [ + # (cmd, data) or (None, ms) for delay + (_SWRESET, None), + (None, 150), + (_SLPOUT, None), + (None, 100), + (0xFF, [0x77, 0x01, 0x00, 0x00, 0x10]), + (0xC0, [0x3B, 0x00]), + (0xC1, [0x0D, 0x02]), + (0xC2, [0x21, 0x08]), + (0xCD, [0x08]), + (0xB0, [0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18]), + (0xB1, [0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18]), + (0xFF, [0x77, 0x01, 0x00, 0x00, 0x11]), + (0xB0, [0x60]), + (0xB1, [0x30]), + (0xB2, [0x87]), + (0xB3, [0x80]), + (0xB5, [0x49]), + (0xB7, [0x85]), + (0xB8, [0x21]), + (0xC1, [0x78]), + (0xC2, [0x78]), + (None, 20), + (0xE0, [0x00, 0x1B, 0x02]), + (0xE1, [0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44]), + (0xE2, [0x11, 0x11, 0x44, 0x44, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00]), + (0xE3, [0x00, 0x00, 0x11, 0x11]), + (0xE4, [0x44, 0x44]), + (0xE5, [0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0]), + (0xE6, [0x00, 0x00, 0x11, 0x11]), + (0xE7, [0x44, 0x44]), + (0xE8, [0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0]), + (0xEB, [0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40]), + (0xEC, [0x3C, 0x00]), + (0xED, [0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA]), + (0xFF, [0x77, 0x01, 0x00, 0x00, 0x00]), + (_MADCTL, [0x00]), + (_COLMOD, [0x66]), + (0x21, None), + (None, 120), + (None, 120), + # Column address set (CASET): + (_CASET, [0x00, 0x00, (self.display_width >> 8) & 0xFF, self.display_width & 0xFF]), + # Page address set (RASET): + (_RASET, [0x00, 0x00, (self.display_height >> 8) & 0xFF, self.display_height & 0xFF]), + (_DISPON, None), + (None, 120), + (_INVOFF, None), + ] + + if self.set_params_func: + print("Using custom set_params_func for initialization") + set_params = self.set_params_func + else: + print("Using default set_params method for initialization") + set_params = self.set_params + + for cmd, data in seq: + if cmd is None: + time.sleep_ms(data) + else: + if data is None: + set_params(cmd) + else: + set_params(cmd, bytearray(data)) + + print("_st7701s_init.py initialization complete") diff --git a/internal_filesystem/lib/drivers/display/st7701s/st7701s.py b/internal_filesystem/lib/drivers/display/st7701s/st7701s.py new file mode 100644 index 00000000..c695d025 --- /dev/null +++ b/internal_filesystem/lib/drivers/display/st7701s/st7701s.py @@ -0,0 +1,53 @@ +import lvgl as lv +import rgb_display_framework + + +class ST7701S(rgb_display_framework.RGBDisplayDriver): + def __init__( + self, + data_bus, + display_width, + display_height, + frame_buffer1=None, + frame_buffer2=None, + reset_pin=None, + reset_state=rgb_display_framework.STATE_HIGH, + power_pin=None, + power_on_state=rgb_display_framework.STATE_HIGH, + backlight_pin=None, + backlight_on_state=rgb_display_framework.STATE_HIGH, + offset_x=0, + offset_y=0, + color_byte_order=rgb_display_framework.BYTE_ORDER_RGB, + color_space=lv.COLOR_FORMAT.RGB888, + set_params_func=None, + ): + super().__init__( + data_bus=data_bus, + display_width=display_width, + display_height=display_height, + frame_buffer1=frame_buffer1, + frame_buffer2=frame_buffer2, + reset_pin=reset_pin, + reset_state=reset_state, + power_pin=power_pin, + power_on_state=power_on_state, + backlight_pin=backlight_pin, + backlight_on_state=backlight_on_state, + offset_x=offset_x, + offset_y=offset_y, + color_byte_order=color_byte_order, + color_space=color_space, + rgb565_byte_swap=False, + ) + self.set_params_func = set_params_func + + mod_name = f'_st7701s_init' + mod = __import__(mod_name) + mod.init(self) + + def set_params(self, cmd, params=None): + if self.set_params_func: + self.set_params_func(cmd, params) + else: + super().set_params(cmd, params) diff --git a/internal_filesystem/lib/drivers/io_expander/__init__.py b/internal_filesystem/lib/drivers/io_expander/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/internal_filesystem/lib/drivers/io_expander/tca9555.py b/internal_filesystem/lib/drivers/io_expander/tca9555.py new file mode 100644 index 00000000..5030ef9c --- /dev/null +++ b/internal_filesystem/lib/drivers/io_expander/tca9555.py @@ -0,0 +1,81 @@ +import struct + +import i2c +import machine +from micropython import const + + +class TCA9555: + """ + LCA9555 / TCA9555 IO expansion chip + (LCA9555 is register-compatible with TCA9555) + """ + + # Register addresses + REG_INPUT = const(0x00) + REG_OUTPUT = const(0x02) + REG_CONFIG = const(0x06) + + def __init__(self, i2c_bus: i2c.I2C.Bus, dev_id: int): + self.tca_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=dev_id) + self.directions = 0xFFFF # All inputs by default + self.output_states = 0x0000 # All low by default + + # Set IO expander initially as all inputs + self._write_word(0x06, self.directions) + + # Read current directions and states + self.directions = self._read_word(0x06) + self.output_states = self._read_word(0x02) + + def _write_word(self, reg, value): + print(f"Writing to TCA9555: reg={reg:#02x}, value={value:#04x}") + self.tca_dev.write(bytes([reg, value & 0xFF, (value >> 8) & 0xFF])) + + def _read_word(self, reg): + self.tca_dev.write(bytes([reg])) + data = self.tca_dev.read(2) + return struct.unpack("> bit) & 1) + self.tca.pin_mode(self.CLK, machine.Pin.PULL_UP) + # Send data bytes if any + if params: + for data_byte in params: + # DC bit = 1 for data + self.tca.pin_mode(self.CLK, machine.Pin.PULL_DOWN) + self.tca.pin_mode(self.MOSI, 1) + self.tca.pin_mode(self.CLK, machine.Pin.PULL_UP) + for bit in range(7, -1, -1): + self.tca.pin_mode(self.CLK, machine.Pin.PULL_DOWN) + self.tca.pin_mode(self.MOSI, (data_byte >> bit) & 1) + self.tca.pin_mode(self.CLK, machine.Pin.PULL_UP) + # End transaction + self.tca.pin_mode(self.CS, machine.Pin.PULL_UP) + + def get_vbus_present(self): + print("Detect if VBUS (5V) power source is present:", end=" ") + # return self.tca.read(VBUS_SENSE) == 1 + raw_value = self.tca.digital_read(self.VBUS_SENSE) + print(f"{raw_value=}") + return raw_value == 1 + + def set_lcd_backlight(self, *, on: bool): + """ + Enable or disable the LCD backlight via IO expander BL_EN pin. + :param enable: True to turn on, False to turn off + """ + self.tca.pin_mode(self.BL_EN, machine.Pin.OUT) + print(f"Setting LCD backlight {'ON' if on else 'OFF'} (BL_EN={self.BL_EN})") + self.tca.digital_write(self.BL_EN, 1 if on else 0) + + def lcd_reset(self): + print("Resetting LCD...") + self.tca.pin_mode(self.LCD_RST, machine.Pin.OUT) + + self.tca.digital_write(self.LCD_RST, 0) # Assert reset + time.sleep_ms(100) # Hold reset for 100ms + self.tca.digital_write(self.LCD_RST, 1) # Deassert reset + time.sleep_ms(100) # Wait for LCD to come out of reset + + +print("Create instance of the LCA9555 IO Expander...") +squixl = SQUiXL(i2c=i2c_bus) +squixl.set_lcd_backlight(on=False) +time.sleep(0.5) +squixl.set_lcd_backlight(on=True) + + +# TODO: Initialise the GT911 touch IC +# touch = GT911(i2c, irq_pin=TP_I2C_IRQ, reset_pin=TP_I2C_RESET, ioex=ioex) + +# TODO: Initialise the DRV2605 haptic engine +# drv = DRV2605(i2c) + + +try: + print("squixl.py RGB parallel bus display initialization") + display_bus = lcd_bus.RGBBus( + hsync=HSYNC, + vsync=VSYNC, + de=DE, + pclk=PCLK, + data0=RGB_IO[0], + data1=RGB_IO[1], + data2=RGB_IO[2], + data3=RGB_IO[3], + data4=RGB_IO[4], + data5=RGB_IO[5], + data6=RGB_IO[6], + data7=RGB_IO[7], + data8=RGB_IO[8], + data9=RGB_IO[9], + data10=RGB_IO[10], + data11=RGB_IO[11], + data12=RGB_IO[12], + data13=RGB_IO[13], + data14=RGB_IO[14], + data15=RGB_IO[15], + freq=6_000_000, # 6 MHz + hsync_front_porch=50, + hsync_back_porch=10, + hsync_pulse_width=8, + hsync_idle_low=False, + vsync_front_porch=8, + vsync_back_porch=8, + vsync_pulse_width=3, + vsync_idle_low=False, + de_idle_high=False, # ??? + pclk_idle_high=False, # ??? + pclk_active_low=False, # ??? + rgb565_dither=False, + ) + print("squixl.py ST7701S() display initialization") + mpos.ui.main_display = ST7701S( + data_bus=display_bus, + display_width=480, + display_height=480, + set_params_func=squixl.set_params, + ) +except Exception as e: + sys.print_exception(e) + print("Attempting hard reset in 3sec...") + time.sleep(3) + machine.reset() + + +print("squixl.py display.init()") +mpos.ui.main_display.init() + +time.sleep_ms(200) # Short delay to help things settle +startup_rotation = lv.DISPLAY_ROTATION._0 +print("squixl.py display.set_rotation() initialization") +mpos.ui.main_display.set_rotation( + startup_rotation +) # must be done after initializing display and creating the touch drivers, to ensure proper handling + +print("squixl.py lv.init() initialization") +lv.init() + +group = lv.group_create() +group.set_default() + +# Create and set up the input device +indev = lv.indev_create() +indev.set_type(lv.INDEV_TYPE.KEYPAD) +# indev.set_read_cb(input_callback) +indev.set_group( + group +) # is this needed? maybe better to move the default group creation to main.py so it's available everywhere... +disp = lv.display_get_default() # NOQA +indev.set_display(disp) # different from display +indev.enable(True) # NOQA +# InputManager.register_indev(indev) + + +# CHnage teh state of the IOMUX from Off, to I2S or uSD +def set_iomux(state=IOMUX_OFF): + """Set the state of the IOMUX - for I2S Amp or SD Card or OFF""" + global current_iomux_state + global audio_out + + if current_iomux_state == state: + return + + if current_iomux_state == IOMUX_I2S: + audio_out.deinit() + + # IO MUX - Set default to I2S - machine.Pin.PULL_DOWN is SD + + if state == IOMUX_OFF: + self.tca.pin_mode(self.MUX_EN, machine.Pin.PULL_UP) + print("SQUiXL IOMUX is OFF") + elif state == IOMUX_SD: + self.tca.pin_mode(self.MUX_EN, machine.Pin.PULL_DOWN) + self.tca.pin_mode(self.MUX_SEL, machine.Pin.PULL_DOWN) + print("SQUiXL IOMUX is uSD") + elif state == IOMUX_I2S: + self.tca.pin_mode(self.MUX_EN, machine.Pin.PULL_DOWN) + self.tca.pin_mode(self.MUX_SEL, machine.Pin.PULL_UP) + + sd_mode = machine.Pin(IOMUX_D1, machine.Pin.OUT) + sd_mode.value(1) + + audio_out = I2S( + 1, + sck=machine.Pin(IOMUX_D4), + ws=machine.Pin(IOMUX_D2), + sd=machine.Pin(IOMUX_D3), + mode=I2S.TX, + bits=SAMPLE_SIZE_IN_BITS, + format=FORMAT, + rate=SAMPLE_RATE_IN_HZ, + ibuf=I2S_BUFFER_LENGTH_IN_BYTES, + ) + print("SQUiXL IOMUX is I2S") + + current_iomux_state = state + + +# General Helper Functions + + +# Battery voltage +def get_bat_voltage(): + """Read the battery voltage from the fuel gauge""" + voltage = max17048.cell_voltage + print(f"Bat Voltage: {voltage}V") + return voltage + + +# Battery charge state +def get_state_of_charge(): + """Read the battery state of charge from the fuel gauge""" + soc = max17048.state_of_charge + print(f"State of Charge: {soc}%") + return soc + + +print(f"{squixl.get_vbus_present()=}") + + +print("squixl.py initialization complete") diff --git a/internal_filesystem/lib/mpos/board/unphone.py b/internal_filesystem/lib/mpos/board/unphone.py index 299afda0..cee34937 100644 --- a/internal_filesystem/lib/mpos/board/unphone.py +++ b/internal_filesystem/lib/mpos/board/unphone.py @@ -15,7 +15,6 @@ Original author: https://github.com/jedie """ -import struct import sys import time @@ -27,6 +26,7 @@ import mpos.ui from drivers.display.hx8357d import hx8357d from drivers.indev.xpt2046 import XPT2046 +from drivers.io_expander.tca9555 import TCA9555 from machine import Pin from micropython import const from mpos import InputManager @@ -68,6 +68,7 @@ # SPI_TOUCH_FREQ = const(500_000) # SPI_TOUCH_FREQ = const(100_000) +TCA9555_I2C_DEV_ID = const(38) # 0x26 - TI TCA9555's I²C addr EXPANDER_POWER = const(0x40) LED_GREEN = const(0x49) LED_BLUE = const(0x4D) # 13 | 0x40 @@ -95,83 +96,6 @@ print("unphone.py init...") -class UnPhoneTCA: - """ - unPhone spin 9 - TCA9555 IO expansion chip - """ - - I2C_DEV_ID = const(38) # 0x26 - TI TCA9555's I²C addr - - # Register addresses - REG_INPUT = const(0x00) - REG_OUTPUT = const(0x02) - REG_CONFIG = const(0x06) - - def __init__(self, i2c_bus: i2c.I2C.Bus): - self.tca_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=self.I2C_DEV_ID) - self.directions = 0xFFFF # All inputs by default - self.output_states = 0x0000 # All low by default - - # Set IO expander initially as all inputs - self._write_word(0x06, self.directions) - - # Read current directions and states - self.directions = self._read_word(0x06) - self.output_states = self._read_word(0x02) - - def _write_word(self, reg, value): - print(f"Writing to TCA9555: reg={reg:#02x}, value={value:#04x}") - self.tca_dev.write(bytes([reg, value & 0xFF, (value >> 8) & 0xFF])) - - def _read_word(self, reg): - self.tca_dev.write(bytes([reg])) - data = self.tca_dev.read(2) - return struct.unpack("