Feather M0, DotStars, and CircuitPython. What happens when you put them all together?

Preamble

The Feather platform from Adafruit has really gotten my attention recently, especially since its move to using the ATSAMD21 and eventually (I expect) ATSAMD51 MCUs. These are powerhouse chips based on the ARM Cortex-M0+ and M4 cores, respectively. The other thing about Feathers is the large (and constantly growing) ecosystem of add-on boards, called FeatherWings.

Another thing I’ve enjoyed playing with are NeoPixels. I’ve used them in various formats for a variety of projects. One of the joys is that you don’t need 3 PWM outputs per LED; you just need a single digital output to drive a fairly large string of NeoPixels. Recently, a new RGB LED has been showing up in Adafruit products: the DotStar. It requires 2 outputs, but is less timing sensitive, and faster. And is smaller. The latter point is huge, it means you can pack them denser, getting more pixels in the same space.

You got DotStars on my FeatherWing

Two great things that go great together. Enter the DotStar FeatherWing. This board packs 72 DotStars in a 6×12 grid onto a FeatherWing. In case you weren’t paying attention, a FeatherWing is only 50mm by 23mm. That’s small. Lady Ada has packed the space with DotStars so densely they almost touch. That’s enough pixels to make simple images, and display text using a simple font. The DotStar FeatherWing learning guide has C code for doing just that. See the guide for the basics on the wing, assembly instructions, and an introduction to the C libraries.

The other thing I’ve been doing

On the software side of things, I’ve been spending time with Adafruit’s CircuitPython. This is an education focused offshoot of MicroPython ported to Atmel/Microchip’s Cortex-M based ATSAMD MCUs. When I got the DotStar FeatherWing there wasn’t even the C guide available yet. There was, however, a CircuitPython library for working with DotStars as several of the new SAMD based boards have one on-board. I should point out that the ATSAMD based boards and CircuitPython go hand in hand. You don’t need to use CircuitPython on these boards, but you need one of these boards to use CircuitPython.

The basics

The grid of DotStars on the wing is electrically one strand of 72 DotStars.

I’ve written a class that wraps the linear string of pixels into a 6×12 grid, which you create like this:

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

The first argument is the clock pin and the second is the data pin. See the guide for more information on pin selection. An option third argument is the brightness: from 0.0 to 1.0.

Once you have an instance created, you can start manipulating the pixels. There are the simple things like clear() and fill(color), and show().

Here’s an example using just those functions:

import board
import dotstar_featherwing
import time
import random

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

wing.clear()
wing.show()

while True:
    color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    wing.fill(color)
    wing.show()
    time.sleep(0.25)
    wing.clear()
    wing.show()
    time.sleep(0.25)

This just flashes the entire display in random colors:

The next function is set_color() which takes a row, column, and color and sets that pixel:

import board
import dotstar_featherwing
import time
import random

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11, brightness=0.10)

wing.clear()
wing.show()

while True:
    row = random.randint(0, 5)
    column = random.randint(0, 11)
    color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    wing.set_color(row, column, color)
    row = random.randint(0, 5)
    column = random.randint(0, 11)
    wing.set_color(row, column, (0, 0, 0))
    wing.show()

 

Images

Displaying a single color (and “black”) image is straight-forward. It uses a list of strings to encode which pixels are colored and which are off. Uppercase X is used to indicate a lit pixel, any other character indicates an unlit pixel. I’ve found a period to work well visually.

import board
import dotstar_featherwing

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)
starfleet = ["XXXXXXX.....",
             "..XXXXXXX...",
             "....XXXXXXX.",
             ".....XXXXXXX",
             "....XXXXXXX.",
             "..XXXXXXX..."]

wing.display_image(starfleet, (0x20, 0x20, 0x00))

dotstar-starfleet

Yes, I was watching Star Trek: Discovery while working on that example.

Multi-coloured images

Using a dictionary to map characters in the bitmap strings to colors, a color image can be displayed.

import board
import dotstar_featherwing
import time

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

xmas = ["..y.w......w",
        "..G.....w...",
        "..G..w....w.",
        ".GGG...w....",
        "GGGGG.......",
        "wwwwwwwwwwww"]

