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("