Merge pull request #25 from theredwagoneer/dma_solution

DMA solution to glitching/tearing problem
main
Blaž Rolih 2025-04-20 09:10:27 +02:00 zatwierdzone przez GitHub
commit ddba697440
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
1 zmienionych plików z 59 dodań i 11 usunięć

Wyświetl plik

@ -73,7 +73,7 @@ class Neopixel:
# 'brightnessvalue', # brightness scale factor 1..255
# ]
def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=0.0003, critical=False):
def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=0.0003, transfer_mode="PUT"):
"""
Constructor for library class
@ -83,8 +83,13 @@ class Neopixel:
:param mode: [default: "RGB"] mode and order of bits representing the color value.
This can be any order of RGB or RGBW (neopixels are usually GRB)
:param delay: [default: 0.0001] delay used for latching of leds when sending data
:param critical: [default: False] if True, disable interrupts while sending data to the PIO
This will eliminate glitching tearing, but could be a problem is have other high priority interrupts
:param transfer_mode: [default: "PUT"] transfer mode used for sending data to the PIO.
"PUT" : Use MicroPython put() method to send data to the PIO
This is straightforward, but can result in glitching from FIFO underflow.
"PUT_CRITICAL" : Use MicroPython put() method to send data to the PIO and disable IRQs during transfer
Solves the glitching problem, but disables interrupts might be undesireable.
"DMA" : Use DMA to send data to the PIO.
Prevents glitching without disabling interrupts, but is more complex.
"""
self.pixels = array.array("I", [0] * num_leds)
self.mode = mode
@ -103,7 +108,26 @@ class Neopixel:
self.num_leds = num_leds
self.delay = delay
self.brightnessvalue = 255
self.critical = critical
self.transfer_mode = transfer_mode
if transfer_mode == "DMA":
self.dma = rp2.DMA()
# The TX Data Request index for PIO is the (pio << 3) + state_machine
# where state_machine is the sm number FOR THAT PIO. e.g. PIO1, SM1 is 0x8 + 0x1 = 0x9
# (See the System DREQ table in the RP2040 or RP2350 datasheet.)
# However the micropython rp2 library does not allow selection of PIO and SM separately.
# Instead, it counts state machines from 0 to however many there are total.
# e.g. PIO1, SM1 is rp2 state machine 5.
# So we derive the DREQ from the state machine number by shifting the implied PIO number up a bit.
DATA_REQUEST_INDEX = ((state_machine & 0xC) << 1) | (state_machine & 0x3)
self.dma_ctrl = self.dma.pack_ctrl(size=2, inc_write=False, treq_sel=DATA_REQUEST_INDEX)
elif transfer_mode == "PUT_CRITICAL" or \
transfer_mode == "PUT":
pass
else:
raise ValueError("Invalid transfer mode: {}".format(transfer_mode))
def brightness(self, brightness=None):
"""
@ -345,15 +369,38 @@ class Neopixel:
if self.W_in_mode:
cut = 0
if self.critical:
if self.transfer_mode == "DMA":
# Wait until the last transfer completes if it is not done
while self.dma.active():
pass
# Guarrantee minimum sleep time
time.sleep(self.delay)
# always copy, otherwise we can mess with pixels buffer while DMA reads it
data = array.array('I', self.pixels)
if cut != 0:
for i, _ in enumerate(data):
data[i] <<= cut
self.dma.config(read=data,
write=self.sm,
count=len(data),
ctrl=self.dma_ctrl,
trigger=True)
elif self.transfer_mode == "PUT_CRITICAL":
irq_state = disable_irq()
self.sm.put(self.pixels, cut)
if self.critical:
self.sm.put(self.pixels, cut)
enable_irq(irq_state)
time.sleep(self.delay)
elif self.transfer_mode == "PUT":
self.sm.put(self.pixels, cut)
time.sleep(self.delay)
else:
raise ValueError("Invalid transfer mode: {}".format(self.transfer_mode))
time.sleep(self.delay)
def fill(self, rgb_w, how_bright=None):
"""
@ -372,4 +419,5 @@ class Neopixel:
:return: None
"""
self.pixels = array.array("I", [0] * self.num_leds)
self.pixels = array.array("I", [0] * self.num_leds)