FastLED 3.10.8
Loading...
Searching...
No Matches
ObjectFLEDDmaManager.h
Go to the documentation of this file.
1#pragma once
2
3#include "platforms/arm/teensy/is_teensy.h"
4
5#if defined(FL_IS_TEENSY_4X) // Teensy 4.0/4.1 only
6
7#include <Arduino.h>
8#include "DMAChannel.h"
9#include "platforms/arm/teensy/teensy4_common/dmamem.h"
11
12namespace fl {
13
14// Forward declarations
15// BYTES_PER_DMA controls the dma2 ESG-chained refill chunk size. The ISR
16// runs on each major-loop completion and has only one WS2812 bit period
17// (~1.25 us) of slack before the next TMR4 trigger reads from the refilled
18// half. The ISR's memset + fillbits + dcache_flush takes ~10 us at 600 MHz
19// CPU; at BYTES_PER_DMA=60 (480 bits = 600 us per chunk) the race produced
20// the residual 0->1 phantom bit flips clustered at 20-LED boundaries
21// observed in #3406. At BYTES_PER_DMA=120 the major loop runs 1200 us, so
22// the ISR has plenty of margin to refill before the next chunk starts.
23// Memory cost: bitdata is at most BYTES_PER_DMA*256 bytes (38,400 B at
24// 150), heap-allocated in OCRAM2 and sized to the frame (#4711).
25#ifndef BYTES_PER_DMA
26// EXPERIMENT: 150 makes our 100-LED test fit in a single major loop
27// (numbytes=300 <= BYTES_PER_DMA*2=300) so the ESG/ISR refill path is
28// completely bypassed. If the residual #3406 byte corruption disappears
29// with this setting, the bug IS in the refill chain.
30#define BYTES_PER_DMA 150
31#endif
32
33// #3416 OF-HIGH-3: at BYTES_PER_DMA < 90 the ISR refill margin
34// (~10 us memset+fillbits+dcache_flush) approaches the major-loop
35// duration (BYTES_PER_DMA * 10 us / 8 bits-per-byte = X us), reintroducing
36// the race that Round 2 of #3406 fixed. Compile-time guard so a
37// user override below the safe threshold fails loudly.
38static_assert(BYTES_PER_DMA >= 90,
39 "BYTES_PER_DMA must be >= 90 to keep the ISR refill margin "
40 "ahead of the DMA major-loop tick. See #3406 round-2 race.");
41
42// The ISR refills one BYTES_PER_DMA*32-word half of bitdata, which needs
43// the >2*BYTES_PER_DMA path to own the full double buffer.
44static_assert(objectfled::bitdataWordsFor(2 * BYTES_PER_DMA + 1, BYTES_PER_DMA) ==
45 2 * BYTES_PER_DMA * 32,
46 "ObjectFLED ISR half-buffer refill needs a full double buffer");
47
52class ObjectFLEDDmaManager {
53 public:
54 static ObjectFLEDDmaManager& getInstance() {
55 static ObjectFLEDDmaManager instance;
56 return instance;
57 }
58
59 void acquire(void* owner);
60 void release(void* owner);
61 void waitForCompletion();
62 bool isBusy();
63 void* getCurrentOwner() const { return mCurrentOwner; }
64
65 // Shared DMA resources
66 DMAChannel dma1;
67 DMAChannel dma2;
68 DMAChannel dma3;
69 DMASetting dma2next;
70
75 bool ensureBitdata(uint32_t numbytes);
76
77 // DMA bit buffer, heap-allocated on first show() and sized to the
78 // largest frame seen instead of a fixed 38,400 B DMAMEM array (#4711).
79 // Teensy 4 malloc serves OCRAM2 (_heap_start = end of .bss.dma up to
80 // the end of RAM in imxrt1062*.ld), the same DMA-capable region as
81 // DMAMEM. The buffer is 32-byte aligned for arm_dcache_flush_delete.
82 //
83 // Raw owning pointer by design (cpp-standards "Long-Lived Pointers"):
84 // its only owner is this process-lifetime singleton, it is replaced
85 // only in ensureBitdata() while no DMA/ISR is running, and neither the
86 // ISR nor the DMA engine can hold a shared_ptr reference.
87 uint32_t* bitdata = nullptr;
88 uint32_t bitdataWords = 0;
89 // DMAMEM buffers must be static to work with section attributes
90 static FL_DMAMEM uint32_t bitmask[4] __attribute__((aligned(32)));
91
92 // Shared state for ISR. These are written once in showInternal()
93 // before DMA arms and then read by the ISR refill path. The ARM Cortex-M7
94 // strongly-ordered memory model + the explicit dma.enable() that follows
95 // every showInternal write ensures the ISR sees coherent values without
96 // requiring volatile qualifiers (volatile would break memcpy of the
97 // array source/destinations). Kept non-volatile; #3406 audit Agent 5
98 // was conservative.
99 volatile uint32_t framebuffer_index = 0;
100 uint8_t* frameBuffer = nullptr;
101 uint32_t numbytes = 0;
102 uint8_t numpins = 0;
103 uint8_t pin_bitnum[NUM_DIGITAL_PINS];
104 uint8_t pin_offset[NUM_DIGITAL_PINS];
105
106 private:
107 ObjectFLEDDmaManager() = default;
108 ~ObjectFLEDDmaManager() = default;
109 ObjectFLEDDmaManager(const ObjectFLEDDmaManager&) = delete;
110 ObjectFLEDDmaManager& operator=(const ObjectFLEDDmaManager&) = delete;
111
112 void* mCurrentOwner = nullptr;
113 void* mBitdataAlloc = nullptr; // unaligned malloc() block behind bitdata
114};
115
116} // namespace fl
117
118#endif // FL_IS_TEENSY_4X
fl::Grid< CRGB > frameBuffer
Definition Corkscrew.h:102
fl::u32 uint32_t
Definition s16x16x4.h:219
unsigned char uint8_t
Definition s16x16x4.h:209
Base definition for an LED controller.
constexpr uint32_t bitdataWordsFor(uint32_t numbytes, uint32_t bytesPerDma)
Words of bitdata a frame of numbytes bytes per strip needs.