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
157 changes: 157 additions & 0 deletions internal_filesystem/lib/mpos/board/m5stack_fire.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Hardware initialization for ESP32 M5Stack-Fire board
# Manufacturer's website at https://https://docs.m5stack.com/en/core/fire_v2.7
import ili9341
import lcd_bus
import machine

import lvgl as lv
import task_handler

import mpos.ui
import mpos.ui.focus_direction
from mpos import InputManager

# Pin configuration
SPI_BUS = 1 # SPI2
SPI_FREQ = 40000000
LCD_SCLK = 18
LCD_MOSI = 23
LCD_DC = 27
LCD_CS = 14
LCD_BL = 32
LCD_RST = 33
LCD_TYPE = 2 # ILI9341 type 2

TFT_HOR_RES=320
TFT_VER_RES=240

spi_bus = machine.SPI.Bus(
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.

Seems this doesn't work in a soft reset. So why not doing this:

try:
    spi_bus = machine.SPI.Bus(host=SPI_BUS, mosi=LCD_MOSI, sck=LCD_SCLK)
except Exception as e:
    print(f"Error initializing SPI bus: {e}")
    print("Attempting hard reset in 3sec...")
    time.sleep(3)
    machine.reset()

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.

Probably fine, I mean it's a workaround, not a fix, or something that seems trivial, but it's better than nothing, I guess...

I never understood why those soft resets even happen... any idea? I think I disabled them at some point in mpremote.py because of... issues...

host=SPI_BUS,
mosi=LCD_MOSI,
sck=LCD_SCLK
)
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=SPI_FREQ,
dc=LCD_DC,
cs=LCD_CS
)

# M5Stack-Fire ILI9342 uses ILI9341 type 2 with a modified orientation table.
class ILI9341(ili9341.ILI9341):
_ORIENTATION_TABLE = (
0x00,
0x40 | 0x20, # _MADCTL_MX | _MADCTL_MV
0x80 | 0x40, # _MADCTL_MY | _MADCTL_MX
0x80 | 0x20 # _MADCTL_MY | _MADCTL_MV
)

mpos.ui.main_display = ILI9341(
data_bus=display_bus,
display_width=TFT_HOR_RES,
display_height=TFT_VER_RES,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=ili9341.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
reset_pin=LCD_RST,
reset_state=ili9341.STATE_LOW,
backlight_pin=LCD_BL,
backlight_on_state=ili9341.STATE_PWM
)
mpos.ui.main_display.init(LCD_TYPE)
mpos.ui.main_display.set_power(True)
mpos.ui.main_display.set_color_inversion(True)
mpos.ui.main_display.set_backlight(25)

lv.init()

# Button handling code:
from machine import Pin
import time

btn_a = Pin(39, Pin.IN, Pin.PULL_UP) # A
btn_b = Pin(38, Pin.IN, Pin.PULL_UP) # B
btn_c = Pin(37, Pin.IN, Pin.PULL_UP) # C

# Key repeat configuration
# This whole debounce logic is only necessary because LVGL 9.2.2 seems to have an issue where
# the lv_keyboard widget doesn't handle PRESSING (long presses) properly, it loses focus.
REPEAT_INITIAL_DELAY_MS = 300 # Delay before first repeat
REPEAT_RATE_MS = 100 # Interval between repeats
last_key = None
last_state = lv.INDEV_STATE.RELEASED
key_press_start = 0 # Time when key was first pressed
last_repeat_time = 0 # Time of last repeat event

# Read callback
# Warning: This gets called several times per second, and if it outputs continuous debugging on the serial line,
# that will break tools like mpremote from working properly to upload new files over the serial line, thus needing a reflash.
def keypad_read_cb(indev, data):
global last_key, last_state, key_press_start, last_repeat_time
since_last_repeat = 0

# Check buttons
current_key = None
current_time = time.ticks_ms()
if btn_a.value() == 0:
current_key = lv.KEY.PREV
elif btn_b.value() == 0:
current_key = lv.KEY.ENTER
elif btn_c.value() == 0:
current_key = lv.KEY.NEXT

if (btn_a.value() == 0) and (btn_c.value() == 0):
current_key = lv.KEY.ESC

# Key repeat logic
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.

In #35 i implement a other auto-key-repeat solution. Maybe it fits also for the FIRE ?

REPEAT_INITIAL_DELAY_MS = 300  # Delay before first repeat
REPEAT_RATE_MS = 100  # Interval between repeats
next_repeat = None  # Used for auto-repeat key handling


