diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bf911321f14..f3a856bc7f6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -107,6 +107,16 @@ jobs:
with:
name: targets
path: targets.txt
+ - name: Save PR number
+ if: github.event_name == 'pull_request'
+ run: echo "${{ github.event.pull_request.number }}" > pr_number.txt
+ - name: Upload PR number
+ if: github.event_name == 'pull_request'
+ uses: actions/upload-artifact@v4
+ with:
+ name: pr-number
+ path: pr_number.txt
+ retention-days: 1
build-SITL-Linux-arm64:
runs-on: ubuntu-22.04-arm
diff --git a/.github/workflows/pr-test-builds.yml b/.github/workflows/pr-test-builds.yml
new file mode 100644
index 00000000000..cf4a32b7b57
--- /dev/null
+++ b/.github/workflows/pr-test-builds.yml
@@ -0,0 +1,153 @@
+name: PR Test Builds
+
+# Runs after "Build firmware" completes. Uses workflow_run (rather than
+# pull_request directly) so that secrets are available even for PRs from forks.
+#
+# Requires a repository secret PR_BUILDS_TOKEN with Contents: write access
+# to iNavFlight/pr-test-builds (fine-grained PAT or classic PAT with repo scope).
+on:
+ workflow_run:
+ workflows: ["Build firmware"]
+ types: [completed]
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ # Only act on pull_request-triggered runs that succeeded.
+ if: >
+ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success'
+ # Prevent concurrent runs for the same PR branch racing on the
+ # release delete/create cycle.
+ concurrency:
+ group: pr-test-build-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
+ cancel-in-progress: true
+ permissions:
+ actions: read # to download artifacts from the triggering workflow run
+ issues: write # github.rest.issues.* endpoints used to post PR comments
+ pull-requests: write # to post the PR comment
+
+ steps:
+ - name: Download PR number
+ uses: actions/download-artifact@v4
+ with:
+ name: pr-number
+ run-id: ${{ github.event.workflow_run.id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Read PR number
+ id: pr
+ run: |
+ PR_NUM=$(tr -dc '0-9' < pr_number.txt)
+ if [ -z "$PR_NUM" ]; then
+ echo "::error::Invalid PR number in artifact"
+ exit 1
+ fi
+ echo "number=${PR_NUM}" >> $GITHUB_OUTPUT
+
+ - name: Download firmware artifacts
+ uses: actions/download-artifact@v4
+ with:
+ pattern: matrix-inav-*
+ merge-multiple: true
+ path: hexes
+ run-id: ${{ github.event.workflow_run.id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Get build info
+ id: info
+ run: |
+ COUNT=$(find hexes -name '*.hex' -type f | wc -l)
+ if [ "$COUNT" -eq 0 ]; then
+ echo "::error::No .hex files found in downloaded artifacts"
+ exit 1
+ fi
+ echo "count=${COUNT}" >> $GITHUB_OUTPUT
+ echo "short_sha=$(echo '${{ github.event.workflow_run.head_sha }}' | cut -c1-7)" >> $GITHUB_OUTPUT
+
+ # Delete the previous release for this PR (if any) so assets are replaced
+ # cleanly on each new commit. --cleanup-tag removes the old tag so it is
+ # recreated fresh pointing to the new commit.
+ - name: Delete existing PR release
+ env:
+ GH_TOKEN: ${{ secrets.PR_BUILDS_TOKEN }}
+ run: |
+ gh release delete "pr-${{ steps.pr.outputs.number }}" \
+ --repo iNavFlight/pr-test-builds --cleanup-tag --yes 2>/dev/null || true
+
+ - name: Create PR release
+ env:
+ GH_TOKEN: ${{ secrets.PR_BUILDS_TOKEN }}
+ PR_NUMBER: ${{ steps.pr.outputs.number }}
+ SHORT_SHA: ${{ steps.info.outputs.short_sha }}
+ HEX_COUNT: ${{ steps.info.outputs.count }}
+ HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ REPO: ${{ github.repository }}
+ run: |
+ PR_URL="https://github.com/${REPO}/pull/${PR_NUMBER}"
+ printf '%s\n\n%s\n\n%s\n' \
+ "Test build for [PR #${PR_NUMBER}](${PR_URL}) — commit \`${SHORT_SHA}\`" \
+ "**${HEX_COUNT} targets built.** Find your board's \`.hex\` file by name (e.g. \`MATEKF405SE.hex\`)." \
+ "> Development build for testing only. Use Full Chip Erase when flashing." \
+ > release-notes.md
+ gh release create "pr-${PR_NUMBER}" hexes/*.hex \
+ --repo iNavFlight/pr-test-builds \
+ --prerelease \
+ --target "${HEAD_SHA}" \
+ --title "PR #${PR_NUMBER} (${SHORT_SHA})" \
+ --notes-file release-notes.md
+
+ - name: Post or update PR comment
+ uses: actions/github-script@v7
+ env:
+ PR_NUMBER: ${{ steps.pr.outputs.number }}
+ SHORT_SHA: ${{ steps.info.outputs.short_sha }}
+ HEX_COUNT: ${{ steps.info.outputs.count }}
+ with:
+ script: |
+ const prNumber = parseInt(process.env.PR_NUMBER, 10);
+ if (isNaN(prNumber)) throw new Error(`Invalid PR number: ${process.env.PR_NUMBER}`);
+ const shortSha = process.env.SHORT_SHA;
+ const count = process.env.HEX_COUNT;
+ const releaseUrl = `https://github.com/iNavFlight/pr-test-builds/releases/tag/pr-${prNumber}`;
+
+ const body = [
+ '',
+ '**Test firmware build ready** — commit `' + shortSha + '`',
+ '',
+ `[Download firmware for PR #${prNumber}](${releaseUrl})`,
+ '',
+ `${count} targets built. Find your board's \`.hex\` file by name on that page ` +
+ '(e.g. `MATEKF405SE.hex`). Files are individually downloadable — no GitHub login required.',
+ '',
+ '> Development build for testing only. Use Full Chip Erase when flashing.',
+ ].join('\n');
+
+ const comments = await github.paginate(
+ github.rest.issues.listComments,
+ {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ }
+ );
+
+ const existing = comments.find(c =>
+ c.user.type === 'Bot' && c.body.includes('')
+ );
+
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body,
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body,
+ });
+ }
diff --git a/docs/policies/NEW_HARDWARE_POLICY.md b/docs/policies/NEW_HARDWARE_POLICY.md
index bd4bddedf05..10767524700 100644
--- a/docs/policies/NEW_HARDWARE_POLICY.md
+++ b/docs/policies/NEW_HARDWARE_POLICY.md
@@ -67,7 +67,7 @@ If one of the core developers has the hardware in possession they may opt in and
1. Requester is advised to open a feature request to add support for certain hardware to INAV by following [this link](https://github.com/iNavFlight/inav/issues/new/choose)
-2. After opening a feature request, Requester is advised to contact the core development team by [email](mailto:coredev@inavflight.com) mentioning the open feature request and communicate with developer team via email to arrange hardware and specifications delivery.
+2. After opening a feature request, Requester is advised to contact the core development team via [Discord](https://discord.gg/peg2hhbYwN) mentioning the open feature request and communicate with developer team via email to arrange hardware and specifications delivery.
## See also
diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c
index e4c2afb3eec..7597f6d581f 100644
--- a/src/main/blackbox/blackbox.c
+++ b/src/main/blackbox/blackbox.c
@@ -99,7 +99,7 @@
#define BLACKBOX_INVERTED_CARD_DETECTION 0
#endif
-PG_REGISTER_WITH_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig, PG_BLACKBOX_CONFIG, 4);
+PG_REGISTER_WITH_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig, PG_BLACKBOX_CONFIG, 5);
PG_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig,
.device = DEFAULT_BLACKBOX_DEVICE,
diff --git a/src/main/blackbox/blackbox.h b/src/main/blackbox/blackbox.h
index 1901201fa22..296291af0c7 100644
--- a/src/main/blackbox/blackbox.h
+++ b/src/main/blackbox/blackbox.h
@@ -54,11 +54,11 @@ typedef enum BlackboxState {
} BlackboxState;
typedef struct blackboxConfig_s {
+ uint32_t includeFlags;
uint16_t rate_num;
uint16_t rate_denom;
uint8_t device;
uint8_t invertedCardDetection;
- uint32_t includeFlags;
int8_t arm_control;
} blackboxConfig_t;
diff --git a/src/main/fc/stats.c b/src/main/fc/stats.c
index 2fc4c63633d..f48b0862330 100644
--- a/src/main/fc/stats.c
+++ b/src/main/fc/stats.c
@@ -21,7 +21,7 @@
#define MIN_FLIGHT_DISTANCE_M 30 // minimum distance flown for a flight to be registered [m]
-PG_REGISTER_WITH_RESET_TEMPLATE(statsConfig_t, statsConfig, PG_STATS_CONFIG, 2);
+PG_REGISTER_WITH_RESET_TEMPLATE(statsConfig_t, statsConfig, PG_STATS_CONFIG, 3);
PG_RESET_TEMPLATE(statsConfig_t, statsConfig,
.stats_enabled = SETTING_STATS_DEFAULT,
diff --git a/src/main/fc/stats.h b/src/main/fc/stats.h
index d0b4b162b60..b2195165ffc 100644
--- a/src/main/fc/stats.h
+++ b/src/main/fc/stats.h
@@ -5,10 +5,10 @@
typedef struct statsConfig_s {
uint32_t stats_total_time; // [Seconds]
uint32_t stats_total_dist; // [Metres]
- uint16_t stats_flight_count;
#ifdef USE_ADC
uint32_t stats_total_energy; // deciWatt hour (x0.1Wh)
#endif
+ uint16_t stats_flight_count;
uint8_t stats_enabled;
} statsConfig_t;
diff --git a/src/main/flight/pid.c b/src/main/flight/pid.c
index 0a2faa648ca..cab56d73618 100644
--- a/src/main/flight/pid.c
+++ b/src/main/flight/pid.c
@@ -179,7 +179,7 @@ static EXTENDED_FASTRAM bool angleHoldIsLevel = false;
static EXTENDED_FASTRAM float fixedWingLevelTrim;
static EXTENDED_FASTRAM pidController_t fixedWingLevelTrimController;
-PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(pidProfile_t, pidProfile, PG_PID_PROFILE, 11);
+PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(pidProfile_t, pidProfile, PG_PID_PROFILE, 12);
PG_RESET_TEMPLATE(pidProfile_t, pidProfile,
.bank_mc = {
diff --git a/src/main/flight/pid.h b/src/main/flight/pid.h
index ff2e85031b7..523af380bb0 100644
--- a/src/main/flight/pid.h
+++ b/src/main/flight/pid.h
@@ -97,71 +97,72 @@ typedef enum {
} itermRelax_e;
typedef struct pidProfile_s {
- uint8_t pidControllerType;
+ // Fields ordered largest-to-smallest to eliminate alignment padding holes
pidBank_t bank_fw;
pidBank_t bank_mc;
- uint8_t dterm_lpf_type; // Dterm LPF type: PT1, BIQUAD
- uint16_t dterm_lpf_hz;
-
- uint8_t yaw_lpf_hz;
-
- uint8_t heading_hold_rate_limit; // Maximum rotation rate HEADING_HOLD mode can feed to yaw rate PID controller
-
- uint8_t itermWindupPointPercent; // Experimental ITerm windup threshold, percent of motor saturation
-
- uint32_t axisAccelerationLimitYaw; // Max rate of change of yaw angular rate setpoint (deg/s^2 = dps/s)
- uint32_t axisAccelerationLimitRollPitch; // Max rate of change of roll/pitch angular rate setpoint (deg/s^2 = dps/s)
-
- int16_t max_angle_inclination[ANGLE_INDEX_COUNT]; // Max possible inclination (roll and pitch axis separately
-
- uint16_t pidItermLimitPercent;
-
// Airplane-specific parameters
float fixedWingReferenceAirspeed; // Reference tuning airspeed for the airplane - the speed for which PID gains are tuned
float fixedWingCoordinatedYawGain; // This is the gain of the yaw rate required to keep the yaw rate consistent with the turn rate for a coordinated turn.
- float fixedWingCoordinatedPitchGain; // This is the gain of the pitch rate to keep the pitch angle constant during coordinated turns.
- uint16_t fixedWingYawItermBankFreeze; // Freeze yaw Iterm when bank angle is more than this many degrees
-
+ float fixedWingCoordinatedPitchGain; // This is the gain of the pitch rate to keep the pitch angle constant during coordinated turns.
float navVelXyDTermLpfHz;
- uint8_t navVelXyDtermAttenuation; // VEL_XY dynamic Dterm scale: Dterm will be attenuatedby this value (in percent) when UAV is traveling with more than navVelXyDtermAttenuationStart percents of max velocity
- uint8_t navVelXyDtermAttenuationStart; // VEL_XY dynamic Dterm scale: Dterm attenuation will begin at this percent of max velocity
- uint8_t navVelXyDtermAttenuationEnd; // VEL_XY dynamic Dterm scale: Dterm will be fully attenuated at this percent of max velocity
- uint8_t iterm_relax_cutoff; // This cutoff frequency specifies a low pass filter which predicts average response of the quad to setpoint
- uint8_t iterm_relax; // Enable iterm suppression during stick input
+ float fixedWingLevelTrim;
+ float fixedWingLevelTrimGain;
#ifdef USE_D_BOOST
float dBoostMin;
float dBoostMax;
float dBoostMaxAtAlleceleration;
- uint8_t dBoostGyroDeltaLpfHz;
#endif
#ifdef USE_ANTIGRAVITY
float antigravityGain;
float antigravityAccelerator;
- uint8_t antigravityCutoff;
#endif
- uint16_t navFwPosHdgPidsumLimit;
- uint8_t controlDerivativeLpfHz;
-
- float fixedWingLevelTrim;
- float fixedWingLevelTrimGain;
-
- uint8_t fwAltControlResponseFactor;
- bool fwAltControlUsePos;
#ifdef USE_SMITH_PREDICTOR
float smithPredictorStrength;
float smithPredictorDelay;
- uint16_t smithPredictorFilterHz;
#endif
+ uint32_t axisAccelerationLimitYaw; // Max rate of change of yaw angular rate setpoint (deg/s^2 = dps/s)
+ uint32_t axisAccelerationLimitRollPitch; // Max rate of change of roll/pitch angular rate setpoint (deg/s^2 = dps/s)
+ int16_t max_angle_inclination[ANGLE_INDEX_COUNT]; // Max possible inclination (roll and pitch axis separately
+ uint16_t pidItermLimitPercent;
+ uint16_t fixedWingYawItermBankFreeze; // Freeze yaw Iterm when bank angle is more than this many degrees
+ uint16_t navFwPosHdgPidsumLimit;
uint16_t fwItermLockTimeMaxMs;
+ uint16_t dterm_lpf_hz;
+
+#ifdef USE_SMITH_PREDICTOR
+ uint16_t smithPredictorFilterHz;
+#endif
+
+ uint8_t pidControllerType;
+ uint8_t dterm_lpf_type; // Dterm LPF type: PT1, BIQUAD
+ uint8_t yaw_lpf_hz;
+ uint8_t heading_hold_rate_limit; // Maximum rotation rate HEADING_HOLD mode can feed to yaw rate PID controller
+ uint8_t itermWindupPointPercent; // Experimental ITerm windup threshold, percent of motor saturation
+ uint8_t navVelXyDtermAttenuation; // VEL_XY dynamic Dterm scale: Dterm will be attenuated by this value (in percent) when UAV is traveling with more than navVelXyDtermAttenuationStart percents of max velocity
+ uint8_t navVelXyDtermAttenuationStart; // VEL_XY dynamic Dterm scale: Dterm attenuation will begin at this percent of max velocity
+ uint8_t navVelXyDtermAttenuationEnd; // VEL_XY dynamic Dterm scale: Dterm will be fully attenuated at this percent of max velocity
+ uint8_t iterm_relax_cutoff; // This cutoff frequency specifies a low pass filter which predicts average response of the quad to setpoint
+ uint8_t iterm_relax; // Enable iterm suppression during stick input
+ uint8_t controlDerivativeLpfHz;
+ uint8_t fwAltControlResponseFactor;
+ bool fwAltControlUsePos;
uint8_t fwItermLockRateLimit;
uint8_t fwItermLockEngageThreshold;
+#ifdef USE_D_BOOST
+ uint8_t dBoostGyroDeltaLpfHz;
+#endif
+
+#ifdef USE_ANTIGRAVITY
+ uint8_t antigravityCutoff;
+#endif
+
} pidProfile_t;
typedef struct pidAutotuneConfig_s {
diff --git a/src/main/io/displayport_msp_osd.c b/src/main/io/displayport_msp_osd.c
index 9dc99889bc1..2e70b8548f7 100644
--- a/src/main/io/displayport_msp_osd.c
+++ b/src/main/io/displayport_msp_osd.c
@@ -64,7 +64,6 @@
typedef enum { // defines are from hdzero code
SD_3016,
HD_5018,
- HD_3016, // Special HDZERO mode that just sends the centre 30x16 of the 50x18 canvas to the VRX
HD_6022, // added to support DJI wtfos 60x22 grid
HD_5320 // added to support Avatar and BetaflightHD
} resolutionType_e;
@@ -139,31 +138,6 @@ static int output(displayPort_t *displayPort, uint8_t cmd, uint8_t *subcmd, int
return sent;
}
-static uint8_t determineHDZeroOsdMode(void)
-{
- if (cmsInMenu) {
- return HD_5018;
- }
-
- // Check if all visible widgets are in the center 30x16 chars of the canvas.
- int activeLayout = osdGetActiveLayout(NULL);
- osd_items_e index = 0;
- do {
- index = osdIncElementIndex(index);
- uint16_t pos = osdLayoutsConfig()->item_pos[activeLayout][index];
- if (OSD_VISIBLE(pos)) {
- uint8_t elemPosX = OSD_X(pos);
- uint8_t elemPosY = OSD_Y(pos);
- if (!osdItemIsFixed(index) && (elemPosX < 10 || elemPosX > 39 || elemPosY == 0 || elemPosY == 17)) {
- return HD_5018;
- }
- }
- } while (index > 0);
-
- return HD_3016;
-}
-
-
uint8_t setAttrPage(uint8_t origAttr, uint8_t page)
{
return (origAttr & ~DISPLAYPORT_MSP_ATTR_FONTPAGE_MASK) | (page & DISPLAYPORT_MSP_ATTR_FONTPAGE_MASK);
@@ -181,10 +155,6 @@ uint8_t setAttrVersion(uint8_t origAttr, uint8_t version)
static int setDisplayMode(displayPort_t *displayPort)
{
- if (osdVideoSystem == VIDEO_SYSTEM_HDZERO) {
- currentOsdMode = determineHDZeroOsdMode(); // Can change between layouts
- }
-
uint8_t subcmd[] = { MSP_DP_OPTIONS, 0, currentOsdMode }; // Font selection, mode (SD/HD)
return output(displayPort, MSP_DISPLAYPORT, subcmd, sizeof(subcmd));
}
diff --git a/src/main/navigation/navigation.h b/src/main/navigation/navigation.h
index ddc7e23e76d..f4cb5120fc3 100644
--- a/src/main/navigation/navigation.h
+++ b/src/main/navigation/navigation.h
@@ -167,11 +167,11 @@ typedef struct geoZoneConfig_s
typedef struct geozone_config_s
{
uint32_t fenceDetectionDistance;
+ uint32_t copterFenceStopDistance;
uint16_t avoidAltitudeRange;
uint16_t safeAltitudeDistance;
bool nearestSafeHomeAsInclusivZone;
uint8_t safeHomeFenceAction;
- uint32_t copterFenceStopDistance;
uint8_t noWayHomeAction;
} geozone_config_t;
diff --git a/src/main/navigation/navigation_geozone.c b/src/main/navigation/navigation_geozone.c
index dfc7539859b..beb3051e0c5 100755
--- a/src/main/navigation/navigation_geozone.c
+++ b/src/main/navigation/navigation_geozone.c
@@ -124,7 +124,7 @@ static bool lockRTZ = false;
geozone_t geozone;
-PG_REGISTER_WITH_RESET_TEMPLATE(geozone_config_t, geoZoneConfig, PG_GEOZONE_CONFIG, 0);
+PG_REGISTER_WITH_RESET_TEMPLATE(geozone_config_t, geoZoneConfig, PG_GEOZONE_CONFIG, 1);
PG_RESET_TEMPLATE(geozone_config_t, geoZoneConfig,
.fenceDetectionDistance = SETTING_GEOZONE_DETECTION_DISTANCE_DEFAULT,
diff --git a/src/main/target/SDMODELH7V2/CMakeLists.txt b/src/main/target/SDMODELH7V2/CMakeLists.txt
new file mode 100644
index 00000000000..ac26094e636
--- /dev/null
+++ b/src/main/target/SDMODELH7V2/CMakeLists.txt
@@ -0,0 +1 @@
+target_stm32h743xi(SDMODELH7V2)
diff --git a/src/main/target/SDMODELH7V2/config.c b/src/main/target/SDMODELH7V2/config.c
new file mode 100644
index 00000000000..47f9da30bc5
--- /dev/null
+++ b/src/main/target/SDMODELH7V2/config.c
@@ -0,0 +1,38 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * INAV is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with INAV. If not, see .
+ */
+
+#include
+
+#include "platform.h"
+
+#include "fc/fc_msp_box.h"
+#include "fc/config.h"
+
+#include "io/piniobox.h"
+#include "drivers/serial.h"
+#include "io/serial.h"
+
+void targetConfiguration(void)
+{
+ pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
+ pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
+ pinioBoxConfigMutable()->permanentId[2] = findBoxByActiveBoxId(BOXARM)->permanentId;
+
+ // UART2 is connected to an onboard Bluetooth module (not user-accessible)
+ serialConfigMutable()->portConfigs[findSerialPortIndexByIdentifier(SERIAL_PORT_USART2)].functionMask = FUNCTION_MSP;
+ serialConfigMutable()->portConfigs[findSerialPortIndexByIdentifier(SERIAL_PORT_USART2)].msp_baudrateIndex = BAUD_115200;
+}
diff --git a/src/main/target/SDMODELH7V2/target.c b/src/main/target/SDMODELH7V2/target.c
new file mode 100644
index 00000000000..b3da3c075b1
--- /dev/null
+++ b/src/main/target/SDMODELH7V2/target.c
@@ -0,0 +1,45 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * INAV is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with INAV. If not, see .
+ */
+
+#include
+
+#include "platform.h"
+
+#include "drivers/bus.h"
+#include "drivers/io.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+#include "drivers/pinio.h"
+#include "drivers/sensor.h"
+
+timerHardware_t timerHardware[] = {
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // M1
+ DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 1), // M2
+
+ DEF_TIM(TIM2, CH2, PB3, TIM_USE_OUTPUT_AUTO, 0, 2), // M3
+ DEF_TIM(TIM2, CH3, PB10, TIM_USE_OUTPUT_AUTO, 0, 3), // M4
+
+ DEF_TIM(TIM5, CH1, PA0, TIM_USE_OUTPUT_AUTO, 0, 4), // M5
+ DEF_TIM(TIM5, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 5), // M6
+
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 6), // M7
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_OUTPUT_AUTO, 0, 7), // M8
+
+ DEF_TIM(TIM4, CH1, PD12, TIM_USE_LED, 0, 14), // LED strip
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/SDMODELH7V2/target.h b/src/main/target/SDMODELH7V2/target.h
new file mode 100644
index 00000000000..fcdc0898f18
--- /dev/null
+++ b/src/main/target/SDMODELH7V2/target.h
@@ -0,0 +1,183 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * INAV is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with INAV. If not, see .
+ */
+
+#pragma once
+
+#define TARGET_BOARD_IDENTIFIER "SDH7V2"
+#define USBD_PRODUCT_STRING "SDMODELH7V2"
+
+#define USE_TARGET_CONFIG
+
+// *************** LED / BEEPER **********************
+#define LED0 PC2
+
+#define BEEPER PC13
+#define BEEPER_INVERTED
+
+// *************** SPI1 - SD Card ********************
+#define USE_SPI
+#define USE_SPI_DEVICE_1
+#define SPI1_SCK_PIN PA5
+#define SPI1_MISO_PIN PA6
+#define SPI1_MOSI_PIN PA7
+
+// *************** SPI2 - OSD (MAX7456) **************
+#define USE_SPI_DEVICE_2
+#define SPI2_SCK_PIN PB13
+#define SPI2_MISO_PIN PB14
+#define SPI2_MOSI_PIN PB15
+
+#define USE_MAX7456
+#define MAX7456_SPI_BUS BUS_SPI2
+#define MAX7456_CS_PIN PB12
+
+// *************** SPI4 - IMU (MPU6000) **************
+#define USE_SPI_DEVICE_4
+#define SPI4_SCK_PIN PE2
+#define SPI4_MISO_PIN PE5
+#define SPI4_MOSI_PIN PE6
+
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW270_DEG
+#define MPU6000_SPI_BUS BUS_SPI4
+#define MPU6000_CS_PIN PE4
+#define MPU6000_EXTI_PIN PE1
+
+// *************** I2C1 - Baro / Mag *****************
+#define USE_I2C
+#define USE_I2C_DEVICE_1
+#define I2C1_SCL PB6
+#define I2C1_SDA PB7
+
+#define USE_BARO
+#define BARO_I2C_BUS BUS_I2C1
+#define USE_BARO_BMP280
+#define USE_BARO_MS5611
+
+#define USE_MAG
+#define MAG_I2C_BUS BUS_I2C1
+#define USE_MAG_IST8310
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS BUS_I2C1
+#define PITOT_I2C_BUS BUS_I2C1
+
+#define USE_RANGEFINDER
+#define RANGEFINDER_I2C_BUS BUS_I2C1
+
+// *************** SD Card (SPI) *********************
+#define USE_SDCARD
+#define USE_SDCARD_SPI
+#define SDCARD_SPI_BUS BUS_SPI1
+#define SDCARD_CS_PIN PA4
+#define SDCARD_DETECT_PIN PA3
+#define SDCARD_DETECT_INVERTED
+
+#define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT
+
+// *************** UART ******************************
+#define USE_VCP
+
+#define USE_UART1
+#define UART1_TX_PIN PA9
+#define UART1_RX_PIN PA10
+
+#define USE_UART2
+#define UART2_TX_PIN PD5
+#define UART2_RX_PIN PD6
+
+#define USE_UART3
+#define UART3_TX_PIN PD8
+#define UART3_RX_PIN PD9
+
+#define USE_UART4
+#define UART4_TX_PIN PD1
+#define UART4_RX_PIN PD0
+
+#define USE_UART6
+#define UART6_TX_PIN PC6
+#define UART6_RX_PIN PC7
+
+// UART7 RX-only (ESC sensor); TX pin not routed but AF mapping requires a valid pin
+#define USE_UART7
+#define UART7_TX_PIN PE8
+#define UART7_RX_PIN PE7
+
+#define SERIAL_PORT_COUNT 7 // VCP, UART1-4, UART6, UART7
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_CRSF
+#define SERIALRX_UART SERIAL_PORT_USART6
+
+// *************** ADC *******************************
+#define USE_ADC
+#define ADC_INSTANCE ADC1
+
+#define ADC_CHANNEL_1_PIN PC0
+#define ADC_CHANNEL_2_PIN PC1
+#define ADC_CHANNEL_3_PIN PC5
+
+#define VBAT_ADC_CHANNEL ADC_CHN_1
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_2
+#define RSSI_ADC_CHANNEL ADC_CHN_3
+
+#define VBAT_SCALE_DEFAULT 1090
+#define CURRENT_METER_SCALE 168
+
+// *************** USB detect ************************
+#define USB_DETECT_PIN PA8
+
+// *************** PINIO *****************************
+#define USE_PINIO
+#define USE_PINIOBOX
+
+// VTX power
+#define PINIO1_PIN PB11
+#define PINIO1_FLAGS PINIO_FLAGS_INVERTED
+
+// Cam pin
+#define PINIO2_PIN PE9
+#define PINIO2_FLAGS PINIO_FLAGS_INVERTED
+
+// Bluetooth (off on arm)
+#define PINIO3_PIN PE13
+#define PINIO3_FLAGS PINIO_FLAGS_INVERTED
+
+// *************** LED STRIP *************************
+#define USE_LED_STRIP
+#define WS2811_PIN PD12
+
+// *************** DEFAULT FEATURES ******************
+#define DEFAULT_FEATURES (FEATURE_OSD | FEATURE_TELEMETRY | FEATURE_CURRENT_METER | FEATURE_VBAT | FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX)
+
+// *************** SERIAL 4WAY ***********************
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+// *************** ESC SENSOR ************************
+#define USE_ESC_SENSOR
+
+// *************** DSHOT *****************************
+#define USE_DSHOT
+
+// *************** IO PORT MASK **********************
+#define TARGET_IO_PORTA 0xffff
+#define TARGET_IO_PORTB 0xffff
+#define TARGET_IO_PORTC 0xffff
+#define TARGET_IO_PORTD 0xffff
+#define TARGET_IO_PORTE 0xffff
+
+#define MAX_PWM_OUTPUT_PORTS 8