Setup Feather M4 with CircuitPython

We'll need to get our board setup so we can run CircuitPython code. Let's walk through these steps to get the latest version of CircuitPython onto your board. 

The Mu Python Editor

Mu is a simple Python editor that works with Adafruit CircuitPython hardware. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in so you get immediate feedback from your board's serial output! While you can use any text editor with your code, Mu makes it super simple.

Installing or upgrading CircuitPython

You should ensure you have CircuitPython 5.0 or greater on your board. Plug your board in with a known good data + power cable (not the cheesy USB cable that comes with USB power packs, they are power only). You should see a new flash drive pop up.

If the drive is CIRCUITPY, then open the boot_out.txt file to ensure the version number is 5.0 or greater. 

Adafruit CircuitPython 5.0.0-beta.0 on 2019-11-19; Adafruit Feather M4 Express with samd51j19

If the version is less than 5 -or- you only get a drive named FEATHERBOOT then follow the Feather M4 guide on installing CircuitPython.

Download the Adafruit CircuitPython Library Bundle

In order to run the code, we'll need to download a few libraries. Libraries contain code to help interface with hardware a lot easier for us.

Use the Feather M4 page on Installing Libraries to get the library that matches the major version of CircuitPython you are using noted above.

The green button below links to a file containing all the libraries available for CircuitPython. To run the code for this project, we need the large number of libraries in the Required Libraries list below. Unzip the library bundle and search for the libraries. Drag and drop the files into a folder named lib on the CIRCUITPY drive (create the folder if it is not already on the Feather M4).

Required Libraries 

  • adafruit_display_text
  • adafruit_bitmap_font
  • adafruit_imageload
  • adafruit_motor
  • adafruit_register
  • adafruit_motorkit.mpy
  • adafruit_pca9685.mpy
  • adafruit_st7735r.mpy
  • adafruit_bus_device
  • adafruit_featherwing
  • adafruit_seesaw

Once we have all the files we need, a directory listing will look similar to below as far as files and directories.

Upload Code

Click on the Download: Project Zip link below to grab the project files in a zip file directly from GitHub. Place the code.py file and bitmap graphics files onto the CIRCUITPY main (root) directory. The code will run properly when all of the files have been uploaded including libraries.

Use any text editor or favorite IDE to modify the code. We suggest using Mu as noted above.

# SPDX-FileCopyrightText: 2019 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import displayio
import terminalio
import adafruit_imageload
from adafruit_display_text.label import Label
from adafruit_featherwing import minitft_featherwing
from adafruit_motorkit import MotorKit
from adafruit_motor import stepper

# setup stepper motor
kit = MotorKit()

# setup minitft featherwing
minitft = minitft_featherwing.MiniTFTFeatherWing()

# setup bitmap file locations
five_minBMP = "/5min_bmp.bmp"
ten_minBMP = "/10min_bmp.bmp"
twenty_minBMP = "/20min_bmp.bmp"
hourBMP = "/60min_bmp.bmp"
runningBMP = "/camSlide_bmp.bmp"
reverseqBMP = "/reverseQ_bmp.bmp"
backingUpBMP = "/backingup_bmp.bmp"
stopBMP = "/stopping_bmp.bmp"

# variables for state machines in loop
mode = 0
onOff = 0
pause = 0
stop = 0
z = 0

# image groups
five_minGroup = displayio.Group()
ten_minGroup = displayio.Group()
twenty_minGroup = displayio.Group()
hourGroup = displayio.Group()
reverseqGroup = displayio.Group()
backingUpGroup = displayio.Group()
stopGroup = displayio.Group()
progBarGroup = displayio.Group()