def input_callback(indev, data):
    global next_repeat

    current_key = None

    if crossbar_pressed := crossbar.poll():
        current_key = crossbar_pressed
    elif button_menu.value() == 0:
        current_key = lv.KEY.ESC
        
    # ... handle other buttons here ...
    
    else:
        # No crossbar/buttons pressed
        if data.key:  # A key was previously pressed and now released
            # print(f"Key {data.key=} released")
            data.key = 0
            data.state = lv.INDEV_STATE.RELEASED
            next_repeat = None
        return

    # A key is currently pressed
    current_time = time.ticks_ms()
    repeat = current_time > next_repeat if next_repeat else False  # Auto repeat?
    if repeat or current_key != data.key:
        print(f"Key {current_key} pressed {repeat=}")

        data.key = current_key
        data.state = lv.INDEV_STATE.PRESSED

        if current_key == lv.KEY.ESC:  # Handle ESC for back navigation
            mpos.ui.back_screen()
        elif current_key == lv.KEY.RIGHT:
            mpos.ui.focus_direction.move_focus_direction(90)
            
        # ... handle other buttons here ...

        if not repeat:
            # Initial press: Delay before first repeat
            next_repeat = current_time + REPEAT_INITIAL_DELAY_MS
        else:
            # Faster auto repeat after initial press
            next_repeat = current_time + REPEAT_RATE_MS

if current_key:
if current_key != last_key:
# New key press
data.key = current_key
data.state = lv.INDEV_STATE.PRESSED
last_key = current_key
last_state = lv.INDEV_STATE.PRESSED
key_press_start = current_time
last_repeat_time = current_time
else: # same key
# Key held: Check for repeat
elapsed = time.ticks_diff(current_time, key_press_start)
since_last_repeat = time.ticks_diff(current_time, last_repeat_time)
if elapsed >= REPEAT_INITIAL_DELAY_MS and since_last_repeat >= REPEAT_RATE_MS:
# Send a new PRESSED/RELEASED pair for repeat
data.key = current_key
data.state = lv.INDEV_STATE.PRESSED if last_state == lv.INDEV_STATE.RELEASED else lv.INDEV_STATE.RELEASED
last_state = data.state
last_repeat_time = current_time
else:
# No repeat yet, send RELEASED to avoid PRESSING
data.state = lv.INDEV_STATE.RELEASED
last_state = lv.INDEV_STATE.RELEASED
else:
# No key pressed
data.key = last_key if last_key else lv.KEY.ENTER
data.state = lv.INDEV_STATE.RELEASED
last_key = None
last_state = lv.INDEV_STATE.RELEASED
key_press_start = 0
last_repeat_time = 0

# Handle ESC for back navigation (only on initial PRESSED)
if last_state == lv.INDEV_STATE.PRESSED:
if current_key == lv.KEY.ESC and since_last_repeat == 0:
mpos.ui.back_screen()

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(keypad_read_cb)
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)

print("m5stack_fire.py finished")
8 changes: 8 additions & 0 deletions internal_filesystem/lib/mpos/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ def detect_board():
return "linux"
elif sys.platform == "esp32":
from machine import Pin, I2C
# Check for ESP32 boards first to avoid conflicts with SPI flash reserved pins (GPIO6 to GPIO11)
try:
i2c0 = I2C(0, sda=Pin(21), scl=Pin(22))
if {0x68} <= set(i2c0.scan()): # IMU (MPU6886)
return "m5stack_fire"
except ValueError: # GPIO22 doesn't exist in ESP32-S3
pass
# Check for ESP32-S3 boards
i2c0 = I2C(0, sda=Pin(48), scl=Pin(47))
if {0x15, 0x6B} <= set(i2c0.scan()): # touch screen and IMU (at least, possibly more)
return "waveshare_esp32_s3_touch_lcd_2"
Expand Down
4 changes: 2 additions & 2 deletions scripts/build_mpos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ if [ "$target" == "esp32" ]; then
manifest=$(readlink -f "$codebasedir"/manifests/manifest.py)
frozenmanifest="FROZEN_MANIFEST=$manifest"
echo "Note that you can also prevent the builtin filesystem from being mounted by umounting it and creating a builtin/ folder."
# Build for https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-2.
# Build for https://https://docs.m5stack.com/en/core/fire_v2.7.
# See https://github.com/lvgl-micropython/lvgl_micropython
# --ota: support Over-The-Air updates
# --partition size: both OTA partitions are 4MB
Expand All @@ -112,7 +112,7 @@ if [ "$target" == "esp32" ]; then
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
pushd "$codebasedir"/lvgl_micropython/
rm -rf lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/
python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=cst816s USER_C_MODULE="$codebasedir"/micropython-camera-API/src/micropython.cmake USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y "$frozenmanifest"
python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM DISPLAY=ili9341 USER_C_MODULE="$codebasedir"/micropython-camera-API/src/micropython.cmake USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y "$frozenmanifest"
popd
echo "Grepping..."
pwd
Expand Down
Loading