Skip to content

Ins rc orig#11477

Open
insertzs wants to merge 3 commits intoiNavFlight:masterfrom
insertzs:INS_RC_ORIG
Open

Ins rc orig#11477
insertzs wants to merge 3 commits intoiNavFlight:masterfrom
insertzs:INS_RC_ORIG

Conversation

@insertzs
Copy link
Copy Markdown

@insertzs insertzs commented Apr 5, 2026

No description provided.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add bidirectional DShot telemetry and configurable RC channel mirroring

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add experimental bidirectional DShot telemetry support for motor RPM feedback
  - Implements GCR decoding and eRPM to RPM conversion
  - Supports up to 4 motors on TIM4 with DMA or interrupt capture
  - Integrates with RPM filter and telemetry systems
• Add configurable RC channel mirroring to preserve original RX values before MSP override
  - Four copy rules allow routing pre-override channels to spare destinations
  - Enables external controllers to read both overridden and original stick data
• Refactor RPM source abstraction to support multiple telemetry backends
  - New rpm_source module unifies ESC sensor and DShot bidir RPM access
  - Replaces direct ESC sensor calls throughout codebase
• Enable DShot bidir on SpeedyBee F405 V3 target with validation
Diagram
flowchart LR
  RX["RX Channels"] -->|store original| ORIG["Original Channels"]
  RX -->|apply override| MSP["MSP Override"]
  ORIG -->|copy rules| MIRROR["Mirrored Channels"]
  DSHOT["DShot Motors"] -->|capture telemetry| BIDIR["Bidir Decoder"]
  BIDIR -->|decode eRPM| RPM_SRC["RPM Source"]
  ESC["ESC Sensor"] -->|fallback| RPM_SRC
  RPM_SRC -->|provide RPM| FILTER["RPM Filter"]
  RPM_SRC -->|provide RPM| TELEM["Telemetry"]
Loading

Grey Divider

File Changes

1. src/main/drivers/dshot_bidir.c ✨ Enhancement +761/-0

New bidirectional DShot telemetry decoder implementation

src/main/drivers/dshot_bidir.c


2. src/main/drivers/dshot_bidir.h ✨ Enhancement +19/-0

Bidirectional DShot driver public API header

src/main/drivers/dshot_bidir.h


3. src/main/sensors/rpm_source.c ✨ Enhancement +162/-0

New RPM source abstraction layer for multiple backends

src/main/sensors/rpm_source.c


View more (23)
4. src/main/sensors/rpm_source.h ✨ Enhancement +40/-0

RPM source abstraction public interface

src/main/sensors/rpm_source.h


5. src/main/drivers/pwm_output.c ✨ Enhancement +72/-2

Integrate DShot bidir support and telemetry requests

src/main/drivers/pwm_output.c


6. src/main/drivers/pwm_output.h ✨ Enhancement +2/-1

Add DShot bidir mode status query function

src/main/drivers/pwm_output.h


7. src/main/rx/rx.c ✨ Enhancement +54/-1

Add RC channel mirroring before MSP override

src/main/rx/rx.c


8. src/main/rx/rx.h ✨ Enhancement +4/-0

Add RC channel copy rule configuration and constants

src/main/rx/rx.h


9. src/main/flight/mixer.c ✨ Enhancement +3/-2

Add DShot bidir configuration and update PG version

src/main/flight/mixer.c


10. src/main/flight/mixer.h ✨ Enhancement +1/-0

Add DShot bidir enabled field to motor config

src/main/flight/mixer.h


11. src/main/flight/rpm_filter.c ✨ Enhancement +8/-3

Refactor to use RPM source abstraction instead of ESC sensor

src/main/flight/rpm_filter.c


12. src/main/blackbox/blackbox.c ✨ Enhancement +9/-3

Update ESC RPM handling to use RPM source abstraction

src/main/blackbox/blackbox.c


13. src/main/fc/fc_init.c ✨ Enhancement +3/-1

Initialize DShot bidir and update RPM filter condition

src/main/fc/fc_init.c


14. src/main/fc/fc_msp.c ✨ Enhancement +29/-5

Update MSP RPM telemetry to use RPM source abstraction

src/main/fc/fc_msp.c


15. src/main/fc/cli.c ✨ Enhancement +34/-1

Add DShot bidir status and debug output to CLI

src/main/fc/cli.c


16. src/main/io/osd.c ✨ Enhancement +5/-4

Update OSD RPM display to use RPM source abstraction

src/main/io/osd.c


17. src/main/io/osd_dji_hd.c ✨ Enhancement +6/-3

Refactor DJI OSD ESC telemetry to use RPM source

src/main/io/osd_dji_hd.c


18. src/main/telemetry/jetiexbus.c ✨ Enhancement +7/-10

Update Jeti telemetry to use RPM source abstraction

src/main/telemetry/jetiexbus.c


19. src/main/telemetry/sbus2.c ✨ Enhancement +3/-4

Update SBUS2 telemetry to use RPM source abstraction

src/main/telemetry/sbus2.c


20. src/main/build/debug.h ✨ Enhancement +1/-0

Add DEBUG_DSHOT_BIDIR debug mode type

src/main/build/debug.h


21. src/main/target/SPEEDYBEEF405V3/target.h ✨ Enhancement +2/-0

Enable DShot bidir and RPM filter for SpeedyBee F405 V3

src/main/target/SPEEDYBEEF405V3/target.h


22. src/main/target/SPEEDYBEEF405V3/config.c ✨ Enhancement +25/-0

Add DShot bidir validation and protocol downgrade logic

src/main/target/SPEEDYBEEF405V3/config.c


23. src/main/CMakeLists.txt ✨ Enhancement +4/-0

Add new DShot bidir and RPM source source files

src/main/CMakeLists.txt


24. src/main/fc/settings.yaml ✨ Enhancement +54/-1

Add RC channel mirroring and DShot bidir configuration settings

src/main/fc/settings.yaml


25. docs/INS\ RC\ ORIG.md 📝 Documentation +0/-0

New documentation for RC channel mirroring feature

docs/INS\ RC\ ORIG.md


26. docs/INS RC ORIG.md Additional files +66/-0

...

docs/INS RC ORIG.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Apr 5, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. dshotBidirDecodeTelemetryPacket() OOB read 📘 Rule violation ⛨ Security
Description
dshotBidirDecodeTelemetryPacket() unconditionally reads buffer[count] in the final loop
iteration (i == count), which can go past captureBuffer when `count ==
DSHOT_BIDIR_CAPTURE_BUFFER_LEN`. This violates the requirement to length-check protocol/payload
buffers before reads and can cause undefined behavior/crashes.
Code

src/main/drivers/dshot_bidir.c[R150-168]

