Skip to content

Commit 2ef3b1c

Browse files
Merge branch 'm5stack-fire' of https://github.com/ancebfer/MicroPythonOS into ancebfer-m5stack-fire
2 parents 7d83209 + f355ddf commit 2ef3b1c

3 files changed

Lines changed: 174 additions & 14 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Hardware initialization for ESP32 M5Stack-Fire board
2+
# Manufacturer's website at https://https://docs.m5stack.com/en/core/fire_v2.7
3+
import ili9341
4+
import lcd_bus
5+
import machine
6+
7+
import lvgl as lv
8+
import task_handler
9+
10+
import mpos.ui
11+
import mpos.ui.focus_direction
12+
from mpos import InputManager
13+
14+
# Pin configuration
15+
SPI_BUS = 1 # SPI2
16+
SPI_FREQ = 40000000
17+
LCD_SCLK = 18
18+
LCD_MOSI = 23
19+
LCD_DC = 27
20+
LCD_CS = 14
21+
LCD_BL = 32
22+
LCD_RST = 33
23+
LCD_TYPE = 2 # ILI9341 type 2
24+
25+
TFT_HOR_RES=320
26+
TFT_VER_RES=240
27+
28+
spi_bus = machine.SPI.Bus(
29+
host=SPI_BUS,
30+
mosi=LCD_MOSI,
31+
sck=LCD_SCLK
32+
)
33+
display_bus = lcd_bus.SPIBus(
34+
spi_bus=spi_bus,
35+
freq=SPI_FREQ,
36+
dc=LCD_DC,
37+
cs=LCD_CS
38+
)
39+
40+
# M5Stack-Fire ILI9342 uses ILI9341 type 2 with a modified orientation table.
41+
class ILI9341(ili9341.ILI9341):
42+
_ORIENTATION_TABLE = (
43+
0x00,
44+
0x40 | 0x20, # _MADCTL_MX | _MADCTL_MV
45+
0x80 | 0x40, # _MADCTL_MY | _MADCTL_MX
46+
0x80 | 0x20 # _MADCTL_MY | _MADCTL_MV
47+
)
48+
49+
mpos.ui.main_display = ILI9341(
50+
data_bus=display_bus,
51+
display_width=TFT_HOR_RES,
52+
display_height=TFT_VER_RES,
53+
color_space=lv.COLOR_FORMAT.RGB565,
54+
color_byte_order=ili9341.BYTE_ORDER_BGR,
55+
rgb565_byte_swap=True,
56+
reset_pin=LCD_RST,
57+
reset_state=ili9341.STATE_LOW,
58+
backlight_pin=LCD_BL,
59+
backlight_on_state=ili9341.STATE_PWM
60+
)
61+
mpos.ui.main_display.init(LCD_TYPE)
62+
mpos.ui.main_display.set_power(True)
63+
mpos.ui.main_display.set_color_inversion(True)
64+
mpos.ui.main_display.set_backlight(25)
65+
66+
lv.init()
67+
68+
# Button handling code:
69+
from machine import Pin
70+
import time
71+
72+
btn_a = Pin(39, Pin.IN, Pin.PULL_UP) # A
73+
btn_b = Pin(38, Pin.IN, Pin.PULL_UP) # B
74+
btn_c = Pin(37, Pin.IN, Pin.PULL_UP) # C
75+
76+
# Key repeat configuration
77+
# This whole debounce logic is only necessary because LVGL 9.2.2 seems to have an issue where
78+
# the lv_keyboard widget doesn't handle PRESSING (long presses) properly, it loses focus.
79+
REPEAT_INITIAL_DELAY_MS = 300 # Delay before first repeat
80+
REPEAT_RATE_MS = 100 # Interval between repeats
81+
last_key = None
82+
last_state = lv.INDEV_STATE.RELEASED
83+
key_press_start = 0 # Time when key was first pressed
84+
last_repeat_time = 0 # Time of last repeat event
85+
86+
# Read callback
87+
# Warning: This gets called several times per second, and if it outputs continuous debugging on the serial line,
88+
# that will break tools like mpremote from working properly to upload new files over the serial line, thus needing a reflash.
89+
def keypad_read_cb(indev, data):
90+
global last_key, last_state, key_press_start, last_repeat_time
91+
since_last_repeat = 0
92+
93+
# Check buttons
94+
current_key = None
95+
current_time = time.ticks_ms()
96+
if btn_a.value() == 0:
97+
current_key = lv.KEY.PREV
98+
elif btn_b.value() == 0:
99+
current_key = lv.KEY.ENTER
100+
elif btn_c.value() == 0:
101+
current_key = lv.KEY.NEXT
102+
103+
if (btn_a.value() == 0) and (btn_c.value() == 0):
104+
current_key = lv.KEY.ESC
105+
106+
# Key repeat logic
107+
if current_key:
108+
if current_key != last_key:
109+
# New key press
110+
data.key = current_key
111+
data.state = lv.INDEV_STATE.PRESSED
112+
last_key = current_key
113+
last_state = lv.INDEV_STATE.PRESSED
114+
key_press_start = current_time
115+
last_repeat_time = current_time
116+
else: # same key
117+
# Key held: Check for repeat
118+
elapsed = time.ticks_diff(current_time, key_press_start)
119+
since_last_repeat = time.ticks_diff(current_time, last_repeat_time)
120+
if elapsed >= REPEAT_INITIAL_DELAY_MS and since_last_repeat >= REPEAT_RATE_MS:
121+
# Send a new PRESSED/RELEASED pair for repeat
122+
data.key = current_key
123+
data.state = lv.INDEV_STATE.PRESSED if last_state == lv.INDEV_STATE.RELEASED else lv.INDEV_STATE.RELEASED
124+
last_state = data.state
125+
last_repeat_time = current_time
126+
else:
127+
# No repeat yet, send RELEASED to avoid PRESSING
128+
data.state = lv.INDEV_STATE.RELEASED
129+
last_state = lv.INDEV_STATE.RELEASED
130+
else:
131+
# No key pressed
132+
data.key = last_key if last_key else lv.KEY.ENTER
133+
data.state = lv.INDEV_STATE.RELEASED
134+
last_key = None
135+
last_state = lv.INDEV_STATE.RELEASED
136+
key_press_start = 0
137+
last_repeat_time = 0
138+
139+
# Handle ESC for back navigation (only on initial PRESSED)
140+
if last_state == lv.INDEV_STATE.PRESSED:
141+
if current_key == lv.KEY.ESC and since_last_repeat == 0:
142+
mpos.ui.back_screen()
143+
144+
group = lv.group_create()
145+
group.set_default()
146+
147+
# Create and set up the input device
148+
indev = lv.indev_create()
149+
indev.set_type(lv.INDEV_TYPE.KEYPAD)
150+
indev.set_read_cb(keypad_read_cb)
151+
indev.set_group(group) # is this needed? maybe better to move the default group creation to main.py so it's available everywhere...
152+
disp = lv.display_get_default() # NOQA
153+
indev.set_display(disp) # different from display
154+
indev.enable(True) # NOQA
155+
InputManager.register_indev(indev)
156+
157+
print("m5stack_fire.py finished")

