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
253 changes: 253 additions & 0 deletions internal_filesystem/lib/mpos/board/odroid_go.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
print("odroid_go.py initialization")

# Hardware initialization for Hardkernel ODROID-Go
# https://github.com/hardkernel/ODROID-GO/
# https://wiki.odroid.com/odroid_go/odroid_go

import time

import ili9341
import lcd_bus
import lvgl as lv
import machine
import mpos.ui
from machine import ADC, Pin
from micropython import const
from mpos import InputManager

# Display settings:
SPI_HOST = const(1)
SPI_FREQ = const(40000000)

LCD_SCLK = const(18)
LCD_MOSI = const(23)
LCD_DC = const(21)
LCD_CS = const(5)
LCD_BL = const(32)
LCD_RST = const(33)
LCD_TYPE = const(2) # ILI9341 type 2

TFT_VER_RES = const(320)
TFT_HOR_RES = const(240)


# Button settings:
BUTTON_MENU = const(13)
BUTTON_VOLUME = const(0)
BUTTON_SELECT = const(27)
BUTTON_START = const(39)

BUTTON_B = const(33)
BUTTON_A = const(32)

# The crossbar pin numbers:
CROSSBAR_X = const(34)
CROSSBAR_Y = const(35)


# Misc settings:
LED_BLUE = const(2)
BATTERY_PIN = const(36)
BATTERY_RESISTANCE_NUM = const(2)
SPEAKER_ENABLE_PIN = const(25)
SPEAKER_PIN = const(26)


print("odroid_go.py turn on blue LED")
blue_led = machine.Pin(LED_BLUE, machine.Pin.OUT)
blue_led.on()


print("odroid_go.py machine.SPI.Bus() initialization")
try:
spi_bus = machine.SPI.Bus(host=SPI_HOST, 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()

print("odroid_go.py lcd_bus.SPIBus() initialization")
display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=LCD_DC, cs=LCD_CS)

print("odroid_go.py ili9341.ILI9341() initialization")
try:
mpos.ui.main_display = ili9341.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,
)
except Exception as e:
print(f"Error initializing ILI9341: {e}")
print("Attempting hard reset in 3sec...")
time.sleep(3)
machine.reset()

print("odroid_go.py display.init()")
mpos.ui.main_display.init(type=LCD_TYPE)
mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._270)
mpos.ui.main_display.set_power(True)
mpos.ui.main_display.set_color_inversion(False)
mpos.ui.main_display.set_backlight(25)

print("odroid_go.py lv.init() initialization")
lv.init()


print("odroid_go.py Battery initialization...")
from mpos import BatteryManager


def adc_to_voltage(adc_value):
return adc_value * BATTERY_RESISTANCE_NUM


BatteryManager.init_adc(BATTERY_PIN, adc_to_voltage)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get these values (while connected via USB):

--> from mpos import BatteryManager
--> BatteryManager.has_battery()
True
--> BatteryManager.get_battery_percentage()
100.0
--> BatteryManager.read_battery_voltage()
4884.8
--> BatteryManager.read_raw_adc()
2442.1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after a while running:

--> from mpos import BatteryManager
--> BatteryManager.get_battery_percentage()
100.0
--> BatteryManager.read_battery_voltage()
614.0
--> BatteryManager.read_raw_adc()
307.0
--> 

So i assume that 100% is wrong ;)



print("odroid_go.py button initialization...")

button_menu = Pin(BUTTON_MENU, Pin.IN, Pin.PULL_UP)
button_volume = Pin(BUTTON_VOLUME, Pin.IN, Pin.PULL_UP)
button_select = Pin(BUTTON_SELECT, Pin.IN, Pin.PULL_UP)
button_start = Pin(BUTTON_START, Pin.IN, Pin.PULL_UP) # -> ENTER

# PREV <- B | A -> NEXT
button_b = Pin(BUTTON_B, Pin.IN, Pin.PULL_UP)
button_a = Pin(BUTTON_A, Pin.IN, Pin.PULL_UP)


class CrossbarHandler:
# ADC values are around low: ~236 and high ~511
# So the mid value is around (236+511)/2 = 373.5
CROSSBAR_MIN_ADC_LOW = const(100)
CROSSBAR_MIN_ADC_MID = const(370)

def __init__(self, pin, high_key, low_key):
self.adc = ADC(Pin(pin, mode=Pin.IN))
self.adc.width(ADC.WIDTH_9BIT)
self.adc.atten(ADC.ATTN_11DB)

self.high_key = high_key
self.low_key = low_key

def poll(self):
value = self.adc.read()
if value > self.CROSSBAR_MIN_ADC_LOW:
if value > self.CROSSBAR_MIN_ADC_MID:
return self.high_key
elif value < self.CROSSBAR_MIN_ADC_MID:
return self.low_key


class Crossbar:
def __init__(self, *, up, down, left, right):
self.joy_x = CrossbarHandler(CROSSBAR_X, high_key=left, low_key=right)
self.joy_y = CrossbarHandler(CROSSBAR_Y, high_key=up, low_key=down)

def poll(self):
crossbar_pressed = self.joy_x.poll() or self.joy_y.poll()
return crossbar_pressed


