It is suggested you use the latest version of CircuitPython available (8.x at the time of writing this guide) for best results.
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your PyPortal CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
Files in the bundle:
# SPDX-FileCopyrightText: 2023 Anne Barela for Adafruit Industries # # SPDX-License-Identifier: MIT # # Faux Floppy Disk with LCD Screen # Display file icons on screen import os import time import board import displayio import adafruit_imageload import terminalio import adafruit_touchscreen from adafruit_display_text import label from adafruit_display_shapes.rect import Rect # Get a dictionary of filenames at the passed base directory # each entry is a tuple (filename, bool) where bool = true # means the filename is a directory, else false. def get_files(base): files = os.listdir(base) file_names = [] for isdir, filetext in enumerate(files): if not filetext.startswith("."): if filetext not in ('boot_out.txt', 'System Volume Information'): stats = os.stat(base + filetext) isdir = stats[0] & 0x4000 if isdir: file_names.append((filetext, True)) else: file_names.append((filetext, False)) return file_names def get_touch(screen): p = None while p is None: time.sleep(0.05) p = screen.touch_point return p[0] # Icon Positions ICONSIZE = 48 SPACING = 18 LEFTSPACE = 38 TOPSPACE = 10 TEXTSPACE = 10 ICONSACROSS = 4 ICONSDOWN = 3 PAGEMAXFILES = ICONSACROSS * ICONSDOWN # For the chosen display, this is the # maximum number of file icons that will fit # on the display at once (display dependent) # File Types BLANK = 0 FILE = 1 DIR = 2 BMP = 3 WAV = 4 PY = 5 RIGHT = 6 LEFT = 7 # Use the builtin display display = board.DISPLAY WIDTH = board.DISPLAY.width HEIGHT = board.DISPLAY.height ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YD, board.TOUCH_YU, calibration=((5200, 59000), (5800, 57000)), size=(WIDTH, HEIGHT)) # Create base display group displaygroup = displayio.Group() # Load the bitmap (this is the "spritesheet") sprite_sheet, palette = adafruit_imageload.load("/icons.bmp") background = Rect(0, 0, WIDTH - 1, HEIGHT - 1, fill=0x000000) displaygroup.append(background) # Create enough sprites & labels for the icons that will fit on screen sprites = [] labels = [] for _ in range(PAGEMAXFILES): sprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) sprites.append(sprite) # Append the sprite to the sprite array displaygroup.append(sprite) filelabel = label.Label(terminalio.FONT, color=0xFFFFFF) labels.append(filelabel) displaygroup.append(filelabel) # Make the more files and less files icons (> <) moresprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) displaygroup.append(moresprite) moresprite.x = WIDTH - ICONSIZE + TEXTSPACE moresprite.y = int((HEIGHT - ICONSIZE) / 2) lesssprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) displaygroup.append(lesssprite) lesssprite.x = -10 lesssprite.y = int((HEIGHT - ICONSIZE) / 2) display.root_group = displaygroup filecount = 0 xpos = LEFTSPACE ypos = TOPSPACE displaybase = "/" # Get file names in base directory filenames = get_files(displaybase) currentfile = 0 # Which file is being processed in all files spot = 0 # Which spot on the screen is getting a file icon PAGE = 1 # Which page of icons is displayed on screen, 1 is first while True: if currentfile < len(filenames) and spot < PAGEMAXFILES: filename, dirfile = filenames[currentfile] if dirfile: filetype = DIR elif filename.endswith(".bmp"): filetype = BMP elif filename.endswith(".wav"): filetype = WAV elif filename.endswith(".py"): filetype = PY else: filetype = FILE # Set icon location information and icon type sprites[spot].x = xpos sprites[spot].y = ypos sprites[spot][0] = filetype # # Set filename labels[spot].x = xpos labels[spot].y = ypos + ICONSIZE + TEXTSPACE # The next line gets the filename without the extension, first 11 chars labels[spot].text = filename.rsplit('.', 1)[0][0:10] currentpage = PAGE # Pagination Handling if spot >= PAGEMAXFILES - 1: if currentfile < (len(filenames) + 1): # Need to display the greater than touch sprite moresprite[0] = RIGHT else: # Blank out more and extra icon spaces moresprite[0] = BLANK if PAGE > 1: # Need to display the less than touch sprite lesssprite[0] = LEFT else: lesssprite[0] = BLANK # Time to check for user touch of screen (BLOCKING) touch_x = get_touch(ts) print("Touch Registered ") # Check if touch_x is around the LEFT or RIGHT arrow currentpage = PAGE if touch_x >= int(WIDTH - ICONSIZE): # > Touched if moresprite[0] != BLANK: # Ensure there are more if spot == (PAGEMAXFILES - 1): # Page full if currentfile < (len(filenames)): # and more files PAGE = PAGE + 1 # Increment page if touch_x <= ICONSIZE: # < Touched if PAGE > 1: PAGE = PAGE - 1 # Decrement page else: lesssprite[0] = BLANK # Not show < for first page print("Page ", PAGE) # Icon Positioning if PAGE != currentpage: # We have a page change # Reset icon locations to upper left xpos = LEFTSPACE ypos = TOPSPACE spot = 0 if currentpage > PAGE: # Decrement files by a page (current page & previous page) currentfile = currentfile - (PAGEMAXFILES * 2) + 1 else: # Forward go to the next file currentfile = currentfile + 1 else: currentfile += 1 # Increment file counter spot += 1 # Increment icon space counter if spot == PAGEMAXFILES: # Last page ended with print("hit") # calculate next icon location if spot % ICONSACROSS: # not at end of icon row xpos += SPACING + ICONSIZE else: # start new icon row ypos += ICONSIZE + SPACING + TEXTSPACE xpos = LEFTSPACE # End If Changed Page # Blank out rest if needed if currentfile == len(filenames): for i in range(spot, PAGEMAXFILES): sprites[i][0] = BLANK labels[i].text = " " # End while
Coding the application on the Adafruit PyPortal with CircuitPython provides a clean, high level method of creating a user interface which can be adapted for other displays of varying sizes and types.
If a greyscale display, smaller (or larger) display or possibly an eInk device was desired, the application only would need a few changes (if there is capacitive touch, a bit more coding to use buttons if not).
import os import time import board import displayio import adafruit_imageload import terminalio import adafruit_touchscreen from adafruit_display_text import label from adafruit_display_shapes.rect import Rect
A function, get_files()
, gets a dictionary of files for the passed-in director name. For this demo, the base directory "/" is used. If the code is modified to do subdirectories, etc. the function only needs the correct path. The return is a dictionary of filename, boolean tuples. If the boolean is True, the filename refers to a directory.
# Get a dictionary of filenames at the passed base directory # each entry is a tuple (filename, bool) where bool = true # means the filename is a directory, else false. def get_files(base): files = os.listdir(base) file_names = [] for j, filetext in enumerate(files): if not filetext.startswith("."): if filetext not in ('boot_out.txt', 'System Volume Information'): stats = os.stat(base + filetext) isdir = stats[0] & 0x4000 if isdir: file_names.append((filetext, True)) else: file_names.append((filetext, False)) return filenames
And then a function to wait and get a touchscreen touch and return the x coordinate only (all that is needed):
def get_touch(screen) p = None while p is None: time.sleep(0.05) p = screen.touch_point return p[0]
The values which get the file icons spaced out appropriately. The values are for PyPortal, but if you want to use a different display, you can change the values here rather than "magic numbers" scattered throughout the code.
# Icon Positions ICONSIZE = 48 SPACING = 18 LEFTSPACE = 38 TOPSPACE = 10 TEXTSPACE = 10 ICONSACROSS = 4 ICONSDOWN = 3 PAGEMAXFILES = ICONSACROSS * ICONSDOWN # For the chosen display, this is the # maximum number of file icons that will fit # on the display at once (display dependent)
There is blank icon, followed by five defined file icons, and the left & right arrows for pagination. If more icons are wanted, add them after the last file icon (currently PY).
# File Types BLANK = 0 FILE = 1 DIR = 2 BMP = 3 WAV = 4 PY = 5 RIGHT = 6 LEFT = 7
The display is then set up. The code uses the CircuitPython displayio framework. The display touchscreen is set up for use.
# Use the builtin display display = board.DISPLAY WIDTH = board.DISPLAY.width HEIGHT = board.DISPLAY.height ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YD, board.TOUCH_YU, calibration=((5200, 59000), (5800, 57000)), size=(WIDTH, HEIGHT)) # Create base display group displaygroup = displayio.Group()
Now to load the icons:
# Load the bitmap (this is the "spritesheet") sprite_sheet, palette = adafruit_imageload.load("/icons.bmp")
This code sets up all the icon positions based on the constants defined earlier:
sprites = [] labels = [] for _ in range(PAGEMAXFILES): sprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) sprites.append(sprite) # Append the sprite to the sprite array displaygroup.append(sprite) filelabel = label.Label(terminalio.FONT, color=0xFFFFFF) labels.append(filelabel) displaygroup.append(filelabel) # Make the more files and less files icons (> <) moresprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) displaygroup.append(moresprite) moresprite.x = WIDTH - ICONSIZE + TEXTSPACE moresprite.y = int((HEIGHT - ICONSIZE) / 2) lesssprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_height=48, tile_width=48,) displaygroup.append(lesssprite) lesssprite.x = -10 lesssprite.y = int((HEIGHT - ICONSIZE) / 2) display.root_group = displaygroup
The code iterates through all the filenames, displaying them in sequential icon locations. The icons are labeled with the first 10 characters of the filename (without the extension). This can be changed, there isn't a lot of room.
filename, dirfile = filenames[currentfile] if dirfile: filetype = DIR elif filename.endswith(".bmp"): filetype = BMP elif filename.endswith(".wav"): filetype = WAV elif filename.endswith(".py"): filetype = PY else: filetype = FILE # Set icon location information and icon type sprites[spot].x = xpos sprites[spot].y = ypos sprites[spot][0] = filetype # # Set filename labels[spot].x = xpos labels[spot].y = ypos + ICONSIZE + TEXTSPACE # The next line gets the filename without the extension, first 11 chars labels[spot].text = filename.rsplit('.', 1)[0][0:10]
The next chunk of code is lengthy, handling the pagination. Putting the < and > icons on screen as necessary, check for a touch on the screen, and changing pages (clearing any icons as needed). It's page oriented, there is not smooth scrolling or any dragging a finger to see files.
touch_x = get_touch(ts) print("Touch Registered ") # Check if touch_x is around the LEFT or RIGHT arrow currentpage = PAGE if touch_x >= int(WIDTH - ICONSIZE): # > Touched if moresprite[0] != BLANK: # Ensure there are more if spot == (PAGEMAXFILES - 1): # Page full if currentfile < (len(filenames)): # and more files PAGE = PAGE + 1 # Increment page if touch_x <= ICONSIZE: # < Touched if PAGE > 1: PAGE = PAGE - 1 # Decrement page else: lesssprite[0] = BLANK # Not show < for first page print("Page ", PAGE) # Icon Positioning if PAGE != currentpage: # We have a page change # Reset icon locations to upper left xpos = LEFTSPACE ypos = TOPSPACE spot = 0 if currentpage > PAGE: # Decrement files by a page (current page & previous page) currentfile = currentfile - (PAGEMAXFILES * 2) + 1 else: # Forward go to the next file currentfile = currentfile + 1 else: currentfile += 1 # Increment file counter spot += 1 # Increment icon space counter if spot == PAGEMAXFILES: # Last page ended with print("hit") # calculate next icon location if spot % ICONSACROSS: # not at end of icon row xpos += SPACING + ICONSIZE else: # start new icon row ypos += ICONSIZE + SPACING + TEXTSPACE xpos = LEFTSPACE # End If Changed Page # Blank out rest if needed if currentfile == len(filenames): for i in range(spot, PAGEMAXFILES): sprites[i][0] = BLANK labels[i].text = " " # End while
Text editor powered by tinymce.