Add bidirectional DShot ESC telemetry - #11605
halfmanbear wants to merge 13 commits into
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>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Branch Targeting SuggestionYou've targeted the
If This is an automated suggestion to help route contributions to the appropriate branch. |
|
Have you had a chance to look at log data. To compare the latency improvement bdshot dynamic notch has over the matrix filter ? Or even over ESC telemetry RPM, to be sure it's working correctly. |
|
@halfmanbear INAV is about to enter the 9.1 release phase. Once that is finished I will conduct some testing. |
|
Thanks for your work in this. Getting it to work is one thing, the impact on performance with everything else on a hexcopter running on a less-capable chip another issue. |
For testing you can flash Bluejay, it is the recommended firmware with all new features for old BLHELI_S |
|
Thanks again for all of your work on this. Sorry it took so long for me to review. Going through your excellent PR, it looks like it mostly matches the plan I had put together but didn't actually implement in full. Since we independently came up with the same design, that might be a hint that it's a good design. :) I could go over various points that look good, but suffice to say - nice work! It is my understanding that on the H7, the D-cache means DMA-written data isn't automatically visible to the CPU without `SCB_InvalidateDCache_by_Addr() being called, so we may need to call that? F7 has the same D-cache, but it's disabled in INAV. I currently see the build fails with: There may be a need to gate this on #if (MCU_FLASH_SIZE > 512), for F722. It's my understanding this won't work in CH1N-CH4N, which is probably okay because it looks no targets use that on the first four outputs (quad motors). But we'd need to keep the unidirectional DShot for hexacopters and octocopters. ┌─────────────────── |
|
A note re F722 for future reference: this feature would add 2,972 bytes of flash. |
…ht#11605 # Conflicts: # src/main/target/common_post.h
|
Current build errors: ggdb3 -DNDEBUG -std=gnu99 -flto=auto -fno-fat-lto-objects -ffunction-sections -fdata-sections -fno-common -mthumb -mcpu=cortex-m4 -march=armv7e-m -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fsingle-precision-constant -Wdouble-promotion -Werror -O2 -Wall -Wextra -Wunsafe-loop-optimizations -Wstrict-prototypes -Werror=switch -MD -MT src/main/target/FF_F35_LIGHTNING/CMakeFiles/WINGFC.elf.dir///drivers/pwm_output.c.obj -MF src/main/target/FF_F35_LIGHTNING/CMakeFiles/WINGFC.elf.dir///drivers/pwm_output.c.obj.d -o src/main/target/FF_F35_LIGHTNING/CMakeFiles/WINGFC.elf.dir///drivers/pwm_output.c.obj -c /home/runner/work/inav/inav/src/main/drivers/pwm_output.c Also: |
|
Just an FYI for contributors: The tentative schedule for INAV 10 is to have a full release in mid December. That means RC2 needs to be in early to mid November, which places INAV 10.0RC1 at September 1. Please plan to have any new features for INAV 10.0 ready for RC1 no later than September 1. After that, 10.1 will follow about six to seven months later. |
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.
|
If someone is able, I would appreciate it if someone could run a CPU load check, just to confirm: For a pilot with a bench setup: FC + bidir-capable ESC (BLHeli32, AM32, or Safety
What you need
Steps1. Confirm the ESC is actually reporting telemetry. 2. Run A — bidirectional DShot ON (already the default in this build). 3. Run B — bidirectional DShot OFF (comparison baseline). 4. Restore the setting so the board is back the way it started: What to send back
That's enough for us to compare the |
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.
|
I have made four commits fixing four different kinds of compile failures on this. I would love to be able to include this in INAV 10, but worry about the correctness and quality when there are multiple different types of errors caught just by trying to build it. |
|
The current compile failures on all the F722 targets are because this will not fit in ITCM on F722 and it's single-caller within the scheduler() FAST_CODE call chain. |
|
Memory issue is resolved with this PR: halfmanbear#1 Probably auto transition + this one merged together into base branch will need more memory optimisations. |
fix: keep DShot telemetry decode out of ITCM
|
RAM / Flash usage vs. base branch — commit
|
|
Test firmware build ready — commit Download firmware for PR #11605 244 targets built. Find your board's
|
…lemetry, magic H7 workdown from betaflight
|
Here is some suggestion what could be fixed in dshot bidir. There are three fixes (mainly for H7)
My is probably only for DMA version of dshot, I did not tested DMAR on F4 CPU. It's in 95% code generated by AI. But I tried to understand what is happening there. But for me how bidir-dshot works is something between magic and miracle. |
|
With the last commit using a Blheli_32 4 in 1. When The kiss serial ESC telemetry that INAV previously supported no longer works when |
|
Have to to tested it with my fixes? |
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
BidirDshot - fix for H7, invertion of timer, reset DMA for reading te…
|
About this commit halfmanbear@4499455 I'm not sure if it's good solution. If bidir dshot is enabled then during eeprom save is dshot turned off. It't not possible to set cicrcular mode. More complicated solution could be:
This is quite critical place because if we try to set circular mode if bidir dshot is enambled then all outputs stopped working. I tested bidir dshot on H7 on the field, it worked. But it needs probably some love, to make it work for 100%. |
|
To correct running bidir dshot on F7/H7 is needed #11931 |
|
Yes, circular mode is absolutely required. The issue is that if the output stops working, some ESCs will re-detect the protocol, think they see something like standard PWM, Multi shot, or One-shot and throttle up. If that means the bidirectional DSHOT has to deal with corrupted telemetry frames immediately after a save, then we have to figure out how to deal with corrupted telemetry frames immediately after a save. Once the DSHOT is initiated, it cannot stop until power off. |
|
Circular mode is not possible to use for bidir dshot, there is switching between output and input. Or I have no idea how to do that. I'm not familiar with full throttle issue. It's weird because betaflight stops DSHOT during flash saving. There is only one diference, Inav kept pin high, betaflight keeps pin low. Use circular DMA is possible only if we would disable bidir dshot, set circular DMA, save eeprom, reconfigure dma for bidir dshot. Dont know what you mean with corrupted telemetry. The issue in PR I linked is not caused by bidir dshot. The issue can be caused by any other irq with highter priority. It's bug in UART irq. |
It sounds like you have an excellent idea of how to do that. :) Note the regular Dshot path might already be disabled when we switch to circular, and re-enabled when we switch back. Bidirectional would just to preserve that, if that's the case. |
|
yea, it's my nainve plan :D. Short sumarization what I found so far: Diference between dshot and bidir dshot is not only that ESC sends telemetry but diference is also invertion, Dhot is not inverted, bidir dshot does. If ESC detect bidir dshot then is not possible to change to dshot, becasue ESC keep protocol until restart or until ESC lost valid signal. So probably we need to somehow set circular DMA for bidir dshot, inverted and with window when ESC sends telemetry to FC. FC does not have to read telemetry but FC must give ESC time to send telemetry. Main issue what I found is that durring window for telemetry, cicular DMA would hold pin down, but ESC would send telemetry and there would be hight levels. So FC would keep pin low and ESC high, it would cause short circuit. Acording AI is solution next DMA stream on same timer for releasing pin to avoid short circuit. Cons is that it works only for H7 and for DMA1 on F7 and F4. It sounds to fragile for me, and lot of hassle with diferent settings and platforms.
I checked betaflight, rotroflight and ardupilot and all projects durring saving to eeprom, all projects just disable dshot and set pin to low (depend on invertion). But I understand you don't want it. |
|
I did some measurment: Betaflight dshot600, normal, saving to eeprom: Betaflight dshot600, bidir, saving to eeprom: Inav dshot 600, normal, saving to eeprom: Inav dshot 600, bidir, saving to eeprom: If INAV uses same ESCs as betaflight there should not be a problem with start motor due to broken signal. But what is interesting. Betaflight DSHOT is nicely aligned. 110us frame, wait 15us, and next frame But inav DSHOT frames vary a little bit. Time between frames in betaflight is 110 + 15 = 115us, it seems that betaflight keep space from telemetry even in normal mode, so bidir and normal dshot frames have same lenght 115us. In INAV is time between frames 64us. And sometimes the time is prolonged to 110us, so some task which runs soo slow, clouade thinks it's OSD task, I did not verified it yet. In Inav bidir dshot frame does not have lot of space, bidir dshot frame takes 103us, and after that starts next dshot frame. It's diference from betaflight with 115us. But I flew with it and it works. Betaflight normal mode, dshot600: The next question is if should I go deep and deep to problem or author @halfmanbear of PR somehow works on it also. |
|
Yeah there is a certain version of ESC firmware firmware that has an issue such that it can trigger during the eeprom save or as it is leaving EEPROM save if the line is left in a single state. One could say "well, that's a problem with that ESC firmware version, not INAV's problem". Until it chops your fingers up, then pointing out that it's someone else's fault doesn't solve the problem. :( I had been hoping that either setting it high or setting it low would work, but alas neither ended up working out to be safe for different reasons. That's an interesting problem about the ESC potentially pulling a line low while the FC is setting it high or vice-versa. If there is a sufficient resistor on either side that may be okay, but we can't guarantee all hardware will have that. I suppose that's an issue if we drive it either high or low - the ESC could be trying to go the opposite direction. The only safe thing, if the ESC is driving the pin, is for the FC to tri-state it. We can control what the FC does, but the only control we have over the ESC is to tell it to stop bidirectional Dshot for a moment. |
|
Not sure if that‘s relevant, but I still have the problem of motor spinning up in INAV 9.1 after disarm with an ESCape32 driven ESC running DSHOT600, even w/ their latest version 17.2, which is meant to resolve an issue w/ erroneous recognition of PWM signals |
|
@karstendf it's probably not relevant for this PR, but it's a huge problem. Could you please create issue https://github.com/iNavFlight/inav/issues with informations how often it happend, what ESC you have, what version of ESCape32, etc.. Thank you very much. |
|
about to bench test on my T1. if that gets finished and implemented we should have toggles in the Outputs Tab and not just CLI. Very cool if it works as it helps my other projects as well. |
|
@b14ckyy probably I would have to somehow solve issue with circular dshot, in current implementation is pin low durring save config to eeprom, I will have to implement circular dma for that, it's quite tricky because I have to release pin for receiving telemetry (don't have to read telemetry but pin must be released). So far I have particular solution for H7. Have no idea if it will be possible to do for F7 and F4 |
|
On my T1 with Matek F405WTE_SD it won't work at all. Neither normal telemetry nor the extended telemetry. The motors won't spin. But that might be the cheapo escs my lite version has. Need to test with my 7" copter and H7 |
|
Seems that posibility to sent on for bidir dshot is allowed for all targtes, unfortunately for boards which uses DMAR is not possible to use bidir dshot. inav/src/main/target/MATEKF405TE/target.h Line 176 in e4278ef Due too needs to generate dshot signal durring eeprom save will be probably bidir dshot avaiable only for H7. |
|
For further discussion of circular DSHOT during EEPROM save, see also #11954 |
|
A small progress, but seems quite overcomplicated for me. Recapilation: circular DMA is possible to set for bidir dshot, but must be solved sitiation when ESC sends telemetry, FC must release pins, otherwise = short circuit (FC keeps pin hight, ESC low, two opened mosfets). Only solution what I found is set DMA to circuit mode and use next DMA stream to release pin. It's extra DMA stream for each TIMER where is attached motor. So tipically for quadcopter two extra streams for fixedwing one extra stream. Next problem is that universal solution works only for H7 because only H7 constains DMAMUX, other CPUs like F7 F4 use fixed table, and it's quite hassple. If anybody has some idea, it's welcome :). I tested it for H7, I'm not sure when I should alocate DMA stream/s. Currently I allocate DMA stream a while before I need it/them, it means a while before eeprom saving. I can alocate DMA stream/s durring boot, but I would DMA stream hold for future and maybe never use it. Next question if it's ok to use 1/2 DMA streams for that, DMA stream is very limited resource in CPU, H7 has only 16 DMA streams. But on other hands, it works :) https://github.com/error414/inav/commits/error414/feat/dshot-bidir-circular/ |
|
Using another DMA per motor to keep birectional working, setting CCMR1 per bit, might work but it may be overkill just for handling the eeprom save. Two other possible options (plus a bonus) switch to standard DSHOT while the EEPROM is savingDon't request birectional telemetry during the save. Send regular DSHOT, which is idle low, and don't invert the CRC. If the line never idles high, the ESC won't try to send telemetry back. It will see a brief error, but won't try to toggle the pin and won't spin up the motor. use open drainWe could set OTYPER so the output runs in open drain mode during the save. That would mean it doesn't matter if the ESC drives the pin while the MCU also allows it to rise from the internal pull-up Set the pin registers rather than PWM timingWe could also use the one DMA channel to directly set the pin registers, including the high/low state AND CCMR or CCER to set input Option 1 seems simplest, perhaps? |
|
I did not describe it well :(. I do not set registers CCMR1 and CCMR2, but second DMA stream which releases pins set to register CCER, values CCxE or CCxNE, it depends on polarity
Second DMA stream is on same timer so it's synchronized with DMA (because it's same timer). I do not use second DMA per motor, but I use second DMA per timer, because registers CCER contains all four channels. Note: so far I checked datsheet only for H7, I afraid that registers for F7 and F7 will be more limited. switch to standard DSHOT while the EEPROM is savingit's easiest way, but ESC during boot detect signal type, if signal is detected then ESC keeps this type until ESC is rebooted. Bidir dshot is inverted to clasic dshot so if we would send clasic dshot to ESC which is set to bidir dshot then ESC will not be able to decode signal (in the best case) or will read nonsece (dangerous). It would be better to send nothing. use open drainFor using opendrain you need small pull-resitor to be fast, but big enought to reduce current via resistor. I did fast resarch and STM32 uses aprox 30-40kohm pull-up resistor, it can be used for few kHz. But I have to sayI did not tested yet, it's only my theory from my eletricity knowledge. Set the pin registers rather than PWM timingI would say my solution is like that. Second DMA stream writes to CCER register. It's not direct register to drive pin, but it's register for timer how to drive pin. |
I absolutely hear you if we think of it theoretically. Some firmware (including ESCape) will re-detect if there is no signal at all (line held). If the protocol isn't what was expected, it will look at the signal on the line and pick whichever protocol seems to best fit. This is the case continuous was required to solve for, because it spins the motor if you hold line during the eeprom save, then start sending midstream. It thinks it detected standard PWM, or Multishot or something and misinterprets the data if it's not DSHOT. I think other firmware will not spin the motor when it doesn't see the signal it's expecting. But testing would be a very good thing. |
|
I'll have to look into "one additional DMA per timer" idea. On my quick research, it looks like open drain could likely barely achieve DSHOT 150, and likely not work at DSHOT 300. We don't need it to work 100% for normal flight, of course. We just need to cover one second for the case of a buggy ESC version. So maybe 99% would do it. I don't think it'll hit even that low threshold. It COULD probably generate a 1ms pulse train that looks like zero throttle PWM for those ESCs that re-detect on signal loss. So theoretically: Bring the line low Start continuous Dshot sending classic PWM with open drain. Save EEPROM Drop low Re-enable bidirectional DSHOT |













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:
Changes:
Tested on SPEEDYBEEF405V4 (STM32F405, StdPeriph) with Bluejay ESC firmware. eRPM telemetry confirmed in blackbox;