Step 3: In which I actually look through the example source bundle and refactor

Goal: Get more code working.

I’ve been working on two different approaches to this problem, in parallel. On one side, there’s the Feather M4 running CircuitPython. On the other, there’s a ATmega328 running Arduino C++.

In both cases, I’ve got buttons and the driver for an APA102 “DotStar” strand. It’s just that on the Feather M4, I’ve got an OLED display as well.

Last weekend I made some huge progress on the ATMega328 version, which is largely because a lot of people have spent a lot of time since before CircuitPython was a thing doing collective effort on the FastLED library. Therefore, there are both tools and examples out there for a lot of useful things to use as inspiration, but only with FastLED.

I also question if emulating it blindly is the right thing, because there’s a lot in FastLED that works really well to get a lot of moderate-complexity math done with 8 and 16 bit integers and when you look at how an M4 has a FPU and the M7 cores have absurd amounts of processing power, it’s probably better for CircuitPython to avoid making you think about those sorts of issues.

Given this, I decided is that I really should use the M4 where it’s stronger: It’s got a lot more storage for bitmap patterns. So, all of this is going to be more bitmap-oriented.

Also, I discovered that I can totally violate a lot of rules of good sense and turn the power on my strip down to 10%, I don’t need to worry too heavily about the power consumption and there seems to be ampre power from my USB port.

I actually poked through the source bundle for the tutorial and there’s some neat stuff there. For example, there’s a RichButton library that frankly ought to become part of the standard library bundle because it does about the same thing as an incredibly useful library on the Arduino side that I tend to lean on.

I also looked at how they were laying out things and did a big refactor to my codebase.

I decided I don’t necessarily need a virtual filesystem with the ability to drag images on and use them for painting nearly as much as I need the ability to have a set of patterns loaded and ready to cycle through, so I’m writing my converter on the desktop side, which means I can use regular Python and so my converter looks like this:

from PIL import Image

img = Image.open("rainbow.bmp")

baseheight = 9
wpercent = (baseheight/float(img.size[1]))
vsize = int((float(img.size[0])*float(wpercent)))
img = img.resize((vsize,baseheight), Image.ANTIALIAS)
print (list(img.getdata()))
print (vsize)

And the code, after refactoring, looks more like this:

# Write your code here :-)
import board
import digitalio
import displayio
import time
from digitalio import DigitalInOut, Direction
from neopixel_write import neopixel_write
import adafruit_dotstar
import terminalio
from adafruit_display_text import label
from richbutton import RichButton
import adafruit_displayio_ssd1306
from adafruit_debouncer import Debouncer

NUM_PIXELS = 60
PIXEL_PINS = board.SCK, board.MOSI # Data, clock pins for DotStars
PIXEL_ORDER = 'bgr'               # Pixel color order
GAMMA = 2.4                   # Correction for perceptually linear brightness
BRIGHTNESS = 0.1
DELAY = 0.1

displayio.release_displays()

i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32)

bitmap = [(253, 29, 13), (254, 0, 140), (209, 3, 255), (58, 3, 254), (0, 81, 254), (255, 191, 0), (255, 43, 6), (255, 0, 132), (213, 2, 254), (73, 0, 255), (148, 255, 0), (253, 215, 1), (254, 55, 0), (255, 0, 113), (232, 3, 247), (5, 251, 24), (123, 255, 0), (251, 226, 3), (255, 69, 0), (255, 0, 88), (1, 255, 182), (0, 252, 39), (107, 255, 0), (244, 234, 3), (255, 93, 0), (0, 168, 255), (2, 255, 202), (0, 252, 48), (92, 255, 0), (232, 245, 4), (18, 11, 252), (0, 144, 255), (3, 255, 215), (0, 254, 61), (70, 255, 0), (173, 0, 255), (36, 3, 253), (0, 125, 255), (2, 248, 224), (0, 255, 87), (255, 2, 178), (189, 0, 253), (39, 0, 252), (0, 117, 255), (3, 241, 235)]
bitmap_height = 9

# pylint: disable=too-many-instance-attributes
class M4LightPainter:

    # pylint: disable=too-many-arguments
    def __init__(self, num_pixels, pixel_order, pixel_pins, gamma, brightness, delay):

        self.mute = True
        self.program = 0
        self.num_pixels = num_pixels
        self.delay = delay

        # Turn off onboard NeoPixel
        onboard_pixel_pin = DigitalInOut(board.NEOPIXEL)
        onboard_pixel_pin.direction = Direction.OUTPUT
        neopixel_write(onboard_pixel_pin, bytearray(3))

        # Configure the DotStar pixel bus
        self.pixels = adafruit_dotstar.DotStar(PIXEL_PINS[0], PIXEL_PINS[1], NUM_PIXELS, brightness=brightness, auto_write=False)

        # Turn off the onboard red LED
        self.red_led = digitalio.DigitalInOut(board.D13)
        self.red_led.direction = digitalio.Direction.OUTPUT
        self.red_led.value = False

        # Configure the buttons
        self.button_a = RichButton(board.D9)
         
        self.button_b = RichButton(board.D6)

        self.button_c = RichButton(board.D5)

    def fill_color(self, color):
        for i in range(self.num_pixels):
            self.pixels[i] = color

    def clear_strip(self):
        self.fill_color((0,0,0))

    def show_screen(self):
        # Make the display context
        splash = displayio.Group(max_size=10)
        display.show(splash)

        if self.mute:
            text = "Program: {0}".format(self.program)
            text_area = label.Label(terminalio.FONT, text=text, color=0xFFFF00, x=0, y=10)
            splash.append(text_area)

    def setup_program(self):
        self.show_screen()

    def run(self):
        print("Hello, CircuitPython!")
        self.show_screen()

        self.clear_strip()
        self.pixels.show()
        action_list = [None, None, None]

        count = 0

        while True:
            if self.mute:
                self.clear_strip()
                self.pixels.show()
            else:
                count = count + 1
                count = count % 5

                for i in range(self.num_pixels):
                    self.pixels[i] = bitmap[count * bitmap_height + (i % bitmap_height)]
                self.pixels.show()
                time.sleep(0.1)


            action_list[0] = self.button_a.action()
            action_list[1] = self.button_b.action()
            action_list[2] = self.button_c.action()
            if action_list[0] is RichButton.TAP:
                print('Mute Toggle - {0} {1}'.format(self.mute, self.program))
                self.mute = not self.mute
                self.setup_program()

            if action_list[1] is RichButton.TAP:
                print('Program + - {0} {1}'.format(self.mute, self.program))
                self.program = self.program + 1
                self.program = self.program % 8
                self.setup_program()

            if action_list[2] is RichButton.TAP:
                print('Program - {0} {1}'.format(self.mute, self.program))
                self.program = self.program - 1
                if self.program < 0:
                    self.program = 7
                self.setup_program()


M4LightPainter(NUM_PIXELS, PIXEL_ORDER, PIXEL_PINS, GAMMA,
                 BRIGHTNESS, DELAY).run()

Posted:

Updated: