PIO and DMA: how to auto-re-trigger ? #14420
-
Hello, I've been using PIO to drive a HUB75 RGB 64*32 (1/16 scan) LED matrix. How to have the DMA auto retrigger at the end of the transfer ? Here is the full code as it may be useful to others: # HUB75 64*32, 1/16 scan
#
# R0 - GPIO0
# G0 - GPIO1
# B0 - GPIO2
# R1 - GPIO3
# G1 - GPIO4
# B1 - GPIO5
#
# A - GPIO6
# B - GPIO7
# C - GPIO8
# D - GPIO9
# E - GPIO10
#
# CLK - GPIO11
# STB/LAT - GPIO12
# OEn - GPIO13
import time
from machine import Pin
import rp2
import array
# reset shift register
Pin(16).on()
Pin(16).off()
@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, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW),
sideset_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW),
fifo_join=rp2.PIO.JOIN_TX,
out_shiftdir=rp2.PIO.SHIFT_RIGHT,
autopull=True,
pull_thresh=11
)
# side is OE,LAT,CLK (MSB to LSB)
def matrix():
# both loops means 2*32=64 pixels
set(y, 1).side(0b100)
label("data_loop")
set(x, 31)
label("data_loop_inner")
out(pins, 11).side(0b101)
jmp(x_dec, "data_loop_inner").side(0b100)
jmp(y_dec, "data_loop")
nop().side(0b110) # Latch
# Block screen ON. Ratio is on=y,off=1, brightness is y/(y+1)
# so y=1 => ratio is on=1,off=1, e.q. half on, half off
# y=1 => 50%, y=2 => 66%, y=3 => 75%, y=4 => 80%, y=5 => 83%, ..., y=10 => 90%
set(y, 10).side(0b000) # brightness 1-10
label("screen_on")
set(x, 31)
nop()
label("screen_on_inner")
jmp(x_dec, "screen_on_inner")
jmp(y_dec, "screen_on")
sm = rp2.StateMachine(0, matrix, freq=1000000, out_base=Pin(0), sideset_base=Pin(11))
# Start the StateMachine.
sm.active(1)
data = array.array("I")
for row in range(16):
row_part = row << 6
for col in range(64):
rgbrgb = 0
if (col < 21):
rgbrgb = 0b011001
elif col < 41:
rgbrgb = 0b101010
else:
rgbrgb = 0b110100
values = rgbrgb | row_part
data.append(values)
legacy = False
while True:
if legacy:
sm.put(data)
else:
dma = rp2.DMA()
ctrl = dma.pack_ctrl(size=2, inc_write=False, inc_read=True, treq_sel=0)
dma.config(
read=data,
write=sm,
count=len(data), # 1024
ctrl=ctrl,
trigger=True
)
# do blocking wait, for the example
while dma.active():
# print(dma.count)
pass
dma.close() Apart from that, I also tried to play with |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
A couple of comments: DMA chaining is what you want, but you aren't allowed to chain back to yourself, so you can make a 2-buffer chain and it will do ping-pong DMA between them. This may do what you need. You can cheat and make both DMA channels point to the same buffer, if you don't need real double-buffering. Of course, if you just need some finite refresh rate (the DMA is VERY fast), you can just set a timer and retrigger at a reasonable speed. |
Beta Was this translation helpful? Give feedback.
-
Thanks @mendenm for the tips. I was able to have a control DMA writing the address of the data to the This was sporty and required to deep dive into the doc but it works! Now PIO feeding is completely handled by DMA. addresses_of_data = array.array("I", [addressof(data)])
data_dma = rp2.DMA()
control_dma = rp2.DMA()
data_dma.config(
read=data,
write=sm,
count=len(data),
ctrl=data_dma.pack_ctrl(size=2, inc_write=False, treq_sel=0, chain_to=control_dma.channel)
)
control_dma.config(
read=addresses_of_data,
write=data_dma.registers[15:16], # 15 is CHx_AL3_READ_ADDR_TRIG
count=len(addresses_of_data),
ctrl=control_dma.pack_ctrl(size=2, inc_read=False, inc_write=False), # we read/write only from/to one address
)
try:
# Fire and forget...
control_dma.active(1)
while True:
time.sleep(0.1)
# generate new image
new_data = array.array("I", 0 for _ in range(16*64))
for row in range(16):
row_part = row << 6
for col in range(64):
rgbrgb = 0
rand = random.randint(0, 3)
if rand == 0:
rgbrgb = 0b011001
elif rand == 1:
rgbrgb = 0b101010
else:
rgbrgb = 0b110100
new_data[row*64+col] = rgbrgb | row_part
data[0:] = new_data
finally:
data_dma.active(0)
control_dma.active(0)
data_dma.close()
control_dma.close()
sm.active(0) On the topic of alignment, it would be nice to have something built-in, at least for array or bytesarray. |
Beta Was this translation helpful? Give feedback.
Thanks @mendenm for the tips.
I was able to have a control DMA writing the address of the data to the
CHx_AL3_READ_ADDR_TRIG
register of the data DMA to reset its read address and trigger it. And the data DMA now chains to that control DMA, that does an infinite loop.Since the control DMA only read and write one thing (a two word address), having
inc_read=False, inc_write=False
makes it handy.This was sporty and required to deep dive into the doc but it works! Now PIO feeding is completely handled by DMA.
And since I was able to time a single transfer from 3 to 6ms (limited by the state machine ingestion), that's quite a nice FPS for the buffer to display step.