drivers: wait for DMA EN bit to clear before reconfiguring DShot DMA (F7/H7)#11390
drivers: wait for DMA EN bit to clear before reconfiguring DShot DMA (F7/H7)#11390sensei-hacker wants to merge 4 commits intoiNavFlight:maintenance-9.xfrom
Conversation
Per STM32F7 Reference Manual (RM0410 Section 8.3.5), writes to DMA_SxNDTR and DMA_SxM0AR are silently ignored while the EN bit is asserted. After calling LL_DMA_DisableStream(), EN does not clear synchronously -- the hardware may still be completing an in-progress burst. impl_timerPWMPrepareDMA() previously called LL_DMA_SetDataLength() and LL_DMA_ConfigAddresses() immediately after LL_DMA_DisableStream() with no wait. In the race window where EN is still 1, both writes are discarded, leaving stale count and address in the DMA registers. The subsequent LL_DMA_EnableStream() then fires DMA with the old (now incorrect) transfer count and/or buffer address, producing garbled DShot frames. Fix: add a bounded wait loop for EN to clear before reconfiguring. If EN has not cleared after the timeout (indicating a hardware fault), skip the reconfiguration entirely rather than proceeding with stale register values. This race is most visible on STM32F765 targets, where the larger 16 KB I-Cache keeps the interrupt handler hot path in cache, resulting in faster execution that consistently lands within the ~5-18 ns EN clear window. On STM32F745 (4 KB I-Cache), cache misses add stall cycles that naturally extend the window enough for EN to clear before reconfiguration begins.
Review Summary by QodoWait for DMA EN bit to clear before DShot reconfiguration
WalkthroughsDescription• Add EN bit wait loop before DMA register reconfiguration • Prevents silent data corruption from stale DMA count/address • Skips reconfiguration if EN bit fails to clear (hardware fault) • Fixes intermittent F765 lockups at DShot DMA startup Diagramflowchart LR
A["LL_DMA_DisableStream"] --> B["Wait for EN bit clear"]
B --> C{EN cleared?}
C -->|Yes| D["Reconfigure DMA registers"]
C -->|No| E["Skip reconfiguration"]
D --> F["Enable DMA stream"]
E --> G["Return early"]
File Changes1. src/main/drivers/timer_impl_hal.c
|
Code Review by Qodo
1.
|
|
Test firmware build ready — commit Download firmware for PR #11390 223 targets built. Find your board's
|
…unter underflow Two issues in the EN-bit wait loop added to impl_timerPWMPrepareDMA(): 1. On timeout, the early return skipped setting tch->dmaState, leaving it in whatever state it had on entry (could be TCH_DMA_ACTIVE if the DMA TC interrupt was suppressed by the preceding ATOMIC_BLOCK). Subsequent calls to impl_timerPWMStartDMA() check for TCH_DMA_READY, so an ACTIVE or stale state would not cause a spurious fire, but timer.c timerIsMotorBusy() uses dmaState != TCH_DMA_IDLE as a busy check, which would return true forever on a stuck channel. Fix: explicitly set TCH_DMA_IDLE on timeout. 2. The while-loop condition used post-decrement (timeout--), causing the counter to wrap from 0 to UINT32_MAX on the final iteration. The counter is not used after the loop so this was harmless, but the pattern is fragile and easy to misread. Fix: use a for-loop where timeout decrements cleanly to 0 and the loop exit condition is unambiguous.
… fault The timeout (~140-230 us at 216 MHz) is long enough that any in-progress DMA transfer has certainly completed or been aborted by the time it expires. The stuck EN bit means we cannot reconfigure DMA registers this cycle, not that the hardware is permanently broken. Update the comment to reflect that: the ESC holds its last command for one missed frame, and EN will almost certainly have cleared before the next PrepareDMA call (~1-2 ms later).
Brings a fix already done on H7 over to F7. It may be the cause of some mystery lock ups on F765.
F765 are more likely to hit it because of the caching structure.
Problem
In
timer_impl_hal.c:impl_timerPWMPrepareDMA()callsLL_DMA_DisableStream()then immediately callsLL_DMA_SetDataLength()andLL_DMA_ConfigAddresses()with no wait between them.Per STM32F7 Reference Manual (RM0410, Section 8.3.5):
In the race window between
LL_DMA_DisableStream()and EN actually clearing, the writes toDMA_SxNDTR(count) andDMA_SxM0AR(address) are silently discarded by hardware. The subsequentLL_DMA_EnableStream()then fires DMA with the previous transfer's stale count and address, producing garbled Shot frames.Why This Manifests on F765 More Than F745
The race window is only ~5–18 ns (1–4 AHB cycles at 216 MHz). On STM32F745 (4 KB I-Cache), frequent cache misses in the interrupt handler may add enough pipeline stalls that EN clears naturally before
LL_DMA_SetDataLength()is reached. On STM32F765 (16 KB I-Cache), the hot path fits entirely in cache and executes without stalls, consistently landing within the race window.The race condition exists on all F7/H7 targets; the cache difference may makes it more reproducible on F765.
Fix
Add a bounded spin-wait for EN to clear after
LL_DMA_DisableStream(), before any DMA register writes. If EN has not cleared after the timeout (indicating a hardware fault), skip reconfiguration entirely rather than proceeding with stale register values — a skipped DShot cycle holds last good motor values, which is far preferable to a corrupted frame.Files Changed
src/main/drivers/timer_impl_hal.c— add EN-bit wait inimpl_timerPWMPrepareDMA()Testing
Build test: MATEKF765 and FRSKYPILOT targets build cleanly with this change.
Hardware test: Not performed by me — no physical STM32F765 hardware available. The fix is a straightforward application of the RM guidance; the logic is equivalent to the wait loops used in
stm32f7xx_hal_dma.c.Reports of intermittent F765 lockups at arm time (when DShot DMA starts for the first time) are potentially consistent with this race condition.