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:
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
PIXEL_ORDER = 'bgr'
GAMMA = 2.4
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
class M4LightPainter:
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
onboard_pixel_pin = DigitalInOut(board.NEOPIXEL)
onboard_pixel_pin.direction = Direction.OUTPUT
neopixel_write(onboard_pixel_pin, bytearray(3))
self.pixels = adafruit_dotstar.DotStar(PIXEL_PINS[0], PIXEL_PINS[1], NUM_PIXELS, brightness=brightness, auto_write=False)
self.red_led = digitalio.DigitalInOut(board.D13)
self.red_led.direction = digitalio.Direction.OUTPUT
self.red_led.value = False
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):
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()