Setup ItsyBitsy 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 4.0 or greater on your ItsyBitsy M4. 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 4.0 or greater. 

Download: file
Adafruit CircuitPython 5.0.0-alpha.4 on 2019-09-15; Adafruit ItsyBitsy M4 Express with samd51j19

If the version is less than 4 -or- you only get a drive named ITSYM4BOOT then follow the steps below to update your board CircuitPython software:

  • Download the CircuitPython UF2 for ItsyBitsy M4 via the green button below.
  • Connect ItsyBitsy M4 to your computer over USB and press the Reset button.
  • Drag-n-drop the CircuitPython UF2 onto the ITSYM4BOOT drive - the drive will vanish and a new CIRCUITPY drive should appear.

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.

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 two 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 ItsyBitsy M4).

Required Libraries 

  • adafruit_st7789
  • adafruit_imageload

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

Upload Code

This project offers two different code.py sketches. Both programs can be used with either display. 

Click on the download link below to grab the main code directly from GitHub. Rename the file to code.py and drop it 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.

Upload Bitmaps

Download the bitmap images below and save them to the root of the CIRCUITPY drive.

  • tilesheet-2x.bmp is for the Mario Clouds code.
  • spritesheet-2x.bmp is for the Flying Toasters code.

The bitmap images were named differently so that both files can reside in the CIRCUITPY drive.

Scrolling Clouds

"""
Continuously scroll randomly generated Mario style clouds.
Designed fr an ItsyBitsy M4 Express and a 1.3" 240x240 TFT

Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!

Written by Dave Astels for Adafruit Industries
Copyright (c) 2019 Adafruit Industries
Licensed under the MIT license.

All text above must be included in any redistribution.
"""

import time
from random import seed, randint
import board
import displayio
from adafruit_st7789 import ST7789
import adafruit_imageload

# Sprite cell values
EMPTY = 0
LEFT = 1
MIDDLE = 2
RIGHT = 3

# These constants determine what happens when tiles are shifted.
# if randint(1, 10) > the value, the thing happens

# The chance a new cloud will enter
CHANCE_OF_NEW_CLOUD = 4

# The chance an existing cloud gets extended
CHANCE_OF_EXTENDING_A_CLOUD = 5

# Global variables
display = None
tilegrid = None

seed(int(time.monotonic()))

def make_display():
    """Set up the display support.
    Return the Display object.
    """
    spi = board.SPI()
    while not spi.try_lock():
        pass
    spi.configure(baudrate=24000000) # Configure SPI for 24MHz
    spi.unlock()

    displayio.release_displays()
    display_bus = displayio.FourWire(spi, command=board.D7, chip_select=board.D10, reset=board.D9)

    return ST7789(display_bus, width=240, height=240, rowstart=80, auto_refresh=True)

def make_tilegrid():
    """Construct and return the tilegrid."""
    group = displayio.Group(max_size=10)

    sprite_sheet, palette = adafruit_imageload.load("/tilesheet-2x.bmp",
                                                    bitmap=displayio.Bitmap,
                                                    palette=displayio.Palette)
    grid = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
                              width=9, height=5,
                              tile_height=48, tile_width=32,
                              default_tile=EMPTY)
    group.append(grid)
    display.show(group)
    return grid

def evaluate_position(row, col):
    """Return how long of a cloud is placable at the given location.
    :param row: the tile row (0-4)
    :param col: the tile column (0-8)
    """
    if tilegrid[col, row] != EMPTY or tilegrid[col + 1, row] != EMPTY:
        return 0
    end_col = col + 1
    while end_col < 9 and tilegrid[end_col, row] == EMPTY:
        end_col += 1
    return min([4, end_col - col])

def seed_clouds(number_of_clouds):
    """Create the initial clouds so it doesn't start empty"""
    for _ in range(number_of_clouds):
        while True:
            row = randint(0, 4)
            col = randint(0, 7)
            cloud_length = evaluate_position(row, col)
            if cloud_length > 0:
                break
        l = randint(1, cloud_length)
        tilegrid[col, row] = LEFT
        for _ in range(l - 2):
            col += 1
            tilegrid[col, row] = MIDDLE
        tilegrid[col + 1, row] = RIGHT

def slide_tiles():
    """Move the tilegrid to the left, one pixel at a time, a full time width"""
    for _ in range(32):
        tilegrid.x -= 1
        display.refresh(target_frames_per_second=60)


def shift_tiles():
    """Move tiles one spot to the left, and reset the tilegrid's position"""
    for row in range(5):
        for col in range(8):
            tilegrid[col, row] = tilegrid[col + 1, row]
        tilegrid[8, row] = EMPTY
    tilegrid.x = 0

def extend_clouds():
    """Extend any clouds on the right edge, either finishing them with a right
    end or continuing them with a middle piece
    """
    for row in range(5):
        if tilegrid[7, row] == LEFT or tilegrid[7, row] == MIDDLE:
            if randint(1, 10) > CHANCE_OF_EXTENDING_A_CLOUD:
                tilegrid[8, row] = MIDDLE
            else:
                tilegrid[8, row] = RIGHT

def add_cloud():
    """Maybe add a new cloud on the right at a randon open row"""
    if randint(1, 10) > CHANCE_OF_NEW_CLOUD:
        count = 0
        while True:
            count += 1
            if count == 50:
                return
            row = randint(0, 4)
            if tilegrid[7, row] == EMPTY and tilegrid[8, row] == EMPTY:
                break
        tilegrid[8, row] = LEFT

