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.