internal_filesystem/lib/mpos/main.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,33 +88,30 @@ def detect_board():
8888
if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS
8989
return "linux"
9090
elif sys.platform == "esp32":
91-
print("Detecting ESP32 board by scanning I2C addresses...")
9291

9392
print("matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?")
9493
if i2c0 := fail_save_i2c(sda=39, scl=38):
95-
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(
96-
i2c0, 0x5D
97-
):
98-
# "ghost" or real GT911 touch screen
94+
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen
9995
return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660"
10096

10197
print("waveshare_esp32_s3_touch_lcd_2 ?")
10298
if i2c0 := fail_save_i2c(sda=48, scl=47):
103-
# IO48 is floating on matouch and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660
104-
if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(
105-
i2c0, 0x6B
106-
):
107-
# CST816S touch screen and IMU
99+
# IO48 is floating on matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660
100+
if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(i2c0, 0x6B): # CST816S touch screen and IMU
108101
return "waveshare_esp32_s3_touch_lcd_2"
109102

103+
print("m5stack_fire ?")
104+
if i2c0 := fail_save_i2c(sda=21, scl=22):
105+
if single_address_i2c_scan(i2c0, 0x68): # IMU (MPU6886)
106+
return "m5stack_fire"
107+
110108
print("odroid_go ?")
111109
if check_pins(0, 13, 27, 39):
112110
return "odroid_go"
113111

114112
print("fri3d_2024 ?")
115113
if i2c0 := fail_save_i2c(sda=9, scl=18):
116-
# IMU (plus possibly the Communicator's LANA TNY at 0x38)
117-
if single_address_i2c_scan(i2c0, 0x6B):
114+
if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38)
118115
return "fri3d_2024"
119116

120117
print("Fallback to fri3d_2026")

tests/test_sensor_manager.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,14 @@ def _mock_mcu_temperature(*args, **kwargs):
159159

160160
# Inject mocks into sys.modules
161161
sys.modules['machine'] = mock_machine
162-
sys.modules['mpos.hardware.drivers.qmi8658'] = mock_qmi8658
163-
sys.modules['mpos.hardware.drivers.wsen_isds'] = mock_wsen_isds
162+
163+
# Mock parent packages for driver imports
164+
# These need to exist for the import path to work
165+
sys.modules['drivers'] = type('module', (), {})()
166+
sys.modules['drivers.imu_sensor'] = type('module', (), {})()
167+
168+
sys.modules['drivers.imu_sensor.qmi8658'] = mock_qmi8658
169+
sys.modules['drivers.imu_sensor.wsen_isds'] = mock_wsen_isds
164170
sys.modules['esp32'] = mock_esp32
165171
sys.modules['mpos.config'] = mock_config
166172

0 commit comments

Comments
 (0)