Last active
March 26, 2024 09:49
-
-
Save Gadgetoid/08217ab0c5422cda9c2d2baf5253348f to your computer and use it in GitHub Desktop.
MicroPython PIO DMA for Pimoroni Unicorn Pack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import rp2 | |
import time | |
import uctypes | |
from array import array | |
from machine import Pin | |
PIN_LED_DATA = 8 | |
PIN_LED_CLOCK = 9 | |
PIN_LED_LATCH = 10 | |
PIN_LED_BLANK = 11 | |
PIN_ROW_0 = 22 | |
PIN_ROW_1 = 21 | |
PIN_ROW_2 = 20 | |
PIN_ROW_3 = 19 | |
PIN_ROW_4 = 18 | |
PIN_ROW_5 = 17 | |
PIN_ROW_6 = 16 | |
PIN_A = 12 | |
PIN_B = 13 | |
PIN_X = 14 | |
PIN_Y = 15 | |
WIDTH = 16 | |
HEIGHT = 7 | |
ROW_COUNT = 7 | |
ROW_BYTES = 12 | |
BCD_FRAMES = 15 | |
BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * BCD_FRAMES) | |
# Do I even need to do this? | |
led_data = Pin(PIN_LED_DATA, Pin.OUT) | |
led_clock = Pin(PIN_LED_CLOCK, Pin.OUT) | |
led_latch = Pin(PIN_LED_LATCH, Pin.OUT) | |
led_blank = Pin(PIN_LED_BLANK, Pin.OUT) | |
row_0 = Pin(PIN_ROW_0, Pin.OUT) | |
row_1 = Pin(PIN_ROW_1, Pin.OUT) | |
row_2 = Pin(PIN_ROW_2, Pin.OUT) | |
row_3 = Pin(PIN_ROW_3, Pin.OUT) | |
row_4 = Pin(PIN_ROW_4, Pin.OUT) | |
row_5 = Pin(PIN_ROW_5, Pin.OUT) | |
row_6 = Pin(PIN_ROW_6, Pin.OUT) | |
@rp2.asm_pio( | |
out_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW), | |
set_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW), | |
sideset_init=rp2.PIO.OUT_LOW, | |
fifo_join=rp2.PIO.JOIN_TX, | |
autopull=False, | |
out_shiftdir=rp2.PIO.SHIFT_RIGHT, | |
pull_thresh=32 | |
) | |
def unicorn_pack(): | |
# fmt: off | |
# clock out 16 pixels worth of data | |
set(y, 15) # 15 because jmp test is pre decrement | |
label("pixels") | |
pull(ifempty) | |
# dummy bit used to align pixel data to nibbles | |
out(null, 1) | |
# red bit | |
out(x, 1) .side(0) # pull first bit from OSX into X, clear clock | |
set(pins, 8) # clear data bit (maintain blank) | |
jmp(not_x, "endr") # if bit was zero jump to endr | |
set(pins, 9) # set data bit (maintain blank) | |
label("endr") | |
nop() .side(1) # clock in bit | |
# green bit | |
out(x, 1) .side(0) # pull first bit from OSX into X, clear clock | |
set(pins, 8) # clear data bit (maintain blank) | |
jmp(not_x, "endg") # if bit was zero jump to endg | |
set(pins, 9) # set data bit (maintain blank) | |
label("endg") | |
nop() .side(1) # clock in bit | |
# blue bit | |
out(x, 1) .side(0) # pull first bit from OSX into X, clear clock | |
set(pins, 8) # clear data bit (maintain blank) | |
jmp(not_x, "endb") # if bit was zero jump to endb | |
set(pins, 9) # set data bit (maintain blank) | |
label("endb") | |
nop() .side(1) # clock in bit | |
jmp(y_dec, "pixels") | |
pull() | |
# dummy byte to 32 bit align row data | |
out(null, 8) | |
# select active row | |
out(null, 1) # discard dummy bit | |
out(pins, 7) # output row selection mask | |
# pull bcd tick count into X | |
out(x, 16) | |
# set latch pin to output column data on shift registers | |
set(pins, 12) # set latch pin (while keeping blank high) | |
# set blank pin to enable column drivers | |
set(pins, 4) | |
label("bcd_count") | |
jmp(x_dec, "bcd_count") | |
# disable all row outputs | |
set(x, 0) # load X with 0 (we can't set more than 5 bits at a time) | |
mov(pins, not_x) # write inverted X (0xff) to row pins latching them all high | |
# disable led output (blank) and clear latch pin | |
set(pins, 8) | |
# fmt: on | |
sm = rp2.StateMachine(0, unicorn_pack, freq=2_000_000, | |
set_base=Pin(PIN_LED_DATA), | |
out_base=Pin(PIN_ROW_6), | |
sideset_base=Pin(PIN_LED_CLOCK), | |
out_shiftdir=rp2.PIO.SHIFT_RIGHT, | |
pull_thresh=32) | |
dma = rp2.DMA() | |
dma_ctrl = rp2.DMA() | |
bitstream = bytearray(BITSTREAM_LENGTH) | |
bitstream_addr = uctypes.addressof(bitstream) | |
print(f"Bitstream Length: {BITSTREAM_LENGTH}, Xfer count: {BITSTREAM_LENGTH // 4}") | |
print(f"Bitstream address: {hex(bitstream_addr)}") | |
for row in range(HEIGHT): | |
for frame in range(BCD_FRAMES): | |
offset = (row * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame) | |
row_select_offset = offset + 9 | |
bcd_offset = offset + 10 | |
if frame == BCD_FRAMES - 1: | |
bitstream[row_select_offset] = 0b11111111; | |
for col in range(6): | |
bitstream[offset + col] = 0xff | |
bcd_ticks = 65535 | |
else: | |
row_select_mask = (1 << (7 - row)) ^ 0xff | |
bitstream[row_select_offset] = row_select_mask | |
bcd_ticks = 1 << frame | |
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8 | |
bitstream[bcd_offset] = (bcd_ticks & 0xff) | |
control_bytes = array("L") | |
control_bytes.append(bitstream_addr) # CH0_READ_ADDR | |
CH0_READ_ADDR = uctypes.addressof(dma.registers[0:]) | |
print("Start...") | |
#def test(dma): | |
# print(f"Hello {dma}") | |
#dma.irq(test) | |
dma.config( | |
read = bitstream_addr, | |
write = sm, | |
count = len(bitstream) // 4, | |
ctrl = dma.pack_ctrl( | |
size = 2, # 0 = byte, 1 = half word, 2 = word | |
irq_quiet = True, | |
inc_read = True, | |
inc_write = False, | |
bswap = False, | |
chain_to = dma_ctrl.channel, | |
), | |
trigger = False | |
) | |
dma_ctrl.config( | |
read = control_bytes, | |
write = CH0_READ_ADDR, | |
count = 1, | |
ctrl = dma_ctrl.pack_ctrl( | |
size = 2, # 0 = byte, 1 = half word, 2 = word | |
inc_read = False, | |
inc_write = False, | |
irq_quiet = True, | |
chain_to = dma.channel, # since the register write is non triggering | |
), | |
trigger = False | |
) | |
sm.active(1) | |
dma_ctrl.active(True) | |
def set_pixel(x, y, r, g, b): | |
if x < 0 or x > WIDTH or y < 0 or y > HEIGHT: | |
return | |
x = (WIDTH - 1) - x | |
byte_offset = x // 2 | |
shift = 4 if (x & 0b1) else 0 | |
nibble_mask = 0b00001111 << shift | |
gr = r | |
gg = g | |
gb = b | |
for frame in range(BCD_FRAMES): | |
offset = (y * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame) | |
rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2); | |
rgbd <<= shift; | |
bitstream[offset + byte_offset] &= (nibble_mask ^ 0xff) | |
bitstream[offset + byte_offset] |= rgbd | |
gr >>= 1 | |
gg >>= 1 | |
gb >>= 1 | |
#set_pixel(0, 0, 8191, 0, 0) | |
#set_pixel(1, 1, 0, 8191, 0) | |
#set_pixel(2, 2, 0, 0, 8191) | |
#set_pixel(3, 3, 5000, 0, 5000) | |
#set_pixel(4, 4, 5000, 5000, 0) | |
x = 0 | |
try: | |
while True: | |
#set_pixel(x, 1, 8191, 0, 0) | |
x %= WIDTH | |
print(list(hex(r) for r in dma.registers)) | |
time.sleep(0.5) | |
except KeyboardInterrupt: | |
pass | |
dma.active(False) | |
dma.close() | |
dma_ctrl.active(False) | |
dma_ctrl.close() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment