diff --git a/.flake8 b/.flake8 index aa524c2..5dad6e7 100644 --- a/.flake8 +++ b/.flake8 @@ -9,6 +9,8 @@ builtins = pen, tick, init, + start, + quit, _reset, _logo, play, diff --git a/micropython/examples/picosystem/colour.py b/micropython/examples/picosystem/colour.py index 07c9b88..9ff547e 100644 --- a/micropython/examples/picosystem/colour.py +++ b/micropython/examples/picosystem/colour.py @@ -143,6 +143,4 @@ def draw(tick): prepare_rgb_palette() - -while True: - tick() +start() diff --git a/micropython/examples/picosystem/launcher.py b/micropython/examples/picosystem/launcher.py index 4e04922..b0cc652 100644 --- a/micropython/examples/picosystem/launcher.py +++ b/micropython/examples/picosystem/launcher.py @@ -13,7 +13,7 @@ notes = [ - (None, 100), ("G6", 10), ("E6", 30), ("A6", 10), ("G6", 30), (None, 50), ("B7", 1), ("C7", 1) + (None, 50), ("G6", 5), ("E6", 15), ("A6", 5), ("G6", 15), (None, 25), ("B7", 1), ("C7", 1) ] intro = Voice() intro.envelope(attack=50, decay=10, sustain=70, release=2000) @@ -27,12 +27,14 @@ if "colour.py" not in files: files.append("colour") +# HACK to add a quick menu item +files.append("__quit__") + filecount = len(files) target_angle = 0 current_angle = 0 selected = 0 -running = True blip = Voice(10, 10, 10, 10, 40, 2) @@ -51,32 +53,32 @@ def update_melody(tick): last_note = tick note, note_duration = notes[note_idx] if note: - intro.play(note, note_duration * 4) + intro.play(note, note_duration * 8) note_idx += 1 if note_idx >= len(notes): intro_melody = False def update(tick): - global selected, target_angle, running + global selected, target_angle if intro_melody: update_melody(tick) + else: + if pressed(LEFT): + selected -= 1 + blip.play(1600, 30, 100) - if pressed(LEFT): - selected -= 1 - blip.play(1600, 30, 100) - - if pressed(RIGHT): - selected += 1 - blip.play(1800, 30, 100) + if pressed(RIGHT): + selected += 1 + blip.play(1800, 30, 100) - if pressed(A): - ding.play(880, 30, 100) - running = False + if pressed(A): + ding.play(880, 30, 100) + quit() - selected %= filecount - target_angle = -get_item_angle(selected) + selected %= filecount + target_angle = -get_item_angle(selected) if tick <= 75: backlight(tick) @@ -123,15 +125,17 @@ def draw(tick): ) # centre name of file at bottom of screen - label_width = text_width(files[selected]) + label = files[selected] + if label == "__quit__": + label = "quit" + label_width = text_width(label) pen(11, 11, 8) frect(int(60 - label_width / 2 - 3), 102 - 3, label_width + 6, 13) pen(0, 0, 0) - text(files[selected], int(60 - (label_width / 2)), 102) + text(label, int(60 - (label_width / 2)), 102) -while running: - tick() +start() # Will unblock when "quit" is called __launch_file__ = files[selected] @@ -140,5 +144,5 @@ def draw(tick): del locals()[k] gc.collect() -_reset() -__import__(__launch_file__) +if __launch_file__ != "__quit__": + __import__(__launch_file__) diff --git a/micropython/examples/picosystem/shapes.py b/micropython/examples/picosystem/shapes.py index df8f3cb..d584a4b 100644 --- a/micropython/examples/picosystem/shapes.py +++ b/micropython/examples/picosystem/shapes.py @@ -147,5 +147,4 @@ def draw(tick): reset() -while True: - tick() +start() diff --git a/micropython/examples/picosystem/sprites.py b/micropython/examples/picosystem/sprites.py index b65c34e..06ae118 100644 --- a/micropython/examples/picosystem/sprites.py +++ b/micropython/examples/picosystem/sprites.py @@ -88,6 +88,4 @@ def draw(tick): text(weapons[selected][1], int(60 - (label_width / 2)), 102) -while True: - tick() - time.sleep(1.0 / 50) +start() diff --git a/micropython/examples/picosystem/spritesheets.py b/micropython/examples/picosystem/spritesheets.py index 87d3309..c4b85bf 100644 --- a/micropython/examples/picosystem/spritesheets.py +++ b/micropython/examples/picosystem/spritesheets.py @@ -32,5 +32,4 @@ def draw(tick): sprite(x + y * int(120 / 8), x * 8, y * 8) -while True: - tick() +start() diff --git a/micropython/examples/picosystem/test.py b/micropython/examples/picosystem/test.py index 64514fb..f7f0ce4 100644 --- a/micropython/examples/picosystem/test.py +++ b/micropython/examples/picosystem/test.py @@ -40,5 +40,4 @@ def draw(tick): line(110, 10, 10, 110) -while True: - tick() +start() diff --git a/micropython/modules/picosystem/micropython.cmake b/micropython/modules/picosystem/micropython.cmake index 03db55d..5e7384a 100644 --- a/micropython/modules/picosystem/micropython.cmake +++ b/micropython/modules/picosystem/micropython.cmake @@ -31,7 +31,6 @@ target_include_directories(usermod_${MOD_NAME} INTERFACE target_compile_definitions(usermod_${MOD_NAME} INTERFACE MODULE_${MOD_NAME_UPPER}_ENABLED=1 - DYNAMIC_BUFFER=1 PIXEL_DOUBLE=1 ) diff --git a/micropython/modules/picosystem/picosystem.c b/micropython/modules/picosystem/picosystem.c index beec268..884ab04 100755 --- a/micropython/modules/picosystem/picosystem.c +++ b/micropython/modules/picosystem/picosystem.c @@ -69,6 +69,8 @@ MP_DEFINE_CONST_FUN_OBJ_0(picosystem_init_obj, picosystem_init); MP_DEFINE_CONST_FUN_OBJ_0(picosystem_reset_obj, picosystem_reset); MP_DEFINE_CONST_FUN_OBJ_0(picosystem_tick_obj, picosystem_tick); MP_DEFINE_CONST_FUN_OBJ_0(picosystem_logo_obj, picosystem_logo); +MP_DEFINE_CONST_FUN_OBJ_0(picosystem_run_obj, picosystem_run); +MP_DEFINE_CONST_FUN_OBJ_0(picosystem_quit_obj, picosystem_quit); // stats.cpp diff --git a/micropython/modules/picosystem/picosystem.cpp b/micropython/modules/picosystem/picosystem.cpp index 78b8149..a7f72e5 100644 --- a/micropython/modules/picosystem/picosystem.cpp +++ b/micropython/modules/picosystem/picosystem.cpp @@ -12,6 +12,7 @@ extern "C" { #include "picosystem.h" #include "math.h" #include "cstring" +#include "py/gc.h" //#define NOT_INITIALISED_MSG "Cannot call this function, as picodisplay is not initialised. Call picodisplay.init() first." @@ -30,6 +31,7 @@ extern "C" { uint32_t update_rate_ms = 10; uint32_t pending_update_ms = 0; uint32_t last_ms = time(); +bool running = true; uint32_t tick = 0; @@ -57,8 +59,10 @@ mp_obj_t pimoroni_mp_load_global(qstr qst) { } mp_obj_t picosystem_reset() { + running = false; update_callback_obj = mp_const_none; draw_callback_obj = mp_const_none; + tick = 0; return mp_const_none; } @@ -81,8 +85,8 @@ mp_obj_t picosystem_logo() { mp_obj_t picosystem_init() { - MP_STATE_PORT(picosystem_framebuffer) = m_new(color_t, 120 * 120); - SCREEN = buffer(120, 120, MP_STATE_PORT(picosystem_framebuffer)); + //MP_STATE_PORT(picosystem_framebuffer) = m_new(color_t, 120 * 120); + //SCREEN = buffer(120, 120, MP_STATE_PORT(picosystem_framebuffer)); target(SCREEN); update_callback_obj = mp_const_none; @@ -132,6 +136,95 @@ mp_obj_t picosystem_init() { return mp_const_none; } +mp_obj_t picosystem_run() { + if(update_callback_obj == mp_const_none) { + update_callback_obj = pimoroni_mp_load_global(qstr_from_str("update")); + if(update_callback_obj == mp_const_none) { + //TODO switch out this URL for the final one + mp_raise_msg(&mp_type_NameError, "a function named 'update(ticks)' is not defined. Check out https://github.com/pimoroni/picosystem/blob/main/micropython/README.md for instructions"); + } + } + + if(draw_callback_obj == mp_const_none) { + draw_callback_obj = mp_load_global(qstr_from_str("draw")); + if(draw_callback_obj == mp_const_none) { + //TODO switch out this URL for the final one + mp_raise_msg(&mp_type_NameError, "a function named 'draw()' is not defined. Check out https://github.com/pimoroni/picosystem/blob/main/micropython/README.md for instructions"); + } + } + + _io = _gpio_get(); + running = true; + + while(running) { + uint32_t start_tick_us = time_us(); + + // store previous io state and get new io state + _lio = _io; + _io = _gpio_get(); + + // call users update() function + uint32_t start_update_us = time_us(); + mp_call_function_1(update_callback_obj, mp_obj_new_int(tick++)); + stats.update_us = time_us() - start_update_us; + + // if we're currently transferring the the framebuffer to the screen then + // wait until that is complete before allowing the user to do their drawing + uint32_t wait_us = 0; + uint32_t start_wait_flip_us = time_us(); + while(_is_flipping()) { + best_effort_wfe_or_timeout(make_timeout_time_us(500)); + } + wait_us += time_us() - start_wait_flip_us; + + // call user render function to draw world + uint32_t start_draw_us = time_us(); + mp_call_function_1(draw_callback_obj, mp_obj_new_int(tick)); + stats.draw_us = time_us() - start_draw_us; + + // wait for the screen to vsync before triggering flip + // to ensure no tearing + uint32_t start_wait_vsync_us = time_us(); + _wait_vsync(); + wait_us += time_us() - start_wait_vsync_us; + + // flip the framebuffer to the screen + _flip(); + + // force a per-frame gc.collect() to avoid stutter + // cost on the order of microseconds. + gc_collect(); + + stats.tick_us = time_us() - start_tick_us; + + // calculate fps and round to nearest value (instead of truncating/floor) + stats.fps = (1000000 - 1) / stats.tick_us + 1; + + if(stats.fps > 40) { + // if fps is high enough then we definitely didn't miss vsync + stats.idle = (wait_us * 100) / stats.tick_us; + }else{ + // if we missed vsync then we overran the frame time and hence had + // no idle time + stats.idle = 0; + } + + MICROPY_EVENT_POLL_HOOK + + } + + update_callback_obj = mp_const_none; + draw_callback_obj = mp_const_none; + tick = 0; + + return mp_const_none; +} + +mp_obj_t picosystem_quit(){ + running = false; + return mp_const_none; +} + mp_obj_t picosystem_tick() { uint32_t start_tick_us = time_us(); @@ -164,7 +257,9 @@ mp_obj_t picosystem_tick() { // wait until that is complete before allowing the user to do their drawing uint32_t wait_us = 0; uint32_t start_wait_flip_us = time_us(); - while(_is_flipping()) {} + while(_is_flipping()) { + MICROPY_EVENT_POLL_HOOK + } wait_us += time_us() - start_wait_flip_us; // call user render function to draw world diff --git a/micropython/modules/picosystem/picosystem.h b/micropython/modules/picosystem/picosystem.h index ddf1881..6f3a6dc 100644 --- a/micropython/modules/picosystem/picosystem.h +++ b/micropython/modules/picosystem/picosystem.h @@ -57,6 +57,8 @@ extern mp_obj_t picosystem_init(); extern mp_obj_t picosystem_tick(); extern mp_obj_t picosystem_reset(); extern mp_obj_t picosystem_logo(); +extern mp_obj_t picosystem_run(); +extern mp_obj_t picosystem_quit(); // stats.cpp extern mp_obj_t picosystem_stats();