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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = 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.root_group = stopGroup
# state changes
kit.stepper1.release()
pause = 1
onOff = 0
stop = 1
z = 1
check = 0
time.sleep(2)
minitft.display.root_group = reverseqGroup
Page last edited January 22, 2025
Text editor powered by tinymce.