Files

201 lines
6.3 KiB
Python
Raw Permalink Normal View History

2026-02-07 17:32:31 +01:00
# Hardware initialization for ESP32 M5Stack-Fire board
# Manufacturer's website at https://https://docs.m5stack.com/en/core/fire_v2.7
2026-02-13 22:47:52 +01:00
# Original author: https://github.com/ancebfer
2026-02-16 21:45:56 +01:00
import time
2026-02-13 21:59:24 +01:00
import drivers.display.ili9341 as ili9341
2026-02-07 17:32:31 +01:00
import lcd_bus
import lvgl as lv
2026-02-16 21:45:56 +01:00
import machine
2026-02-07 17:32:31 +01:00
import mpos.ui
import mpos.ui.focus_direction
from machine import I2C, PWM, Pin
2026-02-16 21:45:56 +01:00
from micropython import const
from mpos import AudioManager, InputManager, SensorManager
2026-02-07 17:32:31 +01:00
2026-02-16 21:45:56 +01:00
# Display settings:
SPI_BUS = const(1) # SPI2
SPI_FREQ = const(40000000)
2026-02-07 17:32:31 +01:00
2026-02-16 21:45:56 +01:00
LCD_SCLK = const(18)
LCD_MOSI = const(23)
LCD_DC = const(27)
LCD_CS = const(14)
LCD_BL = const(32)
LCD_RST = const(33)
LCD_TYPE = const(2) # ILI9341 type 2
TFT_HOR_RES = const(320)
TFT_VER_RES = const(240)
# Button settings:
BUTTON_A = const(39) # A
BUTTON_B = const(38) # B
BUTTON_C = const(37) # C
# Misc settings:
BATTERY_PIN = const(35)
# Buzzer
BUZZER_PIN = const(25)
# MPU6886 Sensor settings:
MPU6886_I2C_ADDR = const(0x68)
MPU6886_I2C_SCL = const(22)
MPU6886_I2C_SDA = const(21)
MPU6886_I2C_FREQ = const(400000)
2026-02-16 21:45:56 +01:00
print("m5stack_fire.py init buzzer")
buzzer = PWM(Pin(BUZZER_PIN, Pin.OUT, value=1), duty=5)
2026-02-24 16:39:26 +01:00
AudioManager.add(AudioManager.Output("buzzer", "buzzer", buzzer_pin=BUZZER_PIN))
2026-02-16 21:45:56 +01:00
AudioManager.set_volume(40)
2026-02-24 16:39:26 +01:00
player = AudioManager.player(
rtttl="Star Trek:o=4,d=20,b=200:8f.,a#,4d#6.,8d6,a#.,g.,c6.,4f6",
stream_type=AudioManager.STREAM_NOTIFICATION,
)
player.start()
while player.is_playing():
2026-02-16 21:45:56 +01:00
time.sleep(0.1)
print("m5stack_fire.py init IMU")
i2c_bus = I2C(0, scl=Pin(MPU6886_I2C_SCL), sda=Pin(MPU6886_I2C_SDA), freq=MPU6886_I2C_FREQ)
SensorManager.init(
i2c_bus=i2c_bus,
address=MPU6886_I2C_ADDR,
mounted_position=SensorManager.FACING_EARTH,
)
2026-02-16 21:45:56 +01:00
print("m5stack_fire.py machine.SPI.Bus() initialization")
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()
display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=LCD_DC, cs=LCD_CS)
2026-02-07 17:32:31 +01:00
2026-02-08 11:38:12 +01:00
# 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
2026-02-16 21:45:56 +01:00
0x80 | 0x20, # _MADCTL_MY | _MADCTL_MV
2026-02-08 11:38:12 +01:00
)
2026-02-16 21:45:56 +01:00
2026-02-08 11:38:12 +01:00
mpos.ui.main_display = ILI9341(
2026-02-07 17:32:31 +01:00
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,
2026-02-16 21:45:56 +01:00
backlight_on_state=ili9341.STATE_PWM,
2026-02-07 17:32:31 +01:00
)
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:
2026-02-16 21:45:56 +01:00
btn_a = Pin(BUTTON_A, Pin.IN, Pin.PULL_UP) # A
btn_b = Pin(BUTTON_B, Pin.IN, Pin.PULL_UP) # B
btn_c = Pin(BUTTON_C, Pin.IN, Pin.PULL_UP) # C
2026-02-07 17:32:31 +01:00
# 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
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")