Shrink WS2811/WS2812 LED strip DMA buffer with a chunked circular refill - #11798
Shrink WS2811/WS2812 LED strip DMA buffer with a chunked circular refill#11798sensei-hacker wants to merge 7 commits into
Conversation
TIM3/TIM4's CCR is a 16-bit register; the buffer only needs to hold compare values 0-3, so 32-bit elements wasted RAM without matching any hardware requirement. Halves ledStripDMABuffer from 12,460 B to 6,230 B on affected F4 targets.
Lets a circular-DMA consumer refill each half of its buffer as DMA finishes sending it, instead of pre-loading the whole transfer up front. Opt-in and null-guarded: DMA_IT_HT is only enabled, and the callback only invoked, when a consumer registers one via impl_timerPWMSetDMARefillCallback, so existing circular-DMA users (motor DShot idle-packet repeat) see no behavior change.
hsvToRgb24 reruns its full divide/multiply conversion for every LED on every strip update (up to 100Hz), even though most updates set only a handful of distinct colors across the whole strip. A 4-entry direct-mapped cache, keyed on exact HSV input equality, skips the recompute on a repeat.
ledStripDMABuffer held every WS2811_LED_STRIP_LENGTH (128) LED's worth of protocol bits for a single one-shot DMA burst, regardless of how many LEDs were actually configured (6,230 bytes, uint16_t elements). It's now a 2-half, 4-LED-per-half circular buffer (384 bytes) refilled from the DMA half/full-transfer interrupt as each group finishes transmitting, via the refill callback hook added in the timer driver. ws2811UpdateStrip() now takes the actual configured LED count and bounds the transfer to it instead of always processing the full 128 slots. WS2812's reset/latch condition is a minimum, not a maximum, low duration, so the old fixed DMA preamble and idle-tail buffer elements are gone too: starting a transfer now does a cheap timestamp check against how long the line has already been idling low (falling back to a blocking wait only if it hasn't been, e.g. right after PINIO idle-high), and stopping does a direct write to the timer's preload-buffered compare register instead of one more DMA element.
The refill-callback hook was only wired up in the F4 StdPeriph timer backend, but light_ws2811strip.c calls it unconditionally on every platform with USE_LED_STRIP — leaving H7/F7 (HAL) and AT32 targets with an undefined reference to impl_timerPWMSetDMARefillCallback at link time. Mirrors the same opt-in, null-guarded HT/TC dispatch added to the StdPeriph backend. Also has all three backends' impl_timerPWMStopDMA poll for the DMA stream's enable bit to actually clear before returning, matching the poll already used by impl_timerPWMSetDMACircular for the same hardware constraint (disabling a stream isn't instantaneous) - stop is now called from a circular-DMA refill callback, not just after a one-shot transfer's TC event, so it can run while a stream is still mid-flight.
The chunked-buffer refill state (activeLedCount, totalGroups, nextGroupToAssign, groupInHalf, lineIdleLow, lastLowAtUs) is written from both task context and the DMA refill ISR. Accesses don't actually overlap in practice, but marking them volatile documents that and matches the existing convention (TCH_t's dmaState is volatile for the same reason) instead of relying on the compiler not reordering around the assumption.
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
PR Summary by QodoStream WS2811 DMA data through a compact circular buffer
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1.
|
|
RAM / Flash usage vs. base branch — commit
|
|
Test firmware build ready — commit Download firmware for PR #11798 244 targets built. Find your board's
|
The old idle-tail lived in the DMA buffer itself, so it was memory- safe by construction and got resent every transfer. Replacing it with a direct CCR write dropped both properties: - ws2811SetIdleHigh() dereferenced ws2811TCH unconditionally, so a PINIO call after a failed/absent LED strip init (ws2811TCH still NULL) would hard fault. Now guarded the same way ws2811UpdateStrip already is. - ws2811StopTransfer() unconditionally forced the line low, silently discarding a prior ws2811SetIdleHigh(true) once the in-flight transfer finished. Now tracks the requested idle level separately and restores it instead of always going low.
Summary
Reduces static RAM used by the WS2811/WS2812 LED strip driver, part of the ongoing RAM reduction effort for 128KB-RAM F4 targets.
Changes
uint32_t->uint16_t), fixing a latent 2x waste. (First commit, already landed separately in an earlier pass.)hsvToRgb24()so repeated colors (very common — most strips use only a handful of distinct colors) skip the conversion math.impl_timerPWMStopDMAnow poll for the DMA stream's enable bit to actually clear before returning, matching the same poll already used elsewhere for the same hardware constraint — needed because this stop path can now run from a circular-DMA refill callback while a stream is still mid-flight, not just after a one-shot transfer completes.RAM impact
ledStripDMABuffer: 12,460 B (originaluint32_t[3115]) -> 6,230 B (uint16_t[3115], already landed) -> 384 B (uint16_t[192], this PR). BLUEBERRYF405 total RAM: 114,708 B -> 108,876 B.Testing
volatileon ISR-shared state, and the DMA-stop poll gap above).