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 # '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 Constructor for library class
@ -83,8 +83,13 @@ class Neopixel:
:param mode: [default: "RGB"] mode and order of bits representing the color value. :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) 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 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 :param transfer_mode: [default: "PUT"] transfer mode used for sending data to the PIO.
This will eliminate glitching tearing, but could be a problem is have other high priority interrupts "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.pixels = array.array("I", [0] * num_leds)
self.mode = mode self.mode = mode
@ -103,7 +108,26 @@ class Neopixel:
self.num_leds = num_leds self.num_leds = num_leds
self.delay = delay self.delay = delay
self.brightnessvalue = 255 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): def brightness(self, brightness=None):
""" """
@ -345,15 +369,38 @@ class Neopixel:
if self.W_in_mode: if self.W_in_mode:
cut = 0 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() irq_state = disable_irq()
self.sm.put(self.pixels, cut)
self.sm.put(self.pixels, cut)
if self.critical:
enable_irq(irq_state) 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): def fill(self, rgb_w, how_bright=None):
""" """
@ -372,4 +419,5 @@ class Neopixel:
:return: None :return: None
""" """
self.pixels = array.array("I", [0] * self.num_leds) self.pixels = array.array("I", [0] * self.num_leds)