code.py
1 # SPDX-FileCopyrightText: 2022 Phillip Burgess for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 """ 6 CircuitPython random blinkenlights for Little Connection Machine. For 7 Raspberry Pi Pico RP2040, but could be adapted to other CircuitPython- 8 capable boards with two or more I2C buses. Requires adafruit_bus_device 9 and adafruit_is31fl3731 libraries. 10 11 This code plays dirty pool to get fast matrix updates and is NOT good code 12 to learn from, and might fail to work with future versions of the IS31FL3731 13 library. But doing things The Polite Way wasn't fast enough. Explained as 14 we go... 15 """ 16 17 # pylint: disable=import-error 18 import random 19 import board 20 import busio 21 from adafruit_is31fl3731.matrix import Matrix as Display 22 23 BRIGHTNESS = 40 # CONFIGURABLE: LED brightness, 0 (off) to 255 (max) 24 PERCENT = 33 # CONFIGURABLE: amount of 'on' LEDs, 0 (none) to 100 (all) 25 26 # This code was originally written for the Raspberry Pi Pico, but should be 27 # portable to any CircuitPython-capable board WITH TWO OR MORE I2C BUSES. 28 # IS31FL3731 can have one of four addresses, so to run eight of them we 29 # need *two* I2C buses, and not all boards can provide that. Here's where 30 # you'd define the pin numbers for a board... 31 I2C1_SDA = board.GP18 # First I2C bus 32 I2C1_SCL = board.GP19 33 I2C2_SDA = board.GP16 # Second I2C bus 34 I2C2_SCL = board.GP17 35 36 # pylint: disable=too-few-public-methods 37 class FakePILImage: 38 """Minimal class meant to simulate a small subset of a Python PIL image, 39 so we can pass it to the IS31FL3731 image() function later. THIS IS THE 40 DIRTY POOL PART OF THE CODE, because CircuitPython doesn't have PIL, 41 it's too much to handle. That image() function is normally meant for 42 robust "desktop" Python, using the Blinka package...but it's still 43 present (but normally goes unused) in CircuitPython. Having worked with 44 that library source, I know exactly what object members its looking for, 45 and can fake a minimal set here...BUT THIS MAY BREAK IF THE LIBRARY OR 46 PIL CHANGES!""" 47 48 def __init__(self): 49 self.mode = "L" # Grayscale mode in PIL 50 self.size = (16, 9) # 16x9 pixels 51 self.pixels = bytearray(16 * 9) # Pixel buffer 52 53 def tobytes(self): 54 """IS31 lib requests image pixels this way, more dirty pool.""" 55 return self.pixels 56 57 58 # Okay, back to business... 59 # Instantiate the two I2C buses. 400 KHz bus speed is recommended. 60 # Default 100 KHz is a bit slow, and 1 MHz has occasional glitches. 61 I2C = [ 62 busio.I2C(I2C1_SCL, I2C1_SDA, frequency=400000), 63 busio.I2C(I2C2_SCL, I2C2_SDA, frequency=400000), 64 ] 65 # Four matrices on each bus, for a total of eight... 66 DISPLAY = [ 67 Display(I2C[0], address=0x74, frames=(0, 1)), # Upper row 68 Display(I2C[0], address=0x75, frames=(0, 1)), 69 Display(I2C[0], address=0x76, frames=(0, 1)), 70 Display(I2C[0], address=0x77, frames=(0, 1)), 71 Display(I2C[1], address=0x74, frames=(0, 1)), # Lower row 72 Display(I2C[1], address=0x75, frames=(0, 1)), 73 Display(I2C[1], address=0x76, frames=(0, 1)), 74 Display(I2C[1], address=0x77, frames=(0, 1)), 75 ] 76 77 IMAGE = FakePILImage() # Instantiate fake PIL image object 78 FRAME_INDEX = 0 # Double-buffering frame index 79 80 while True: 81 # Draw to each display's "back" frame buffer 82 for disp in DISPLAY: 83 for pixel in range(0, 16 * 9): # Randomize each pixel 84 IMAGE.pixels[pixel] = BRIGHTNESS if random.randint(1, 100) <= PERCENT else 0 85 # Here's the function that we're NOT supposed to call in 86 # CircuitPython, but is still present. This writes the pixel 87 # data to the display's back buffer. Pass along our "fake" PIL 88 # image and it accepts it. 89 disp.image(IMAGE, frame=FRAME_INDEX) 90 91 # Then quickly flip all matrix display buffers to FRAME_INDEX 92 for disp in DISPLAY: 93 disp.frame(FRAME_INDEX, show=True) 94 FRAME_INDEX ^= 1 # Swap buffers 95 96 97 # This is actually the LESS annoying way to get fast updates. Other involved 98 # writing IS31 registers directly and accessing intended-as-private methods 99 # in the IS31 lib. That's a really bad look. It's pretty simple here because 100 # this code is just drawing random dots. Producing a spatially-coherent 101 # image would take a lot more work, because matrices are rotated, etc. 102 # The PIL+Blinka code for Raspberry Pi easily handles such things, so 103 # consider working with that if you need anything more sophisticated.