Skip to content

Commit

Permalink
upload ws2812
Browse files Browse the repository at this point in the history
  • Loading branch information
vertexi committed Feb 20, 2022
1 parent 19f65f1 commit a4a7a3d
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 3 deletions.
10 changes: 9 additions & 1 deletion graphic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import italiccs
import scriptc
import utime
from libs import ws2812

tetromino_width = 0
prev_map: list[list]
Expand All @@ -15,8 +16,11 @@
score_pos_settings: dict
rows_pos_settings: dict

# white red yellow brown green purple blue dark blue black
# black red yellow brown green purple blue dark blue white
tetromino_colors = [0x0000, 0xF182, 0xF788, 0xF400, 0x4788, 0xF21E, 0x479E, 0x001E, 0xFFFF]
tetromino_colors_led = [ws2812.BLACK, ws2812.RED, ws2812.YELLOW, ws2812.PURPLE,
ws2812.GREEN, ws2812.PURPLE, ws2812.BLUE, ws2812.CYAN,
ws2812.WHITE]


def cycle(p):
Expand Down Expand Up @@ -57,6 +61,7 @@ def init_graphic(display_: st7789.ST7789, game_rows_, game_cols_):
tetromino_width = eval_tetromino_shape(game_rows, display_height)
prev_map = init_prev_map(game_cols, game_rows)
display.fill(st7789.BLACK)
ws2812.neo[:] = (0, 0, 0)

