Installing or upgrading CircuitPython

You should ensure you have CircuitPython 5.3 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.3 or greater. 

Adafruit CircuitPython 5.3.0 on 2020-04-29; 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 a few of the libraries in the 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_bus_device (directory)
  • adafruit_display_shapes (directory)
  • adafruit_imageload (directory)

You'll also need the .bmp files. You can add your own files or customize ours. If you're adding your own .bmp files, be sure they're 64 pixels wide. I had the most success when I saved as 8 bit color-indexed files.

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 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: 2020 Jeff Epler for Adafruit Industries
# SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
# SPDX-License-Identifier: MIT

RGB Matrix Ocean Scroller
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Jeff Epler & Limor Fried for Adafruit Industries
Copyright (c) 2019-2020 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.

import math
import time
import random

import adafruit_imageload.bmp
import board
import displayio
import framebufferio
import rgbmatrix
import ulab


class Reshader:
    '''reshader fades the image to mimic brightness control'''
    def __init__(self, palette):
        self.palette = palette
        ulab_palette = ulab.numpy.zeros((len(palette), 3))
        for i in range(len(palette)):
            rgb = int(palette[i])
            ulab_palette[i, 2] = rgb & 0xff
            ulab_palette[i, 1] = (rgb >> 8) & 0xff
            ulab_palette[i, 0] = rgb >> 16
        self.ulab_palette = ulab_palette

    def reshade(self, brightness):
        palette = self.palette
        shaded = ulab.numpy.array(self.ulab_palette * brightness, dtype=ulab.numpy.uint8)
        for i in range(len(palette)):
            palette[i] = tuple(shaded[i])

def do_crawl_down(image_file, *,
                  speed=12, weave=4, pulse=.5,
                  weave_speed=1/6, pulse_speed=1/7):
    # pylint:disable=too-many-locals
    '''function to scroll the image'''
    the_bitmap, the_palette = adafruit_imageload.load(

    shader = Reshader(the_palette)

    group = displayio.Group()
    tile_grid = displayio.TileGrid(bitmap=the_bitmap, pixel_shader=the_palette)
    display.root_group = group

    start_time = time.monotonic_ns()
    start_y = display.height   # High enough to be "off the top"
    end_y = -the_bitmap.height     # Low enough to be "off the bottom"

    # Mix up how the bobs and brightness change on each run
    r1 = random.random() * math.pi
    r2 = random.random() * math.pi

    y = start_y
    while y > end_y:
        now = time.monotonic_ns()
        y = start_y - speed * ((now - start_time) / 1e9)
        group.y = round(y)

        # wave from side to side
        group.x = round(weave * math.cos(y * weave_speed + r1))

        # Change the brightness
        if pulse > 0:
            shader.reshade((1 - pulse) + pulse * math.sin(y * pulse_speed + r2))

        display.refresh(minimum_frames_per_second=0, target_frames_per_second=60)

def do_pulse(image_file, *, duration=4, pulse_speed=1/8, pulse=.5):
    '''pulse animation'''
    the_bitmap, the_palette = adafruit_imageload.load(

    shader = Reshader(the_palette)

    group = displayio.Group()
    tile_grid = displayio.TileGrid(bitmap=the_bitmap, pixel_shader=the_palette)
    group.x = (display.width - the_bitmap.width) // 2
    group.y = (display.height - the_bitmap.height) // 2
    display.root_group = group

    start_time = time.monotonic_ns()
    end_time = start_time + int(duration * 1e9)

    now_ns = time.monotonic_ns()
    while now_ns < end_time:
        now_ns = time.monotonic_ns()
        current_time = (now_ns - start_time) / 1e9

        shader.reshade((1 - pulse) - pulse
                       * math.cos(2*math.pi*current_time*pulse_speed)**2)

        display.refresh(minimum_frames_per_second=0, target_frames_per_second=60)

matrix = rgbmatrix.RGBMatrix(
    width=64, height=32, bit_depth=5,
    rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12],
    addr_pins=[board.A5, board.A4, board.A3, board.A2],
    clock_pin=board.D13, latch_pin=board.D0, output_enable_pin=board.D1)
display = framebufferio.FramebufferDisplay(matrix, auto_refresh=False)

# Image playlist - set to run randomly. Set pulse from 0 to .5
while True:
    show_next = random.randint(1, 5) #change to reflect how many images you add
    if show_next == 1:
    elif show_next == 2:
        do_crawl_down("/waves1.bmp", speed=7, weave=0, pulse=.35)
    elif show_next == 3:
        do_crawl_down("/waves2.bmp", speed=9, weave=0, pulse=.35)
    elif show_next == 4:
        do_pulse("/heart.bmp", duration=4, pulse=.45)
    elif show_next == 5:

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 Aug 12, 2020. It was last updated on Jul 12, 2024.

This page (CircuitPython Code) was last updated on Jul 12, 2024.

Text editor powered by tinymce.