diff --git a/drivers/uc8151/uc8151.cpp b/drivers/uc8151/uc8151.cpp index 249ca3d95..5b7fd0993 100644 --- a/drivers/uc8151/uc8151.cpp +++ b/drivers/uc8151/uc8151.cpp @@ -323,6 +323,8 @@ namespace pimoroni { void UC8151::setup(uint8_t speed) { reset(); + _update_speed = speed; + if(speed == 0) { command(PSR, { RES_128x296 | LUT_OTP | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE @@ -468,6 +470,25 @@ namespace pimoroni { setup(speed); } + uint8_t UC8151::update_speed() { + return _update_speed; + } + + uint32_t UC8151::update_time() { + switch(_update_speed) { + case 0: + return 5500; + case 1: + return 2600; + case 2: + return 1000; + case 3: + return 300; + default: + return 5500; + } + } + void UC8151::partial_update(int x, int y, int w, int h, bool blocking) { // y is given in columns ("banks"), which are groups of 8 horiontal pixels // x is given in pixels diff --git a/drivers/uc8151/uc8151.hpp b/drivers/uc8151/uc8151.hpp index fb60c5a4b..051c33f34 100644 --- a/drivers/uc8151/uc8151.hpp +++ b/drivers/uc8151/uc8151.hpp @@ -146,6 +146,8 @@ namespace pimoroni { bool inverted = false; + uint8_t _update_speed = 0; + public: UC8151(uint16_t width, uint16_t height) : width(width), height(height), frame_buffer(new uint8_t[width * height / 8]) { @@ -199,6 +201,8 @@ namespace pimoroni { void invert(bool invert); void update_speed(uint8_t speed); + uint8_t update_speed(); + uint32_t update_time(); void update(bool blocking = true); void partial_update(int x, int y, int w, int h, bool blocking = true); void off(); diff --git a/libraries/badger2040/badger2040.cpp b/libraries/badger2040/badger2040.cpp index 451fb0973..695a56d7e 100644 --- a/libraries/badger2040/badger2040.cpp +++ b/libraries/badger2040/badger2040.cpp @@ -40,7 +40,7 @@ namespace pimoroni { gpio_set_function(USER, GPIO_FUNC_SIO); gpio_set_dir(USER, GPIO_IN); - gpio_set_pulls(USER, false, true); + gpio_set_pulls(USER, true, false); gpio_set_function(VBUS_DETECT, GPIO_FUNC_SIO); gpio_set_dir(VBUS_DETECT, GPIO_IN); @@ -195,8 +195,9 @@ namespace pimoroni { } void Badger2040::update_button_states() { - uint32_t mask = (1UL << A) | (1UL << B) | (1UL << C) | (1UL << D) | (1UL << E); + uint32_t mask = (1UL << A) | (1UL << B) | (1UL << C) | (1UL << D) | (1UL << E) | (1UL << USER); _button_states = gpio_get_all() & mask; + _button_states ^= (1UL << USER); // USER button state is inverted } uint32_t Badger2040::button_states() { @@ -219,6 +220,10 @@ namespace pimoroni { uc8151.update_speed(speed); } + uint32_t Badger2040::update_time() { + return uc8151.update_time(); + } + void Badger2040::partial_update(int x, int y, int w, int h, bool blocking) { uc8151.partial_update(x, y, w, h, blocking); } diff --git a/libraries/badger2040/badger2040.hpp b/libraries/badger2040/badger2040.hpp index c640714c1..f9622d6f1 100644 --- a/libraries/badger2040/badger2040.hpp +++ b/libraries/badger2040/badger2040.hpp @@ -30,6 +30,7 @@ namespace pimoroni { void update(bool blocking=false); void partial_update(int x, int y, int w, int h, bool blocking=false); void update_speed(uint8_t speed); + uint32_t update_time(); void halt(); void sleep(); bool is_busy(); diff --git a/micropython/examples/badger2040/badge.py b/micropython/examples/badger2040/badge.py index fdeae159b..a72a674bc 100644 --- a/micropython/examples/badger2040/badge.py +++ b/micropython/examples/badger2040/badge.py @@ -1,6 +1,6 @@ -import badger2040 -import machine import time +import badger2040 +import badger_os # Global Constants WIDTH = badger2040.WIDTH @@ -20,10 +20,6 @@ NAME_PADDING = 20 DETAIL_SPACING = 10 -OVERLAY_BORDER = 40 -OVERLAY_SPACING = 20 -OVERLAY_TEXT_SIZE = 0.6 - DEFAULT_TEXT = """mustelid inc H. Badger RP2040 @@ -63,42 +59,6 @@ def truncatestring(text, text_size, width): # Drawing functions # ------------------------------ -# Draw an overlay box with a given message within it -def draw_overlay(message, width, height, line_spacing, text_size): - - # Draw a light grey background - display.pen(12) - display.rectangle((WIDTH - width) // 2, (HEIGHT - height) // 2, width, height) - - # Take the provided message and split it up into - # lines that fit within the specified width - words = message.split(" ") - lines = [] - line = "" - appended_line = "" - for word in words: - if len(word) > 0: - appended_line += " " - appended_line += word - if display.measure_text(appended_line, text_size) >= width: - lines.append(line) - appended_line = word - else: - line = appended_line - if len(line) != 0: - lines.append(line) - - display.pen(0) - display.thickness(2) - - # Display each line of text from the message, centre-aligned - num_lines = len(lines) - for i in range(num_lines): - length = display.measure_text(lines[i], text_size) - current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2 - display.text(lines[i], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size) - - # Draw the badge, including user text def draw_badge(): display.pen(0) @@ -170,21 +130,19 @@ def draw_badge(): # Program setup # ------------------------------ -# Global variables -show_overlay = False - # Create a new Badger and set it to update NORMAL display = badger2040.Badger2040() +display.led(128) display.update_speed(badger2040.UPDATE_NORMAL) # Open the badge file try: badge = open("badge.txt", "r") except OSError: - badge = open("badge.txt", "w") - badge.write(DEFAULT_TEXT) - badge.flush() - badge.seek(0) + with open("badge.txt", "w") as f: + f.write(DEFAULT_TEXT) + f.flush() + badge = open("badge.txt", "r") # Read in the next 6 lines company = badge.readline() # "mustelid inc" @@ -205,63 +163,21 @@ def draw_badge(): detail2_text = truncatestring(detail2_text, DETAILS_TEXT_SIZE, TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail2_title, DETAILS_TEXT_SIZE)) -# Set up the buttons -button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) - - -# Button handling function -def button(pin): - global show_overlay - - if pin == button_a: - show_overlay = True - return - - if pin == button_b: - show_overlay = True - return - - if pin == button_c: - show_overlay = True - return - - if pin == button_up: - show_overlay = True - return - - if pin == button_down: - show_overlay = True - return - - -# Register the button handling function with the buttons -button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) - # ------------------------------ -# Main program loop +# Main program # ------------------------------ draw_badge() -display.update() while True: - if show_overlay: - draw_overlay("To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt", - WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE) - display.update() + if display.pressed(badger2040.BUTTON_A) or display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C) or display.pressed(badger2040.BUTTON_UP) or display.pressed(badger2040.BUTTON_DOWN): + badger_os.warning(display, "To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt") time.sleep(4) draw_badge() - display.update() - show_overlay = False - time.sleep(0.1) + display.update() + + # If on battery, halt the Badger to save power, it will wake up if any of the front buttons are pressed + display.halt() diff --git a/micropython/examples/badger2040/badger_os.py b/micropython/examples/badger2040/badger_os.py new file mode 100644 index 000000000..6eb70972b --- /dev/null +++ b/micropython/examples/badger2040/badger_os.py @@ -0,0 +1,183 @@ +"""Keep track of app state in persistent flash storage.""" + +import os +import gc +import time +import json +import machine +import badger2040 + + +def get_battery_level(): + # Battery measurement + vbat_adc = machine.ADC(badger2040.PIN_BATTERY) + vref_adc = machine.ADC(badger2040.PIN_1V2_REF) + vref_en = machine.Pin(badger2040.PIN_VREF_POWER) + vref_en.init(machine.Pin.OUT) + vref_en.value(0) + + # Enable the onboard voltage reference + vref_en.value(1) + + # Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries + vdd = 1.24 * (65535 / vref_adc.read_u16()) + vbat = ( + (vbat_adc.read_u16() / 65535) * 3 * vdd + ) # 3 in this is a gain, not rounding of 3.3V + + # Disable the onboard voltage reference + vref_en.value(0) + + # Convert the voltage to a level to display onscreen + return vbat + + +def get_disk_usage(): + # f_bfree and f_bavail should be the same? + # f_files, f_ffree, f_favail and f_flag are unsupported. + f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs("/") + + f_total_size = f_frsize * f_blocks + f_total_free = f_bsize * f_bfree + f_total_used = f_total_size - f_total_free + + f_used = 100 / f_total_size * f_total_used + f_free = 100 / f_total_size * f_total_free + + return f_total_size, f_used, f_free + + +def state_running(): + state = {"running": "launcher"} + state_load("launcher", state) + return state["running"] + + +def state_clear_running(): + running = state_running() + state_modify("launcher", {"running": "launcher"}) + return running != "launcher" + + +def state_set_running(app): + state_modify("launcher", {"running": app}) + + +def state_launch(): + app = state_running() + if app is not None and app != "launcher": + launch("_" + app) + + +def state_delete(app): + try: + os.remove("/state/{}.json".format(app)) + except OSError: + pass + + +def state_save(app, data): + try: + with open("/state/{}.json".format(app), "w") as f: + f.write(json.dumps(data)) + f.flush() + except OSError: + import os + try: + os.stat("/state") + except OSError: + os.mkdir("/state") + state_save(app, data) + + +def state_modify(app, data): + state = {} + state_load(app, state) + state.update(data) + state_save(app, state) + + +def state_load(app, defaults): + try: + data = json.loads(open("/state/{}.json".format(app), "r").read()) + if type(data) is dict: + defaults.update(data) + return True + except (OSError, ValueError): + pass + + state_save(app, defaults) + return False + + +def launch(file): + state_set_running(file[1:]) + + gc.collect() + + button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) + button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) + + def quit_to_launcher(pin): + if button_a.value() and button_c.value(): + machine.reset() + + button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher) + button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher) + + try: + try: + __import__(file[1:]) # Try to import _[file] (drop underscore prefix) + except ImportError: + __import__(file) # Failover to importing [_file] + + except ImportError: + # If the app doesn't exist, notify the user + warning(None, "Could not launch: " + file[1:]) + time.sleep(4.0) + except Exception as e: + # If the app throws an error, catch it and display! + print(e) + warning(None, str(e)) + time.sleep(4.0) + + # If the app exits or errors, do not relaunch! + state_clear_running() + machine.reset() # Exit back to launcher + + +# Draw an overlay box with a given message within it +def warning(display, message, width=badger2040.WIDTH - 40, height=badger2040.HEIGHT - 40, line_spacing=20, text_size=0.6): + if display is None: + display = badger2040.Badger2040() + display.led(128) + + # Draw a light grey background + display.pen(12) + display.rectangle((badger2040.WIDTH - width) // 2, (badger2040.HEIGHT - height) // 2, width, height) + + # Take the provided message and split it up into + # lines that fit within the specified width + words = message.split(" ") + + lines = [] + current_line = "" + for word in words: + if display.measure_text(current_line + word + " ", text_size) < width: + current_line += word + " " + else: + lines.append(current_line.strip()) + current_line = word + " " + lines.append(current_line.strip()) + + display.pen(0) + display.thickness(2) + + # Display each line of text from the message, centre-aligned + num_lines = len(lines) + for i in range(num_lines): + length = display.measure_text(lines[i], text_size) + current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2 + display.text(lines[i], (badger2040.WIDTH - length) // 2, (badger2040.HEIGHT // 2) + current_line, text_size) + + display.update() diff --git a/micropython/examples/badger2040/clock.py b/micropython/examples/badger2040/clock.py index 834ab5ed1..973e1d48c 100644 --- a/micropython/examples/badger2040/clock.py +++ b/micropython/examples/badger2040/clock.py @@ -4,9 +4,10 @@ rtc = machine.RTC() -screen = badger2040.Badger2040() -screen.update_speed(badger2040.UPDATE_TURBO) -screen.font("gothic") +display = badger2040.Badger2040() +display.led(128) +display.update_speed(badger2040.UPDATE_TURBO) +display.font("gothic") cursors = ["year", "month", "day", "hour", "minute"] set_clock = False @@ -32,10 +33,13 @@ def days_in_month(month, year): def button(pin): global last, set_clock, cursor, year, month, day, hour, minute - time.sleep(0.05) + time.sleep(0.01) if not pin.value(): return + if button_a.value() and button_c.value(): + machine.reset() + adjust = 0 changed = False @@ -94,42 +98,42 @@ def draw_clock(): hms = "{:02}:{:02}:{:02}".format(hour, minute, second) ymd = "{:04}/{:02}/{:02}".format(year, month, day) - hms_width = screen.measure_text(hms, 1.8) + hms_width = display.measure_text(hms, 1.8) hms_offset = int((badger2040.WIDTH / 2) - (hms_width / 2)) - h_width = screen.measure_text(hms[0:2], 1.8) - mi_width = screen.measure_text(hms[3:5], 1.8) - mi_offset = screen.measure_text(hms[0:3], 1.8) + h_width = display.measure_text(hms[0:2], 1.8) + mi_width = display.measure_text(hms[3:5], 1.8) + mi_offset = display.measure_text(hms[0:3], 1.8) - ymd_width = screen.measure_text(ymd, 1.0) + ymd_width = display.measure_text(ymd, 1.0) ymd_offset = int((badger2040.WIDTH / 2) - (ymd_width / 2)) - y_width = screen.measure_text(ymd[0:4], 1.0) - m_width = screen.measure_text(ymd[5:7], 1.0) - m_offset = screen.measure_text(ymd[0:5], 1.0) - d_width = screen.measure_text(ymd[8:10], 1.0) - d_offset = screen.measure_text(ymd[0:8], 1.0) - - screen.pen(15) - screen.clear() - screen.pen(0) - screen.thickness(5) - screen.text(hms, hms_offset, 40, 1.8) - screen.thickness(3) - screen.text(ymd, ymd_offset, 100, 1.0) + y_width = display.measure_text(ymd[0:4], 1.0) + m_width = display.measure_text(ymd[5:7], 1.0) + m_offset = display.measure_text(ymd[0:5], 1.0) + d_width = display.measure_text(ymd[8:10], 1.0) + d_offset = display.measure_text(ymd[0:8], 1.0) + + display.pen(15) + display.clear() + display.pen(0) + display.thickness(5) + display.text(hms, hms_offset, 40, 1.8) + display.thickness(3) + display.text(ymd, ymd_offset, 100, 1.0) if set_clock: if cursors[cursor] == "year": - screen.line(ymd_offset, 120, ymd_offset + y_width, 120) + display.line(ymd_offset, 120, ymd_offset + y_width, 120) if cursors[cursor] == "month": - screen.line(ymd_offset + m_offset, 120, ymd_offset + m_offset + m_width, 120) + display.line(ymd_offset + m_offset, 120, ymd_offset + m_offset + m_width, 120) if cursors[cursor] == "day": - screen.line(ymd_offset + d_offset, 120, ymd_offset + d_offset + d_width, 120) + display.line(ymd_offset + d_offset, 120, ymd_offset + d_offset + d_width, 120) if cursors[cursor] == "hour": - screen.line(hms_offset, 70, hms_offset + h_width, 70) + display.line(hms_offset, 70, hms_offset + h_width, 70) if cursors[cursor] == "minute": - screen.line(hms_offset + mi_offset, 70, hms_offset + mi_offset + mi_width, 70) + display.line(hms_offset + mi_offset, 70, hms_offset + mi_offset + mi_width, 70) - screen.update() + display.update() year, month, day, wd, hour, minute, second, _ = rtc.datetime() @@ -145,4 +149,4 @@ def draw_clock(): if second != last_second: draw_clock() last_second = second - time.sleep(0.1) + time.sleep(0.01) diff --git a/micropython/examples/badger2040/ebook.py b/micropython/examples/badger2040/ebook.py index c2bb2898a..7f21239e3 100644 --- a/micropython/examples/badger2040/ebook.py +++ b/micropython/examples/badger2040/ebook.py @@ -1,8 +1,7 @@ import badger2040 -import machine import time import gc - +import badger_os # **** Put the name of your text file here ***** text_file = "book.txt" # File must be on the MicroPython device @@ -15,7 +14,10 @@ # If the specified file doesn't exist, # pre-populate with Wind In The Willows import witw - open(text_file, "wb").write(witw.data()) + with open(text_file, "wb") as f: + f.write(witw.data()) + f.flush() + time.sleep(0.1) del witw except ImportError: pass @@ -33,9 +35,6 @@ ARROW_PADDING = 2 TEXT_PADDING = 4 - -TEXT_SIZE = 0.5 -TEXT_SPACING = int(34 * TEXT_SIZE) TEXT_WIDTH = WIDTH - TEXT_PADDING - TEXT_PADDING - ARROW_WIDTH FONTS = ["sans", "gothic", "cursive", "serif"] @@ -71,7 +70,7 @@ def draw_frame(): display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT) display.pen(0) display.thickness(ARROW_THICKNESS) - if current_page > 1: + if state["current_page"] > 0: draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2), ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2), @@ -83,53 +82,23 @@ def draw_frame(): # ------------------------------ # Global variables -next_page = True -prev_page = False -change_font_size = False -change_font = False -last_offset = 0 -current_page = 0 +state = { + "last_offset": 0, + "current_page": 0, + "font_idx": 0, + "text_size": 0.5, + "offsets": [] +} +badger_os.state_load("ebook", state) + +text_spacing = int(34 * state["text_size"]) + # Create a new Badger and set it to update FAST display = badger2040.Badger2040() +display.led(128) display.update_speed(badger2040.UPDATE_FAST) -# Set up the buttons -button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) - -button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) - -# Set up the activity LED -led = machine.Pin(badger2040.PIN_LED, machine.Pin.OUT) - -offsets = [] - - -# Button handling function -def button(pin): - global next_page, prev_page, change_font_size, change_font - - if pin == button_down: - next_page = True - - if pin == button_up: - prev_page = True - - if pin == button_a: - change_font_size = True - - if pin == button_b: - change_font = True - - -# Register the button handling function with the buttons -button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) - # ------------------------------ # Render page @@ -141,7 +110,7 @@ def render_page(): pos = ebook.tell() next_pos = pos add_newline = False - display.font(FONTS[0]) + display.font(FONTS[state["font_idx"]]) while True: # Read a full line and split it into words @@ -180,7 +149,7 @@ def render_page(): if len(line) > 0 and len(next_word) > 0: appended_line += " " appended_line += next_word - appended_length = display.measure_text(appended_line, TEXT_SIZE) + appended_length = display.measure_text(appended_line, state["text_size"]) # Would this appended line be longer than the text display area, or was a blank line spotted? if appended_length >= TEXT_WIDTH or add_newline: @@ -189,14 +158,14 @@ def render_page(): print(line) display.pen(0) display.thickness(FONT_THICKNESSES[0]) - display.text(line, TEXT_PADDING, (row * TEXT_SPACING) + (TEXT_SPACING // 2) + TEXT_PADDING, TEXT_SIZE) + display.text(line, TEXT_PADDING, (row * text_spacing) + (text_spacing // 2) + TEXT_PADDING, state["text_size"]) # Clear the line and move on to the next row line = "" row += 1 # Have we reached the end of the page? - if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT: + if (row * text_spacing) + text_spacing >= HEIGHT: print("+++++") display.update() @@ -212,7 +181,7 @@ def render_page(): if add_newline: print("") row += 1 - if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT: + if (row * text_spacing) + text_spacing >= HEIGHT: print("+++++") display.update() return @@ -227,50 +196,68 @@ def render_page(): # Main program loop # ------------------------------ +launch = True +changed = False + # Open the book file ebook = open(text_file, "r") +if len(state["offsets"]) > state["current_page"]: + ebook.seek(state["offsets"][state["current_page"]]) +else: + state["current_page"] = 0 + state["offsets"] = [] while True: # Was the next page button pressed? - if next_page: - current_page += 1 + if display.pressed(badger2040.BUTTON_DOWN): + state["current_page"] += 1 - # Is the next page one we've not displayed before? - if current_page > len(offsets): - offsets.append(ebook.tell()) # Add its start position to the offsets list - draw_frame() - render_page() - next_page = False # Clear the next page button flag + changed = True # Was the previous page button pressed? - if prev_page: - if current_page > 1: - current_page -= 1 - ebook.seek(offsets[current_page - 1]) # Retrieve the start position of the last page - draw_frame() - render_page() - prev_page = False # Clear the prev page button flag - - if change_font_size: - TEXT_SIZE += 0.1 - if TEXT_SIZE > 0.8: - TEXT_SIZE = 0.5 - TEXT_SPACING = int(34 * TEXT_SIZE) - offsets = [0] + if display.pressed(badger2040.BUTTON_UP): + if state["current_page"] > 0: + state["current_page"] -= 1 + if state["current_page"] == 0: + ebook.seek(0) + else: + ebook.seek(state["offsets"][state["current_page"] - 1]) # Retrieve the start position of the last page + changed = True + + if display.pressed(badger2040.BUTTON_A): + state["text_size"] += 0.1 + if state["text_size"] > 0.8: + state["text_size"] = 0.5 + text_spacing = int(34 * state["text_size"]) + state["offsets"] = [] ebook.seek(0) - current_page = 1 - draw_frame() - render_page() - change_font_size = False - - if change_font: - FONTS.append(FONTS.pop(0)) - FONT_THICKNESSES.append(FONT_THICKNESSES.pop(0)) - offsets = [0] + state["current_page"] = 0 + changed = True + + if display.pressed(badger2040.BUTTON_B): + state["font_idx"] += 1 + if (state["font_idx"] >= len(FONTS)): + state["font_idx"] = 0 + state["offsets"] = [] ebook.seek(0) - current_page = 1 + state["current_page"] = 0 + changed = True + + if launch and not changed: + if state["current_page"] > 0 and len(state["offsets"]) > state["current_page"] - 1: + ebook.seek(state["offsets"][state["current_page"] - 1]) + changed = True + launch = False + + if changed: draw_frame() render_page() - change_font = False - time.sleep(0.1) + # Is the next page one we've not displayed before? + if state["current_page"] >= len(state["offsets"]): + state["offsets"].append(ebook.tell()) # Add its start position to the state["offsets"] list + badger_os.state_save("ebook", state) + + changed = False + + display.halt() diff --git a/micropython/examples/badger2040/fonts.py b/micropython/examples/badger2040/fonts.py index 499bda47f..9cf3d3663 100644 --- a/micropython/examples/badger2040/fonts.py +++ b/micropython/examples/badger2040/fonts.py @@ -1,6 +1,5 @@ import badger2040 -import machine -import time +import badger_os # Global Constants FONT_NAMES = ("sans", "gothic", "cursive", "serif", "serif_italic") @@ -65,13 +64,13 @@ def draw_fonts(): for i in range(len(FONT_NAMES)): name = FONT_NAMES[i] display.pen(0) - if i == selected_font: + if i == state["selected_font"]: display.rectangle(0, i * MENU_SPACING, MENU_WIDTH, MENU_SPACING) display.pen(15) display.text(name, MENU_PADDING, (i * MENU_SPACING) + (MENU_SPACING // 2), MENU_TEXT_SIZE) - display.font(FONT_NAMES[selected_font]) + display.font(FONT_NAMES[state["selected_font"]]) display.thickness(2) display.pen(0) @@ -91,51 +90,36 @@ def draw_fonts(): # ------------------------------ # Global variables -selected_font = 0 -pressed = False +state = {"selected_font": 0} +badger_os.state_load("fonts", state) # Create a new Badger and set it to update FAST display = badger2040.Badger2040() +display.led(128) display.update_speed(badger2040.UPDATE_FAST) -# Set up the buttons -button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) - - -# Button handling function -def button(pin): - global pressed - global selected_font - - if not pressed: - if pin == button_up: - selected_font -= 1 - if selected_font < 0: - selected_font = len(FONT_NAMES) - 1 - pressed = True - return - if pin == button_down: - selected_font += 1 - if selected_font >= len(FONT_NAMES): - selected_font = 0 - pressed = True - return - - -# Register the button handling function with the buttons -button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) -button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) - +changed = not badger2040.woken_by_button() # ------------------------------ # Main program loop # ------------------------------ while True: - draw_frame() - draw_fonts() - - pressed = False - while not pressed: - time.sleep(0.1) + if display.pressed(badger2040.BUTTON_UP): + state["selected_font"] -= 1 + if state["selected_font"] < 0: + state["selected_font"] = len(FONT_NAMES) - 1 + changed = True + if display.pressed(badger2040.BUTTON_DOWN): + state["selected_font"] += 1 + if state["selected_font"] >= len(FONT_NAMES): + state["selected_font"] = 0 + changed = True + + if changed: + draw_frame() + draw_fonts() + badger_os.state_save("fonts", state) + changed = False + + display.halt() diff --git a/micropython/examples/badger2040/help.py b/micropython/examples/badger2040/help.py index ff6102be5..4f000f715 100644 --- a/micropython/examples/badger2040/help.py +++ b/micropython/examples/badger2040/help.py @@ -1,11 +1,12 @@ import badger2040 -import time from badger2040 import WIDTH TEXT_SIZE = 0.45 LINE_HEIGHT = 16 display = badger2040.Badger2040() +display.led(128) + display.pen(0) display.rectangle(0, 0, WIDTH, 16) display.thickness(1) @@ -27,7 +28,7 @@ y += LINE_HEIGHT display.thickness(2) -display.text("Holding USER button:", 0, y, TEXT_SIZE) +display.text("Hold USER after:", 0, y, TEXT_SIZE) display.thickness(1) y += LINE_HEIGHT display.text("Up / Down - Change font size", 0, y, TEXT_SIZE) @@ -36,5 +37,4 @@ y += LINE_HEIGHT display.update() -while True: - time.sleep(1) +display.halt() diff --git a/micropython/examples/badger2040/image.py b/micropython/examples/badger2040/image.py index d8956a273..db31fc156 100644 --- a/micropython/examples/badger2040/image.py +++ b/micropython/examples/badger2040/image.py @@ -1,9 +1,9 @@ import os import sys import time -import machine import badger2040 -from badger2040 import WIDTH, HEIGHT +from badger2040 import HEIGHT +import badger_os REAMDE = """ @@ -22,13 +22,14 @@ TOTAL_IMAGES = 0 + +# Turn the act LED on as soon as possible +display = badger2040.Badger2040() +display.led(128) + # Try to preload BadgerPunk image try: os.mkdir("images") -except OSError: - pass - -try: import badgerpunk with open("images/badgerpunk.bin", "wb") as f: f.write(badgerpunk.data()) @@ -40,6 +41,7 @@ except (OSError, ImportError): pass +# Load images try: IMAGES = [f for f in os.listdir("/images") if f.endswith(".bin")] TOTAL_IMAGES = len(IMAGES) @@ -47,50 +49,12 @@ pass -display = badger2040.Badger2040() - -button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) - -button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) - image = bytearray(int(296 * 128 / 8)) -current_image = 0 -show_info = True - - -# Draw an overlay box with a given message within it -def draw_overlay(message, width, height, line_spacing, text_size): - - # Draw a light grey background - display.pen(12) - display.rectangle((WIDTH - width) // 2, (HEIGHT - height) // 2, width, height) - - # Take the provided message and split it up into - # lines that fit within the specified width - words = message.split(" ") - lines = [] - current_line = "" - for word in words: - if display.measure_text(current_line + word + " ", text_size) < width: - current_line += word + " " - else: - lines.append(current_line.strip()) - current_line = word + " " - lines.append(current_line.strip()) - - display.pen(0) - display.thickness(2) - - # Display each line of text from the message, centre-aligned - num_lines = len(lines) - for i in range(num_lines): - length = display.measure_text(lines[i], text_size) - current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2 - display.text(lines[i], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size) +state = { + "current_image": 0, + "show_info": True +} def show_image(n): @@ -99,7 +63,7 @@ def show_image(n): open("images/{}".format(file), "r").readinto(image) display.image(image) - if show_info: + if state["show_info"]: name_length = display.measure_text(name, 0.5) display.pen(0) display.rectangle(0, HEIGHT - 21, name_length + 11, 21) @@ -113,7 +77,7 @@ def show_image(n): y = int((128 / 2) - (TOTAL_IMAGES * 10 / 2) + (i * 10)) display.pen(0) display.rectangle(x, y, 8, 8) - if current_image != i: + if state["current_image"] != i: display.pen(15) display.rectangle(x + 1, y + 1, 6, 6) @@ -123,32 +87,41 @@ def show_image(n): if TOTAL_IMAGES == 0: display.pen(15) display.clear() - draw_overlay("To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE) - display.update() + badger_os.warning(display, "To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.") + time.sleep(4.0) sys.exit() -show_image(current_image) +badger_os.state_load("image", state) + +changed = not badger2040.woken_by_button() while True: - if button_up.value(): - if current_image > 0: - current_image -= 1 - show_image(current_image) - if button_down.value(): - if current_image < TOTAL_IMAGES - 1: - current_image += 1 - show_image(current_image) - if button_a.value(): - show_info = not show_info - show_image(current_image) - if button_b.value() or button_c.value(): + if display.pressed(badger2040.BUTTON_UP): + if state["current_image"] > 0: + state["current_image"] -= 1 + changed = True + if display.pressed(badger2040.BUTTON_DOWN): + if state["current_image"] < TOTAL_IMAGES - 1: + state["current_image"] += 1 + changed = True + if display.pressed(badger2040.BUTTON_A): + state["show_info"] = not state["show_info"] + changed = True + if display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C): display.pen(15) display.clear() - draw_overlay("To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, 0.5) + badger_os.warning(display, "To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/") display.update() + print(state["current_image"]) time.sleep(4) - show_image(current_image) + changed = True + + if changed: + badger_os.state_save("image", state) + show_image(state["current_image"]) + changed = False - time.sleep(0.01) + # Halt the Badger to save power, it will wake up if any of the front buttons are pressed + display.halt() diff --git a/micropython/examples/badger2040/info.py b/micropython/examples/badger2040/info.py index 94c3b1af8..f3b056833 100644 --- a/micropython/examples/badger2040/info.py +++ b/micropython/examples/badger2040/info.py @@ -1,11 +1,12 @@ import badger2040 -import time from badger2040 import WIDTH TEXT_SIZE = 0.45 LINE_HEIGHT = 16 display = badger2040.Badger2040() +display.led(128) + display.pen(0) display.rectangle(0, 0, WIDTH, 16) display.thickness(1) @@ -33,6 +34,4 @@ display.text("https://pimoroni.com/badger2040", 0, y, TEXT_SIZE) display.update() - -while True: - time.sleep(1) +display.halt() diff --git a/micropython/examples/badger2040/launcher.py b/micropython/examples/badger2040/launcher.py index 0f0c10823..f003a9407 100644 --- a/micropython/examples/badger2040/launcher.py +++ b/micropython/examples/badger2040/launcher.py @@ -1,20 +1,44 @@ import gc -import os import time import math import machine import badger2040 from badger2040 import WIDTH import launchericons +import badger_os + +# Reduce clock speed to 48MHz, that's fast enough! +machine.freq(48000000) + +changed = False +exited_to_launcher = False +woken_by_button = badger2040.woken_by_button() # Must be done before we clear_pressed_to_wake + +if badger2040.pressed_to_wake(badger2040.BUTTON_A) and badger2040.pressed_to_wake(badger2040.BUTTON_C): + # Pressing A and C together at start quits app + exited_to_launcher = badger_os.state_clear_running() + badger2040.clear_pressed_to_wake() +else: + # Otherwise restore previously running app + badger_os.state_launch() # for e.g. 2xAAA batteries, try max 3.4 min 3.0 MAX_BATTERY_VOLTAGE = 4.0 MIN_BATTERY_VOLTAGE = 3.2 +display = badger2040.Badger2040() +display.led(128) + +state = { + "page": 0, + "font_size": 1, + "inverted": False, + "running": "launcher" +} + +badger_os.state_load("launcher", state) -page = 0 -font_size = 1 -inverted = False +display.invert(state["inverted"]) icons = bytearray(launchericons.data()) icons_width = 576 @@ -38,46 +62,11 @@ MAX_PAGE = math.ceil(len(examples) / 3) -button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) -button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) -# Inverted. For reasons. -button_user = machine.Pin(badger2040.BUTTON_USER, machine.Pin.IN, machine.Pin.PULL_UP) - -# Battery measurement -vbat_adc = machine.ADC(badger2040.PIN_BATTERY) -vref_adc = machine.ADC(badger2040.PIN_1V2_REF) -vref_en = machine.Pin(badger2040.PIN_VREF_POWER) -vref_en.init(machine.Pin.OUT) -vref_en.value(0) - - -display = badger2040.Badger2040() - def map_value(input, in_min, in_max, out_min, out_max): return (((input - in_min) * (out_max - out_min)) / (in_max - in_min)) + out_min -def get_battery_level(): - # Enable the onboard voltage reference - vref_en.value(1) - - # Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries - vdd = 1.24 * (65535 / vref_adc.read_u16()) - vbat = ( - (vbat_adc.read_u16() / 65535) * 3 * vdd - ) # 3 in this is a gain, not rounding of 3.3V - - # Disable the onboard voltage reference - vref_en.value(0) - - # Convert the voltage to a level to display onscreen - return int(map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, 4)) - - def draw_battery(level, x, y): # Outline display.thickness(1) @@ -103,17 +92,7 @@ def draw_battery(level, x, y): def draw_disk_usage(x): - # f_bfree and f_bavail should be the same? - # f_files, f_ffree, f_favail and f_flag are unsupported. - f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs( - "/") - - f_total_size = f_frsize * f_blocks - f_total_free = f_bsize * f_bfree - f_total_used = f_total_size - f_total_free - - f_used = 100 / f_total_size * f_total_used - # f_free = 100 / f_total_size * f_total_free + _, f_used, _ = badger_os.get_disk_usage() display.image( bytearray( @@ -148,23 +127,23 @@ def render(): display.pen(0) display.thickness(2) - max_icons = min(3, len(examples[(page * 3):])) + max_icons = min(3, len(examples[(state["page"] * 3):])) for i in range(max_icons): x = centers[i] - label, icon = examples[i + (page * 3)] + label, icon = examples[i + (state["page"] * 3)] label = label[1:].replace("_", " ") display.pen(0) display.icon(icons, icon, icons_width, 64, x - 32, 24) - w = display.measure_text(label, font_sizes[font_size]) - display.text(label, x - int(w / 2), 16 + 80, font_sizes[font_size]) + w = display.measure_text(label, font_sizes[state["font_size"]]) + display.text(label, x - int(w / 2), 16 + 80, font_sizes[state["font_size"]]) for i in range(MAX_PAGE): x = 286 y = int((128 / 2) - (MAX_PAGE * 10 / 2) + (i * 10)) display.pen(0) display.rectangle(x, y, 8, 8) - if page != i: + if state["page"] != i: display.pen(15) display.rectangle(x + 1, y + 1, 6, 6) @@ -172,89 +151,93 @@ def render(): display.rectangle(0, 0, WIDTH, 16) display.thickness(1) draw_disk_usage(90) - draw_battery(get_battery_level(), WIDTH - 22 - 3, 3) + vbat = badger_os.get_battery_level() + bat = int(map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, 4)) + draw_battery(bat, WIDTH - 22 - 3, 3) display.pen(15) display.text("badgerOS", 3, 8, 0.4) display.update() -def launch(file): +def wait_for_user_to_release_buttons(): + pr = display.pressed + while pr(badger2040.BUTTON_A) or pr(badger2040.BUTTON_B) or pr(badger2040.BUTTON_C) or pr(badger2040.BUTTON_UP) or pr(badger2040.BUTTON_DOWN): + time.sleep(0.01) + + +def launch_example(index): + wait_for_user_to_release_buttons() + + file = examples[(state["page"] * 3) + index][0] + for k in locals().keys(): - if k not in ("gc", "file", "machine"): + if k not in ("gc", "file", "badger_os"): del locals()[k] - gc.collect() - try: - __import__(file[1:]) # Try to import _[file] (drop underscore prefix) - except ImportError: - __import__(file) # Failover to importing [_file] - machine.reset() # Exit back to launcher + gc.collect() -def launch_example(index): - try: - launch(examples[(page * 3) + index][0]) - return True - except IndexError: - return False + badger_os.launch(file) def button(pin): - global page, font_size, inverted + global changed + changed = True - if button_user.value(): # User button is NOT held down - if pin == button_a: + if not display.pressed(badger2040.BUTTON_USER): # User button is NOT held down + if pin == badger2040.BUTTON_A: launch_example(0) - if pin == button_b: + if pin == badger2040.BUTTON_B: launch_example(1) - if pin == button_c: + if pin == badger2040.BUTTON_C: launch_example(2) - if pin == button_up: - if page > 0: - page -= 1 - render() - if pin == button_down: - if page < MAX_PAGE - 1: - page += 1 - render() + if pin == badger2040.BUTTON_UP: + if state["page"] > 0: + state["page"] -= 1 + render() + if pin == badger2040.BUTTON_DOWN: + if state["page"] < MAX_PAGE - 1: + state["page"] += 1 + render() else: # User button IS held down - if pin == button_up: - font_size += 1 - if font_size == len(font_sizes): - font_size = 0 + if pin == badger2040.BUTTON_UP: + state["font_size"] += 1 + if state["font_size"] == len(font_sizes): + state["font_size"] = 0 render() - if pin == button_down: - font_size -= 1 - if font_size < 0: - font_size = 0 + if pin == badger2040.BUTTON_DOWN: + state["font_size"] -= 1 + if state["font_size"] < 0: + state["font_size"] = 0 render() - if pin == button_a: - inverted = not inverted - display.invert(inverted) + if pin == badger2040.BUTTON_A: + state["inverted"] = not state["inverted"] + display.invert(state["inverted"]) render() -display.update_speed(badger2040.UPDATE_MEDIUM) -render() -display.update_speed(badger2040.UPDATE_FAST) - - -# Wait for wakeup button to be released -while button_a.value() or button_b.value() or button_c.value() or button_up.value() or button_down.value(): - pass +if exited_to_launcher or not woken_by_button: + wait_for_user_to_release_buttons() + display.update_speed(badger2040.UPDATE_MEDIUM) + render() +display.update_speed(badger2040.UPDATE_FAST) while True: - if button_a.value(): - button(button_a) - if button_b.value(): - button(button_b) - if button_c.value(): - button(button_c) - - if button_up.value(): - button(button_up) - if button_down.value(): - button(button_down) - - time.sleep(0.01) + if display.pressed(badger2040.BUTTON_A): + button(badger2040.BUTTON_A) + if display.pressed(badger2040.BUTTON_B): + button(badger2040.BUTTON_B) + if display.pressed(badger2040.BUTTON_C): + button(badger2040.BUTTON_C) + + if display.pressed(badger2040.BUTTON_UP): + button(badger2040.BUTTON_UP) + if display.pressed(badger2040.BUTTON_DOWN): + button(badger2040.BUTTON_DOWN) + + if changed: + badger_os.state_save("launcher", state) + changed = False + + display.halt() diff --git a/micropython/examples/badger2040/list.py b/micropython/examples/badger2040/list.py index a2846ca7c..d71bc0395 100644 --- a/micropython/examples/badger2040/list.py +++ b/micropython/examples/badger2040/list.py @@ -143,6 +143,7 @@ def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding): # Create a new Badger and set it to update FAST display = badger2040.Badger2040() +display.led(128) display.update_speed(badger2040.UPDATE_FAST) # Set up the buttons @@ -177,6 +178,13 @@ def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding): def button(pin): global update, current_item, needs_save + time.sleep(0.05) + if not pin.value(): + return + + if button_a.value() and button_c.value(): + machine.reset() + if len(list_content) > 0 and not update: if pin == button_a: if current_item > 0: diff --git a/micropython/examples/badger2040/micropython-builtins.cmake b/micropython/examples/badger2040/micropython-builtins.cmake index ac5996b0c..68c04aee7 100644 --- a/micropython/examples/badger2040/micropython-builtins.cmake +++ b/micropython/examples/badger2040/micropython-builtins.cmake @@ -52,3 +52,4 @@ copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/badge.py _badge) copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/help.py _help) copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/info.py _info) copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/qrgen.py _qrgen) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/badger_os.py badger_os) diff --git a/micropython/examples/badger2040/qrgen.py b/micropython/examples/badger2040/qrgen.py index 1c980c1ea..f4ea40c8b 100644 --- a/micropython/examples/badger2040/qrgen.py +++ b/micropython/examples/badger2040/qrgen.py @@ -1,14 +1,13 @@ import badger2040 import qrcode -import time # Open the qrcode file try: text = open("qrcode.txt", "r") except OSError: - text = open("qrcode.txt", "w") - text.write("""https://pimoroni.com/badger2040 + with open("qrcode.txt", "w") as text: + text.write("""https://pimoroni.com/badger2040 Badger 2040 * 296x128 1-bit e-ink * six user buttons @@ -18,8 +17,8 @@ Scan this code to learn more about Badger 2040. """) - text.flush() - text.seek(0) + text.flush() + text = open("qrcode.txt", "r") lines = text.read().strip().split("\n") @@ -28,6 +27,7 @@ detail_text = lines display = badger2040.Badger2040() +display.led(128) code = qrcode.QRCode() @@ -65,6 +65,4 @@ def draw_qr_code(ox, oy, size, code): top += 10 display.update() - -while True: - time.sleep(1.0) +display.halt() diff --git a/micropython/modules/badger2040/badger2040.c b/micropython/modules/badger2040/badger2040.c index 3a09276a8..2b42a96a7 100644 --- a/micropython/modules/badger2040/badger2040.c +++ b/micropython/modules/badger2040/badger2040.c @@ -16,6 +16,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(Badger2040_pen_obj, Badger2040_pen); MP_DEFINE_CONST_FUN_OBJ_2(Badger2040_thickness_obj, Badger2040_thickness); MP_DEFINE_CONST_FUN_OBJ_2(Badger2040_pressed_obj, Badger2040_pressed); +MP_DEFINE_CONST_FUN_OBJ_2(Badger2040_pressed_to_wake2_obj, Badger2040_pressed_to_wake2); MP_DEFINE_CONST_FUN_OBJ_1(Badger2040_clear_obj, Badger2040_clear); MP_DEFINE_CONST_FUN_OBJ_3(Badger2040_pixel_obj, Badger2040_pixel); MP_DEFINE_CONST_FUN_OBJ_KW(Badger2040_line_obj, 4, Badger2040_line); @@ -32,6 +33,11 @@ MP_DEFINE_CONST_FUN_OBJ_KW(Badger2040_measure_glyph_obj, 2, Badger2040_measure_g MP_DEFINE_CONST_FUN_OBJ_3(Badger2040_command_obj, Badger2040_command); +MP_DEFINE_CONST_FUN_OBJ_1(Badger2040_pressed_to_wake_obj, Badger2040_pressed_to_wake); +MP_DEFINE_CONST_FUN_OBJ_0(Badger2040_clear_pressed_to_wake_obj, Badger2040_clear_pressed_to_wake); +MP_DEFINE_CONST_FUN_OBJ_1(Badger2040_halt_obj, Badger2040_halt); +MP_DEFINE_CONST_FUN_OBJ_0(Badger2040_woken_by_button_obj, Badger2040_woken_by_button); + /***** Binding of Methods *****/ STATIC const mp_rom_map_elem_t Badger2040_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Badger2040___del___obj) }, @@ -40,6 +46,8 @@ STATIC const mp_rom_map_elem_t Badger2040_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&Badger2040_update_obj) }, { MP_ROM_QSTR(MP_QSTR_partial_update), MP_ROM_PTR(&Badger2040_partial_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_halt), MP_ROM_PTR(&Badger2040_halt_obj) }, + { MP_ROM_QSTR(MP_QSTR_invert), MP_ROM_PTR(&Badger2040_invert_obj) }, { MP_ROM_QSTR(MP_QSTR_led), MP_ROM_PTR(&Badger2040_led_obj) }, { MP_ROM_QSTR(MP_QSTR_font), MP_ROM_PTR(&Badger2040_font_obj) }, @@ -47,6 +55,7 @@ STATIC const mp_rom_map_elem_t Badger2040_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_thickness), MP_ROM_PTR(&Badger2040_thickness_obj) }, { MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&Badger2040_pressed_obj) }, + { MP_ROM_QSTR(MP_QSTR_pressed_to_wake), MP_ROM_PTR(&Badger2040_pressed_to_wake2_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&Badger2040_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&Badger2040_pixel_obj) }, @@ -78,10 +87,14 @@ const mp_obj_type_t Badger2040_type = { /***** Globals Table *****/ -STATIC const mp_map_elem_t badger2040_globals_table[] = { +STATIC const mp_rom_map_elem_t badger2040_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_badger2040) }, { MP_OBJ_NEW_QSTR(MP_QSTR_Badger2040), (mp_obj_t)&Badger2040_type }, + { MP_ROM_QSTR(MP_QSTR_pressed_to_wake), MP_ROM_PTR(&Badger2040_pressed_to_wake_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear_pressed_to_wake), MP_ROM_PTR(&Badger2040_clear_pressed_to_wake_obj) }, + { MP_ROM_QSTR(MP_QSTR_woken_by_button), MP_ROM_PTR(&Badger2040_woken_by_button_obj) }, + { MP_ROM_QSTR(MP_QSTR_UPDATE_NORMAL), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_UPDATE_MEDIUM), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_UPDATE_FAST), MP_ROM_INT(2) }, @@ -121,4 +134,4 @@ const mp_obj_module_t badger2040_user_cmodule = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_badger2040_globals, }; -MP_REGISTER_MODULE(MP_QSTR_badger2040, badger2040_user_cmodule, MODULE_BADGER2040_ENABLED); \ No newline at end of file +MP_REGISTER_MODULE(MP_QSTR_badger2040, badger2040_user_cmodule, MODULE_BADGER2040_ENABLED); diff --git a/micropython/modules/badger2040/badger2040.cpp b/micropython/modules/badger2040/badger2040.cpp index 657b04000..3bc580313 100644 --- a/micropython/modules/badger2040/badger2040.cpp +++ b/micropython/modules/badger2040/badger2040.cpp @@ -1,8 +1,45 @@ #include +#include "hardware/watchdog.h" #include "badger2040.hpp" #define MP_OBJ_TO_PTR2(o, t) ((t *)(uintptr_t)(o)) +namespace { + struct Badger2040_WakeUpInit { + Badger2040_WakeUpInit() + : state(gpio_get_all() & (0x1f << pimoroni::Badger2040::DOWN)) // Record state of front buttons + { + gpio_set_function(pimoroni::Badger2040::ENABLE_3V3, GPIO_FUNC_SIO); + gpio_set_dir(pimoroni::Badger2040::ENABLE_3V3, GPIO_OUT); + gpio_put(pimoroni::Badger2040::ENABLE_3V3, 1); + + gpio_set_function(pimoroni::Badger2040::LED, GPIO_FUNC_SIO); + gpio_set_dir(pimoroni::Badger2040::LED, GPIO_OUT); + gpio_put(pimoroni::Badger2040::LED, 1); + } + + bool any() const { + return state > 0; + } + + bool get(uint32_t pin) const { + return state & (0b1 << pin); + } + + bool get_once(uint32_t pin) { + uint32_t mask = 0b1 << pin; + bool value = state & mask; + state &= ~mask; + return value; + } + void clear() { state = 0; } + + private: + uint32_t state; + }; + + Badger2040_WakeUpInit button_wake_state __attribute__ ((init_priority (101))); +}; extern "C" { #include "badger2040.h" @@ -116,9 +153,12 @@ MICROPY_EVENT_POLL_HOOK #endif } + absolute_time_t t_end = make_timeout_time_ms(self->badger2040->update_time()); self->badger2040->update(false); - while(self->badger2040->is_busy()) { + // Ensure blocking for the minimum amount of time + // in cases where "is_busy" is unreliable. + while(self->badger2040->is_busy() || absolute_time_diff_us(t_end, get_absolute_time()) > 0) { #ifdef MICROPY_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK #endif @@ -157,9 +197,12 @@ MICROPY_EVENT_POLL_HOOK #endif } + absolute_time_t t_end = make_timeout_time_ms(self->badger2040->update_time()); self->badger2040->partial_update(x, y, w, h); - while(self->badger2040->is_busy()) { + // Ensure blocking for the minimum amount of time + // in cases where "is_busy" is unreliable. + while(self->badger2040->is_busy() || absolute_time_diff_us(t_end, get_absolute_time()) > 0) { #ifdef MICROPY_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK #endif @@ -170,7 +213,27 @@ MICROPY_EVENT_POLL_HOOK return mp_const_none; } -// halt +mp_obj_t Badger2040_woken_by_button() { + return button_wake_state.any() ? mp_const_true : mp_const_false; +} + +mp_obj_t Badger2040_halt(mp_obj_t self_in) { + _Badger2040_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Badger2040_obj_t); + + // Don't use the Badger halt so we can allow Micropython to be interrupted. + gpio_put(pimoroni::Badger2040::ENABLE_3V3, 0); + + self->badger2040->update_button_states(); + while (self->badger2040->button_states() == 0) { +#ifdef MICROPY_EVENT_POLL_HOOK +MICROPY_EVENT_POLL_HOOK +#endif + self->badger2040->update_button_states(); + } + //watchdog_reboot(0, SRAM_END, 0); + + return mp_const_none; +} // sleep mp_obj_t Badger2040_invert(mp_obj_t self_in, mp_obj_t invert) { @@ -206,10 +269,25 @@ mp_obj_t Badger2040_thickness(mp_obj_t self_in, mp_obj_t thickness) { mp_obj_t Badger2040_pressed(mp_obj_t self_in, mp_obj_t button) { _Badger2040_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Badger2040_obj_t); self->badger2040->update_button_states(); + bool wake_state = button_wake_state.get_once(mp_obj_get_int(button)); bool state = self->badger2040->pressed(mp_obj_get_int(button)); + return (state || wake_state) ? mp_const_true : mp_const_false; +} + +mp_obj_t Badger2040_pressed_to_wake(mp_obj_t button) { + bool state = button_wake_state.get(mp_obj_get_int(button)); return state ? mp_const_true : mp_const_false; } +mp_obj_t Badger2040_pressed_to_wake2(mp_obj_t self_in, mp_obj_t button) { + return Badger2040_pressed_to_wake(button); +} + +mp_obj_t Badger2040_clear_pressed_to_wake() { + button_wake_state.clear(); + return mp_const_none; +} + // pressed // pressed_to_wake // wait_for_press - implement in terms of MicroPython! @@ -467,4 +545,4 @@ mp_obj_t Badger2040_measure_glyph(size_t n_args, const mp_obj_t *pos_args, mp_ma return mp_obj_new_int(self->badger2040->measure_glyph(c, scale)); } -} \ No newline at end of file +} diff --git a/micropython/modules/badger2040/badger2040.h b/micropython/modules/badger2040/badger2040.h index b60a31541..ae08294a4 100644 --- a/micropython/modules/badger2040/badger2040.h +++ b/micropython/modules/badger2040/badger2040.h @@ -15,6 +15,8 @@ extern mp_obj_t Badger2040_update_speed(mp_obj_t self_in, mp_obj_t speed); extern mp_obj_t Badger2040_update(mp_obj_t self_in); extern mp_obj_t Badger2040_partial_update(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t Badger2040_halt(mp_obj_t self_in); + extern mp_obj_t Badger2040_invert(mp_obj_t self_in, mp_obj_t invert); extern mp_obj_t Badger2040_led(mp_obj_t self_in, mp_obj_t brightness); extern mp_obj_t Badger2040_font(mp_obj_t self_in, mp_obj_t font); @@ -22,6 +24,7 @@ extern mp_obj_t Badger2040_pen(mp_obj_t self_in, mp_obj_t color); extern mp_obj_t Badger2040_thickness(mp_obj_t self_in, mp_obj_t thickness); extern mp_obj_t Badger2040_pressed(mp_obj_t self_in, mp_obj_t button); +extern mp_obj_t Badger2040_pressed_to_wake2(mp_obj_t self_in, mp_obj_t button); extern mp_obj_t Badger2040_clear(mp_obj_t self_in); extern mp_obj_t Badger2040_pixel(mp_obj_t self_in, mp_obj_t x, mp_obj_t y); @@ -37,4 +40,8 @@ extern mp_obj_t Badger2040_glyph(size_t n_args, const mp_obj_t *pos_args, mp_map extern mp_obj_t Badger2040_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t Badger2040_measure_glyph(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -extern mp_obj_t Badger2040_command(mp_obj_t self_in, mp_obj_t reg, mp_obj_t data); \ No newline at end of file +extern mp_obj_t Badger2040_command(mp_obj_t self_in, mp_obj_t reg, mp_obj_t data); + +extern mp_obj_t Badger2040_pressed_to_wake(mp_obj_t button); +extern mp_obj_t Badger2040_clear_pressed_to_wake(); +extern mp_obj_t Badger2040_woken_by_button();