Skip to content

Commit a0bdeb0

Browse files
committed
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=<lv_color_t> 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() ```
1 parent 781cc97 commit a0bdeb0

9 files changed

Lines changed: 725 additions & 79 deletions

File tree

internal_filesystem/lib/drivers/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import sys
2+
from . import st7701s
3+
from . import _st7701s_init
4+
5+
# Register _st7701s_init in sys.modules so __import__('_st7701s_init') can find it
6+
# This is needed because display_driver_framework.py uses __import__('_st7701s_init')
7+
# expecting a top-level module, but _st7701s_init is in the st7701s package subdirectory
8+
sys.modules['_st7701s_init'] = _st7701s_init
9+
10+
# Explicitly define __all__ and re-export public symbols from st7701s module
11+
__all__ = [
12+
'ST7701S',
13+
]
14+
15+
# Re-export the public symbols
16+
ST7701S = st7701s.ST7701S
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# ST7701S initialization
2+
3+
import time
4+
from micropython import const
5+
6+
_SWRESET = const(0x01)
7+
_SLPOUT = const(0x11)
8+
_DISPON = const(0x29)
9+
_MADCTL = const(0x36)
10+
_COLMOD = const(0x3A)
11+
_LCD_DELAY = const(0xFF)
12+
_CASET = const(0x2A)
13+
_RASET = const(0x2B)
14+
_INVOFF = const(0x20)
15+
_INVON = const(0x21)
16+
17+
18+
def init(self):
19+
print("_st7701s_init.py Send initialization commands to ST7701S...")
20+
21+
# ST7701S init sequence
22+
seq = [
23+
# (cmd, data) or (None, ms) for delay
24+
(_SWRESET, None),
25+
(None, 150),
26+
(_SLPOUT, None),
27+
(None, 100),
28+
(0xFF, [0x77, 0x01, 0x00, 0x00, 0x10]),
29+
(0xC0, [0x3B, 0x00]),
30+
(0xC1, [0x0D, 0x02]),
31+
(0xC2, [0x21, 0x08]),
32+
(0xCD, [0x08]),
33+
(0xB0, [0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18]),
34+
(0xB1, [0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18]),
35+
(0xFF, [0x77, 0x01, 0x00, 0x00, 0x11]),
36+
(0xB0, [0x60]),
37+
(0xB1, [0x30]),
38+
(0xB2, [0x87]),
39+
(0xB3, [0x80]),
40+
(0xB5, [0x49]),
41+
(0xB7, [0x85]),
42+
(0xB8, [0x21]),
43+
(0xC1, [0x78]),
44+
(0xC2, [0x78]),
45+
(None, 20),
46+
(0xE0, [0x00, 0x1B, 0x02]),
47+
(0xE1, [0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44]),
48+
(0xE2, [0x11, 0x11, 0x44, 0x44, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00]),
49+
(0xE3, [0x00, 0x00, 0x11, 0x11]),
50+
(0xE4, [0x44, 0x44]),
51+
(0xE5, [0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0]),
52+
(0xE6, [0x00, 0x00, 0x11, 0x11]),
53+
(0xE7, [0x44, 0x44]),
54+
(0xE8, [0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0]),
55+
(0xEB, [0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40]),
56+
(0xEC, [0x3C, 0x00]),
57+
(0xED, [0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA]),
58+
(0xFF, [0x77, 0x01, 0x00, 0x00, 0x00]),
59+
(_MADCTL, [0x00]),
60+
(_COLMOD, [0x66]),
61+
(0x21, None),
62+
(None, 120),
63+
(None, 120),
64+
# Column address set (CASET):
65+
(_CASET, [0x00, 0x00, (self.display_width >> 8) & 0xFF, self.display_width & 0xFF]),
66+
# Page address set (RASET):
67+
(_RASET, [0x00, 0x00, (self.display_height >> 8) & 0xFF, self.display_height & 0xFF]),
68+
(_DISPON, None),
69+
(None, 120),
70+
(_INVOFF, None),
71+
]
72+
73+
if self.set_params_func:
74+
print("Using custom set_params_func for initialization")
75+
set_params = self.set_params_func
76+
else:
77+
print("Using default set_params method for initialization")
78+
set_params = self.set_params
79+
80+
for cmd, data in seq:
81+
if cmd is None:
82+
time.sleep_ms(data)
83+
else:
84+
if data is None:
85+
set_params(cmd)
86+
else:
87+
set_params(cmd, bytearray(data))
88+
89+
print("_st7701s_init.py initialization complete")
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import lvgl as lv
2+
import rgb_display_framework
3+
4+
5+
class ST7701S(rgb_display_framework.RGBDisplayDriver):
6+
def __init__(
7+
self,
8+
data_bus,
9+
display_width,
10+
display_height,
11+
frame_buffer1=None,
12+
frame_buffer2=None,
13+
reset_pin=None,
14+
reset_state=rgb_display_framework.STATE_HIGH,
15+
power_pin=None,
16+
power_on_state=rgb_display_framework.STATE_HIGH,
17+
backlight_pin=None,
18+
backlight_on_state=rgb_display_framework.STATE_HIGH,
19+
offset_x=0,
20+
offset_y=0,
21+
color_byte_order=rgb_display_framework.BYTE_ORDER_RGB,
22+
color_space=lv.COLOR_FORMAT.RGB888,
23+
set_params_func=None,
24+
):
25+
super().__init__(
26+
data_bus=data_bus,
27+
display_width=display_width,
28+
display_height=display_height,
29+
frame_buffer1=frame_buffer1,
30+
frame_buffer2=frame_buffer2,
31+
reset_pin=reset_pin,
32+
reset_state=reset_state,
33+
power_pin=power_pin,
34+
power_on_state=power_on_state,
35+
backlight_pin=backlight_pin,
36+
backlight_on_state=backlight_on_state,
37+
offset_x=offset_x,
38+
offset_y=offset_y,
39+
color_byte_order=color_byte_order,
40+
color_space=color_space,
41+
rgb565_byte_swap=False,
42+
)
43+
self.set_params_func = set_params_func
44+
45+
mod_name = f'_st7701s_init'
46+
mod = __import__(mod_name)
47+
mod.init(self)
48+
49+
def set_params(self, cmd, params=None):
50+
if self.set_params_func:
51+
self.set_params_func(cmd, params)
52+
else:
53+
super().set_params(cmd, params)

internal_filesystem/lib/drivers/io_expander/__init__.py

Whitespace-only changes.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import struct
2+
3+
import i2c
4+
import machine
5+
from micropython import const
6+
7+
8+
class TCA9555:
9+
"""
10+
LCA9555 / TCA9555 IO expansion chip
11+
(LCA9555 is register-compatible with TCA9555)
12+
"""
13+
14+
# Register addresses
15+
REG_INPUT = const(0x00)
16+
REG_OUTPUT = const(0x02)
17+
REG_CONFIG = const(0x06)
18+
19+
def __init__(self, i2c_bus: i2c.I2C.Bus, dev_id: int):
20+
self.tca_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=dev_id)
21+
self.directions = 0xFFFF # All inputs by default
22+
self.output_states = 0x0000 # All low by default
23+
24+
# Set IO expander initially as all inputs
25+
self._write_word(0x06, self.directions)
26+
27+
# Read current directions and states
28+
self.directions = self._read_word(0x06)
29+
self.output_states = self._read_word(0x02)
30+
31+
def _write_word(self, reg, value):
32+
print(f"Writing to TCA9555: reg={reg:#02x}, value={value:#04x}")
33+
self.tca_dev.write(bytes([reg, value & 0xFF, (value >> 8) & 0xFF]))
34+
35+
def _read_word(self, reg):
36+
self.tca_dev.write(bytes([reg]))
37+
data = self.tca_dev.read(2)
38+
return struct.unpack("<H", data)[0]
39+
40+
def pin_mode(self, pin, mode):
41+
if pin & 0x40: # Pins with 0x40 bit set are controlled by TCA9555
42+
pin &= 0xBF # Mask out high bit
43+
if mode == machine.Pin.OUT:
44+
self.directions &= ~(1 << pin)
45+
else:
46+
self.directions |= 1 << pin
47+
self._write_word(self.REG_OUTPUT, self.directions)
48+
else:
49+
# Handle standard ESP32 pin
50+
machine.Pin(pin, mode)
51+
52+
def digital_write(self, pin, value):
53+
if pin & 0x40: # Pins with 0x40 bit set are controlled by TCA9555
54+
pin &= 0xBF
55+
if value:
56+
self.output_states |= 1 << pin
57+
else:
58+
self.output_states &= ~(1 << pin)
59+
self._write_word(self.REG_OUTPUT, self.output_states)
60+
61+
# Ensure pin is set to output
62+
self.directions &= ~(1 << pin)
63+
self._write_word(self.REG_CONFIG, self.directions)
64+
else:
65+
# Handle standard ESP32 pin
66+
p = machine.Pin(pin, machine.Pin.OUT)
67+
p.value(value)
68+
69+
def digital_read(self, pin):
70+
if pin & 0x40: # Pins with 0x40 bit set are controlled by TCA9555
71+
pin &= 0xBF
72+
# Ensure pin is set to input
73+
self.directions |= 1 << pin
74+
self._write_word(self.REG_CONFIG, self.directions)
75+
76+
inputs = self._read_word(self.REG_INPUT)
77+
return 1 if (inputs & (1 << pin)) else 0
78+
else:
79+
# Handle standard ESP32 pin
80+
p = machine.Pin(pin, machine.Pin.IN)
81+
return p.value()

0 commit comments

Comments
 (0)