# bitmap setup for all of the menu screens
five_minBG, five_minPal = adafruit_imageload.load(
    five_minBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
five_minDis = displayio.TileGrid(five_minBG, pixel_shader=five_minPal)
ten_minBG, ten_minPal = adafruit_imageload.load(
    ten_minBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
ten_minDis = displayio.TileGrid(ten_minBG, pixel_shader=ten_minPal)
twenty_minBG, twenty_minPal = adafruit_imageload.load(
    twenty_minBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
twenty_minDis = displayio.TileGrid(twenty_minBG, pixel_shader=twenty_minPal)
hourBG, hourPal = adafruit_imageload.load(
    hourBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
hourDis = displayio.TileGrid(hourBG, pixel_shader=hourPal)
runningBG, runningPal = adafruit_imageload.load(
    runningBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
runningDis = displayio.TileGrid(runningBG, pixel_shader=runningPal)
reverseqBG, reverseqPal = adafruit_imageload.load(
    reverseqBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
reverseqDis = displayio.TileGrid(reverseqBG, pixel_shader=reverseqPal)
backingUpBG, backingUpPal = adafruit_imageload.load(
    backingUpBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
backingUpDis = displayio.TileGrid(backingUpBG, pixel_shader=backingUpPal)
stopBG, stopPal = adafruit_imageload.load(
    stopBMP, bitmap=displayio.Bitmap, palette=displayio.Palette
)
stopDis = displayio.TileGrid(stopBG, pixel_shader=stopPal)

# setup for timer display when camera is sliding
text_area = Label(terminalio.FONT, text="      ")
text_area.x = 55
text_area.y = 65

# adding the bitmaps to the image groups so they can be displayed
five_minGroup.append(five_minDis)
ten_minGroup.append(ten_minDis)
twenty_minGroup.append(twenty_minDis)
hourGroup.append(hourDis)
progBarGroup.append(runningDis)
progBarGroup.append(text_area)
reverseqGroup.append(reverseqDis)
backingUpGroup.append(backingUpDis)
stopGroup.append(stopDis)

# setting button states on minitft featherwing to None
down_state = None
up_state = None
a_state = None
b_state = None
select_state = None

# arrays to match up with the different slide speeds
# graphics menu array
graphics = [five_minGroup, ten_minGroup, twenty_minGroup, hourGroup]
# delay for the stepper motor
speed = [0.0154, 0.034, 0.0688, 0.2062]
# time duration for the camera slide
slide_duration = [300, 600, 1200, 3600]
# beginning timer display
slide_begin = ["5:00", "10:00", "20:00", "60:00"]
# stepper motor steps that corresponds with the timer display
# fmt: off
slide_checkin = [  860,  1720,  2580,  3440,  4300,  5160,
                  6020,  6880,  7740,  8600,  9460, 10320,
                 11180, 12040, 12900, 13760, 14620, 15480,
                 16340, 17195]
# fmt: on
# variable that counts up through the slide_checkin array
check = 0

# start time
begin = time.monotonic()
print(begin)
# when feather is powered up it shows the initial graphic splash
minitft.display.show(graphics[mode])

while True:
    # setup minitft featherwing buttons
    buttons = minitft.buttons
    # define the buttons' state changes
    if not buttons.down and down_state is None:
        down_state = "pressed"
    if not buttons.up and up_state is None:
        up_state = "pressed"
    if not buttons.select and select_state is None:
        select_state = "pressed"
    if not buttons.a and a_state is None:
        a_state = "pressed"
    if not buttons.b and b_state is None:
        b_state = "pressed"
    # scroll down to change slide duration and graphic
    if buttons.down and down_state == "pressed":
        # blocks the button if the slider is sliding or
        # in an in-between state
        if pause == 1 or onOff == 1:
            mode = mode
            down_state = None
        else:
            mode += 1
            down_state = None
            if mode > 3:
                mode = 0
            print("Mode:,", mode)
            minitft.display.show(graphics[mode])
    # scroll up to change slide duration and graphic
    if buttons.up and up_state == "pressed":
        # blocks the button if the slider is sliding or
        # in an in-between state
        if pause == 1 or onOff == 1:
            mode = mode
            up_state = None
        else:
            mode -= 1
            up_state = None
            if mode < 0:
                mode = 3
            print("Mode: ", mode)
            minitft.display.show(graphics[mode])
    # workaround so that the menu graphics show after a slide is finished
    if mode == mode and pause == 0 and onOff == 0:
        minitft.display.show(graphics[mode])
    # starts slide
    if buttons.select and select_state == "pressed" or z == 2:
        # blocks the button if the slider is sliding or
        # in an in-between state
        if pause == 1 or onOff == 1:
            # print("null")
            select_state = None
        else:
            # shows the slider is sliding graphic
            minitft.display.show(progBarGroup)
            # gets time of button press
            press = time.monotonic()
            print(press)
            # displays initial timer
            text_area.text = slide_begin[mode]
            # resets button
            select_state = None
            # changes onOff state
            onOff += 1
            # changes z state
            z = 0
            if onOff > 1:
                onOff = 0
            # number of steps for the length of the aluminum extrusions
            for i in range(17200):
                # for loop start time
                start = time.monotonic()
                # gets actual duration time
                real_time = start - press
                # creates a countdown from the slide's length
                end = slide_duration[mode] - real_time
                # /60 since time is in seconds
                mins_remaining = end / 60
                if mins_remaining < 0:
                    mins_remaining += 60
                # gets second(s) count
                total_sec_remaining = mins_remaining * 60
                # formats to clock time
                mins_remaining, total_sec_remaining = divmod(end, 60)
                # microstep for the stepper
                kit.stepper1.onestep(style=stepper.MICROSTEP)
                # delay determines speed of the slide
                time.sleep(speed[mode])
                if i == slide_checkin[check]:
                    # check-in for time remaining based on motor steps
                    print("0%d:%d" % (mins_remaining, total_sec_remaining))
                    print(check)
                    if total_sec_remaining < 10:
                        text_area.text = "%d:0%d" % (
                            mins_remaining,
                            total_sec_remaining,
                        )
                    else:
                        text_area.text = "%d:%d" % (mins_remaining, total_sec_remaining)
                    check = check + 1
                if check > 19:
                    check = 0
                if end < 10:
                    # displays the stopping graphic for the last 10 secs.
                    minitft.display.show(stopGroup)
            # changes states after slide has completed
            kit.stepper1.release()
            pause = 1
            onOff = 0
            stop = 1
            check = 0
            # delay for safety
            time.sleep(2)
            # shows choice menu
            minitft.display.show(reverseqGroup)
    # b is defined to stop the slider
    # only active if the slider is in the 'stopped' state
    if buttons.b and b_state == "pressed" and stop == 1:
        # z defines location of the camera on the slider
        # 0 means that it is opposite the motor
        if z == 0:
            b_state = None
            time.sleep(1)
            minitft.display.show(backingUpGroup)
            # delay for safety
            time.sleep(2)
            # brings camera back to 'home' at double speed
            for i in range(1145):
                kit.stepper1.onestep(direction=stepper.BACKWARD, style=stepper.DOUBLE)
            time.sleep(1)
            kit.stepper1.release()
            # changes states
            pause = 0
            stop = 0
        # 1 means that the camera is next to the motor
        if z == 1:
            b_state = None
            time.sleep(2)
            # changes states
            pause = 0
            stop = 0
            z = 0
    # a is defined to slide in reverse of the prev. slide
    # only active if the slider is in the 'stopped' state
    if buttons.a and a_state == "pressed" and stop == 1:
        # z defines location of the camera on the slider
        # 1 means that the camera is next to the motor
        if z == 1:
            a_state = None
            time.sleep(2)
            stop = 0
            pause = 0
            # 2 allows the 'regular' slide loop to run
            # as if the 'select' button has been pressed
            z = 2
        # 0 means that the camera is opposite the motor
        if z == 0:
            a_state = None
            # same script as the 'regular' slide loop
            time.sleep(2)
            minitft.display.show(progBarGroup)
            press = time.monotonic()
            print(press)
            text_area.text = slide_begin[mode]
            onOff += 1
            pause = 0
            stop = 0
            if onOff > 1:
                onOff = 0
            for i in range(17200):
                start = time.monotonic()
                real_time = start - press
                end = slide_duration[mode] - real_time
                mins_remaining = end / 60
                if mins_remaining < 0:
                    mins_remaining += 60
                total_sec_remaining = mins_remaining * 60
                mins_remaining, total_sec_remaining = divmod(end, 60)
                # only difference is that the motor is stepping backwards
                kit.stepper1.onestep(
                    direction=stepper.BACKWARD, style=stepper.MICROSTEP
                )
                time.sleep(speed[mode])
                if i == slide_checkin[check]:
                    print("0%d:%d" % (mins_remaining, total_sec_remaining))
                    if total_sec_remaining < 10:
                        text_area.text = "%d:0%d" % (
                            mins_remaining,
                            total_sec_remaining,
                        )
                    else:
                        text_area.text = "%d:%d" % (mins_remaining, total_sec_remaining)
                    check = check + 1
                if check > 19:
                    check = 0
                if end < 10:
                    minitft.display.show(stopGroup)
            # state changes
            kit.stepper1.release()
            pause = 1
            onOff = 0
            stop = 1
            z = 1
            check = 0
            time.sleep(2)
            minitft.display.show(reverseqGroup)

Double Check

See the directory listing above and double check that you have all the files listed to make this project function. If any are missing or in an incorrect directory, move them so they're in the right places.

This guide was first published on Dec 18, 2019. It was last updated on Dec 18, 2019.

This page (Software Setup) was last updated on May 20, 2023.

Text editor powered by tinymce.