|
| 1 | +print("odroid_go.py initialization") |
| 2 | + |
| 3 | +# Hardware initialization for Hardkernel ODROID-Go |
| 4 | +# https://github.com/hardkernel/ODROID-GO/ |
| 5 | +# https://wiki.odroid.com/odroid_go/odroid_go |
| 6 | + |
| 7 | +import time |
| 8 | + |
| 9 | +import ili9341 |
| 10 | +import lcd_bus |
| 11 | +import lvgl as lv |
| 12 | +import machine |
| 13 | +import mpos.ui |
| 14 | +from machine import ADC, Pin |
| 15 | +from micropython import const |
| 16 | +from mpos import InputManager |
| 17 | + |
| 18 | +# Display settings: |
| 19 | +SPI_HOST = const(1) |
| 20 | +SPI_FREQ = const(40000000) |
| 21 | + |
| 22 | +LCD_SCLK = const(18) |
| 23 | +LCD_MOSI = const(23) |
| 24 | +LCD_DC = const(21) |
| 25 | +LCD_CS = const(5) |
| 26 | +LCD_BL = const(32) |
| 27 | +LCD_RST = const(33) |
| 28 | +LCD_TYPE = const(2) # ILI9341 type 2 |
| 29 | + |
| 30 | +TFT_VER_RES = const(320) |
| 31 | +TFT_HOR_RES = const(240) |
| 32 | + |
| 33 | + |
| 34 | +# Button settings: |
| 35 | +BUTTON_MENU = const(13) |
| 36 | +BUTTON_VOLUME = const(0) |
| 37 | +BUTTON_SELECT = const(27) |
| 38 | +BUTTON_START = const(39) |
| 39 | + |
| 40 | +BUTTON_B = const(33) |
| 41 | +BUTTON_A = const(32) |
| 42 | + |
| 43 | +# The crossbar pin numbers: |
| 44 | +CROSSBAR_X = const(34) |
| 45 | +CROSSBAR_Y = const(35) |
| 46 | + |
| 47 | + |
| 48 | +# Misc settings: |
| 49 | +LED_BLUE = const(2) |
| 50 | +BATTERY_PIN = const(36) |
| 51 | +BATTERY_RESISTANCE_NUM = const(2) |
| 52 | +SPEAKER_ENABLE_PIN = const(25) |
| 53 | +SPEAKER_PIN = const(26) |
| 54 | + |
| 55 | + |
| 56 | +print("odroid_go.py turn on blue LED") |
| 57 | +blue_led = machine.Pin(LED_BLUE, machine.Pin.OUT) |
| 58 | +blue_led.on() |
| 59 | + |
| 60 | + |
| 61 | +print("odroid_go.py machine.SPI.Bus() initialization") |
| 62 | +try: |
| 63 | + spi_bus = machine.SPI.Bus(host=SPI_HOST, mosi=LCD_MOSI, sck=LCD_SCLK) |
| 64 | +except Exception as e: |
| 65 | + print(f"Error initializing SPI bus: {e}") |
| 66 | + print("Attempting hard reset in 3sec...") |
| 67 | + time.sleep(3) |
| 68 | + machine.reset() |
| 69 | + |
| 70 | +print("odroid_go.py lcd_bus.SPIBus() initialization") |
| 71 | +display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=LCD_DC, cs=LCD_CS) |
| 72 | + |
| 73 | +print("odroid_go.py ili9341.ILI9341() initialization") |
| 74 | +try: |
| 75 | + mpos.ui.main_display = ili9341.ILI9341( |
| 76 | + data_bus=display_bus, |
| 77 | + display_width=TFT_HOR_RES, |
| 78 | + display_height=TFT_VER_RES, |
| 79 | + color_space=lv.COLOR_FORMAT.RGB565, |
| 80 | + color_byte_order=ili9341.BYTE_ORDER_BGR, |
| 81 | + rgb565_byte_swap=True, |
| 82 | + reset_pin=LCD_RST, |
| 83 | + reset_state=ili9341.STATE_LOW, |
| 84 | + backlight_pin=LCD_BL, |
| 85 | + backlight_on_state=ili9341.STATE_PWM, |
| 86 | + ) |
| 87 | +except Exception as e: |
| 88 | + print(f"Error initializing ILI9341: {e}") |
| 89 | + print("Attempting hard reset in 3sec...") |
| 90 | + time.sleep(3) |
| 91 | + machine.reset() |
| 92 | + |
| 93 | +print("odroid_go.py display.init()") |
| 94 | +mpos.ui.main_display.init(type=LCD_TYPE) |
| 95 | +mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._270) |
| 96 | +mpos.ui.main_display.set_power(True) |
| 97 | +mpos.ui.main_display.set_color_inversion(False) |
| 98 | +mpos.ui.main_display.set_backlight(25) |
| 99 | + |
| 100 | +print("odroid_go.py lv.init() initialization") |
| 101 | +lv.init() |
| 102 | + |
| 103 | + |
| 104 | +print("odroid_go.py Battery initialization...") |
| 105 | +from mpos import BatteryManager |
| 106 | + |
| 107 | + |
| 108 | +def adc_to_voltage(adc_value): |
| 109 | + return adc_value * BATTERY_RESISTANCE_NUM |
| 110 | + |
| 111 | + |
| 112 | +BatteryManager.init_adc(BATTERY_PIN, adc_to_voltage) |
| 113 | + |
| 114 | + |
| 115 | +print("odroid_go.py button initialization...") |
| 116 | + |
| 117 | +button_menu = Pin(BUTTON_MENU, Pin.IN, Pin.PULL_UP) |
| 118 | +button_volume = Pin(BUTTON_VOLUME, Pin.IN, Pin.PULL_UP) |
| 119 | +button_select = Pin(BUTTON_SELECT, Pin.IN, Pin.PULL_UP) |
| 120 | +button_start = Pin(BUTTON_START, Pin.IN, Pin.PULL_UP) # -> ENTER |
| 121 | + |
| 122 | +# PREV <- B | A -> NEXT |
| 123 | +button_b = Pin(BUTTON_B, Pin.IN, Pin.PULL_UP) |
| 124 | +button_a = Pin(BUTTON_A, Pin.IN, Pin.PULL_UP) |
| 125 | + |
| 126 | + |
| 127 | +class CrossbarHandler: |
| 128 | + # ADC values are around low: ~236 and high ~511 |
| 129 | + # So the mid value is around (236+511)/2 = 373.5 |
| 130 | + CROSSBAR_MIN_ADC_LOW = const(100) |
| 131 | + CROSSBAR_MIN_ADC_MID = const(370) |
| 132 | + |
| 133 | + def __init__(self, pin, high_key, low_key): |
| 134 | + self.adc = ADC(Pin(pin, mode=Pin.IN)) |
| 135 | + self.adc.width(ADC.WIDTH_9BIT) |
| 136 | + self.adc.atten(ADC.ATTN_11DB) |
| 137 | + |
| 138 | + self.high_key = high_key |
| 139 | + self.low_key = low_key |
| 140 | + |
| 141 | + def poll(self): |
| 142 | + value = self.adc.read() |
| 143 | + if value > self.CROSSBAR_MIN_ADC_LOW: |
| 144 | + if value > self.CROSSBAR_MIN_ADC_MID: |
| 145 | + return self.high_key |
| 146 | + elif value < self.CROSSBAR_MIN_ADC_MID: |
| 147 | + return self.low_key |
| 148 | + |
| 149 | + |
| 150 | +class Crossbar: |
| 151 | + def __init__(self, *, up, down, left, right): |
| 152 | + self.joy_x = CrossbarHandler(CROSSBAR_X, high_key=left, low_key=right) |
| 153 | + self.joy_y = CrossbarHandler(CROSSBAR_Y, high_key=up, low_key=down) |
| 154 | + |
| 155 | + def poll(self): |
| 156 | + crossbar_pressed = self.joy_x.poll() or self.joy_y.poll() |
| 157 | + return crossbar_pressed |
| 158 | + |
| 159 | + |
| 160 | +# see: internal_filesystem/lib/mpos/indev/mpos_sdl_keyboard.py |
| 161 | +# lv.KEY.UP |
| 162 | +# lv.KEY.LEFT - lv.KEY.RIGHT |
| 163 | +# lv.KEY.DOWN |
| 164 | +# |
| 165 | +crossbar = Crossbar( |
| 166 | + up=lv.KEY.UP, down=lv.KEY.DOWN, left=lv.KEY.LEFT, right=lv.KEY.RIGHT |
| 167 | +) |
| 168 | + |
| 169 | +REPEAT_INITIAL_DELAY_MS = 300 # Delay before first repeat |
| 170 | +REPEAT_RATE_MS = 100 # Interval between repeats |
| 171 | +next_repeat = None # Used for auto-repeat key handling |
| 172 | + |
| 173 | + |
| 174 | +def input_callback(indev, data): |
| 175 | + global next_repeat |
| 176 | + |
| 177 | + current_key = None |
| 178 | + |
| 179 | + if crossbar_pressed := crossbar.poll(): |
| 180 | + current_key = crossbar_pressed |
| 181 | + |
| 182 | + elif button_menu.value() == 0: |
| 183 | + current_key = lv.KEY.ESC |
| 184 | + elif button_volume.value() == 0: |
| 185 | + print("Volume button pressed -> reset") |
| 186 | + machine.reset() |
| 187 | + elif button_select.value() == 0: |
| 188 | + current_key = lv.KEY.BACKSPACE |
| 189 | + elif button_start.value() == 0: |
| 190 | + current_key = lv.KEY.ENTER |
| 191 | + |
| 192 | + elif button_b.value() == 0: |
| 193 | + current_key = lv.KEY.PREV |
| 194 | + elif button_a.value() == 0: |
| 195 | + current_key = lv.KEY.NEXT |
| 196 | + else: |
| 197 | + # No crossbar/buttons pressed |
| 198 | + if data.key: # A key was previously pressed and now released |
| 199 | + # print(f"Key {data.key=} released") |
| 200 | + data.key = 0 |
| 201 | + data.state = lv.INDEV_STATE.RELEASED |
| 202 | + next_repeat = None |
| 203 | + blue_led.off() |
| 204 | + return |
| 205 | + |
| 206 | + # A key is currently pressed |
| 207 | + |
| 208 | + blue_led.on() # Blink on key press and auto repeat for feedback |
| 209 | + |
| 210 | + current_time = time.ticks_ms() |
| 211 | + repeat = current_time > next_repeat if next_repeat else False # Auto repeat? |
| 212 | + if repeat or current_key != data.key: |
| 213 | + print(f"Key {current_key} pressed {repeat=}") |
| 214 | + |
| 215 | + data.key = current_key |
| 216 | + data.state = lv.INDEV_STATE.PRESSED |
| 217 | + |
| 218 | + if current_key == lv.KEY.ESC: # Handle ESC for back navigation |
| 219 | + mpos.ui.back_screen() |
| 220 | + elif current_key == lv.KEY.RIGHT: |
| 221 | + mpos.ui.focus_direction.move_focus_direction(90) |
| 222 | + elif current_key == lv.KEY.LEFT: |
| 223 | + mpos.ui.focus_direction.move_focus_direction(270) |
| 224 | + elif current_key == lv.KEY.UP: |
| 225 | + mpos.ui.focus_direction.move_focus_direction(0) |
| 226 | + elif current_key == lv.KEY.DOWN: |
| 227 | + mpos.ui.focus_direction.move_focus_direction(180) |
| 228 | + |
| 229 | + if not repeat: |
| 230 | + # Initial press: Delay before first repeat |
| 231 | + next_repeat = current_time + REPEAT_INITIAL_DELAY_MS |
| 232 | + else: |
| 233 | + # Faster auto repeat after initial press |
| 234 | + next_repeat = current_time + REPEAT_RATE_MS |
| 235 | + blue_led.off() # Blink the LED, too |
| 236 | + |
| 237 | + |
| 238 | +group = lv.group_create() |
| 239 | +group.set_default() |
| 240 | + |
| 241 | +# Create and set up the input device |
| 242 | +indev = lv.indev_create() |
| 243 | +indev.set_type(lv.INDEV_TYPE.KEYPAD) |
| 244 | +indev.set_read_cb(input_callback) |
| 245 | +indev.set_group( |
| 246 | + group |
| 247 | +) # is this needed? maybe better to move the default group creation to main.py so it's available everywhere... |
| 248 | +disp = lv.display_get_default() # NOQA |
| 249 | +indev.set_display(disp) # different from display |
| 250 | +indev.enable(True) # NOQA |
| 251 | +InputManager.register_indev(indev) |
| 252 | + |
| 253 | +print("odroid_go.py finished") |
0 commit comments