Repository navigation
Conversation
Ports bidirectional DShot (GCR) telemetry from Betaflight, enabling RPM-based gyro filtering without a dedicated ESC telemetry UART. After each DShot output frame the motor pin is switched to timer input capture mode via per-channel DMA to receive the ESC's GCR-encoded eRPM response. The decoded eRPM feeds the existing RPM filter infrastructure. New settings: - dshot_bidir_enabled: enable bidirectional DShot telemetry - dshot_edt_enabled: enable Extended DShot Telemetry (temperature, voltage, current in addition to eRPM) Changes: - drivers/dshot.c/.h: GCR decode, EDT frame parsing, eRPM-to-RPM conversion, motor frequency LPF (ported from Betaflight, GPLv3) - drivers/pwm_output.c: per-channel DMA direction switching between DShot output and GCR input capture on STM32 (StdPeriph/HAL) and AT32 - sensors/esc_sensor.c/.h: DShot bidir data path into ESC sensor framework (escSensorSetDshotData, escSensorIsActive) - flight/rpm_filter.c: DShot telemetry as frequency source alongside serial ESC sensor path - target/common_post.h: USE_RPM_FILTER enabled for any USE_DSHOT target - fc/fc_init.c: call initDshotTelemetry() at startup; fix RPM filter init condition to accept DShot bidir as a valid source - blackbox/blackbox.c: fix NULL dereference when escSensorGetData() returns NULL; fix uninitialized blackboxSlowState_t comparison - telemetry/srxl.c: replace Betaflight-specific USE_DSHOT_TELEMETRY guards with INAV's USE_DSHOT/USE_ESC_SENSOR; use escSensorGetData() Tested on SPEEDYBEEF405V4 (STM32F405, StdPeriph) with Bluejay ESC firmware. eRPM telemetry confirmed in blackbox; RPM gyro filter updating notch frequencies with motor speed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ht#11605 # Conflicts: # src/main/target/common_post.h
dshotDmaStream() is only called from the USE_HAL_DRIVER code paths, but was defined unconditionally with a dead fallback stub, so non-HAL F4 targets (e.g. FF_F35_LIGHTNING) built it with -Werror=unused-function. Scope the definition to USE_HAL_DRIVER to match its call sites.
pwm_output.c called LL_TIM_EnableDMAReq_CCx/LL_TIM_DisableDMAReq_CCx, which don't exist in ST's LL timer driver (only per-channel CC1..CC4 variants are provided). Add local static inline wrappers that set/clear the DIER CCxDE bits directly by mask, matching the equivalent helpers already used internally by timer_impl_hal.c.
…metry settings dshot_bidir_enabled and dshot_edt_enabled were added to settings.yaml but docs/Settings.md was never regenerated, failing CI's update_cli_docs.py check. Ran the generator; purely additive diff, no other drift.
loadDmaBufferDshot() was only defined when USE_DSHOT_DMAR was unset, but pwmCompleteMotorUpdate() also calls it as the per-channel DMA fallback on DMAR targets whenever bidir telemetry is enabled (burst DMA can't drive per-channel direction switching). Define it unconditionally; keep loadDmaBufferDshotStride() gated behind USE_DSHOT_DMAR since it's only used by the DMAR-specific burst path.
fix: keep DShot telemetry decode out of ITCM
…lemetry, magic H7 workdown from betaflight
pwmSetMotorDMACircular() predates bidirectional DSHOT and corrupts its per-channel direction-switching state machine when a config save runs (e.g. servo autotrim saving on disarm): - On USE_DSHOT_DMAR targets the burst path is never armed with bidir, so dmaBurstBuffer stays NULL and loadDmaBufferDshotStride() writes the zero-throttle frame through the NULL pointer into 0x00000000 - which is ITCM RAM holding .tcm_code (FAST_CODE), corrupting the firmware. - On per-channel targets impl_timerPWMSetDMACircular() re-enables the stream without regard to the current transfer direction, and on exit disables the TIM DMA request before the stream: a FIFO-enabled mem-to-periph stream then cannot drain, EN sticks at 1 and every later reconfiguration is silently ignored - the output is dead until reboot. - Circular output also repeats frames back-to-back, colliding with the GCR reply the ESC drives onto the same line after every frame. Match Betaflight instead: with bidir telemetry active, leave motor DMA alone during the flash write and accept the frame gap. Saves only happen disarmed, the ESC holds zero throttle, and the existing decode path rebuilds the streams (full DeInit/Init) on the first update afterwards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BB55zdRNZq3Q6hZYsDYwTc
…ot-fix BidirDshot - fix for H7, invertion of timer, reset DMA for reading te…
# Conflicts: # src/main/drivers/pwm_output.c # src/main/fc/settings.yaml # src/main/sensors/esc_sensor.c
Structure: - dshot.c/h -> bidir_dshot.c/h, compiled only with USE_DSHOT_BIDIR. The define comes from common_post.h: USE_DSHOT && STM32H7 && !USE_DSHOT_DMAR (direction switching is verified on H7; DMAR has no per-channel stream). dshot_bidir_enabled / dshot_edt_enabled are hidden elsewhere. - Every bidir path in pwm_output.c sits under USE_DSHOT_BIDIR and, at run time, under useDshotTelemetry: with the setting off, plain DSHOT keeps the timer driver's DMA handler, FIFO setup and channel config from maintenance-10.x. The DMAR fallback to per-channel DMA is gone (excluded by the define instead). - USE_RPM_FILTER follows USE_ESC_SENSOR || USE_DSHOT_BIDIR instead of USE_DSHOT, so targets without ESC sensor do not grow. - No circular keep-alive for bidir: the line idles high for the flash write, as in Betaflight. Fixes: - EDT voltage/current were 10x off (0.25 V and 1 A steps into 0.01 V / 0.01 A fields): x25 / x100 instead of x250 / x1000. - ESC sensor RPM taken from the already computed mechanical RPM instead of computeRpm((int16_t)eRPM/100); an EDT-only first frame no longer publishes rpm = 0. - GCR decoder: guard against len < 1 (shift by -1) on glitch edges and on an exactly filled frame, and against len > 21. - Bidir ESC sensor data is aged at the serial poll rate, so a silent ESC drops out of the combined values like a serial one. - useDshotTelemetry is set once, by pwmMotorPreconfigure(); the ESC sensor asks isDshotTelemetryActive() instead of re-deriving it. - pwmDshotDmaIrqHandler keeps tch->dmaState in step with the timer driver's handler (timerPWMDMAInProgress()). - srxl.c: RPM comes through escSensorGetData(), no dshot include needed; duplicate includes removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
ⓘ 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 QodoAdd bidirectional DShot telemetry and RPM filtering
AI Description
Diagram
High-Level Assessment
Files changed (15)
|
Code Review by Qodo
1.
|
|
Driving the pin high while the ESC has it connected to ground to signal a bit sounds like a dead short, isn't it? Or do you mean tristating the pin and engaging the pull-up? |
|
RAM / Flash usage vs. base commit
See RAM/flash optimization guide for techniques to reduce usage. |
|
Test firmware build ready — commit Download firmware for PR #12027 251 targets built. Find your board's
|
|
@error414 I tested bidirectional DShot on an F722 (Flywoo F722 Pro V2, stack ESC, four motors, no props) with #11605's head (e6dcaf2, your fixes included), and one motor never reported a correct RPM. I traced it to the timing of the switch to input capture, which #12027 does the same way, plus a flag-clearing bug in the F4 path of both PRs. Numbers below; I hope they help. The last motor served is armed too late I added counters to a bench build: per motor, replies decoded or failed, the edge intervals of the last good and bad reply, and cycle-counter stamps of the end-of-frame DMA interrupt and of the capture being armed. All four motors stopped, DShot300, 3 s:
Times are from the start of the frames. All four frames end together, but the DMA interrupts are served one after another in stream order, and each Raising one motor's DMA interrupt priority moves the failure: with motor 3 first, motor 3 decodes 100% and motor 4, now last, 0%. With a busy wait added before each switch, the capture has to be armed within about 85 us of the frames' start on this board, which leaves about 6 us per motor for four motors. A switch written straight to the registers (same DMA and capture settings, no HAL calls, no DMA DeInit/Init) takes about 2.5 us. With it all four motors decode 100%, stopped and spinning at 1080, 1250 and 1400, and motor 3 reads 2,557 / 5,157 / 8,914 RPM at 1080 / 1150 / 1250, like the others.
Betaflight's timer path ( On H7 #12027 is fine. On a Kakute H7 Mini with your head (4a7d4e2) and the same counters, a switch takes about 2.8 us at 480 MHz, the fourth motor is armed at 72 us, and all four decode 100%: stopped, each alone and all together at 1080, 1250 and 1400, RPM in line on all four. #12027 leaves F7 out, but F4 also switches motor by motor in the DMA interrupts, on a slower core, so it may hit the same limit as the F722. I have no F4 board to check. Related to the
DMA_CLEAR_FLAG(port->tch->dma, DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF);The macro expands to The register switch I tested (F7, inside the HAL branch of pwmDshotSetDirectionInput)DMA_Stream_TypeDef *st = port->tch->dma->ref;
TIM_TypeDef *tim = port->tch->timHw->tim;
const unsigned c = port->tch->timHw->channelIndex;
st->CR &= ~DMA_SxCR_EN;
while (st->CR & DMA_SxCR_EN) { }
DMA_CLEAR_FLAG(port->tch->dma, (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF));
SET_BIT(tim->CR1, TIM_CR1_ARPE);
tim->ARR = 0xffff;
tim->CCER &= ~(TIM_CCER_CC1E << (4 * c));
volatile uint32_t *ccmr = (c < 2) ? &tim->CCMR1 : &tim->CCMR2;
const unsigned sh = 8 * (c & 1);
// input capture on TIx, no prescaler, filter 2
*ccmr = (*ccmr & ~((0xFFU << sh) | (TIM_CCMR1_OC1M_3 << sh))) | (0x21U << sh);
tim->CCER |= (TIM_CCER_CC1P | TIM_CCER_CC1NP | TIM_CCER_CC1E) << (4 * c);
st->NDTR = GCR_TELEMETRY_INPUT_LEN;
st->PAR = (uint32_t)port->ccr;
st->M0AR = (uint32_t)port->dmaBuffer;
st->FCR = DMA_SxFCR_DMDIS;
st->CR = (DMATAG_GET_CHANNEL(port->tch->timHw->dmaTag) << DMA_SxCR_CHSEL_Pos) | DMA_SxCR_MINC | DMA_SxCR_PSIZE_1 | DMA_SxCR_MSIZE_1 | DMA_SxCR_PL_1 | DMA_SxCR_TCIE;
st->CR |= DMA_SxCR_EN;
tim->DIER |= TIM_DIER_CC1DE << c;Measured on this F722 and on the Kakute H7 Mini; not tested on F4 or AT32. I can share the bench build and the scripts if that helps. |
|
this PR contains all changes. It's weird because DMA_CLEAR_FLAG is already fixed in this PR |
|
Same on a second F7, a Diatone Mamba F722 (MAMBAF722_2022B: motors on TIM8 and TIM1, DMA2 streams), same bench build on #11605's head. The motor whose DMA interrupt is served last decodes 0.1% of its replies stopped and 62 / 69 / 96% spinning at 1080 / 1250 / 1400, its capture armed at 97 us; with the register switch all four decode 100%, the last one armed at 76.5 us. |
|
Please try to this PR #12027, here are many fixes, this branch https://github.com/error414/inav/tree/error414/feature/bidir-dshot |
|
@error414 You're right about DMA_CLEAR_FLAG, sorry: #12027's dma.h has the brackets (61637a5); I checked the call but read the macro from maintenance-10.x. So that point only applies to #11605's head, and your KakuteF4 result fits. I've now run #12027 itself (4a7d4e2) on the same Flywoo F722, with
Your link counter in MSP2_INAV_ESC_RPM shows it too: motor 3 at 0% decoded of 3,810 replies in the last window, the others at 100%. On this F722 the switch would have to fit in about 6 us per motor, interrupt included, for four motors to make it. On H7 four motors are fine, but eight reach the same edge. On the Kakute H7 Mini with an 8-motor mixer (ESC on outputs 1-4, outputs 5-8 raised in DMA interrupt priority so that 1-4 are served last), each switch takes about 3 us and the eighth capture is armed at about 85 us:
|
|
Thanks for testing. I tested it on h7 with 4 motors and on f4 with 4 motors (even I flew my quad with activated dshot). I did not measured how fast switching is. Probably on f4 is fast enought because f4 does not use HAL. The case when CPU does not relase pin fast enought is not good at all, it makes short circuit when ESC starts send telemetry back. I wil check it. |
|
I did fast check before work. You findings is correct, H7 fast enought but it can barely handle 8 motors. I tested 8 motors on MatekH7WLITE, it works, but when I added code for measure it started failing. F4 uses SPL and by static analysis it can take 0.5- 1.5us, what is enought to drive 8 motors (8. 1.5 = 12us. windows for recofigure all ports is aprox 25 - 35us (depend on ESC), so we can assume 20us as save value. For plan is:
|
|
I did some test now and seems it works better, could you plase confirm my findings? Have no idea if it will be possible to compile for it for F722, I had to add code to ITCM memory. My OmnibusNXTF7 has plenty of ITCM space, interesting. 1. INAV, Omnibus NXT F7 (F722 @ 216 MHz), 4 motors, DSHOT600
2. Betaflight master (4fc1520) timer path, same Omnibus NXT F7, target OMNINXT7,
3. INAV, MATEKH743 (H743 @ 480 MHz), 4 motors, DSHOT600
4. INAV, KakuteF4 (F405 @ 168 MHz), 4 motors, DSHOT600
|
|
@error414 Confirmed on two F722 boards and an H7: with 776761b every motor decodes all its replies, stopped and spinning. For the test I enabled bidir on F7 (
The Mamba's target defines F722 builds with F7 enabled: FLYWOOF722PRO 98.63% flash, MAMBAF722_2022B 98.54% (98.91% without DMAR), MATEKF722SE 99.03%, FLASHHOBBYF722 99.16%. KAKUTEF7 and KAKUTEF7HDV overflow ITCM_RAM, by 488 and 520 B: they were already close to full there. My #12118 ( On H7 (Kakute H7 Mini, ESC on outputs 1-4): 100% on all four with a four-motor mixer, and still 100% on motors 1-4 with an eight-motor mixer (outputs 5-8 active without an ESC), two runs. I can't place motors 1-4 last in the interrupt queue without my bench counters, which no longer apply to this code, so that doesn't prove the eighth place is fixed. Not tested with this commit: an eighth motor with its own ESC, F4, and anything armed or flown. |
|
other guy here tested it https://www.rc-network.de/threads/inav-10-bidirektionales-dshot.12139268/#post-13344498 don't know why he didn't wrote here. |

!!!DONT TURN ON BIDIR DSHOT IF YOU USE BLHELI_32 fw VERSION 32.9 AND EARLIER!!!
This PR is based on PR #11605 @halfmanbear thank you very much, and this PR is for easier testing rebased bidir dshot.
BLHELI_S = no know issue
AM32 = weird issue on discord, must be tested
Bluejay = no know issue
BLHELI_32 = 32.10 hopefully fixed issue
BLHELI_32 = 32.9 and earlier !!!can't properly handle pin hight (no signal)!!! cant be used for bidir dshot
ESCape32 = issue when dshot frames are too close to each other, already fixed in INAV 10, no signal ESCape32 can handle without any problem.
Testing how ESC can handle no 1s no signal
It's not possible to detect what FW ESC uses, so we have to probably add big red warning to configurator.
I decied to keep pin hight instead of set some signal like PWM, multishot, etc... Because then there would be two restarts of ESC. First restart aprox 0.4s after start saving to eeprom, and second aprox 0.4s after finish saving settings to eeprom. ESC would restart first time dshot -> PWM (for example) and then PWM (for example) -> dshot.
If pin is kept hight then there would be only one ESC restart. BLHELI32 issue is that for start spinning was enought only a one correct frame,, which can be caused by interference on signal pin, All other modern ESC FWs needs 6 - 10 valid frames to confirm start spinning!!!