Skip to content

Instantly share code, notes, and snippets.

@Gadgetoid
Last active March 26, 2024 09:49
Show Gist options
  • Save Gadgetoid/08217ab0c5422cda9c2d2baf5253348f to your computer and use it in GitHub Desktop.
Save Gadgetoid/08217ab0c5422cda9c2d2baf5253348f to your computer and use it in GitHub Desktop.
MicroPython PIO DMA for Pimoroni Unicorn Pack
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