score_pos_settings = draw_score_setting("Score", 5/8, 1/8, 0xe7e0, 5, 0xece0)
display.draw(italiccs, "Score", score_pos_settings["text_x"],
Expand Down Expand Up @@ -113,6 +118,9 @@ def diff_draw(game_map: list[list]):
draw_block(j, i, tetromino_colors[game_map[i][j]])
prev_map[i][j] = game_map[i][j]

if 0 < j < 9:
ws2812.neo_set_pixel(j-1, i, tetromino_colors_led[game_map[i][j]])
ws2812.neo.write()

def draw_block(x: int, y: int, color: int):
x *= tetromino_width
Expand Down
224 changes: 224 additions & 0 deletions libs/ws2812.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
from machine import Pin
from machine import SPI
# Implement the neopixel protocol on an SPI bus

# The data is stored expanded into the format that will be sent
# directly to the SPI bus. This is efficient if there are realtively
# few changes to the buffer between refreshes but can be slower if you
# are likely to change each pixel more than once between refreshes.

import gc

# User bytes are spread across four buffer bytes, encoded into bits 6
# and 2 of each byte with the top bits of each nibble set and the
# bottom bits of each nibble all clear.

# 0b00011011 -> 0b10001000 0b10001100 0b11001000 0b11001100

_expanded_bits = [0x88, 0x8C, 0xC8, 0xCC]


def _expand_byte(b, mv):
# Spread a byte across four bytes of a memoryview
mv[0] = _expanded_bits[(b >> 6) & 0x3]
mv[1] = _expanded_bits[(b >> 4) & 0x3]
mv[2] = _expanded_bits[(b >> 2) & 0x3]
mv[3] = _expanded_bits[(b) & 0x3]


def _compress_byte(mv):
# Pack four bytes in a memoryview into a single byte
b1, b2, b3, b4 = mv
T, B = 0x40, 0x04
v = (((b1 & T) << 1) | ((b1 & B) << 4) |
((b2 & T) >> 1) | ((b2 & B) << 2) |
((b3 & T) >> 3) | ((b3 & B)) |
((b4 & T) >> 5) | ((b4 & B) >> 2))
return v


class NeoPixel:
"""The NeoPixel object can be treated as a 2D array, pixel_count long and 3
wide. Pixels can be accessed as a 3-tuple or through their individual Green,
Blue and Red components (in that order). It can also be accessed with 1D
slice operations to read or write a list of 3-tuples and a single
3-tuple can be written to a whole slice to set a row of pixels to the same
value.
sp = machine.SPI(1)
sp.init(baudrate=3200000)
np = NeoPixel(sp, 100)
# Blank the whole set
np[:] = (0,0,0)
# Set the first 10 pixels to dark blue
np[0:10] = (0,0,40)
# Set the second pixel to green
np[1] = (128,0,0)
# Set the third pixel's red value to full brightness
np[2,2] = 255
# Copy the 2nd pixel to the 5th
np[4] = np[1]
# Copy the first 16 pixels to the last 16 pixels
np[-16:] = np[:16]
"""

def __init__(self, spi_device, pixel_count):
self._n = pixel_count
self._data = memoryview(bytearray(pixel_count * 12))
self._spi = spi_device
self[:] = (0, 0, 0)

def _unpack_slice(self, s):
start, stop, step = s.start, s.stop, s.step
if step is not None and step != 1:
raise NotImplementedError("Slices must have step of 1")
step = 1
if start == None:
start = 0
elif start < 0:
start += self._n
if stop == None:
stop = self._n
elif stop < 0:
stop += self._n
return (start, stop, step)

def __getitem__(self, index):
data = self._data
n = self._n
if isinstance(index, int):
if index < -n or index >= n:
raise IndexError("Pixel index out of range")
index *= 12
return (_compress_byte(data[index:index + 4]),
_compress_byte(data[index + 4:index + 8]),
_compress_byte(data[index + 8:index + 12]))
elif isinstance(index, tuple):
if len(index) != 2:
raise IndexError(
"Pixel array has only two dimensions (index and colour)")
ind1, ind2 = index
if isinstance(ind1, slice):
raise NotImplementedError("2D slicing not supported")
if (not isinstance(ind1, int)) or (not isinstance(ind2, int)):
raise IndexError("Indecies must be integers")
if ind1 < -n or ind1 >= n or ind2 < 0 or ind2 >= 3:
raise IndexError("Pixel index out of range")
ind1 *= 12
ind1 += ind2 * 4
return _compress_byte(data[ind1:ind1 + 4])
elif isinstance(index, slice):
start, stop, step = self._unpack_slice(index)
dd = data[start * 12:stop * 12]
return [(_compress_byte(dd[i:i + 4]),
_compress_byte(dd[i + 4:i + 8]),
_compress_byte(dd[i + 8:i + 12])) for i in
range(0, len(dd), 12)]
else:
raise IndexError("Can not index on type {}".format(type(index)))

def __setitem__(self, index, value):
data = self._data
n = self._n
if isinstance(index, int):
if index < -n or index >= n:
raise IndexError("Pixel index out of range")
if not isinstance(value, tuple) or len(value) != 3:
raise ValueError("Pixel value must be a 3-tuple")
index *= 12
_expand_byte(value[0], data[index:index + 4])
_expand_byte(value[1], data[index + 4:index + 8])
_expand_byte(value[2], data[index + 8:index + 12])
elif isinstance(index, tuple):
if len(index) != 2:
raise IndexError(
"Pixel array has only two dimensions (index and colour)")
ind1, ind2 = index
if isinstance(ind1, slice):
raise NotImplementedError("2D slice assignment not supported")
if (not isinstance(ind1, int)) or (not isinstance(ind2, int)):
raise IndexError("Indecies must be integers")
if ind1 < -n or ind1 >= n or ind2 < 0 or ind2 >= 3:
raise IndexError("Pixel index out of range")
if not isinstance(value, int) or value < 0 or value > 255:
raise ValueError(
"Pixel value must be an integer in range 0 to 255")
ind1 *= 12
ind1 += ind2 * 4
_expand_byte(value, data[ind1:ind1 + 4])
elif isinstance(index, slice):
start, stop, step = self._unpack_slice(index)
dd = memoryview(data)[start * 12:stop * 12]
if len(dd) == 0:
return
if isinstance(value, tuple) and len(value) == 3:
_expand_byte(value[0], dd[0: 4])
_expand_byte(value[1], dd[4: 8])
_expand_byte(value[2], dd[8:12])
for i in range(12, len(dd), 12):
dd[i:i + 12] = dd[0:12]
elif isinstance(value, list) and all(
(isinstance(i, tuple) and len(i) == 3) for i in value):
for i in range(0, len(dd), 12):
v0, v1, v2 = value[i // 3]
_expand_byte(v0, dd[i + 0:i + 4])
_expand_byte(v1, dd[i + 4:i + 8])
_expand_byte(v2, dd[i + 8:i + 12])
else:
raise ValueError(
"Assigned value must be a list of 3-tuples or a single 3-tuple")

def write(self):
"""Write the buffer value out to the pixels"""
self._spi.write(self._data)

def rotate(self, count):
"""Rotate the whole buffer content by 'count' pixels"""
count = count % self._n
count *= 12
tail = bytearray(self._data[-count:])
head = bytearray(self._data[:-count])
self._data[count:] = head
self._data[:count] = tail
del head, tail
gc.collect()

@property
def n(self):
return self._n


brightness = 0
neo: NeoPixel = None

BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)


# Configure the number of WS2812 LEDs.
def init_led(led_num, brightness_):
global neo, brightness
sp = SPI(1, baudrate=3200000, phase=1, polarity=1,
sck=Pin(14), mosi=Pin(15), miso=Pin(12))
brightness = brightness_
neo = NeoPixel(sp, led_num)


def decode_x_y(x, y):
y = 31 - y
if y % 2 != 0:
x = 7 - x
return y * 8 + x


def neo_set_pixel(x, y, color):
global neo
neo[decode_x_y(x, y)] = (
int(color[0] * brightness), int(color[1] * brightness),
int(color[2] * brightness))

5 changes: 5 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from tetris import Game
import control
import music
from libs import ws2812
import uos


# initialize
Expand Down Expand Up @@ -63,4 +65,7 @@
# set theme music
game.set_musics(music.musics)

# set led
ws2812.init_led(256, 0.1)

game.run()
6 changes: 4 additions & 2 deletions tetris.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class Game:
musics: Musics

def __init__(self, display):
self.rows = 24
self.cols = 12
self.rows = 30
self.cols = 10
self.wall_width = 1
self.bottom_wall_width = 2
self.display = display
Expand Down Expand Up @@ -193,6 +193,8 @@ def pause_game(self):
def run(self):
self.init_game()
while True:
if self.game_over:
self.musics.pause()
while not self.game_over and not self.pause:
utime.sleep_ms(1)
self.move_down_event.tick()
Expand Down
7 changes: 7 additions & 0 deletions utils/rgb565torgb888.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import math


def rgb565_to_rgb888(rgb565):
return math.floor((rgb565>>11)/0b11111*255),\
math.floor((rgb565&0b11111100000>>5)/0b11111*255),\
math.floor((0xF182&0b11111)/0b11111*255)

0 comments on commit a4a7a3d

Please sign in to comment.