display = make_display()
tilegrid = make_tilegrid()
seed_clouds(5)

while True:
    slide_tiles()
    shift_tiles()
    extend_clouds()
    add_cloud()

Flying Toasters

"""
Continuously scroll randomly generated After Dark style toasters.
Designed for an ItsyBitsy M4 Express and a 1.3" 240x240 TFT

Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!

Written by Dave Astels for Adafruit Industries
Copyright (c) 2019 Adafruit Industries
Licensed under the MIT license.

All text above must be included in any redistribution.

Requires CircuitPython 5.0 or later.
"""

import time
from random import seed, randint
import board
import displayio
from adafruit_st7789 import ST7789
import adafruit_imageload


# Sprite cell values
EMPTY = 0
CELL_1 = EMPTY + 1
CELL_2 = CELL_1 + 1
CELL_3 = CELL_2 + 1
CELL_4 = CELL_3 + 1
TOAST = CELL_4 + 1

NUMBER_OF_SPRITES = TOAST + 1

# Animation support

FIRST_CELL = CELL_1
LAST_CELL = CELL_4

NUMBER_OF_CELLS = (LAST_CELL - FIRST_CELL) + 1

# A boolean array corresponding to the sprites, True if it's part of the animation sequence.
ANIMATED = [_sprite >= FIRST_CELL and _sprite <= LAST_CELL for _sprite in range(NUMBER_OF_SPRITES)]


# The chance (out of 10) that toast will enter
CHANCE_OF_NEW_TOAST = 2

# How many sprites to styart with
INITIAL_NUMBER_OF_SPRITES = 4

# Global variables
display = None
tilegrid = None

seed(int(time.monotonic()))

def make_display():
    """Set up the display support.
    Return the Display object.
    """
    spi = board.SPI()
    while not spi.try_lock():
        pass
    spi.configure(baudrate=24000000) # Configure SPI for 24MHz
    spi.unlock()
    displayio.release_displays()
    display_bus = displayio.FourWire(spi, command=board.D7, chip_select=board.D10, reset=board.D9)

    return ST7789(display_bus, width=240, height=240, rowstart=80, auto_refresh=True)

def make_tilegrid():
    """Construct and return the tilegrid."""
    group = displayio.Group(max_size=10)

    sprite_sheet, palette = adafruit_imageload.load("/spritesheet-2x.bmp",
                                                    bitmap=displayio.Bitmap,
                                                    palette=displayio.Palette)
    grid = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
                              width=5, height=5,
                              tile_height=64, tile_width=64,
                              x=0, y=-64,
                              default_tile=EMPTY)
    group.append(grid)
    display.show(group)
    return grid

def random_cell():
    return randint(FIRST_CELL, LAST_CELL)

def evaluate_position(row, col):
    """Return whether how long of aa toaster is placable at the given location.
    :param row: the tile row (0-9)
    :param col: the tile column (0-9)
    """
    return tilegrid[col, row] == EMPTY

def seed_toasters(number_of_toasters):
    """Create the initial toasters so it doesn't start empty"""
    for _ in range(number_of_toasters):
        while True:
            row = randint(0, 4)
            col = randint(0, 4)
            if evaluate_position(row, col):
                break
        tilegrid[col, row] = random_cell()

def next_sprite(sprite):
    if ANIMATED[sprite]:
        return (((sprite - FIRST_CELL) + 1) % NUMBER_OF_CELLS) + FIRST_CELL
    return sprite

def advance_animation():
    """Cycle through animation cells each time."""
    for tile_number in range(25):
        tilegrid[tile_number] = next_sprite(tilegrid[tile_number])

def slide_tiles():
    """Move the tilegrid one pixel to the bottom-left."""
    tilegrid.x -= 1
    tilegrid.y += 1

def shift_tiles():
    """Move tiles one spot to the left, and reset the tilegrid's position"""
    for row in range(4, 0, -1):
        for col in range(4):
            tilegrid[col, row] = tilegrid[col + 1, row - 1]
        tilegrid[4, row] = EMPTY
    for col in range(5):
        tilegrid[col, 0] = EMPTY
    tilegrid.x = 0
    tilegrid.y = -64

def get_entry_row():
    while True:
        row = randint(0, 4)
        if tilegrid[4, row] == EMPTY and tilegrid[3, row] == EMPTY:
            return row

def get_entry_column():
    while True:
        col = randint(0, 3)
        if tilegrid[col, 0] == EMPTY and tilegrid[col, 1] == EMPTY:
            return col

def add_toaster_or_toast():
    """Maybe add a new toaster or toast on the right and/or top at a randon open location"""
    if randint(1, 10) <= CHANCE_OF_NEW_TOAST:
        tile = TOAST
    else:
        tile = random_cell()
    tilegrid[4, get_entry_row()] = tile

    if randint(1, 10) <= CHANCE_OF_NEW_TOAST:
        tile = TOAST
    else:
        tile = random_cell()
    tilegrid[get_entry_column(), 0] = tile

display = make_display()
tilegrid = make_tilegrid()
seed_toasters(INITIAL_NUMBER_OF_SPRITES)
display.refresh()

while True:
    for _ in range(64):
        display.refresh(target_frames_per_second=80)
        advance_animation()
        slide_tiles()
    shift_tiles()
    add_toaster_or_toast()
    display.refresh(target_frames_per_second=120)

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 Nov 05, 2019. It was last updated on Nov 05, 2019.
This page (Software) was last updated on Jul 03, 2020.