In this project we’re building a view master inspired device using Adafruit’s PyPortal.

The eyepiece makes the viewing experience feel immersive and novel.

When you pull the down on the crank, it goes through the image on the screen like a slideshow.

The mechanism uses a compression spring so it can spring back when it’s released.

The case design is 3D printed and uses fasteners to secure all the components in the assembly.

For portability it uses PowerBoost and battery.

Inside are pieces of Conductive Nylon Tape that close a switch when the crank is pulled.  

Code is written in Circuit Python and uses Adafruit’s libraries to make a slideshow.

Use the Adafruit PyPortal libraries to display bitmap images from either the internal storage or micro SD card.

The images actually fade in and out when cycling through them so it makes for nice and smooth effect.

Parts List

Front view of a Adafruit PyPortal - CircuitPython Powered Internet Display with a pyportal logo image on the display.
PyPortal, our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Make custom touch screen interface...
In Stock
Angled shot of PowerBoost 1000 Charger.
PowerBoost 1000C is the perfect power supply for your portable project! With a built-in load-sharing battery charger circuit, you'll be able to keep your power-hungry...
Out of Stock
Lithium Ion Polymer Battery 3.7v 2000mAh with JST 2-PH connector
Lithium-ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light, and powerful. The output ranges from 4.2V when completely charged to 3.7V. This...
Out of Stock
Breadboard-friendly SPDT Slide Switch
These nice switches are perfect for use with breadboard and perfboard projects. They have 0.1" spacing and snap in nicely into a solderless breadboard. They're easy to switch...
In Stock
Angled shot of STEMMA JST PH 3-Pin to Male Header Cable - 200mm.
This cable will let you turn a JST PH 3-pin cable port into 3 individual wires with high-quality 0.1" male header plugs on the end. We're carrying these to match up with our...
In Stock
Conductive Nylon Fabric Tape - 5mm Wide
With our fun assortment of conductive materials, 
In Stock

This provides a visual reference for wiring of the components. They aren't true to scale, just meant to be used as reference. This diagrams was created using Fritzing software.

Circuit Diagram


