As part of the research for my workshop that will take place at "Sesc 24 de Maio" in the months of May to July 2024, I am looking for solutions to turn two 8x32 Neopixel LED panels into a functional gaming screen.
Our proposal is not to create complex games with very high resolution, but rather, to bring back the spirit of BASIC programming in games from the 80s, using Circuitpython, Seeed Xiao RP2040 and Neopixel LED strips.
In my other project, I showed some of the changes I'm making to the Adafruit standard libraries. Now, I'm trying to use the excellent structure available in the Displayio and Adafruit_Imageload libraries, to display a sequence of Bitmaps, forming an animation.
I've already run into some issues with memory management. I believe that the best solution will be to enable an interface for the Neopixel display within the Displayio library itself and rely on the acceleration already worked on by the Adafruit team.
Using this library, https://github.com/stuartm2/CircuitPython_BMP_Reader, tried two different approaches, for the same sequence of frames: first I saved a different bitmap for each frame and loaded each one using the Circuitpython BMP Reader library, which I have already adapted to handle any Bitmap file size.
import time
import board
import neopixel_spi as neopixel
from bmp_reader import BMPReader
# My custom version
from tile_framebuf import TileFramebuffer
spi = board.SPI()
pixel_pin = board.D10
pixel_width = 32
pixel_height = 8
num_tiles = 2
num_pixels = pixel_width * pixel_height * num_tiles
pixels = neopixel.NeoPixel_SPI(
spi,
pixel_width * pixel_height * num_tiles, # dont forget to multiply for num_tiles
brightness=0.2,
auto_write=False,
)
screen = TileFramebuffer(
pixels,
pixel_width,
pixel_height,
num_tiles,
rotation = 0
)
# Read all frames
frame_file = ['frame_1.bmp', 'frame_2.bmp', 'frame_3.bmp', 'frame_4.bmp']
def getframe (framefile):
frame_img = BMPReader ("images/" + framefile)
frame = frame_img.get_pixels()
return frame_img.width, frame_img.height, frame
while True:
screen.fill(0)
for file in frame_file:
frame_width, frame_height, myframe = getframe(file)
for x in range (frame_width):
for y in range (frame_height):
screen.pixel (y,x,myframe[y][x])
screen.display()
# time.sleep (0.05)
So, I decided to try using Displayio and Adafruit_Imageload. And a Sprite sheet, to see if it would work.
import time
import board
import neopixel_spi as neopixel
import displayio
import adafruit_imageload
# My custom version
from tile_framebuf import TileFramebuffer
spi = board.SPI()
pixel_pin = board.D10
pixel_width = 32
pixel_height = 8
num_tiles = 2
num_pixels = pixel_width * pixel_height * num_tiles
pixels = neopixel.NeoPixel_SPI(
spi,
pixel_width * pixel_height * num_tiles, # dont forget to multiply for num_tiles
brightness=0.2,
auto_write=False,
)
screen = TileFramebuffer(
pixels,
pixel_width,
pixel_height,
num_tiles,
rotation = 1
)
# Load the sprite sheet (bitmap)
sprite_sheet, palette = adafruit_imageload.load(
# Image with 64 pixels width and 32 pixels height. Conteins four frames 16x32
"images/all_frames.bmp",
bitmap=displayio.Bitmap,
palette=displayio.Palette,
)
# Create the sprite TileGrid
sprite = displayio.TileGrid(
sprite_sheet,
pixel_shader=palette,
width=4,
height=1,
tile_width=16,
tile_height=32,
default_tile=0,
)
frame_index = 0 # actual frame
frame_delay = 0.02 # This does not work...
last_frame_time = time.monotonic()
while True:
screen.fill(0)
tile_x = frame_index % sprite.width
tile_y = frame_index // sprite.width
# Exibir o tile atual na tela Neopixel
for x in range(sprite.tile_width):
for y in range(sprite.tile_height):
pixel_color = sprite_sheet[tile_x * sprite.tile_width + x, tile_y * sprite.tile_height + y]
# Extract RGB from 16 bits
r = (pixel_color >> 11) & 0x1F # Componente vermelho
g = (pixel_color >> 5) & 0x3F # Componente verde
b = pixel_color & 0x1F # Componente azul
# Convert 16 bits to 8 bits (0-255)
r = (r * 255) // 31
g = (g * 255) // 63
b = (b * 255) // 31
neopixel_color = (r, g, b)
screen.pixel(x, y, neopixel_color)
screen.display()
current_time = time.monotonic()
if current_time - last_frame_time >= frame_delay:
# Atualizar o índice do frame para o próximo
frame_index = (frame_index + 1) % (sprite.width * sprite.height)
# Atualizar o tempo do último frame exibido
last_frame_time = current_time
Of course, the frame rate is very low. Like I said, it's very likely that frame rates will improve when and if I (or the Adafruit team) can create a bridge between the Display Neopixel and the Displayio library, as was done with the Matrix Display.
Is it possible?
Comments