# see: internal_filesystem/lib/mpos/indev/mpos_sdl_keyboard.py
# lv.KEY.UP
# lv.KEY.LEFT - lv.KEY.RIGHT
# lv.KEY.DOWN
#
crossbar = Crossbar(
up=lv.KEY.UP, down=lv.KEY.DOWN, left=lv.KEY.LEFT, right=lv.KEY.RIGHT
)

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
elif button_volume.value() == 0:
print("Volume button pressed -> reset")
machine.reset()
elif button_select.value() == 0:
current_key = lv.KEY.BACKSPACE
elif button_start.value() == 0:
current_key = lv.KEY.ENTER

elif button_b.value() == 0:
current_key = lv.KEY.PREV
elif button_a.value() == 0:
current_key = lv.KEY.NEXT
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
blue_led.off()
return

# A key is currently pressed

blue_led.on() # Blink on key press and auto repeat for feedback
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.

Nice :-D


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)
elif current_key == lv.KEY.LEFT:
mpos.ui.focus_direction.move_focus_direction(270)
elif current_key == lv.KEY.UP:
mpos.ui.focus_direction.move_focus_direction(0)
elif current_key == lv.KEY.DOWN:
mpos.ui.focus_direction.move_focus_direction(180)

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
blue_led.off() # Blink the LED, too


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)

print("odroid_go.py finished")
83 changes: 65 additions & 18 deletions internal_filesystem/lib/mpos/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,38 +39,85 @@ def single_address_i2c_scan(i2c_bus, address):
Returns:
True if a device responds at the specified address, False otherwise
"""
print(f"Attempt to write a single byte to I2C bus address 0x{address:02x}...")
try:
# Attempt to write a single byte to the address
# This will raise an exception if no device responds
i2c_bus.writeto(address, b'')
i2c_bus.writeto(address, b"")
print("Write test successful")
return True
except OSError:
# No device at this address
except OSError as e:
print(f"No device at this address: {e}")
return False
except Exception as e:
# Handle any other exceptions gracefully
print(f"single_address_i2c_scan: error scanning address 0x{address:02x}: {e}")
print(f"scan error: {e}")
return False


def fail_save_i2c(sda, scl):
from machine import I2C, Pin

print(f"Try to I2C initialized on {sda=} {scl=}")
try:
i2c0 = I2C(0, sda=Pin(sda), scl=Pin(scl))
except Exception as e:
print(f"Failed: {e}")
return None
else:
print("OK")
return i2c0


def check_pins(*pins):
from machine import Pin

print(f"Test {pins=}...")
for pin in pins:
try:
Pin(pin)
except Exception as e:
print(f"Failed to initialize {pin=}: {e}")
return True
print("All pins initialized successfully")
return True


def detect_board():
import sys
if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS
return "linux"
elif sys.platform == "esp32":
from machine import Pin, I2C

i2c0 = I2C(0, sda=Pin(39), scl=Pin(38))
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen
return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660"

i2c0 = I2C(0, sda=Pin(48), scl=Pin(47)) # 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
if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(i2c0, 0x6B): # CST816S touch screen and IMU
return "waveshare_esp32_s3_touch_lcd_2"

i2c0 = I2C(0, sda=Pin(9), scl=Pin(18))
if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38)
return "fri3d_2024"

print("Detecting ESP32 board by scanning I2C addresses...")

print("matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?")
if i2c0 := fail_save_i2c(sda=39, scl=38):
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(
i2c0, 0x5D
):
# "ghost" or real GT911 touch screen
return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660"

print("waveshare_esp32_s3_touch_lcd_2 ?")
if i2c0 := fail_save_i2c(sda=48, scl=47):
# 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
if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(
i2c0, 0x6B
):
# CST816S touch screen and IMU
return "waveshare_esp32_s3_touch_lcd_2"

print("odroid_go ?")
if check_pins(0, 13, 27, 39):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played a little bit around and this seems to work. Looks like:

Image

return "odroid_go"

print("fri3d_2024 ?")
if i2c0 := fail_save_i2c(sda=9, scl=18):
# IMU (plus possibly the Communicator's LANA TNY at 0x38)
if single_address_i2c_scan(i2c0, 0x6B):
return "fri3d_2024"

print("Fallback to fri3d_2026")
# default: if single_address_i2c_scan(i2c0, 0x6A): # IMU but currently not installed
return "fri3d_2026"

Expand Down
26 changes: 23 additions & 3 deletions internal_filesystem/main.py
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes can also be move to a separate PR.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,28 @@

# Make sure the storage partition's lib/ is first in the path, so whatever is placed there overrides frozen libraries.
# This allows any build to be used for development as well, just by overriding the libraries in lib/
import gc
import os
import sys
sys.path.insert(0, 'lib')

print(f"Minimal main.py importing mpos.main with sys.path: {sys.path}")
import mpos.main
sys.path.insert(0, "lib")

print(f"{sys.version=}")
print(f"{sys.implementation=}")


print("Check free space on root filesystem:")
stat = os.statvfs("/")
total_space = stat[0] * stat[2]
free_space = stat[0] * stat[3]
used_space = total_space - free_space
print(f"{total_space=} / {used_space=} / {free_space=} bytes")


gc.collect()
print(
f"RAM: {gc.mem_free()} free, {gc.mem_alloc()} allocated, {gc.mem_alloc() + gc.mem_free()} total"
)

print("Passing execution over to mpos.main")
import mpos.main # noqa: F401
2 changes: 2 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[format]
quote-style = "double"
Loading
Loading