The 2-pin JST cable from the battery connects directly into battery port on the PowerBoost 1000C. Voltage and Ground wires from the PowerBoost 1000C connect to the 3-pin JST port on the PyPortal (doesn't matter which one).

  • 5V/VCC pin from PowerBoost 1000C to 3-pin JST(VCC) on PyPortal
  • GND from PowerBoost 1000C to 3-pin JST(GND) on PyPortal
  • EN from PowerBoost 1000C to switch
  • GND from PowerBoost 100C to switch




Slide Switch

Use a 2-wire cable (110mm long) to connect the slide switch to the PowerBoost. Remove a bit of insulation from the tips of the wire and tin them with a bit of solder. Connect the wires to the middle pin and either the far left or right pin on the switch. A third helping hand tool can hold the switch and wire in place while soldering.

Connect JST Cable

We'll use a 3-pin JST cable (9cm long) to easily connect the PowerBoost to the PyPortal. We only need two of the three wires, so you can remove the white wire. Using wire strippers, remove a bit of insulation from the tips of the wire and tin them with a bit of solder. Solder the wires to the voltage (+) and ground (–) pins on the USB output of the PowerBoost.

As of CircuitPython 9, a mount point (folder) named /sd is required on the CIRCUITPY drive. Make sure to create that directory after upgrading CircuitPython.

CircuitPython Setup

Your Adafruit PyPortal should already come with CircuitPython but maybe there's a new version, or you overwrote your board with Arduino code! In that case, see the below for how to reinstall or update CircuitPython. Otherwise you can skip this and proceed with the build.

Images on internal flash

The code looks for an "images" folder on the CIRCUITPY drive by default. If it can't find any there, it'll search for images on the SD card.

Images on SD Card

If no images are on the internal storage, the code will search on the microSD card. The microSD card must be FAT32 and contain a folder named "images".

Image file names can be titled arbitrarily but avoid using special characters, hyphens and the sort. Your images must be in the following format and resolution.

  • Images must be 320 x 240 pixel 16-bit color .bmp files
Download our sample image pack to get you started!

Upload Code Files

Click the Download Project Bundle button below and save the zip file to your computer. Connect your PyPortal to your computer via a known good USB data+power cable. The PyPortal should show up as a drive in your file explorer app named CIRCUITPY.

Copy the files from the zip file to the CIRCUITPY drive.

# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import board
import busio
import digitalio
import storage
import sdcardio
from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection

# Default location to look is in internal memory

switch = digitalio.DigitalInOut(board.D3)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
    sdcard = sdcardio.SDCard(spi, board.SD_CS)
    vfs = storage.VfsFat(sdcard)
    storage.mount(vfs, "/sd")
    IMAGE_DIRECTORY = "/sd/images"
except OSError as error:
    print("No SD card, will only look on internal memory")

def print_directory(path, tabs=0):
    for file in os.listdir(path):
        stats = os.stat(path + "/" + file)
        filesize = stats[6]
        isdir = stats[0] & 0x4000

        if filesize < 1000:
            sizestr = str(filesize) + " by"
        elif filesize < 1000000:
            sizestr = "%0.1f KB" % (filesize / 1000)
            sizestr = "%0.1f MB" % (filesize / 1000000)

        prettyprintname = ""
        for _ in range(tabs):
            prettyprintname += "   "
        prettyprintname += file
        if isdir:
            prettyprintname += "/"
        print('{0:<20} Size: {1:>6}'.format(prettyprintname, sizestr))

        # recursively print directory contents
        if isdir:
            print_directory(path + "/" + file, tabs + 1)

except OSError as error:
    raise Exception("No images found on flash or SD Card")

# Create the slideshow object that plays through once alphabetically.
slideshow = SlideShow(board.DISPLAY, None, folder=IMAGE_DIRECTORY, loop=True,
                      order=PlayBackOrder.ALPHABETICAL, dwell=0)
while True:
    if not switch.value:
        slideshow.direction = PlayBackDirection.FORWARD
        while not switch.value:

Your CIRCUITPY drive should look like the file directory structure below:



These libraries should be on the CIRCUITPY drive lib/ directory:

  • adafruit_pyportal
  • adafruit_imageload
  • adafruit_slideshow
  • adafruit_bus_device
  • neopixel.mpy

3D Printed Parts

Parts are designed to be 3D printed with FDM based machines. STL files are oriented to print "as is". Parts are listed below with file name and description. Parts require tight tolerances that might need adjusting slice setting. Reference the suggested settings below.


Slice Settings

Use these settings as reference. Values listed were used in Ultimaker's CURA 3.X slicing software.

  • 0.2mm Layer Height / 0.4mm nozzle
  • 0.35mm Line Width (inner & outer widths)
  • 60mm/s printing speed
  • 20% infill
  • Supports: Yes


Cura Support Blockers

We added support blockers to the four standoffs on the Frame part, since we'll only need supports under the roof of the slide switch geometry. 

We can lower the support destiny to 10% using a zigzag pattern to make removal easier. 

Support Removal

To remove the supports, we used tweezers to fit between the zigzag pattern to grip and pull the supports away.



Crank Assembly

First we'll start by assembling the crank that acts as a switch to advance to the next photo. Lay the Crank part inside the groove in the Crank-Holder part. 

Attach Spring

Cut a 5/15"x1 1/2x.02" (7.94mmx38x.5mm) spring compression to 20.5mm in length. Next carefully bend one of the ends flat. Pass the end through the ring on the Crank part as shown in the picture.

Now we can attach the other end to the Crank Holder. Rotate the spring and Crank assembly to pass the end of the spring into the hoop on the Crank Holder.

Test the spring back action. Make sure the spring doesn't block the crank from rotating. 

Eyepiece Layers

Next we'll sandwich the three layers that will holder the crank assembly in place. 

Align the Eyepiece part with the Eyepiece-side and Crank assembly. 

Use the Crank parts center nub to align with the Eyepiece. Insert the post on the Crank into the cavity on the Eyepiece.

Note the orientation of the Eyepiece-Side fits together with the cutaway on the Crank Holder.

Fasten with together with four M3x16mm long screws.

Build Crank Switch

The switch is made with the use of conductive nylon tape. When the crank rotates, one contact meets with the other contact. This triggers the PyPortal to advance to the next picture.

We'll use two strips of conductive nylon tape to attach the GND and D4 pins to the Crank and Crank Holder parts.

Attach Nylon Tape

We'll measure two strips of Conductive Nylon tape about 14mm long and wrap about 2/3 the length around each of pins ends. Stick the remaining length to the Crank and the other pin to the Crank Holder as shown in the picture.

Assemble PowerBoost1000C

Now we can move on to adding our connections for the Slide Switch and Power wires for the PyPortal.

For the slide switch, we'll measure and cut two wires 110mm long.

For power out, cut off the jumper pin ends and tin and solder the wires to the + and - pads on the PowerBoost.

We used silicone wire to make bending them around components easier. 

Mount PowerBoost

Align the PowerBoost to the standoffs inside the Frame part. Oriente the USB port so it faces the edge of the frame.

We used four M2.5x5mm long screws to secure the PowerBoost to the Frame

Mount Slide Switch 

Make sure to first remove the supports inside the slide switch mount.

The slide switch press fits into the mounts inside the Frame part. Place the slide switch at an angle and press fit into place.

Speaker Mount

We can optionally mount an oval speaker to the opposite wall inside the Frame part.

To mount, first peel the sticker from the ring around the front of the speaker, orient so the wires face closer to the slide switch, angle and press into place. 

Attach Lid 

Next we'll mount the Lid part to the Frame. Align the Lid so the slot for the slide switch matches on the Frame.

We used M3x5mm screws to secure the Lid to the Frame.

Route Wires

The wires from the Powerboost can pass through the slot on the Lid part and into the PyPortal. Position the wires so they lay down and don't obstruct the screen. 

Mount Battery 

Next we'll mount our battery to the Frame. We used a small piece of foam tape to secure it.

Orient the battery so the wires are facing the PowerBoost. Position the battery in the center of the Frame part, but with enough space away from the slide switch.

Connect the battery to the JST port on the PowerBoost. We can flip the switch off if it turns on.

Attach PyPortal 

Moving on to mounting the PyPortal. Lay the PyPortal with the screen facing towards in the inside of the Frame. Oriente so the ports on the PyPortal face the PowerBoost cable. The standoffs with the cut aways only align with the correct standoffs on the PyPortal

Add Cover

Now we'll lay the Cover part on top of the PyPortal. Align the Cover part so the Rest button and Port openings match. 

We'll use four M3x10mm long screws to fasten the Cover to the PyPortal and Frame parts.


Attach Frame to Eyepiece

Final stretch! We can now move on to attaching the Eyepiece assembly to the Fame assembly. 

Align the Eyepiece assembly with the Crank on the same side as the PowerBoost

Pass the conductive switch wires through the slots on the Lid and position the wires so they lay down and don't obstruct the display.

Test Switch

Before we mount the Eyepiece assembly, we'll go ahead and switch on the PyPortal to test out the switch and adjust the Conductive Nylon Tape if needed. We can move the conductive nylon tape closer to the "end stop" if the Crank gets stuck during rotation. 


Slide the switch on and try out the slideshow!

This guide was first published on Mar 12, 2019. It was last updated on Jul 24, 2024.