Configure Badger to run my program without USB connection to computer

I’ve used some of the example code to create a microPython program I would like this to be the default program. Once installed on the badger using Thonny, I should be able to disconnect the USB cable and have this program run continuously. If I turn off the attached battery pack and then turn it back on, it should run my program. What steps are required to do this? I’d like badger to run my program without requiring the user to press a button (other than the slide switch on the battery pack).

All you should need to do is name your main code file main.py and you should be all set.

What he said. It’s what I do on my Tufty. I rename main as main_old, Then save my file as main.py

Thank you for the quick response. Renaming my code to main.py and transferring it to the badger allows the board to run my application when detached from the laptop. However, the code does not restart after the power is turned off(power switch on a AAA battery pack) and turned on. I’ve changed the processor speed and display update values to normal (in case the disconnect from USB had an effect.) Any ideas?

Thank you for the quick response.

Another data point. If I reconnect the USB connection to the laptop, the code begins running, again. It just doesn’t want to start again on battery power.

Posting the code your running might help.

Okay. It’s a little more than 530 lines. Should I post it to a special location or drop it in a reply box?

Code tags are three ` at the top of your code and three more at the end. Do that and it’s not so hard to read.

#
# North Penn Marching Band, calendar year 2022-2023
# Champs Dinner Nameplate Project
#
# Author: Tony Reamer
# Hardware Target: Pimoroni badger2040
# Programming Language: MicroPython Pimoroni Pico, version 1.19.5
# Source code heavily based on 'boiler plate' code provided by Pimoroni.
#
# Details:
# 1. Developed using Thonny IDE
# 2. badger2040 connected to the laptop via USB cable
# 3. Thonny Interpreter selected, at bottom right, as 'MicroPython (RP2040) * COM4'
#
##############################################################################################

# ==========================================================
#
#      Module imports
#
# ==========================================================
import time
import badger2040
import badger_os
import qrcode
import os


# ==========================================================
#
#      Global Constants
#
# ==========================================================
WIDTH = badger2040.WIDTH
HEIGHT = badger2040.HEIGHT
IMAGE_WIDTH = 104
NPMK_HEIGHT = 40
NAME_HEIGHT = HEIGHT - NPMK_HEIGHT - 1
TEXT_WIDTH = WIDTH - IMAGE_WIDTH - 1
NPMK_TEXT_SIZE = 0.8
LEFT_PADDING = 15
NAME_PADDING = 20
NPMK_SPACING = 20
BADGE_TEXT = """Sammy
NPMK
2022"""

ANNIVERSARY_PADDING = 11
MARCHING_KNIGHTS_TEXT_SIZE = 0.6
LOGO_TEXT = """50th Anniversary
North Penn
Marching Knights"""

state = {
    "current_qr": 0
}

sys_speed = {
    "SYSTEM_VERY_SLOW": 0, # 4MHz
    "SYSTEM_SLOW": 1,      # 12MHz
    "SYSTEM_NORMAL": 2,    # 48 Mhz - default when connected to USB
    "SYSTEM_FAST": 3,      # 133 MHz
    "SYSTEM_TURBO": 4      # 250 MHz
}

# ----------------------------------------------------------
#      function allocate_and_open_badge_image()
#         purpose:   Allocates space for Badge image. Opens
#                    swirl image.
#         returns:   BADGE_IMAGE bytearray
# ----------------------------------------------------------
def allocate_and_open_badge_image():
    # Allocate space for the Badge Image
    BADGE_IMAGE = bytearray(int(IMAGE_WIDTH * HEIGHT / 8))

    # Open and read Badge Image from Raspberry Pico EPROM
    try:
         open("swirl2.bin", "rb").readinto(BADGE_IMAGE)
    except OSError:
        try:
            import swirl2
            BADGE_IMAGE = bytearray(swirl2.data())
            del swirl2
        except ImportError:
            pass
    return BADGE_IMAGE
    #
    # NOTES:
    # 1. The image file MUST be stored in the root directory of the
    #    RP2040 device, NOT the images subdirectory.
    # 2. The image file must be reduced to 1 bit color (black and white.)
    # 3. The image file must have horizontal and vertical dimensions
    #    that are multiples of 8. For example, 104 wide by 128 high.
    # 4. https://online-image-converter.com/ is a useful tool.


# ==========================================================
#
#      Utility functions
#
# ==========================================================

# ----------------------------------------------------------
#      function truncatestring( text, text_size, width, display )
#         text:      Original text string
#         text_size: Text heigth, usually expressed as a
#                    fraction.
#         width:     Width, measured in pixels.
#         display:   Badger2040 display object
#
#         purpose:   Reduce the size of a string until it
#                    fits within a given width. The function
#                    eliminates characters in 'text' until
#                    the string fits in width.
#         returns:   truncated string (text)
# ----------------------------------------------------------
def truncatestring(text, text_size, width, display):
    while True:
        length = display.measure_text(text, text_size)
        if length > 0 and length > width:
            text = text[:-1]
        else:
            text += ""
            return text

# ==========================================================
#
#      Drawing functions
#
# ==========================================================


# ----------------------------------------------------------
#      function draw_badge()
#
#         purpose:     Draw the badge, including user text
#         returns:     [nothing]
# ----------------------------------------------------------
def draw_badge():
    display.pen(0)
    display.clear()

    # Draw badge image
    display.image(BADGE_IMAGE, IMAGE_WIDTH, HEIGHT, WIDTH - IMAGE_WIDTH, 0)

    # Draw a border around the image
    display.pen(0)
    display.thickness(1)
    display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - 1, 0)
    display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - IMAGE_WIDTH, HEIGHT - 1)
    display.line(WIDTH - IMAGE_WIDTH, HEIGHT - 1, WIDTH - 1, HEIGHT - 1)
    # Typical values:
    #    WIDTH-IMAGE_WIDTH = 192, HEIGHT-1 = 127
    #    WIDTH-1 = 295
    display.line(WIDTH - 1, 0, WIDTH - 1, HEIGHT - 1)

    # Uncomment this if a white background is wanted behind the company
    display.pen(15)
    display.rectangle(1, 1, TEXT_WIDTH, NAME_HEIGHT - 1)

    # Draw the name, scaling it based on the available width
    display.pen(0)
    display.font("sans")
    display.thickness(4)
    name_size = 2.0  # A sensible starting scale
    while True:
        name_length = display.measure_text(name, name_size)
        if name_length >= (TEXT_WIDTH - NAME_PADDING) and name_size >= 0.1:
            name_size -= 0.01
        else:
            display.text(name, (TEXT_WIDTH - name_length) // 2, (NAME_HEIGHT // 2) + 1, name_size)
            break

    # Draw a white backgrounds behind the details
    display.pen(15)
    display.thickness(1)
    display.rectangle(1, HEIGHT - NPMK_HEIGHT, TEXT_WIDTH, NPMK_HEIGHT - 1)

    # Draw the NPMK title and text
    display.pen(0)
    display.font("sans")
    display.thickness(3)
    name_length = display.measure_text(npmk_title, NPMK_TEXT_SIZE)
    display.text(npmk_title, LEFT_PADDING, HEIGHT - (NPMK_HEIGHT // 2), NPMK_TEXT_SIZE)
    display.thickness(2)
    display.text(npmk_text, 5 + name_length + NPMK_SPACING, HEIGHT - (NPMK_HEIGHT // 2), NPMK_TEXT_SIZE)

# ----------------------------------------------------------
#      function measure_qr_code( size, code )
#         size:      Height of QR code
#         code:      The instance of the QRCode class
#
#         purpose:   Scales the width of the QR Code to the
#                    height of the QR Code.
#         returns:   1. Scaled QR code width
#                       (module_size * w)
#                    2. Scaling factor (module_size)
# ----------------------------------------------------------
def measure_qr_code(size, code):
    w, h = code.get_size()
    module_size = int(size / w)
    # Typical values:
    #    size = 128, module_size = 4,
    #    w = 29, module_size * w = 116
    return module_size * w, module_size

# ----------------------------------------------------------
#      function draw_qr_code( ox, oy, size, code )
#         ox:        x-coordinate of top left corner of
#                    rectangle.
#         oy:        y-coordinate of top left corner of
#                    rectangle.
#         size:      Height of QR code
#         code:      The instance of the QRCode class
#
#         purpose:   Scales the width of the QR Code to the
#                    height of the QR Code.
#         returns:   [nothing]
# ----------------------------------------------------------
def draw_qr_code(ox, oy, size, code):
    print(f"ox = {ox}, oy = {oy}, size = {size}")
    # Typical values:
    #    ox = 6, oy = 6, size = 128
    size, module_size = measure_qr_code(size, code)
    # Typical values:
    #    size = 116, module_size = 4
    display.pen(15)
    display.rectangle(ox, oy, size, size)
    display.pen(0)
    for x in range(size):
        for y in range(size):
            if code.get_module(x, y):
                display.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)


# ----------------------------------------------------------
#      function draw_qr_file( n )
#         n:         Index into dictionary
#
#         purpose:   Calculates the positions for 1 or more
#                    QR codes. This also calls
#                    draw_qr_code() for each.
#         returns:   [nothing]
# ----------------------------------------------------------
def draw_qr_file(n):
    display.led(128)
    file = CODES[n]
    codetext = open("qrcodes/{}".format(file), "r")

    lines = codetext.read().strip().split("\n")
    code_text = lines.pop(0)
    title_text = lines.pop(0)
    detail_text = lines

    # Clear the Display
    display.pen(15)  # Change this to 0 if a white background is used
    display.clear()
    display.pen(0)

    code.set_text(code_text)
    size, _ = measure_qr_code(128, code)
    left = top = int((badger2040.HEIGHT / 2) - (size / 2))
    # Typical values:
    #    left = 6, top = 6
    draw_qr_code(left, top, 128, code)

    left = 85
    
    display.thickness(3)
    display.text(title_text, left, 20, 0.7)
    display.thickness(2)

    left = 105
    top = 50
    for line in detail_text:
        display.text(line, left, top, 0.6)
        top += 20

    if TOTAL_CODES > 1:
        for i in range(TOTAL_CODES):
            x = 286
            y = int((128 / 2) - (TOTAL_CODES * 10 / 2) + (i * 10))
            display.pen(0)
            display.rectangle(x, y, 8, 8)
            if state["current_qr"] != i:
                display.pen(15)
                display.rectangle(x + 1, y + 1, 6, 6)
    display.update()

# ----------------------------------------------------------
#      function allocate_and_open_logo_image()
#         purpose:   Allocates space for Logo image. Opens
#                    NorthPennLogoSmall image.
#         returns:   LOGO_IMAGE bytearray
# ----------------------------------------------------------
def allocate_and_open_logo_image():
    # Allocate space for the Anniversary Image
    LOGO_IMAGE = bytearray(int(IMAGE_WIDTH * HEIGHT / 8))

    # Open and read NorthPennLogoSmall Image from Raspberry Pico EPROM
    try:
         open("NorthPennLogoSmall.bin", "rb").readinto(LOGO_IMAGE)
    except OSError:
        try:
            import NorthPennLogoSmall
            LOGO_IMAGE = bytearray(NorthPennLogoSmall.data())
            del NorthPennLogoSmall
        except ImportError:
            pass
    return LOGO_IMAGE
    #
    # NOTES:
    # 1. The image file MUST be stored in the root directory of the
    #    RP2040 device, NOT the images subdirectory.
    # 2. The image file must be reduced to 1 bit color (black and white.)
    # 3. The image file must have horizontal and vertical dimensions
    #    that are multiples of 8. For example, 104 wide by 128 high.
    # 4. https://online-image-converter.com/ is a useful tool.

# ----------------------------------------------------------
#      function draw_logo()
#
#         purpose:     Draw the logo, including user text
#         returns:     [nothing]
# ----------------------------------------------------------
def draw_logo():
    display.pen(0)
    display.clear()

    # Draw Logo image
    display.image(LOGO_IMAGE, IMAGE_WIDTH, HEIGHT, 0, 0)

    # Draw a border around the image
    display.pen(0)
    display.thickness(1)
    display.line(0, 0, WIDTH - 1, 0)
    display.line(0, 0, 0, HEIGHT - 1)
    display.line(0, HEIGHT - 1, WIDTH - 1, HEIGHT - 1)
    display.line(WIDTH - 1, 0, WIDTH - 1, HEIGHT - 1)

    # Draw the name, scaling it based on the available width
    display.pen(15)
    display.font("sans")
    display.thickness(2)
    anniversary_size = 1.0  # A sensible starting scale
    while True:
        anniversary_length = display.measure_text(anniversary, anniversary_size)
        if anniversary_length >= (TEXT_WIDTH - ANNIVERSARY_PADDING) and anniversary_size >= 0.1:
            anniversary_size -= 0.05
        else:
            display.text(anniversary, IMAGE_WIDTH + ANNIVERSARY_PADDING, int(HEIGHT / 4), anniversary_size)
            break
    
    # Draw a white backgrounds behind the details
    display.pen(15)
    display.thickness(1)
    display.rectangle(IMAGE_WIDTH + 1, HEIGHT - (NPMK_HEIGHT * 2), TEXT_WIDTH, (NPMK_HEIGHT * 2) - 1)

    # Draw the NPMK title and text
    display.pen(0)
    display.font("sans")
    display.thickness(2)
    display.text(north_penn, IMAGE_WIDTH + 6, (int(HEIGHT / 4) * 2), MARCHING_KNIGHTS_TEXT_SIZE)
    display.thickness(2)
    display.text(marching_knights, IMAGE_WIDTH + 6, (int(HEIGHT / 4) * 3), MARCHING_KNIGHTS_TEXT_SIZE)

# ==========================================================
#
#      Program setup
#
# ==========================================================

# ----------------------------------------------------------
#      function create_and_configure_badge_page()
#
#         purpose:   Create a Badger2040 object. Open the
#                    badge.txt file. Read the lines from
#                    the text file. Truncate the lines to
#                    fit the text field.
#         returns:   1. display - a Badger2040 object
#                    2. badge - badge.txt file pointer
#                    3. name - badge name text
#                    4. npmk_title - NPMK title text
#                    5. npmk_text - NPMK text
# ----------------------------------------------------------
def create_and_configure_badge_page():
    # Set Raspberry Pico processor speed.
    badger2040.system_speed(sys_speed["SYSTEM_FAST"])
    
    BADGE_IMAGE = allocate_and_open_badge_image()
    # Create a new Badger and set it to update NORMAL
    display = badger2040.Badger2040()
    display.led(128)
    display.update_speed(badger2040.UPDATE_FAST)

    # Open the badge.txt file
    try:
        badge = open("badge.txt", "r")
    except OSError:
        with open("badge.txt", "w") as f:
            f.write(BADGE_TEXT)
            f.flush()
        badge = open("badge.txt", "r")
    
    # Read in the 3 lines from badge.txt
    name = badge.readline()           # "Sammy"
    npmk_title = badge.readline()  # "NPMK"
    npmk_text = badge.readline()   # "2022"

    # Truncate all of the text (except for the name as that is scaled)
    npmk_title = truncatestring(npmk_title, NPMK_TEXT_SIZE, TEXT_WIDTH, display)
    npmk_text = truncatestring(npmk_text, NPMK_TEXT_SIZE,
                          TEXT_WIDTH - NPMK_SPACING - display.measure_text(npmk_title, NPMK_TEXT_SIZE), display)
    return display, badge, name, npmk_title, npmk_text, BADGE_IMAGE


# ----------------------------------------------------------
#      function create_and_configure_qrcode_page()
#
#         purpose:   Create qrcodes directory, if necessary.
#                    Open or create qrcode.txt file. Loads
#                    all QR code files. Sets state.
#         returns:   1. code - An instance of a QR code
#                       object
#                    2. CODES - All QR code files
#                    3. TOTAL_CODES - Number of QR code
#                       files
#                    4. changed - if button is pressed
#                    5. state - dictionary of QR code states
# ----------------------------------------------------------
def create_and_configure_qrcode_page():
    # Check that the qrcodes directory exists, if not, make it
    try:
        os.mkdir("qrcodes")
    except OSError:
        pass

    # Check that there is a qrcode.txt, if not preload
    try:
        text = open("qrcodes/qrcode.txt", "r")
    except OSError:
        text = open("qrcodes/qrcode.txt", "w")
        text.write("""https://pimoroni.com/badger2040
    Clarinet Section
    Abbey, Brendan,
    Bronwyn, Cece,
    Deeya, Sammy
    """)
    text.flush()
    text.seek(0)

    # Load all available QR Code Files
    try:
        CODES = [f for f in os.listdir("/qrcodes") if f.endswith(".txt")]
        TOTAL_CODES = len(CODES)
    except OSError:
        pass

    print(f'There are {TOTAL_CODES} QR Codes available:')
    for codename in CODES:
        print(f'File: {codename}')

    # Create an instance of the QRCode class
    code = qrcode.QRCode()

    badger_os.state_load("qrcodes", state)
    changed = not badger2040.woken_by_button()
    return code, CODES, TOTAL_CODES, changed, state

# ----------------------------------------------------------
#      function create_and_configure_logo_page()
#
#         purpose:   Calls function to allocate and open
#                    logo file. Open or create logo.txt
#                    file. Reads lines of text from logo
#                    file. Limits the length of north_penn
#                    and marching_knights.
#         returns:   1. logo - file pointer
#                    2. anniversary - text line
#                    3. north_penn - text line
#                    4. marching_knights - text line.
#                    5. LOGO_IMAGE - byte array
# ----------------------------------------------------------
def create_and_configure_logo_page():
    LOGO_IMAGE = allocate_and_open_logo_image()

    # Open the logo.txt file
    try:
        logo = open("logo.txt", "r")
    except OSError:
        with open("logo.txt", "w") as f:
            f.write(LOGO_TEXT)
            f.flush()
        logo = open("logo.txt", "r")
    
    # Read in the 3 lines from badge.txt
    anniversary = logo.readline()           # "50th Anniversary"
    north_penn = logo.readline()            # "North Penn"
    marching_knights = logo.readline()      # "Marching Knights"

    # Truncate all of the text (except for the name as that is scaled)
    north_penn = truncatestring(north_penn, MARCHING_KNIGHTS_TEXT_SIZE, TEXT_WIDTH, display)
    marching_knights = truncatestring(marching_knights, MARCHING_KNIGHTS_TEXT_SIZE, TEXT_WIDTH, display)
    return logo, anniversary, north_penn, marching_knights, LOGO_IMAGE

# ------------------------------
#       Main program
# ------------------------------

display, badge, name, npmk_title, npmk_text, BADGE_IMAGE = create_and_configure_badge_page()

code, CODES, TOTAL_CODES, changed, state = create_and_configure_qrcode_page()

logo, anniversary, north_penn, marching_knights, LOGO_IMAGE = create_and_configure_logo_page()

while True:
    draw_badge()
    display.update()

    time.sleep(3)
    
    if changed:
        print( "Changed" )
        draw_qr_file(state["current_qr"])
        badger_os.state_save("qrcodes", state)
        #changed = False
    
    time.sleep(3)
    print( "Logo" )
    draw_logo()
    display.update()
    time.sleep(3)
    # If on battery, halt the Badger to save power, it will wake up if any of the front buttons are pressed
    #display.halt()

Another data point. I copied the example program, clock.py, to main.py and transferred it to the badger. The badger continues to count off the seconds after the badger has been removed from the USB cable connection (AAA battery pack powering the badger.) When I turn off the power on the included battery pack, the badger freezes (as expected.) However, when I turn on the power on the included battery pack, the badger remains frozen. I tried pressing RST. No change. I have another badger 2040 board. I repeated the test described here and saw the same results.

The clock.py program, running as main.py, remained frozen until I pressed the ‘a’, ‘b’ or ‘c’ buttons. The LED turned on and the program began counting seconds again. I copied my program to main.py and transferred main to the badger. I repeated the test. I set the power switch off on the battery pack, then turned it on. The display remained frozen until I pressed the ‘a’, ‘b’ or ‘c’ buttons. The LED turned on and the program began running again, just like clock.py. There’s 1 line in my program:
changed = not badger2040.woken_by_button()
that I modified to:
changed = True;
to remove any obvious reference to the buttons. I repeated the test and again saw that I can get the program running when the battery switch is turned on by pressing ‘a’, ‘b’ or ‘c’ buttons. Is there a library command common to clock.py and my program that requires button presses on power up from battery? Is there some feature in badger_os that requires a button press on power up from battery? Thank you, again for taking the time to look at this.

This works as designed (see the schematic of the Badger2040). Without VBUS, the enable-pin keeps the Badger2040 without current until you press a button.

You can find a solution here if you don’t need all buttons: Badger2040 does not wake up after deep-sleep

Thank you for taking the time to respond. I see in the schematic, integrated circuit U1, AP22802, pin 4, labeled EN, connected to SWA, SWB, SWC, SW_UP, SW_DOWN, VBUS_DETECT, etc. I assume this is the correct location. Also, thank you for the link to a workaround. That helps. I intend to build 6 displays, mounted in 3-D printed housings, as gifts for our Marching Band section. I’ll evaluate the tradeoff between power consumption and adding a pin hole in the housing to press a button (with a paper clip) after replacing the battery.