xmas_colors = {'w': (0x20, 0x20, 0x20),
               'G': (0x00, 0x20, 0x00),
               'y': (0x20, 0x20, 0x00)}

wing.display_colored_image(xmas, xmas_colors)

A snowy winter scene, although the whites came out with a purple tinge.

dotstar-image

Animation

This is just a matter of displaying a series of images in sequence.

import board
import dotstar_featherwing
import time

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

xmas_colours = {'w': (0x20, 0x20, 0x20),
                'W': (0xFF, 0xFF, 0xFF),
                'G': (0x00, 0x20, 0x00),
                'y': (0x20, 0x20, 0x00),
                'Y': (0xFF, 0xFF, 0x00)}

xmas_animation = [["..y.w......w",
                   "..G.....w...",
                   "..G..w....w.",
                   ".GGG...w....",
                   "GGGGG.......",
                   "wwwwwwwwwwww"],
                  ["..y.........",
                   "..G.W......w",
                   "..G.....w...",
                   ".GGG.w....W.",
                   "GGGGG..w....",
                   "wwwwwwwwwwww"],
                  ["..Y....W....",
                   "..G.........",
                   "..G.w......w",
                   ".GGG....w...",
                   "GGGGGw....W.",
                   "wwwwwwwwwwww"],
                  ["..y..w....w.",
                   "..G....W....",
                   "..G.........",
                   ".GGGW......w",
                   "GGGGG...w...",
                   "wwwwwwwwwwww"],
                  ["..Y.....w...",
                   "..G..w....W.",
                   "..G....w....",
                   ".GGG........",
                   "GGGGG......W",
                   "wwwwwwwwwwww"]]

wing.display_animation(xmas_animation, xmas_colors, count=5, delay=0.05)

The first argument is a list of images (each a list of string as before). These are the frames in the animation. Second is the color mapping, The count argument is the number of times to run the animation, which defaults to 1. The delay argument is the time in second to wait after each frame, and defaults to 0.1 seconds.  Note the delay is after each frame, not between frames, so there will be a pause after the final frame, before display_animation returns.

By using brighter versions of a couple colors, we can make the snow glitter and the star on top of the tree flash.

Scrolling

Images that fill the display are fine, but the array is pretty small and we might want to display something bigger. A scrolling display is often the way to do it. To make a scrolling display there are the shift_into_left and shift_into_right methods.

import board
import dotstar_featherwing
import time

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

# count from 0->63, shifting the binary pattern in from the left
while True:
    wing.clear()
    for x in range(64):
        wing.shift_into_left(wing.number_to_pixels(x, (0x40, 0x00, 0x00)))
        time.sleep(0.2)
import board
import dotstar_featherwing
import time

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11)

# count from 0->63, shifting the binary pattern in from the right
while True:
    wing.clear()
    for x in range(64):
        wing.shift_into_right(wing.number_to_pixels(x, (0x40, 0x00, 0x00)))
        time.sleep(0.2)

Now that we can shift in arbitrary bit patterns (shown above by counting and shifting in the binary patterns for each number), we can get more elaborate. This final example will use a simple font to scroll text onto the pixel array.

import board
import dotstar_featherwing
import time
import font

wing = dotstar_featherwing.DotstarFeatherwing(board.D13, board.D11, 0.1)

while True:
    wing.clear()
    wing.shift_in_string(font.font_3, "hello adafruit discord!", (0x20, 0x00, 0x20), 0.05)
    time.sleep(2)

Even a font of character 3 pixels wide takes up some space in memory. That last demo pretty much fills the Feather-M0s RAM. If you have a specific message or messages to scroll, consider removing any character from the font that you don’t need.

Another neat capability of the library is that characters (glyphs really) don’t have to all be the same width. This lets you have some custom images stored in a font to be displayed as and when you wish.

Getting the code

You can find the library as well as the above examples at https://bitbucket.org/dastels/dotstar_featherwing. Just copy dotstar_featherwing.py to the lib folder of your CIRCUITPY drive and you’re ready to go.  The above examples are in the examples directory.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s