+    for (uint32_t i = 1; i <= count; i++) {
+        if (i < count) {
+            const int diff = buffer[i] - oldValue;
+            if (bits >= 21) {
+                break;
+            }
+            len = (diff + 8) / 16;
+        } else {
+            len = 21 - bits;
+        }
+
+        if (len <= 0 || (bits + len) > 21) {
+            return DSHOT_BIDIR_INVALID;
+        }
+
+        value <<= len;
+        value |= 1 << (len - 1);
+        oldValue = buffer[i];
+        bits += len;
Evidence
The checklist requires validating buffer sizes before indexing/reading fields. The new decode loop
iterates i <= count and assigns oldValue = buffer[i], so when called with count == edgeCount
it reads one element beyond the captured buffer; the caller passes edgeCount derived from a
full-length capture (up to DSHOT_BIDIR_CAPTURE_BUFFER_LEN).

src/main/drivers/dshot_bidir.c[143-168]
src/main/drivers/dshot_bidir.c[451-466]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dshotBidirDecodeTelemetryPacket()` can read past the end of `buffer` because the loop runs with `i <= count` and still reads `buffer[i]` when `i == count`.

## Issue Context
`dshotBidirDecodeCapture()` passes `edgeCount` from `dshotBidirGetCapturedEdgeCount()`, which can be equal to `DSHOT_BIDIR_CAPTURE_BUFFER_LEN`, making `buffer[count]` out-of-bounds.

## Fix Focus Areas
- src/main/drivers/dshot_bidir.c[143-168]
- src/main/drivers/dshot_bidir.c[451-466]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing <math.h> for lrintf 📘 Rule violation ⚙ Maintainability
Description
dshot_bidir.c uses lrintf() without explicitly including <math.h>, relying on transitive
includes. This can break builds on toolchains where lrintf is not declared unless <math.h> is
included.
Code

src/main/drivers/dshot_bidir.c[R25-33]

+#include <string.h>
+
+#include "platform.h"
+
+#ifdef USE_DSHOT_BIDIR
+
+#include "build/debug.h"
+
+#include "common/time.h"
Evidence
The checklist requires source files to explicitly include headers for the symbols they use. The new
file calls lrintf() but does not include <math.h> in its direct includes.

src/main/drivers/dshot_bidir.c[25-43]
src/main/drivers/dshot_bidir.c[210-219]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`lrintf()` is used without an explicit `<math.h>` include, which can cause build failures due to missing prototype declarations.

## Issue Context
Compliance requires avoiding reliance on transitive includes for macros/types/functions.

## Fix Focus Areas
- src/main/drivers/dshot_bidir.c[25-43]
- src/main/drivers/dshot_bidir.c[210-219]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. RPM paths still ESC_SENSOR-gated 🐞 Bug ≡ Correctness
Description
RPM consumers (OSD ESC_RPM, Jeti EX_RPM, MSP2_INAV_ESC_RPM/ESC_TELEM) are still compiled under
#ifdef USE_ESC_SENSOR, so DShot-bidir-only targets (e.g. SPEEDYBEEF405V3 enables USE_DSHOT_BIDIR
but not USE_ESC_SENSOR) won’t expose RPM via these features.
Code

src/main/io/osd.c[R3924-3932]

#if defined(USE_ESC_SENSOR)
    case OSD_ESC_RPM:
        {
-            escSensorData_t * escSensor = escSensorGetData();
-            if (escSensor && escSensor->dataAge <= ESC_DATA_MAX_AGE) {
-                osdFormatRpm(buff, escSensor->rpm);
+            uint32_t rpm = 0;
+            if (rpmSourceGetAverageRpm(&rpm)) {
+                osdFormatRpm(buff, rpm);
            }
            else {
                osdFormatRpm(buff, 0);
Evidence
SPEEDYBEEF405V3 explicitly enables DShot bidir while USE_ESC_SENSOR is commented out; meanwhile
the OSD RPM element handler and Jeti/MSP RPM outputs remain wrapped in #ifdef USE_ESC_SENSOR,
meaning they are not built for that target and similar configurations.

src/main/target/SPEEDYBEEF405V3/target.h[149-155]
src/main/io/osd.c[3924-3936]
src/main/telemetry/jetiexbus.c[296-300]
src/main/telemetry/jetiexbus.c[433-443]
src/main/fc/fc_msp.c[1730-1776]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Even though `rpm_source` supports DShot-bidir RPM, multiple RPM output features are still gated by `#ifdef USE_ESC_SENSOR`, so builds with `USE_DSHOT_BIDIR` but without `USE_ESC_SENSOR` don’t include these RPM outputs.

## Issue Context
`escSensorData_t` is defined unconditionally (in `sensors/esc_sensor.h`), and RPM retrieval now goes through `rpmSourceGetAverageRpm()` / `rpmSourceGetMotorRpm()`, so these modules can be compiled without `USE_ESC_SENSOR` when DShot bidir is enabled.

## Fix Focus Areas
- src/main/io/osd.c[3924-3936]
- src/main/telemetry/jetiexbus.c[296-300]
- src/main/telemetry/jetiexbus.c[433-443]
- src/main/fc/fc_msp.c[1730-1776]
- src/main/target/SPEEDYBEEF405V3/target.h[149-155]

## Expected fix
Relax the compile-time guards for RPM-related features to include DShot bidir builds (e.g., `#if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_BIDIR)`), and ensure any remaining ESC-temperature-only logic stays under `USE_ESC_SENSOR` where appropriate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Uninitialized DShot RPM age 🐞 Bug ☼ Reliability
Description
rpm_source.c leaves dshotBidirDataAge[] zero-initialized (treated as valid) unless
rpmSourceResetDshotBidir() is called, but the only call site is inside #ifdef USE_RPM_FILTER, so
other builds can report DShot RPM as valid/active before any telemetry arrives.
Code

src/main/sensors/rpm_source.c[R39-41]

+static uint32_t dshotBidirRpm[MAX_SUPPORTED_MOTORS];
+static uint8_t dshotBidirDataAge[MAX_SUPPORTED_MOTORS];
+
Evidence
In C, file-scope statics default to 0; rpmSourceGetDshotBidirValidCount() treats any age < 255 as
valid, so all motors start “valid” unless reset. The reset that sets age to 255 is only invoked in
fc_init.c when USE_RPM_FILTER is compiled in.

src/main/sensors/rpm_source.c[39-75]
src/main/sensors/rpm_source.c[135-141]
src/main/fc/fc_init.c[730-737]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dshotBidirDataAge[]` defaults to 0 (valid) at startup, so DShot RPM can appear valid/active even before any telemetry is received unless `rpmSourceResetDshotBidir()` is called.

## Issue Context
Currently `rpmSourceResetDshotBidir()` is only called under `#ifdef USE_RPM_FILTER`. DShot bidir RPM can still be used for OSD/telemetry on builds that do not compile RPM filter.

## Fix Focus Areas
- src/main/sensors/rpm_source.c[39-75]
- src/main/sensors/rpm_source.c[135-141]
- src/main/fc/fc_init.c[730-737]

## Expected fix
Ensure `dshotBidirDataAge[]` starts at `RPM_SOURCE_DATA_INVALID` in all builds (e.g., add a static initializer for `dshotBidirDataAge`, or call `rpmSourceResetDshotBidir()` unconditionally during init, or from `dshotBidirInit()` when bidir mode is enabled).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. TIM4 check bypasses validation 🐞 Bug ☼ Reliability
Description
When dshotBidirModeActive is set, pwmMotorConfig() hard-returns false for motors not on TIM4,
and pwm_mapping.c treats that as a fatal PWM init error; since timer usage can be changed by
timerOverrides, this can turn an otherwise bootable config into a motor-init failure rather than
auto-disabling DShot bidir.
Code

src/main/drivers/pwm_output.c[R643-647]

+#ifdef USE_DSHOT_BIDIR
+    if (dshotBidirModeActive) {
+        if (motorIndex >= 4 || timerHardware->tim != TIM4) {
+            return false;
+        }
Evidence
The code enforces TIM4 at motor-config time (fatal), but the earlier enablement condition for
dshotBidirModeActive does not validate timer selection; timer usage flags can be overridden via
timerOverrides() in pwm_mapping, so a config can request dshot bidir yet end up with motor outputs
not on TIM4, triggering a hard init failure.

src/main/drivers/pwm_output.c[561-581]
src/main/drivers/pwm_output.c[641-649]
src/main/drivers/pwm_mapping.c[409-416]
src/main/drivers/pwm_mapping.c[221-235]
src/main/drivers/dshot_bidir.c[531-535]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pwmMotorConfig()` returns `false` (fatal init failure) when DShot bidir is enabled but motor outputs are not on TIM4. Because motor/servo timer usage can be influenced by `timerOverrides()`, this can surface as a configuration-dependent boot failure instead of gracefully disabling DShot bidir.

## Issue Context
The code already has a target validation hook (`validateAndFixTargetConfig`) and a runtime mode flag (`dshotBidirModeActive`), so the firmware can auto-disable DShot bidir before attempting motor allocation if constraints aren’t met.

## Fix Focus Areas
- src/main/drivers/pwm_output.c[561-581]
- src/main/drivers/pwm_output.c[641-649]
- src/main/drivers/pwm_mapping.c[221-235]
- src/main/drivers/pwm_mapping.c[409-416]
- src/main/drivers/dshot_bidir.c[531-535]

## Expected fix
Validate the TIM4 requirement earlier (during config validation or preconfigure) and automatically disable `dshotBidirEnabled`/`dshotBidirModeActive` when constraints aren’t met, rather than failing PWM init.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +150 to +168
for (uint32_t i = 1; i <= count; i++) {
if (i < count) {
const int diff = buffer[i] - oldValue;
if (bits >= 21) {
break;
}
len = (diff + 8) / 16;
} else {
len = 21 - bits;
}

if (len <= 0 || (bits + len) > 21) {
return DSHOT_BIDIR_INVALID;
}

value <<= len;
value |= 1 << (len - 1);
oldValue = buffer[i];
bits += len;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. dshotbidirdecodetelemetrypacket() oob read 📘 Rule violation ⛨ Security

dshotBidirDecodeTelemetryPacket() unconditionally reads buffer[count] in the final loop
iteration (i == count), which can go past captureBuffer when `count ==
DSHOT_BIDIR_CAPTURE_BUFFER_LEN`. This violates the requirement to length-check protocol/payload
buffers before reads and can cause undefined behavior/crashes.
Agent Prompt
## Issue description
`dshotBidirDecodeTelemetryPacket()` can read past the end of `buffer` because the loop runs with `i <= count` and still reads `buffer[i]` when `i == count`.

## Issue Context
`dshotBidirDecodeCapture()` passes `edgeCount` from `dshotBidirGetCapturedEdgeCount()`, which can be equal to `DSHOT_BIDIR_CAPTURE_BUFFER_LEN`, making `buffer[count]` out-of-bounds.

## Fix Focus Areas
- src/main/drivers/dshot_bidir.c[143-168]
- src/main/drivers/dshot_bidir.c[451-466]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +25 to +33
#include <string.h>

#include "platform.h"

#ifdef USE_DSHOT_BIDIR

#include "build/debug.h"

#include "common/time.h"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Missing <math.h> for lrintf 📘 Rule violation ⚙ Maintainability

dshot_bidir.c uses lrintf() without explicitly including <math.h>, relying on transitive
includes. This can break builds on toolchains where lrintf is not declared unless <math.h> is
included.
Agent Prompt
## Issue description
`lrintf()` is used without an explicit `<math.h>` include, which can cause build failures due to missing prototype declarations.

## Issue Context
Compliance requires avoiding reliance on transitive includes for macros/types/functions.

## Fix Focus Areas
- src/main/drivers/dshot_bidir.c[25-43]
- src/main/drivers/dshot_bidir.c[210-219]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 3924 to 3932
#if defined(USE_ESC_SENSOR)
case OSD_ESC_RPM:
{
escSensorData_t * escSensor = escSensorGetData();
if (escSensor && escSensor->dataAge <= ESC_DATA_MAX_AGE) {
osdFormatRpm(buff, escSensor->rpm);
uint32_t rpm = 0;
if (rpmSourceGetAverageRpm(&rpm)) {
osdFormatRpm(buff, rpm);
}
else {
osdFormatRpm(buff, 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Rpm paths still esc_sensor-gated 🐞 Bug ≡ Correctness

RPM consumers (OSD ESC_RPM, Jeti EX_RPM, MSP2_INAV_ESC_RPM/ESC_TELEM) are still compiled under
#ifdef USE_ESC_SENSOR, so DShot-bidir-only targets (e.g. SPEEDYBEEF405V3 enables USE_DSHOT_BIDIR
but not USE_ESC_SENSOR) won’t expose RPM via these features.
Agent Prompt
## Issue description
Even though `rpm_source` supports DShot-bidir RPM, multiple RPM output features are still gated by `#ifdef USE_ESC_SENSOR`, so builds with `USE_DSHOT_BIDIR` but without `USE_ESC_SENSOR` don’t include these RPM outputs.

## Issue Context
`escSensorData_t` is defined unconditionally (in `sensors/esc_sensor.h`), and RPM retrieval now goes through `rpmSourceGetAverageRpm()` / `rpmSourceGetMotorRpm()`, so these modules can be compiled without `USE_ESC_SENSOR` when DShot bidir is enabled.

## Fix Focus Areas
- src/main/io/osd.c[3924-3936]
- src/main/telemetry/jetiexbus.c[296-300]
- src/main/telemetry/jetiexbus.c[433-443]
- src/main/fc/fc_msp.c[1730-1776]
- src/main/target/SPEEDYBEEF405V3/target.h[149-155]

## Expected fix
Relax the compile-time guards for RPM-related features to include DShot bidir builds (e.g., `#if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_BIDIR)`), and ensure any remaining ESC-temperature-only logic stays under `USE_ESC_SENSOR` where appropriate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant