diff --git a/docs/Autotune - fixedwing.md b/docs/Autotune - fixedwing.md
index 3579e8e58d9..75391ccc8ed 100755
--- a/docs/Autotune - fixedwing.md
+++ b/docs/Autotune - fixedwing.md
@@ -6,9 +6,14 @@ Getting a good set of roll/pitch/yaw PIFF parameters for your aircraft is essent
## What AUTOTUNE does
-The AUTOTUNE mode is a flight mode that acts on top of normal ANGLE/HORIZON/ACRO mode and uses changes in flight attitude input by the pilot to learn the tuning values for roll, pitch and yaw tuning.
+AUTOTUNE is a modifier you enable on top of ANGLE, HORIZON or ACRO mode. It has no effect at all while flying in MANUAL mode - you can switch it on, but nothing will be tuned until you leave MANUAL. Where it is active, it uses the aircraft's response to your stick inputs to tune, for roll, pitch and yaw independently:
-In general pilot needs to activate AUTOTUNE mode while in the air and then fly the plane for a few minutes. While flying the pilot needs to input as many sharp attitude changes as possible so that the autotune code can learn how the aircraft responds and figure out PIFF gains.
+* **FeedForward (FF) gain** - in ANGLE, HORIZON or ACRO, whenever you give it enough hard stick input to learn from
+* **Rate** (`roll_rate`/`pitch_rate`/`yaw_rate`) - only while flying in ACRO or HORIZON, and only if `fw_autotune_rate_adjustment` is not set to `FIXED` (see below). Rate is never adjusted while flying in ANGLE mode.
+
+**AUTOTUNE never adjusts P or I gains, on any axis.** Tune P and I manually, either before or after using AUTOTUNE. This is a change from older INAV versions - if you're used to AUTOTUNE also setting P/I, that's no longer how it works.
+
+In general the pilot needs to activate AUTOTUNE while in the air and then fly the plane for a few minutes. While flying the pilot needs to input as many sharp attitude changes as possible, on all three axes, so that the autotune code can learn how the aircraft responds. An axis that isn't given hard stick input won't be tuned - it's easy to spend a whole flight on hard rolls and pitches and forget yaw entirely.
## Before flying with AUTOTUNE
@@ -16,9 +21,12 @@ Before taking off you need to set up a few parameters for your airplane:
parameter | explanation
--------- | -----------
-roll_rate | Maximum roll rate limit for your ariplane. Must not exceed physical limit of your plane
-pitch_rate | Maximum pitch rate limit for your ariplane. Must not exceed physical limit of your plane
-yaw_rate | Maximum yaw rate limit for your ariplane. Must not exceed physical limit of your plane
+roll_rate | Starting roll rate for your airplane. Unless you set `fw_autotune_rate_adjustment` to `FIXED` (see below), AUTOTUNE will change this value while you fly - it's a starting point, not a ceiling it respects
+pitch_rate | Starting pitch rate for your airplane. Same caveat as `roll_rate`
+yaw_rate | Starting yaw rate for your airplane. Same caveat as `roll_rate`
+fw_autotune_rate_adjustment | Controls whether/how AUTOTUNE changes `roll_rate`/`pitch_rate`/`yaw_rate` while tuning. `AUTO` (default) raises or lowers rates to match what the plane can achieve. `LIMIT` adjusts rates the same way but caps them at your starting value - it can still raise a rate it previously lowered, just never above where you started. `FIXED` leaves rates untouched - use this if you want AUTOTUNE to tune FeedForward only
+fw_autotune_min_stick | Minimum stick deflection [%] (default 50) before AUTOTUNE starts recording a maneuver as tuning data
+fw_autotune_max_rate_deflection | Target servo/mixer deflection [%] (default 90) AUTOTUNE aims for when calculating an achievable rate in `AUTO`/`LIMIT`
fw_p_level | Self-leveling strength. Bigger value means sharper response
fw_i_level | Self-leveling filtering. Usual value for airplanes is 1-5 Hz
max_angle_inclination_rll | Maximum roll angle in [0.1 deg] units
@@ -26,7 +34,7 @@ max_angle_inclination_pit | Maximum pitch angle in [0.1 deg] units
tpa_breakpoint | Cruise throttle (expected throttle that you would be flying most of the time)
tpa_rate | Amount of TPS curve to apply (usually should be in range 50-80 for most airplanes)
-For most hobby-sized airplanes roll/pitch rate limits should be in range 70-120 deg/s (7-12 for `roll_rate` and `pitch_rate` values). Small and agile flying wings can reach 180-200 deg/s.
+For most hobby-sized airplanes, starting roll/pitch rates in the range 70-120 deg/s (7-12 for `roll_rate` and `pitch_rate` values) work well. Small and agile flying wings can reach 180-200 deg/s. With the default `AUTO` setting, don't worry about getting this exactly right - AUTOTUNE will adjust it based on what your airplane actually does, down to a floor of 40 deg/s on roll/pitch and 10 deg/s on yaw, up to a ceiling of 720 deg/s.
Other things to check:
@@ -38,15 +46,16 @@ Other things to check:
Once you are all setup you can take off normally and switch to AUTOTUNE mode once you have gained altitude.
-When you engage AUTOTUNE mode a few things will happen:
+When you engage AUTOTUNE a few things will happen:
-* The autotune system will immediately setup some default values for your roll, pitch and yaw P and I gains
-* The autotune system will monitor requested roll and pitch rates (as determined by your transmitter stick movements). When the demanded roll or pitch rate exceeds certain threshold the autotune system will use the response of the aircraft to learn roll or pitch tuning values
-* Every 5 seconds the autotune system will store the snapshot of parameters you have. When you switch out of AUTOTUNE mode the last remembered parameters are restored
+* The autotune system will monitor requested roll, pitch and yaw rates (as determined by your transmitter stick movements, once deflection passes `fw_autotune_min_stick`). When the demanded rate on an axis exceeds that threshold the autotune system will use the response of the aircraft to learn FeedForward (and, depending on `fw_autotune_rate_adjustment` and mode, Rate) for that axis
+* Every 5 seconds the autotune system will store a snapshot of the current values. When you switch out of AUTOTUNE the last snapshot is restored - so if you exit within 5 seconds of enabling it, nothing changes
* You may find the plane is quite sluggish when you first enter AUTOTUNE. You will find that as the tune progresses this will get better. Make sure your flight area has plenty of room for slow large-radius turns.
* Don't land in AUTOTUNE mode - during landing airplane doesn't reach it full performance which may be read by autotune system as insufficient gains.
-The key to a successful autotune is to input rapid movements with the transmitter sticks. You should only do one of either roll or pitch at a time, and you should move the stick rapidly to the maximum deflection.
+The key to a successful autotune is to input rapid movements with the transmitter sticks. You should only exercise one of roll, pitch or yaw at a time, and you should move the stick rapidly to the maximum deflection. Don't neglect yaw - it's easy to spend the whole flight on rolls and pitches and leave yaw untuned.
+
+Fly in ACRO if you can - AUTOTUNE only learns Rate in modes where stick position maps directly to a commanded rotation rate (ACRO, and mostly HORIZON). In ANGLE mode the stick commands an angle instead, so AUTOTUNE will still tune FeedForward but will not touch Rate at all while you're in ANGLE. AUTOTUNE has no effect whatsoever in MANUAL mode - don't fly it there.
## Don't stop too early
diff --git a/docs/development/Converting Betaflight Targets.md b/docs/development/Converting Betaflight Targets.md
index 47f460f634d..e3f6392de43 100644
--- a/docs/development/Converting Betaflight Targets.md
+++ b/docs/development/Converting Betaflight Targets.md
@@ -5,6 +5,8 @@ Locate your Flight Controller target config.h in BetaFlight's repo, eg.: [config
It is also advisable to record the output of the timer command from BetaFlight, as it will provide useful information on `timer` usage that can be used to adjust the generated target later.
+The script generates `CAMERA_CONTROL_PIN` and PINIO `permanentId` wiring in `config.c` automatically when present in the source `config.h`.
+
```
# timer
timer B08 AF3
@@ -38,37 +40,27 @@ Using the BETAFPVF405 target mentioned above, to create the target now we need t
3. Create a target folder that will be used as the output folder for the `bf2inav.py` script, eg: `inav/src/main/targets/BETAFPVF405`
4. Navigate to the script folder in `inav/src/utils/`
5. `python3 ./bf2inav.py -i config.h -o ../main/target/BETAFPVF405/`
-6. Edit generated `target.c` and chose the correct timer definitions to match Betaflight's timer definitions.
+6. Edit generated `target.c` and chose the correct timer definitions to match Betaflight's timer definitions for motor, servo, and LED timers (not gyro).
```
timerHardware_t timerHardware[] = {
DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM8, CH2N, PB0, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM1, CH2N, PB0, TIM_USE_OUTPUT_AUTO, 0, 0),
-
DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM8, CH3N, PB1, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM1, CH3N, PB1, TIM_USE_OUTPUT_AUTO, 0, 0),
-
- //DEF_TIM(TIM5, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM9, CH2, PA3, TIM_USE_OUTPUT_AUTO, 0, 0),
DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 0),
-
- //DEF_TIM(TIM5, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM9, CH1, PA2, TIM_USE_OUTPUT_AUTO, 0, 0),
DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0),
DEF_TIM(TIM8, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM3, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 0),
-
DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0),
- //DEF_TIM(TIM3, CH1, PB4, TIM_USE_BEEPER, 0, 0),
DEF_TIM(TIM4, CH1, PB6, TIM_USE_LED, 0, 0),
};
```
-In this particular example, PA3, PA2 were changed to match Betaflight's mapping, and the timer PB4 was disabled, due to a timer conflict. Normal channels are prefered over N channels (CH1, over CH1N) or C channels in AT32 architectures.
-7. Now update yout build scripts by running `cmake` and build the target you just created. The target name can be checked in the generated `CMakeLists.txt`, but should match the Betaflight target name.
+Normal channels are prefered over N channels (CH1, over CH1N) or C channels in AT32 architectures.
+
+The typical value to be set in target.h for DEFAULT_FEATURES is:
+#define DEFAULT_FEATURES (FEATURE_OSD | FEATURE_TELEMETRY | FEATURE_CURRENT_METER | FEATURE_VBAT | FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX)
+
+7. Now update your build scripts by running `cmake` and build the target you just created. The target name can be checked in the generated `CMakeLists.txt`, but should match the Betaflight target name.
-For information on how to build INAV, check the documents in the [docs/development](https://github.com/iNavFlight/inav/tree/master/docs/development) folder.
\ No newline at end of file
+For information on how to build INAV, check the documents in the [docs/development](https://github.com/iNavFlight/inav/tree/master/docs/development) folder.
diff --git a/src/main/common/log.c b/src/main/common/log.c
index c2cc30a3a48..d1d9ef1e6b0 100644
--- a/src/main/common/log.c
+++ b/src/main/common/log.c
@@ -205,8 +205,12 @@ void _logBufferHex(logTopic_e topic, unsigned level, const void *buffer, size_t
{
// Print lines of up to maxBytes bytes. We need 5 characters per byte
// 0xAB[space|\n]
- const size_t charsPerByte = 5;
- const size_t maxBytes = 8;
+ // charsPerByte/maxBytes must be true compile-time constants (not `const`
+ // locals) so the buffer size below is a real constant expression, not a VLA.
+ enum {
+ charsPerByte = 5,
+ maxBytes = 8,
+ };
char buf[LOG_PREFIX_FORMATTED_SIZE + charsPerByte * maxBytes + 1]; // +1 for the null terminator
size_t bufPos = LOG_PREFIX_FORMATTED_SIZE;
const uint8_t *inputPtr = buffer;
diff --git a/src/main/drivers/accgyro/accgyro_icm42605.c b/src/main/drivers/accgyro/accgyro_icm42605.c
index d0c4e3490aa..b6cd029f9e2 100644
--- a/src/main/drivers/accgyro/accgyro_icm42605.c
+++ b/src/main/drivers/accgyro/accgyro_icm42605.c
@@ -103,7 +103,13 @@
#define ICM426XX_RA_ACCEL_CONFIG_STATIC3 0x04 // User Bank 2
#define ICM426XX_RA_ACCEL_CONFIG_STATIC4 0x05 // User Bank 2
-static bool is42688P = false;
+typedef enum {
+ ICM42605_VARIANT_42605 = 0,
+ ICM42605_VARIANT_42686P,
+ ICM42605_VARIANT_42688P,
+} icm42605Variant_e;
+
+static icm42605Variant_e icm42605DetectedVariant = ICM42605_VARIANT_42605;
typedef struct aafConfig_s {
uint16_t freq;
@@ -145,7 +151,7 @@ static aafConfig_t aafLUT42605[] = { // see table in section 5.3 https://invens
{ 0, 0, 0, 0 }
};
-static const aafConfig_t *getGyroAafConfig(bool is42688, const uint16_t desiredLpf);
+static const aafConfig_t *getGyroAafConfig(icm42605Variant_e variant, const uint16_t desiredLpf);
static void setUserBank(const busDevice_t *dev, const uint8_t user_bank)
{
@@ -214,11 +220,16 @@ static void icm42605AccAndGyroInit(gyroDev_t *gyro)
busWrite(dev, ICM42605_RA_PWR_MGMT0, ICM42605_PWR_MGMT0_TEMP_DISABLE_OFF | ICM42605_PWR_MGMT0_ACCEL_MODE_LN | ICM42605_PWR_MGMT0_GYRO_MODE_LN);
delay(15);
+ uint8_t fsSel = 0;
+ if (icm42605DetectedVariant == ICM42605_VARIANT_42686P) {
+ fsSel = 1; // ICM-42686-P normal range: 2000 deg/s, 16G.
+ }
+
/* ODR and dynamic range */
- busWrite(dev, ICM42605_RA_GYRO_CONFIG0, (0x00) << 5 | (config->gyroConfigValues[1] & 0x0F)); /* 2000 deg/s */
+ busWrite(dev, ICM42605_RA_GYRO_CONFIG0, fsSel << 5 | (config->gyroConfigValues[1] & 0x0F)); /* 2000 deg/s */
delay(15);
- busWrite(dev, ICM42605_RA_ACCEL_CONFIG0, (0x00) << 5 | (config->gyroConfigValues[1] & 0x0F)); /* 16 G deg/s */
+ busWrite(dev, ICM42605_RA_ACCEL_CONFIG0, fsSel << 5 | (config->gyroConfigValues[1] & 0x0F)); /* 16 G deg/s */
delay(15);
/* LPF bandwidth */
@@ -228,14 +239,14 @@ static void icm42605AccAndGyroInit(gyroDev_t *gyro)
if (gyro->lpf != GYRO_LPF_NONE) {
// Configure gyro Anti-Alias Filter (see section 5.3 "ANTI-ALIAS FILTER")
- const aafConfig_t *aafConfig = getGyroAafConfig(is42688P, gyro->lpf);
+ const aafConfig_t *aafConfig = getGyroAafConfig(icm42605DetectedVariant, gyro->lpf);
setUserBank(dev, ICM426XX_BANK_SELECT1);
busWrite(dev, ICM426XX_RA_GYRO_CONFIG_STATIC3, aafConfig->delt);
busWrite(dev, ICM426XX_RA_GYRO_CONFIG_STATIC4, aafConfig->deltSqr & 0xFF);
busWrite(dev, ICM426XX_RA_GYRO_CONFIG_STATIC5, (aafConfig->deltSqr >> 8) | (aafConfig->bitshift << 4));
- aafConfig = getGyroAafConfig(is42688P, 256); // This was hard coded on BF
+ aafConfig = getGyroAafConfig(icm42605DetectedVariant, 256); // This was hard coded on BF
setUserBank(dev, ICM426XX_BANK_SELECT2);
busWrite(dev, ICM426XX_RA_ACCEL_CONFIG_STATIC2, aafConfig->delt << 1);
busWrite(dev, ICM426XX_RA_ACCEL_CONFIG_STATIC3, aafConfig->deltSqr & 0xFF);
@@ -289,12 +300,15 @@ static bool icm42605DeviceDetect(busDevice_t * dev)
busRead(dev, MPU_RA_WHO_AM_I, &tmp);
switch (tmp) {
- /* ICM42605 and ICM42688P share the register structure*/
+ /* ICM42605, ICM42686P and ICM42688P share the register structure */
case ICM42605_WHO_AM_I_CONST:
- is42688P = false;
+ icm42605DetectedVariant = ICM42605_VARIANT_42605;
+ return true;
+ case ICM42686P_WHO_AM_I_CONST:
+ icm42605DetectedVariant = ICM42605_VARIANT_42686P;
return true;
case ICM42688P_WHO_AM_I_CONST:
- is42688P = true;
+ icm42605DetectedVariant = ICM42605_VARIANT_42688P;
return true;
default:
@@ -385,17 +399,30 @@ static uint16_t getAafFreq(const uint8_t gyroLpf)
}
}
-static const aafConfig_t *getGyroAafConfig(bool is42688, const uint16_t desiredLpf)
+static const char *icm42605VariantName(icm42605Variant_e variant)
+{
+ switch (variant) {
+ case ICM42605_VARIANT_42686P:
+ return "42686P";
+ case ICM42605_VARIANT_42688P:
+ return "42688P";
+ case ICM42605_VARIANT_42605:
+ default:
+ return "42605";
+ }
+}
+
+static const aafConfig_t *getGyroAafConfig(icm42605Variant_e variant, const uint16_t desiredLpf)
{
uint16_t desiredFreq = getAafFreq(desiredLpf);
const aafConfig_t *aafConfigs = NULL;
- if (is42688) {
+ if (variant == ICM42605_VARIANT_42686P || variant == ICM42605_VARIANT_42688P) {
aafConfigs = aafLUT42688;
} else {
aafConfigs = aafLUT42605;
}
int i;
- int8_t selectedFreq = aafConfigs[0].freq;
+ uint16_t selectedFreq = aafConfigs[0].freq;
const aafConfig_t * candidate = &aafConfigs[0];
// Choose closest supported LPF value
@@ -406,8 +433,8 @@ static const aafConfig_t *getGyroAafConfig(bool is42688, const uint16_t desiredL
}
}
- LOG_VERBOSE(GYRO, "ICM426%s AAF CONFIG { %d, %d } -> { %d }; delt: %d deltSqr: %d, shift: %d",
- (is42688P ? "88" : "05"),
+ LOG_VERBOSE(GYRO, "ICM%s AAF CONFIG { %d, %d } -> { %d }; delt: %d deltSqr: %d, shift: %d",
+ icm42605VariantName(variant),
desiredLpf, desiredFreq,
candidate->freq,
candidate->delt, candidate->deltSqr, candidate->bitshift);
diff --git a/src/main/drivers/accgyro/accgyro_lsm6dxx.c b/src/main/drivers/accgyro/accgyro_lsm6dxx.c
index 874f32eacb3..d00df7442d7 100644
--- a/src/main/drivers/accgyro/accgyro_lsm6dxx.c
+++ b/src/main/drivers/accgyro/accgyro_lsm6dxx.c
@@ -49,9 +49,19 @@ typedef struct __attribute__ ((__packed__)) lsm6DContextData_s {
#define LSM6DSO_CHIP_ID 0x6C
#define LSM6DSL_CHIP_ID 0x6A
#define LSM6DS3_CHIP_ID 0x69
+// "Gen V" chips - reorganized register map vs. the DSO/DSL/DS3 above, see lsm6dxxConfigGenV()
+#define LSM6DSV16X_CHIP_ID 0x70
+#define LSM6DSK320X_CHIP_ID 0x75
+// Single physical instance assumed: set once by lsm6dxxDetect() and read by lsm6dxxConfig()/
+// lsm6dxxIsGenV() afterward for the same instance - gyro detect always runs before config.
static uint8_t lsm6dID = 0x6C;
+static inline bool lsm6dxxIsGenV(void)
+{
+ return lsm6dID == LSM6DSV16X_CHIP_ID || lsm6dID == LSM6DSK320X_CHIP_ID;
+}
+
static void lsm6dxxWriteRegister(const busDevice_t *dev, lsm6dxxRegister_e registerID, uint8_t value, unsigned delayMs)
{
busWrite(dev, registerID, value);
@@ -85,8 +95,88 @@ static uint8_t getLsmDlpfBandwidth(gyroDev_t *gyro)
return 0;
}
+// LPF1 bandwidth options for the Gen V chips, at the fixed 7.68kHz LPF1+LPF2 table row this
+// driver uses. Values cross-checked against atbetaflight's accgyro_spi_lsm6dsv16x.c, which
+// uses this same mapping on real hardware.
+static uint8_t getLsmVDlpfBandwidth(gyroDev_t *gyro)
+{
+ switch (gyro->lpf) {
+ case GYRO_HARDWARE_LPF_NORMAL:
+ return LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_288HZ;
+ case GYRO_HARDWARE_LPF_OPTION_1:
+ return LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_157HZ;
+ case GYRO_HARDWARE_LPF_OPTION_2:
+ return LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_215HZ;
+ case GYRO_HARDWARE_LPF_EXPERIMENTAL:
+ return LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_455HZ;
+ }
+ return LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_288HZ;
+}
+
+// Configuration for LSM6DSV16X / LSM6DSK320X ("Gen V" chips - see lsm6dxxIsGenV()). These
+// reorganized CTRL1_XL/CTRL2_G to hold ODR + operating mode only (full scale moved to
+// CTRL6_C/CTRL8_XL) with a different ODR bit encoding, so they need their own config sequence
+// rather than sharing the legacy DSO/DSL/DS3 one below. Sequence and register values are
+// cross-checked against the LSM6DSV16X and LSM6DSK320X datasheets and atbetaflight's
+// accgyro_spi_lsm6dsv16x.c (hardware-validated there for LSM6DSV16X).
+static void lsm6dxxConfigGenV(gyroDev_t *gyro)
+{
+ busDevice_t * dev = gyro->busDev;
+
+ busSetSpeed(dev, BUS_SPEED_INITIALIZATION);
+
+ // Software reset, then wait for the device to clear the bit itself
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL3_C, LSM6DXX_MASK_CTRL3_C_RESET, 0);
+ uint8_t ctrl3 = LSM6DXX_MASK_CTRL3_C_RESET;
+ unsigned attemptsRemaining = 10;
+ while ((ctrl3 & LSM6DXX_MASK_CTRL3_C_RESET) && attemptsRemaining--) {
+ delay(1);
+ if (!busRead(dev, LSM6DXX_REG_CTRL3_C, &ctrl3)) {
+ break; // bus read failed - fall through to config once attempts are exhausted below
+ }
+ }
+
+ // Auto-increment addresses for burst reads; hold output registers stable across a burst read
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL3_C, LSM6DXX_VAL_CTRL3_C_IF_INC | LSM6DXX_VAL_CTRL3_C_BDU, 0);
+
+ // Selects the high-accuracy ODR table used by the CTRL1_XL/CTRL2_G writes below
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_HAODR_CFG, LSM6DXX_VAL_HAODR_CFG_MODE1, 0);
+
+ // Accelerometer: high-accuracy mode, 1kHz ODR, +-16g full scale
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL1_XL,
+ (LSM6DXX_VAL_CTRL1_XL_V_OPMODE_HIGH_ACCURACY << 4) | LSM6DXX_VAL_CTRL1_XL_V_ODR_1000HZ_HAODR1, 0);
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL8_XL, LSM6DXX_VAL_CTRL8_XL_V_FS_16G, 0);
+
+ // Gyroscope: high-accuracy mode, 8kHz ODR, +-2000dps full scale, LPF1 bandwidth per user setting
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL2_G,
+ (LSM6DXX_VAL_CTRL2_G_V_OPMODE_HIGH_ACCURACY << 4) | LSM6DXX_VAL_CTRL2_G_V_ODR_8000HZ_HAODR1, 0);
+ // LSM6DSK320X requires bit 3 of this register set to 1 ("must be set to 1 for correct operation of
+ // the device" per its datasheet) - it isn't part of the FS_G field there like it is on LSM6DSV16X
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL6_C,
+ (getLsmVDlpfBandwidth(gyro) << 4) | LSM6DXX_VAL_CTRL6_C_V_FS_G_2000DPS
+ | (lsm6dID == LSM6DSK320X_CHIP_ID ? LSM6DXX_VAL_CTRL6_C_V_DSK320X_RESERVED_BIT3 : 0), 0);
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL7_G, LSM6DXX_VAL_CTRL7_G_V_LPF1_EN, 0);
+
+ // Data-ready interrupt is a pulse (no read required to clear it), routed to pin 1
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_CTRL4_C, LSM6DXX_VAL_CTRL4_C_V_DRDY_PULSED, 0);
+ lsm6dxxWriteRegister(dev, LSM6DXX_REG_INT1_CTRL, LSM6DXX_VAL_INT1_CTRL, 0);
+
+ // Datasheet section 4.1 (Mechanical characteristics): 70mdps/LSB at +-2000dps full scale.
+ // This differs from the 1/16.4 constant the legacy DSO/DSL/DS3 path below uses - that
+ // existing value is left untouched since it's out of scope here and already shipped.
+ gyro->scale = 0.070f;
+ gyro->sampleRateIntervalUs = 125; // 8kHz gyro ODR configured above
+
+ busSetSpeed(dev, BUS_SPEED_FAST);
+}
+
static void lsm6dxxConfig(gyroDev_t *gyro)
-{
+{
+ if (lsm6dxxIsGenV()) {
+ lsm6dxxConfigGenV(gyro);
+ return;
+ }
+
busDevice_t * dev = gyro->busDev;
const gyroFilterAndRateConfig_t * config = mpuChooseGyroConfig(gyro->lpf, 1000000 / gyro->requestedSampleIntervalUs);
gyro->sampleRateIntervalUs = 1000000 / config->gyroRateHz;
@@ -153,7 +243,9 @@ static bool lsm6dxxDetect(busDevice_t * dev)
switch (tmp) {
case LSM6DSO_CHIP_ID:
- case LSM6DSL_CHIP_ID:
+ case LSM6DSL_CHIP_ID:
+ case LSM6DSV16X_CHIP_ID:
+ case LSM6DSK320X_CHIP_ID:
lsm6dID = tmp;
// Compatible chip detected
return true;
diff --git a/src/main/drivers/accgyro/accgyro_lsm6dxx.h b/src/main/drivers/accgyro/accgyro_lsm6dxx.h
index 6da333b065e..2e9169b8ed2 100644
--- a/src/main/drivers/accgyro/accgyro_lsm6dxx.h
+++ b/src/main/drivers/accgyro/accgyro_lsm6dxx.h
@@ -51,6 +51,7 @@ typedef enum {
LSM6DXX_REG_OUTY_H_A = 0x2B, // acc Y axis MSB
LSM6DXX_REG_OUTZ_L_A = 0x2C, // acc Z axis LSB
LSM6DXX_REG_OUTZ_H_A = 0x2D, // acc Z axis MSB
+ LSM6DXX_REG_HAODR_CFG = 0x62, // high-accuracy ODR mode select (LSM6DSV16X/LSM6DSK320X only)
} lsm6dxxRegister_e;
// LSM6DXX register configuration values
@@ -68,7 +69,7 @@ typedef enum {
LSM6DXX_VAL_CTRL1_XL_LPF2 = 0x01, // accelerometer output from LPF2
LSM6DXX_VAL_CTRL2_G_ODR6664 = 0x0A, // gyro 6664hz output data rate
LSM6DXX_VAL_CTRL2_G_2000DPS = 0x03, // gyro 2000dps scale
- // LSM6DXX_VAL_CTRL3_C_BDU = BIT(6), // (bit 6) output registers are not updated until MSB and LSB have been read (prevents MSB from being updated while burst reading LSB/MSB)
+ LSM6DXX_VAL_CTRL3_C_BDU = BIT(6), // (bit 6) output registers are not updated until MSB and LSB have been read (prevents MSB from being updated while burst reading LSB/MSB)
LSM6DXX_VAL_CTRL3_C_H_LACTIVE = 0, // (bit 5) interrupt pins active high
LSM6DXX_VAL_CTRL3_C_PP_OD = 0, // (bit 4) interrupt pins push/pull
LSM6DXX_VAL_CTRL3_C_SIM = 0, // (bit 3) SPI 4-wire interface mode
@@ -87,6 +88,30 @@ typedef enum {
LSM6DXX_VAL_CTRL7_G_HPM_G_260 = 0x02, // (bits 5:4) gyro HPF cutoff 260mHz
LSM6DXX_VAL_CTRL7_G_HPM_G_1040 = 0x03, // (bits 5:4) gyro HPF cutoff 1.04Hz
LSM6DXX_VAL_CTRL9_XL_I3C_DISABLE = BIT(1),// (bit 1) disable I3C interface
+
+ // LSM6DSV16X/LSM6DSK320X ("Gen V") register field values. This generation moved ODR/mode
+ // into CTRL1_XL/CTRL2_G bits[6:4]+[3:0] (full scale is no longer here - see CTRL6_C/CTRL8_XL
+ // below) and uses a completely different ODR encoding from the legacy LSM6DSO/DSL/DS3 chips
+ // above, so none of the _ODR*/_*DPS/_*G values above apply to these two chips.
+ LSM6DXX_VAL_CTRL1_XL_V_OPMODE_HIGH_ACCURACY = 0x01, // (bits 6:4) accel high-accuracy ODR mode
+ LSM6DXX_VAL_CTRL1_XL_V_ODR_1000HZ_HAODR1 = 0x09, // (bits 3:0) 1kHz accel ODR, HAODR mode 1
+ LSM6DXX_VAL_CTRL2_G_V_OPMODE_HIGH_ACCURACY = 0x01, // (bits 6:4) gyro high-accuracy ODR mode
+ LSM6DXX_VAL_CTRL2_G_V_ODR_8000HZ_HAODR1 = 0x0C, // (bits 3:0) 8kHz gyro ODR, HAODR mode 1 (0x0B is 4kHz - off by one row)
+ LSM6DXX_VAL_HAODR_CFG_MODE1 = 0x01, // (bits 1:0) selects the ODR tables above
+ LSM6DXX_VAL_CTRL6_C_V_FS_G_2000DPS = 0x04, // (bits 3:0) gyro full-scale +-2000dps
+ // (LSM6DSV16X: 4-bit FS_G field, 0100 = 2000dps; on
+ // LSM6DSK320X, bit 3 of this value is instead a separate
+ // reserved bit the datasheet says "must be set to 1 for
+ // correct operation" - FS_G there is bits[2:0] only, where
+ // 100 also happens to mean 2000dps - see LSM6DXX_VAL_CTRL6_C_V_DSK320X_RESERVED_BIT3)
+ LSM6DXX_VAL_CTRL6_C_V_DSK320X_RESERVED_BIT3 = BIT(3), // must be 1 on LSM6DSK320X only, see above
+ LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_288HZ = 0x00, // (bits 6:4) gyro LPF1 bandwidth ~288Hz
+ LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_215HZ = 0x01, // (bits 6:4) gyro LPF1 bandwidth ~215Hz
+ LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_157HZ = 0x02, // (bits 6:4) gyro LPF1 bandwidth ~157Hz
+ LSM6DXX_VAL_CTRL6_C_V_LPF1_BW_455HZ = 0x03, // (bits 6:4) gyro LPF1 bandwidth ~455Hz
+ LSM6DXX_VAL_CTRL7_G_V_LPF1_EN = BIT(0), // (bit 0) enable gyro LPF1
+ LSM6DXX_VAL_CTRL8_XL_V_FS_16G = 0x03, // (bits 1:0) accel full-scale +-16g
+ LSM6DXX_VAL_CTRL4_C_V_DRDY_PULSED = BIT(1), // (bit 1) data-ready interrupt is a pulse, not a level
} lsm6dxxConfigValues_e;
// LSM6DXX register configuration bit masks
diff --git a/src/main/drivers/accgyro/accgyro_mpu.h b/src/main/drivers/accgyro/accgyro_mpu.h
index 0f59d10d3e8..4adf54e6f38 100644
--- a/src/main/drivers/accgyro/accgyro_mpu.h
+++ b/src/main/drivers/accgyro/accgyro_mpu.h
@@ -29,6 +29,7 @@
#define ICM20608G_WHO_AM_I_CONST (0xAF)
#define ICM20689_WHO_AM_I_CONST (0x98)
#define ICM42605_WHO_AM_I_CONST (0x42)
+#define ICM42686P_WHO_AM_I_CONST (0x44)
#define ICM42688P_WHO_AM_I_CONST (0x47)
#define ICM45686_WHO_AM_I_CONST (0xE9)
diff --git a/src/main/drivers/flash_m25p16.c b/src/main/drivers/flash_m25p16.c
index c6d5543e448..fdcdaad4826 100644
--- a/src/main/drivers/flash_m25p16.c
+++ b/src/main/drivers/flash_m25p16.c
@@ -99,9 +99,10 @@ struct {
// Winbond W25Q128_DTR
// Datasheet: https://www.winbond.com/resource-files/w25q128jv%20dtr%20revb%2011042016.pdf
{0xEF7018, 256, 256},
- // Puya PY25Q128HA
+ // Puya PY25Q128
// Datasheet: https://www.puyasemi.com/cpzx3/info_271_itemid_87.html
- {0x856018, 256, 256},
+ {0x856018, 256, 256}, // PY25Q128H
+ {0x852018, 256, 256}, // PY25Q128HA
// Winbond W25Q256
// Datasheet: https://www.winbond.com/resource-files/w25q256jv%20spi%20revb%2009202016.pdf
{0xEF4019, 512, 256},
diff --git a/src/main/drivers/sdcard/sdcard_sdio.c b/src/main/drivers/sdcard/sdcard_sdio.c
index d7cd8ff3c54..f76377550a7 100644
--- a/src/main/drivers/sdcard/sdcard_sdio.c
+++ b/src/main/drivers/sdcard/sdcard_sdio.c
@@ -457,16 +457,15 @@ static sdcardOperationStatus_e sdcardSdio_writeBlock(uint32_t blockIndex, uint8_
sdcard.pendingOperation.callback = callback;
sdcard.pendingOperation.callbackData = callbackData;
sdcard.pendingOperation.chunkIndex = 1; // (for non-DMA transfers) we've sent chunk #0 already
- sdcard.state = SDCARD_STATE_SENDING_WRITE;
if (SD_WriteBlocks_DMA(blockIndex, (uint32_t*) buffer, 512, block_count) != SD_OK) {
/* Our write was rejected! Try a few times before giving up.
- * This handles transient DMA/bus issues without full card reset.
+ * This handles transient DMA/bus issues without a full card reset.
+ * Returning busy without blocking: the blackbox/asyncfatfs flush re-issues
+ * this operation on the next PID loop, which paces the retries for us.
*/
if (sdcard.operationRetries < SDCARD_MAX_OPERATION_RETRIES) {
sdcard.operationRetries++;
- // Brief delay before retry
- delay(1);
return SDCARD_OPERATION_BUSY;
}
@@ -481,6 +480,9 @@ static sdcardOperationStatus_e sdcardSdio_writeBlock(uint32_t blockIndex, uint8_
return SDCARD_OPERATION_FAILURE;
}
+ // DMA started successfully - only set state after confirming operation will proceed
+ sdcard.state = SDCARD_STATE_SENDING_WRITE;
+
// Success - reset retry counter
sdcard.operationRetries = 0;
return SDCARD_OPERATION_IN_PROGRESS;
@@ -562,11 +564,11 @@ static bool sdcardSdio_readBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_op
} else {
/* Read was rejected! Try a few times before giving up.
* This handles transient DMA/bus issues without full card reset.
+ * Returning busy without blocking: the asyncfatfs read re-issues
+ * this operation on the next PID loop, which paces the retries for us.
*/
if (sdcard.operationRetries < SDCARD_MAX_OPERATION_RETRIES) {
sdcard.operationRetries++;
- // Brief delay before retry
- delay(1);
return false;
}
diff --git a/src/main/drivers/serial_uart_hal.c b/src/main/drivers/serial_uart_hal.c
index f25b2cbafb6..659fbf3c0d3 100644
--- a/src/main/drivers/serial_uart_hal.c
+++ b/src/main/drivers/serial_uart_hal.c
@@ -61,6 +61,11 @@ static void usartConfigurePinInversion(uartPort_t *uartPort) {
#endif
}
+__attribute__((weak)) void uartConfigurePinSwap(uartPort_t *uartPort)
+{
+ UNUSED(uartPort);
+}
+
static void uartReconfigure(uartPort_t *uartPort)
{
/*RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
@@ -92,6 +97,7 @@ static void uartReconfigure(uartPort_t *uartPort)
usartConfigurePinInversion(uartPort);
+ uartConfigurePinSwap(uartPort);
if (uartPort->port.options & SERIAL_BIDIR)
{
diff --git a/src/main/drivers/serial_uart_stm32h7xx.c b/src/main/drivers/serial_uart_stm32h7xx.c
index 9debfe66c72..f606638651d 100644
--- a/src/main/drivers/serial_uart_stm32h7xx.c
+++ b/src/main/drivers/serial_uart_stm32h7xx.c
@@ -43,6 +43,7 @@ typedef struct uartDevice_s {
uint8_t af_rx;
uint8_t af_tx;
uint8_t irq;
+ bool pinSwap;
} uartDevice_t;
#define UART_PIN_AF_HELPER(uart, pin) CONCAT4(UART_PIN_AF_UART, uart, _, pin)
@@ -64,6 +65,9 @@ static uartDevice_t uart1 =
.af_tx = UART_PIN_AF_HELPER(1, UART1_TX_PIN),
.rcc = RCC_APB2(USART1),
.irq = USART1_IRQn,
+#ifdef USE_UART1_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -77,6 +81,9 @@ static uartDevice_t uart2 =
.af_tx = GPIO_AF7_USART2,
.rcc = RCC_APB1L(USART2),
.irq = USART2_IRQn,
+#ifdef USE_UART2_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -90,6 +97,9 @@ static uartDevice_t uart3 =
.af_tx = GPIO_AF7_USART3,
.rcc = RCC_APB1L(USART3),
.irq = USART3_IRQn,
+#ifdef USE_UART3_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -114,6 +124,9 @@ static uartDevice_t uart4 =
.af_tx = UART_PIN_AF_HELPER(4, UART4_TX_PIN),
.rcc = RCC_APB1L(UART4),
.irq = UART4_IRQn,
+#ifdef USE_UART4_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -134,6 +147,9 @@ static uartDevice_t uart5 =
.af_tx = UART_PIN_AF_HELPER(5, UART5_TX_PIN),
.rcc = RCC_APB1L(UART5),
.irq = UART5_IRQn,
+#ifdef USE_UART5_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -147,6 +163,9 @@ static uartDevice_t uart6 =
.af_tx = GPIO_AF7_USART6,
.rcc = RCC_APB2(USART6),
.irq = USART6_IRQn,
+#ifdef USE_UART6_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -173,6 +192,9 @@ static uartDevice_t uart7 =
#endif
.rcc = RCC_APB1L(UART7),
.irq = UART7_IRQn,
+#ifdef USE_UART7_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -186,6 +208,9 @@ static uartDevice_t uart8 =
.af_tx = GPIO_AF8_UART8,
.rcc = RCC_APB1L(UART8),
.irq = UART8_IRQn,
+#ifdef USE_UART8_PIN_SWAP
+ .pinSwap = true,
+#endif
};
#endif
@@ -234,6 +259,30 @@ static uartDevice_t* uartHardwareMap[] = {
#endif
};
+static uartDevice_t *uartFindDevice(uartPort_t *uartPort)
+{
+ for (unsigned i = 0; i < UARTDEV_MAX; i++) {
+ uartDevice_t *pDevice = uartHardwareMap[i];
+ if (pDevice && pDevice->dev == uartPort->USARTx) {
+ return pDevice;
+ }
+ }
+ return NULL;
+}
+
+void uartConfigurePinSwap(uartPort_t *uartPort)
+{
+ uartDevice_t *uartDevice = uartFindDevice(uartPort);
+ if (!uartDevice) {
+ return;
+ }
+
+ if (uartDevice->pinSwap) {
+ uartPort->Handle.AdvancedInit.AdvFeatureInit |= UART_ADVFEATURE_SWAP_INIT;
+ uartPort->Handle.AdvancedInit.Swap = UART_ADVFEATURE_SWAP_ENABLE;
+ }
+}
+
void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins)
{
uartDevice_t *uart = uartHardwareMap[device];
diff --git a/src/main/io/gps.c b/src/main/io/gps.c
index 4e2bda3157d..41901e1e209 100755
--- a/src/main/io/gps.c
+++ b/src/main/io/gps.c
@@ -547,6 +547,13 @@ bool gpsUpdate(void)
}
#endif
+ // Driver-based providers (MSP, FAKE) never open a serial port; gpsPort stays NULL.
+ // If gps_provider is changed via CLI to a serial-based provider without rebooting,
+ // the serial handler would dereference NULL on the next tick and hard-fault.
+ if (!gpsProviders[gpsState.gpsConfig->provider].isDriverBased && !gpsState.gpsPort) {
+ return false;
+ }
+
switch (gpsState.state) {
default:
case GPS_INITIALIZING:
@@ -597,6 +604,10 @@ bool gpsUpdate(void)
void gpsEnablePassthrough(serialPort_t *gpsPassthroughPort)
{
+ if (!gpsState.gpsPort) {
+ return;
+ }
+
waitForSerialPortToFinishTransmitting(gpsState.gpsPort);
waitForSerialPortToFinishTransmitting(gpsPassthroughPort);
diff --git a/src/main/io/gps_ublox.c b/src/main/io/gps_ublox.c
index 4fd0c332a21..95feedabb6c 100755
--- a/src/main/io/gps_ublox.c
+++ b/src/main/io/gps_ublox.c
@@ -635,7 +635,7 @@ static bool gpsParseFrameUBLOX(void)
gpsSolDRV.time.hours = _buffer.timeutc.hour;
gpsSolDRV.time.minutes = _buffer.timeutc.min;
gpsSolDRV.time.seconds = _buffer.timeutc.sec;
- gpsSolDRV.time.millis = _buffer.timeutc.nano / (1000*1000);
+ gpsSolDRV.time.millis = (uint16_t)(MAX(0, _buffer.timeutc.nano) / (1000*1000));
gpsSolDRV.flags.validTime = true;
} else {
@@ -674,7 +674,7 @@ static bool gpsParseFrameUBLOX(void)
gpsSolDRV.time.hours = _buffer.pvt.hour;
gpsSolDRV.time.minutes = _buffer.pvt.min;
gpsSolDRV.time.seconds = _buffer.pvt.sec;
- gpsSolDRV.time.millis = _buffer.pvt.nano / (1000*1000);
+ gpsSolDRV.time.millis = (uint16_t)(MAX(0, _buffer.pvt.nano) / (1000*1000));
gpsSolDRV.flags.validTime = true;
} else {
diff --git a/src/main/io/osd.c b/src/main/io/osd.c
index 0bdb9e3375f..aa0a01ce71b 100644
--- a/src/main/io/osd.c
+++ b/src/main/io/osd.c
@@ -841,7 +841,7 @@ static void osdFormatCoordinate(char *buff, char sym, int32_t val)
int integerDigits = tfp_sprintf(buff + 1, (integerPart == 0 && val < 0) ? "-%d" : "%d", (int)integerPart);
// We can show up to 7 digits in decimalPart.
int32_t decimalPart = abs(val % (int)GPS_DEGREES_DIVIDER);
- STATIC_ASSERT(GPS_DEGREES_DIVIDER == 1e7, adjust_max_decimal_digits);
+ STATIC_ASSERT(GPS_DEGREES_DIVIDER == 10000000L, adjust_max_decimal_digits);
int decimalDigits;
bool djiCompat = false; // Assume DJICOMPAT mode is no enabled
diff --git a/src/main/target/AETH743Basic/target.c b/src/main/target/AETH743Basic/target.c
index 4a18ed15694..75265190bdb 100644
--- a/src/main/target/AETH743Basic/target.c
+++ b/src/main/target/AETH743Basic/target.c
@@ -26,9 +26,15 @@
#include "drivers/pinio.h"
#include "drivers/sensor.h"
+// V1:two ICM42688
BUSDEV_REGISTER_SPI_TAG(busdev_icm42688_0, DEVHW_ICM42605, ICM42688_0_SPI_BUS, ICM42688_0_CS_PIN, NONE, 0, DEVFLAGS_NONE, IMU_ICM42688_0_ALIGN);
BUSDEV_REGISTER_SPI_TAG(busdev_icm42688_1, DEVHW_ICM42605, ICM42688_1_SPI_BUS, ICM42688_1_CS_PIN, NONE, 2, DEVFLAGS_NONE, IMU_ICM42688_1_ALIGN);
+// V2:BMI088 + BMI270
+BUSDEV_REGISTER_SPI_TAG(busdev_bmi088_gyro, DEVHW_BMI088_GYRO, BMI088_SPI_BUS, BMI088_GYRO_CS_PIN, BMI088_GYRO_EXTI_PIN, 0, DEVFLAGS_NONE, IMU_BMI088_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_bmi088_acc, DEVHW_BMI088_ACC, BMI088_SPI_BUS, BMI088_ACC_CS_PIN, BMI088_ACC_EXTI_PIN, 0, DEVFLAGS_NONE, IMU_BMI088_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_bmi270, DEVHW_BMI270, BMI270_SPI_BUS, BMI270_CS_PIN, NONE, 2, DEVFLAGS_NONE, IMU_BMI270_ALIGN);
+
timerHardware_t timerHardware[] = {
DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S1
DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 1), // S2
diff --git a/src/main/target/AETH743Basic/target.h b/src/main/target/AETH743Basic/target.h
index 642a1f82be5..469b44deda5 100644
--- a/src/main/target/AETH743Basic/target.h
+++ b/src/main/target/AETH743Basic/target.h
@@ -36,26 +36,41 @@
#define USE_TARGET_IMU_HARDWARE_DESCRIPTORS
#define USE_SPI
#define USE_IMU_ICM42605
+#define USE_IMU_BMI088
+#define USE_IMU_BMI270
-// *************** SPI1 IMU0 ICM42688 ****************
+// *************** SPI1 IMU0 ICM42688 or BMI088 ****************
#define USE_SPI_DEVICE_1
#define SPI1_SCK_PIN PA5
#define SPI1_MISO_PIN PA6
#define SPI1_MOSI_PIN PD7
+#define SPI1_NSS_PIN PC15
#define IMU_ICM42688_0_ALIGN CW0_DEG_FLIP
#define ICM42688_0_SPI_BUS BUS_SPI1
-#define ICM42688_0_CS_PIN PC15
+#define ICM42688_0_CS_PIN SPI1_NSS_PIN
-// *************** SPI4 IMU1 ICM42688 **************
+#define IMU_BMI088_ALIGN CW270_DEG_FLIP
+#define BMI088_SPI_BUS BUS_SPI1
+#define BMI088_GYRO_CS_PIN PE11
+#define BMI088_GYRO_EXTI_PIN PE2
+#define BMI088_ACC_CS_PIN SPI1_NSS_PIN
+#define BMI088_ACC_EXTI_PIN PD4
+
+// *************** SPI4 IMU1 ICM42688 or BMI270 **************
#define USE_SPI_DEVICE_4
#define SPI4_SCK_PIN PE12
#define SPI4_MISO_PIN PE13
#define SPI4_MOSI_PIN PE14
+#define SPI4_NSS_PIN PC13
#define IMU_ICM42688_1_ALIGN CW90_DEG_FLIP
#define ICM42688_1_SPI_BUS BUS_SPI4
-#define ICM42688_1_CS_PIN PC13
+#define ICM42688_1_CS_PIN SPI4_NSS_PIN
+
+#define IMU_BMI270_ALIGN CW270_DEG_FLIP
+#define BMI270_SPI_BUS BUS_SPI4
+#define BMI270_CS_PIN SPI4_NSS_PIN
// *************** SPI2 OSD ***********************
#define USE_SPI_DEVICE_2
@@ -80,6 +95,7 @@
#define USE_BARO
#define BARO_I2C_BUS BUS_I2C2
#define USE_BARO_SPL06
+#define USE_BARO_DPS310
#define USE_MAG
#define MAG_I2C_BUS BUS_I2C1
diff --git a/src/main/target/AXISFLYINGH743PRO/CMakeLists.txt b/src/main/target/AXISFLYINGH743PRO/CMakeLists.txt
new file mode 100644
index 00000000000..4bbff13df02
--- /dev/null
+++ b/src/main/target/AXISFLYINGH743PRO/CMakeLists.txt
@@ -0,0 +1 @@
+target_stm32h743xi(AXISFLYINGH743PRO)
diff --git a/src/main/target/AXISFLYINGH743PRO/config.c b/src/main/target/AXISFLYINGH743PRO/config.c
new file mode 100644
index 00000000000..dec78c3b8f6
--- /dev/null
+++ b/src/main/target/AXISFLYINGH743PRO/config.c
@@ -0,0 +1,34 @@
+/*
+ * 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 "io/piniobox.h"
+#include "io/serial.h"
+
+void targetConfiguration(void)
+{
+ pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
+ pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
+
+ // UART5 is RX-only and hard-wired to ESC telemetry
+ serialConfigMutable()->portConfigs[findSerialPortIndexByIdentifier(SERIAL_PORT_USART5)].functionMask = FUNCTION_ESCSERIAL;
+}
diff --git a/src/main/target/AXISFLYINGH743PRO/target.c b/src/main/target/AXISFLYINGH743PRO/target.c
new file mode 100644
index 00000000000..303d2f16f93
--- /dev/null
+++ b/src/main/target/AXISFLYINGH743PRO/target.c
@@ -0,0 +1,59 @@
+/*
+ * 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
+#include "drivers/io.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+#include "drivers/bus.h"
+#include "drivers/sensor.h"
+
+#include "drivers/pwm_output.h"
+#include "common/maths.h"
+#include "fc/config.h"
+
+// Gyro 1 - ICM42688P on SPI1, tag 0
+BUSDEV_REGISTER_SPI_TAG(busdev_gyro1_icm42605, DEVHW_ICM42605, GYRO_1_SPI_BUS, GYRO_1_CS_PIN, GYRO_1_EXTI_PIN, 0, DEVFLAGS_NONE, GYRO_1_ALIGN);
+
+// Gyro 2 - ICM42688P on SPI4, tag 1
+BUSDEV_REGISTER_SPI_TAG(busdev_gyro2_icm42605, DEVHW_ICM42605, GYRO_2_SPI_BUS, GYRO_2_CS_PIN, GYRO_2_EXTI_PIN, 1, DEVFLAGS_NONE, GYRO_2_ALIGN);
+
+timerHardware_t timerHardware[] = {
+ // Motors - DMA1 Stream0-7 (dmaopt 0-7), kept unique per channel so a
+ // fallback to per-channel DMA (USE_DSHOT_DMAR disabled) still works.
+ DEF_TIM(TIM8, CH1, PC6, TIM_USE_OUTPUT_AUTO, 0, 0), // M1
+ DEF_TIM(TIM8, CH2, PC7, TIM_USE_OUTPUT_AUTO, 0, 1), // M2
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 2), // M3
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_OUTPUT_AUTO, 0, 3), // M4
+ DEF_TIM(TIM2, CH1, PA0, TIM_USE_OUTPUT_AUTO, 0, 4), // M5
+ DEF_TIM(TIM2, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 5), // M6
+ DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 6), // M7
+ DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 7), // M8
+
+ // dmaopt 8 (DMA2 Stream0) is reserved for ADC
+ DEF_TIM(TIM4, CH1, PD12, TIM_USE_OUTPUT_AUTO, 0, 9), // S1
+ DEF_TIM(TIM4, CH2, PD13, TIM_USE_OUTPUT_AUTO, 0, 10), // S2
+ DEF_TIM(TIM4, CH3, PD14, TIM_USE_OUTPUT_AUTO, 0, 11), // S3
+ DEF_TIM(TIM4, CH4, PD15, TIM_USE_OUTPUT_AUTO, 0, 12), // S4 - needs USE_DSHOT_DMAR, see target.h
+
+ // LED strip - DMA2 Stream5 (dmaopt 13)
+ DEF_TIM(TIM15, CH1, PE5, TIM_USE_LED, 0, 13), // LED_STRIP_PIN
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/AXISFLYINGH743PRO/target.h b/src/main/target/AXISFLYINGH743PRO/target.h
new file mode 100644
index 00000000000..2b3e9a47422
--- /dev/null
+++ b/src/main/target/AXISFLYINGH743PRO/target.h
@@ -0,0 +1,186 @@
+/*
+ * 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 "AXHP"
+#define USBD_PRODUCT_STRING "AXISFLYINGH743PRO"
+
+#define USE_TARGET_CONFIG
+
+// *************** LED, BEEPER ***************
+#define LED0 PD3
+#define BEEPER PC13
+#define BEEPER_INVERTED
+
+// *************** Gyro / Acc - dual ICM42688P (registered as DEVHW_ICM42605,
+// whose WHO_AM_I switch also matches ICM42688P) ***************
+#define USE_DUAL_GYRO
+#define USE_TARGET_IMU_HARDWARE_DESCRIPTORS // Don't use common busdev descriptors for IMU
+#define USE_IMU_ICM42605
+
+// SPI1: Gyro 1
+#define USE_SPI
+#define USE_SPI_DEVICE_1
+#define SPI1_SCK_PIN PA5
+#define SPI1_MISO_PIN PA6
+#define SPI1_MOSI_PIN PA7
+
+#define GYRO_1_SPI_BUS BUS_SPI1
+#define GYRO_1_CS_PIN PC4
+#define GYRO_1_EXTI_PIN PA4
+#define GYRO_1_ALIGN CW0_DEG
+
+// SPI4: Gyro 2
+#define USE_SPI_DEVICE_4
+#define SPI4_SCK_PIN PE12
+#define SPI4_MISO_PIN PE13
+#define SPI4_MOSI_PIN PE14
+
+#define GYRO_2_SPI_BUS BUS_SPI4
+#define GYRO_2_CS_PIN PE15
+#define GYRO_2_EXTI_PIN PE11
+#define GYRO_2_ALIGN CW90_DEG
+
+// Motor/servo DMA note: SERVO4_PIN (PD15/TIM4_CH4) has no per-channel DMAMUX
+// request on this MCU; USE_DSHOT_DMAR repoints TIM4_CH4 at the TIM4_UP burst
+// request so the channel keeps a valid DMA path. This makes all DSHOT-capable
+// outputs use one shared burst DMA stream per timer
+#define USE_DSHOT_DMAR
+
+// *************** SPI2: OSD *****************
+#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
+
+// *************** SPI3: Flash ***************
+#define USE_SPI_DEVICE_3
+#define SPI3_SCK_PIN PB3
+#define SPI3_MISO_PIN PB4
+#define SPI3_MOSI_PIN PB5
+
+#define USE_FLASHFS
+#define USE_FLASH_M25P16
+#define M25P16_SPI_BUS BUS_SPI3
+#define M25P16_CS_PIN PD7
+#define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT
+
+// *************** I2C1: Mag (external) *********************
+#define USE_I2C
+#define USE_I2C_DEVICE_1
+#define I2C1_SCL PB8
+#define I2C1_SDA PB9
+
+#define USE_MAG
+#define MAG_I2C_BUS BUS_I2C1
+#define USE_MAG_ALL
+
+// *************** I2C2: Baro *********************
+#define USE_I2C_DEVICE_2
+#define I2C2_SCL PB10
+#define I2C2_SDA PB11
+
+#define USE_BARO
+#define BARO_I2C_BUS BUS_I2C2
+#define USE_BARO_DPS310
+
+// *************** Serial ports *****************************
+#define USE_VCP
+
+#define USE_UART1
+#define UART1_TX_PIN PB6
+#define UART1_RX_PIN PB7
+
+#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 PC10
+#define UART4_RX_PIN PC11
+
+// RX only - hard-wired to ESC telemetry, see config.c
+#define USE_UART5
+#define UART5_TX_PIN PC12 // Unconnected placeholder: H7 UART5 driver's .af_tx lacks an #ifdef UART5_TX_PIN guard
+#define UART5_RX_PIN PD2
+
+#define USE_UART7
+#define UART7_TX_PIN PE8
+#define UART7_RX_PIN PE7
+
+#define USE_UART8
+#define UART8_TX_PIN PE1
+#define UART8_RX_PIN PE0
+
+#define SERIAL_PORT_COUNT 8
+
+#define DEFAULT_FEATURES (FEATURE_OSD | FEATURE_TELEMETRY | FEATURE_CURRENT_METER | FEATURE_VBAT | FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX)
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_CRSF
+#define SERIALRX_UART SERIAL_PORT_USART2
+
+#define GPS_UART SERIAL_PORT_USART8
+
+// Default MSP DisplayPort (HD OSD) port
+#define MSP_UART SERIAL_PORT_USART1
+
+#define USE_ESC_SENSOR
+
+// *************** ADC *****************************
+#define USE_ADC
+#define ADC_INSTANCE ADC1
+#define ADC_CHANNEL_1_PIN PC0
+#define ADC_CHANNEL_2_PIN PC1
+#define VBAT_ADC_CHANNEL ADC_CHN_1
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_2
+
+#define DEFAULT_VOLTAGE_METER_SOURCE VOLTAGE_METER_ADC
+#define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_ADC
+
+// *************** LED2812 ************************
+#define USE_LED_STRIP
+#define WS2811_PIN PE5
+
+// *************** PINIO ***************************
+#define USE_PINIO
+#define USE_PINIOBOX
+#define PINIO1_PIN PE2 // VTX PWR box
+#define PINIO2_PIN PA8 // CAM 1,2 box
+
+// *************** Blackbox ************************
+#define DEFAULT_BLACKBOX_DEVICE BLACKBOX_DEVICE_FLASH
+
+// *************** OTHERS *************************
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+#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 USE_DSHOT
+#define MAX_PWM_OUTPUT_PORTS 12
diff --git a/src/main/target/COREWINGF405WINGV2/target.h b/src/main/target/COREWINGF405WINGV2/target.h
index 2176728d84d..e2c4f1a8ee9 100644
--- a/src/main/target/COREWINGF405WINGV2/target.h
+++ b/src/main/target/COREWINGF405WINGV2/target.h
@@ -102,6 +102,10 @@
#define ICM42605_CS_PIN PA4
#define ICM42605_SPI_BUS BUS_SPI1
+#define USE_IMU_BMI270
+#define IMU_BMI270_ALIGN CW270_DEG
+#define BMI270_CS_PIN PA4
+#define BMI270_SPI_BUS BUS_SPI1
// Baro
#define USE_BARO
diff --git a/src/main/target/DAKEFPVF405/config.c b/src/main/target/DAKEFPVF405/config.c
index 73f636cf8c0..906089d3b38 100644
--- a/src/main/target/DAKEFPVF405/config.c
+++ b/src/main/target/DAKEFPVF405/config.c
@@ -28,4 +28,5 @@
void targetConfiguration(void)
{
pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
+ pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
}
diff --git a/src/main/target/DAKEFPVF405/target.h b/src/main/target/DAKEFPVF405/target.h
index 9b1aef0721a..30834b7682b 100644
--- a/src/main/target/DAKEFPVF405/target.h
+++ b/src/main/target/DAKEFPVF405/target.h
@@ -72,7 +72,15 @@
#define BMI270_SPI_BUS BUS_SPI1
#define BMI270_CS_PIN PA4
-//Baro
+#define USE_IMU_LSM6DXX
+#define IMU_LSM6DXX_ALIGN CW90_DEG
+#define LSM6DXX_SPI_BUS BUS_SPI1
+#define LSM6DXX_CS_PIN PA4
+
+// ICM42688P driver handled by USE_IMU_ICM42605 above (shares WHO_AM_I detection)
+// LSM6DSV16X / LSM6DSK320X driver handled by USE_IMU_LSM6DXX above (shares WHO_AM_I detection)
+
+//Baro
#define USE_BARO
#define USE_BARO_BMP280
#define BMP280_SPI_BUS BUS_SPI2
@@ -95,6 +103,11 @@
#define MAX7456_SPI_BUS BUS_SPI2
#define MAX7456_CS_PIN PB12
+// CAMERA_CONTROL
+// PA8 has no consuming driver prior to 10.0. From 10.0 forward this will be set up as a
+// PWM-capable PINIO pin instead -- see the October 2026 migration project.
+#define CAMERA_CONTROL_PIN PA8
+
// Serial ports
#define USE_VCP
@@ -170,4 +183,6 @@
#define USE_PINIO
#define USE_PINIOBOX
#define PINIO1_PIN PC5
-#define PINIO1_FLAGS PINIO_FLAGS_INVERTED
\ No newline at end of file
+#define PINIO1_FLAGS PINIO_FLAGS_INVERTED
+#define PINIO2_PIN PB4
+#define PINIO2_FLAGS PINIO_FLAGS_INVERTED
\ No newline at end of file
diff --git a/src/main/target/DAKEFPVH743PRO/target.c b/src/main/target/DAKEFPVH743PRO/target.c
index ad80082917b..f5455be7016 100644
--- a/src/main/target/DAKEFPVH743PRO/target.c
+++ b/src/main/target/DAKEFPVH743PRO/target.c
@@ -55,7 +55,7 @@ timerHardware_t timerHardware[] = {
DEF_TIM(TIM4, CH4, PD15, TIM_USE_OUTPUT_AUTO, 0, 0), // S4
DEF_TIM(TIM15, CH1, PE5, TIM_USE_ANY, 0, 0), // CAMERA_CONTROL_PIN
DEF_TIM(TIM15, CH2, PE6, TIM_USE_ANY, 0, 0), // GYRO_1_CLKIN_PIN
- DEF_TIM(TIM3, CH3, PB0, TIM_USE_LED, 0, 8), // LED_2812
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_LED, 0, 9), // LED_2812 - moved off DMA2 Stream0 to avoid ADC1 DMA conflict
};
const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/DAKEFPVH743_SLIM/CMakeLists.txt b/src/main/target/DAKEFPVH743_SLIM/CMakeLists.txt
new file mode 100644
index 00000000000..93c2d867499
--- /dev/null
+++ b/src/main/target/DAKEFPVH743_SLIM/CMakeLists.txt
@@ -0,0 +1 @@
+target_stm32h743xi(DAKEFPVH743_SLIM)
diff --git a/src/main/target/DAKEFPVH743_SLIM/config.c b/src/main/target/DAKEFPVH743_SLIM/config.c
new file mode 100644
index 00000000000..c8c3c081a95
--- /dev/null
+++ b/src/main/target/DAKEFPVH743_SLIM/config.c
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+#include
+
+#include "io/serial.h"
+#include "rx/rx.h"
+
+#include "io/piniobox.h"
+
+void targetConfiguration(void)
+{
+ pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
+ pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
+ pinioBoxConfigMutable()->permanentId[2] = BOX_PERMANENT_ID_USER3;
+ pinioBoxConfigMutable()->permanentId[3] = BOX_PERMANENT_ID_USER4;
+}
diff --git a/src/main/target/DAKEFPVH743_SLIM/target.c b/src/main/target/DAKEFPVH743_SLIM/target.c
new file mode 100644
index 00000000000..b85049fa483
--- /dev/null
+++ b/src/main/target/DAKEFPVH743_SLIM/target.c
@@ -0,0 +1,62 @@
+/*
+ * 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
+ #include "drivers/io.h"
+ #include "drivers/pwm_mapping.h"
+ #include "drivers/timer.h"
+ #include "drivers/bus.h"
+ #include "drivers/sensor.h"
+
+ #include "drivers/pwm_output.h"
+ #include "common/maths.h"
+ #include "fc/config.h"
+ // Board hardware definitions - IMU1 slot
+
+BUSDEV_REGISTER_SPI_TAG(busdev_1_mpu6000, DEVHW_MPU6000, IMU_1_SPI_BUS, IMU_1_CS_PIN, IMU_1_IRQ_PIN, 0, DEVFLAGS_NONE, IMU_1_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_1_mpu6500, DEVHW_MPU6500, IMU_1_SPI_BUS, IMU_1_CS_PIN, IMU_1_IRQ_PIN, 0, DEVFLAGS_NONE, IMU_1_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_1_ICM42605, DEVHW_ICM42605, IMU_1_SPI_BUS, IMU_1_CS_PIN, IMU_1_IRQ_PIN, 0, DEVFLAGS_NONE, IMU_1_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_1_BMI270, DEVHW_BMI270, IMU_1_SPI_BUS, IMU_1_CS_PIN, IMU_1_IRQ_PIN, 0, DEVFLAGS_NONE, IMU_1_ALIGN);
+
+// // Board hardware definitions - IMU2 slot
+BUSDEV_REGISTER_SPI_TAG(busdev_2_mpu6000, DEVHW_MPU6000, IMU_2_SPI_BUS, IMU_2_CS_PIN, IMU_2_IRQ_PIN, 1, DEVFLAGS_NONE, IMU_2_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_2_mpu6500, DEVHW_MPU6500, IMU_2_SPI_BUS, IMU_2_CS_PIN, IMU_2_IRQ_PIN, 1, DEVFLAGS_NONE, IMU_2_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_2_ICM42605, DEVHW_ICM42605, IMU_2_SPI_BUS, IMU_2_CS_PIN, IMU_2_IRQ_PIN, 1, DEVFLAGS_NONE, IMU_2_ALIGN);
+BUSDEV_REGISTER_SPI_TAG(busdev_2_BMI270, DEVHW_BMI270, IMU_2_SPI_BUS, IMU_2_CS_PIN, IMU_2_IRQ_PIN, 1, DEVFLAGS_NONE, IMU_2_ALIGN);
+
+timerHardware_t timerHardware[] = {
+ DEF_TIM(TIM1, CH1, PE9, TIM_USE_OUTPUT_AUTO, 0, 0), // S1
+ DEF_TIM(TIM1, CH2, PE11, TIM_USE_OUTPUT_AUTO, 0, 1), // S2
+ DEF_TIM(TIM1, CH3, PE13, TIM_USE_OUTPUT_AUTO, 0, 2), // S3
+ DEF_TIM(TIM1, CH4, PE14, TIM_USE_OUTPUT_AUTO, 0, 3), // S4
+ DEF_TIM(TIM2, CH1, PA0, TIM_USE_OUTPUT_AUTO, 0, 4), // S5
+ DEF_TIM(TIM2, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 5), // S6
+ DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 6), // S7
+ DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 7), // S8 DMA None
+ DEF_TIM(TIM4, CH1, PD12, TIM_USE_OUTPUT_AUTO, 0, 11), // S9 - unique DMA opt, was 0 (collided with TIM1 CH1 S1 on DMA1 Stream0)
+ DEF_TIM(TIM4, CH2, PD13, TIM_USE_OUTPUT_AUTO, 0, 12), // S10
+ DEF_TIM(TIM4, CH3, PD14, TIM_USE_OUTPUT_AUTO, 0, 13), // S11
+ DEF_TIM(TIM4, CH4, PD15, TIM_USE_OUTPUT_AUTO, 0, 14), // S12
+
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // CAMERA_CONTROL_PIN
+ DEF_TIM(TIM17, CH1N,PB7, TIM_USE_ANY, 0, 0), // GYRO_1_CLKIN_PIN
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_LED, 0, 9), // LED_2812 - moved off DMA2 Stream0 to avoid ADC1 DMA conflict
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/DAKEFPVH743_SLIM/target.h b/src/main/target/DAKEFPVH743_SLIM/target.h
new file mode 100644
index 00000000000..f4c324f60ec
--- /dev/null
+++ b/src/main/target/DAKEFPVH743_SLIM/target.h
@@ -0,0 +1,199 @@
+/*
+ * 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 "DAK7"
+#define USBD_PRODUCT_STRING "DAKEFPV H743 SLIM"
+
+#define LED0 PD10
+#define LED1 PD11
+#define LED2 PA8
+
+#define BEEPER PE10
+#define BEEPER_INVERTED
+#define USE_DSHOT_DMAR
+// Buses
+#define USE_SPI
+#define USE_SPI_DEVICE_1
+
+#define SPI1_SCK_PIN PA5
+#define SPI1_MISO_PIN PA6
+#define SPI1_MOSI_PIN PA7
+
+#define USE_SPI_DEVICE_2
+#define SPI2_SCK_PIN PB13
+#define SPI2_MISO_PIN PC2
+#define SPI2_MOSI_PIN PC3
+
+#define USE_SPI_DEVICE_3
+#define SPI3_SCK_PIN PC10
+#define SPI3_MISO_PIN PC11
+#define SPI3_MOSI_PIN PC12
+
+#define USE_SPI_DEVICE_4
+#define SPI4_SCK_PIN PE12
+#define SPI4_MISO_PIN PE5
+#define SPI4_MOSI_PIN PE6
+
+#define USE_I2C
+#define USE_I2C_DEVICE_2
+#define I2C2_SCL PB10
+#define I2C2_SDA PB11
+
+#define DEFAULT_I2C_BUS BUS_I2C2
+
+// GYRO
+#define USE_DUAL_GYRO
+#define USE_TARGET_IMU_HARDWARE_DESCRIPTORS // Don't use common busdev descriptors for IMU
+#define USE_IMU_MPU6500
+#define USE_IMU_MPU6000
+
+#define USE_IMU_ICM42605
+#define USE_IMU_BMI270
+
+// IMU_1 is verified to work on OBF4V6 and Omnibus Fireworks board
+# define IMU_1_CS_PIN PC9
+# define IMU_1_IRQ_PIN PD4
+# define IMU_1_SPI_BUS BUS_SPI1
+# define IMU_1_ALIGN CW0_DEG_FLIP
+// IMU_2 is sketchy and was not verified on actual hardware
+# define IMU_2_CS_PIN PB1
+# define IMU_2_IRQ_PIN PB2
+# define IMU_2_SPI_BUS BUS_SPI4
+# define IMU_2_ALIGN CW90_DEG_FLIP
+
+// M25P256 flash
+#define USE_FLASHFS
+#define USE_FLASH_M25P16
+
+#define M25P16_SPI_BUS BUS_SPI3
+#define M25P16_CS_PIN PA15
+
+// *************** SDIO SD BLACKBOX*******************
+#define USE_SDCARD
+#define USE_SDCARD_SDIO
+#define SDCARD_SDIO_DEVICE SDIODEV_2
+#define SDCARD_SDIO_4BIT
+#define SDCARD_SDIO2_CMD_ALT // spare pin
+
+#define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT
+
+// OSD
+#define USE_MAX7456
+#define MAX7456_SPI_BUS BUS_SPI2
+#define MAX7456_CS_PIN PB12
+
+// CAMERA_CONTROL
+#define CAMERA_CONTROL_PIN PC8 // TIM8 CH3
+
+// Serial ports
+#define USE_VCP
+
+#define USE_UART1
+#define USE_UART2
+#define USE_UART3
+#define USE_UART4
+#define USE_UART5
+#define USE_UART6
+#define USE_UART7
+#define USE_UART8
+#define UART1_TX_PIN PA9
+#define UART1_RX_PIN PA10
+#define UART2_TX_PIN PD5
+#define UART2_RX_PIN PD6
+#define UART3_TX_PIN PD8
+#define UART3_RX_PIN PD9
+#define UART4_TX_PIN PB9
+#define UART4_RX_PIN PB8
+#define UART5_TX_PIN PB6
+#define UART5_RX_PIN PB5
+#define UART6_TX_PIN PC6
+#define UART6_RX_PIN PC7
+
+#define USE_UART7_PIN_SWAP
+#define UART7_TX_PIN PE8
+#define UART7_RX_PIN PE7
+#define UART8_TX_PIN PE1
+#define UART8_RX_PIN PE0
+
+#define SERIAL_PORT_COUNT 9
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_CRSF
+#define SERIALRX_UART SERIAL_PORT_USART5
+#define GPS_UART SERIAL_PORT_USART1
+
+//Baro
+#define USE_BARO
+#define USE_BARO_BMP280
+#define USE_BARO_DPS310
+#define BARO_I2C_BUS DEFAULT_I2C_BUS
+
+// Mag
+#define USE_MAG
+#define MAG_I2C_BUS DEFAULT_I2C_BUS
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS DEFAULT_I2C_BUS
+
+#define USE_RANGEFINDER
+#define USE_RANGEFINDER_MSP
+#define RANGEFINDER_I2C_BUS DEFAULT_I2C_BUS
+
+#define PITOT_I2C_BUS DEFAULT_I2C_BUS
+
+// ADC
+#define USE_ADC
+#define ADC_INSTANCE ADC1
+#define ADC_CHANNEL_1_PIN PA4
+#define ADC_CHANNEL_2_PIN PC5
+#define ADC_CHANNEL_3_PIN PC0
+#define ADC_CHANNEL_4_PIN PC4
+#define VBAT_ADC_CHANNEL ADC_CHN_1
+#define RSSI_ADC_CHANNEL ADC_CHN_2
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_3
+#define AIRSPEED_ADC_CHANNEL ADC_CHN_4
+
+#define USE_LED_STRIP
+#define WS2811_PIN PB0 // TIM3 CH3
+
+#define DEFAULT_FEATURES (FEATURE_OSD | FEATURE_CURRENT_METER | FEATURE_VBAT | FEATURE_TELEMETRY | FEATURE_LED_STRIP | FEATURE_GPS | FEATURE_BLACKBOX)
+
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+#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 USE_DSHOT
+#define USE_ESC_SENSOR
+
+#define MAX_PWM_OUTPUT_PORTS 12
+
+// PINIO
+#define USE_PINIO
+#define USE_PINIOBOX
+#define PINIO1_PIN PE2
+#define PINIO2_PIN PE3
+#define PINIO3_PIN PE4
+#define PINIO4_PIN PD2
+
+// VBAT 10K/160K
+#define VBAT_SCALE_DEFAULT 1600
diff --git a/src/main/target/DYSF4/CMakeLists.txt b/src/main/target/DYSF4/CMakeLists.txt
new file mode 100644
index 00000000000..ec00b5acc49
--- /dev/null
+++ b/src/main/target/DYSF4/CMakeLists.txt
@@ -0,0 +1,2 @@
+target_stm32f405xg(DYSF4PRO)
+target_stm32f405xg(DYSF4PROV2)
diff --git a/src/main/target/DYSF4/target.c b/src/main/target/DYSF4/target.c
new file mode 100644
index 00000000000..d6de2e9bf25
--- /dev/null
+++ b/src/main/target/DYSF4/target.c
@@ -0,0 +1,47 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#include
+
+#include
+#include "drivers/io.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+#include "drivers/bus.h"
+
+timerHardware_t timerHardware[] = {
+
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S1_OUT D1_ST7
+ DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // S2_OUT D1_ST2
+ DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 1), // S3_OUT D1_ST6
+ DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0), // S4_OUT D1_ST1
+
+ // { TIM9, IO_TAG(PA3), TIM_Channel_2, 1, IOCFG_AF_PP_PD, GPIO_AF_TIM9, TIM_USE_OUTPUT_AUTO }, // MOTOR_3
+ DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT MOTOR, SERVO or LED
+ DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0), // S6_OUT
+
+
+ // DEF_TIM(TIM12, CH1, PB14, TIM_USE_PPM, 0, 0), // PPM
+ DEF_TIM(TIM12, CH2, PB15, TIM_USE_ANY, 0, 0), // S2_IN
+ DEF_TIM(TIM8, CH1, PC6, TIM_USE_ANY, 0, 0), // S3_IN, UART6_TX
+ DEF_TIM(TIM8, CH2, PC7, TIM_USE_ANY, 0, 0), // S4_IN, UART6_RX
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // S5_IN
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_ANY, 0, 0), // S6_IN
+
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/DYSF4/target.h b/src/main/target/DYSF4/target.h
new file mode 100644
index 00000000000..76e79106afa
--- /dev/null
+++ b/src/main/target/DYSF4/target.h
@@ -0,0 +1,159 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#pragma once
+
+// This directory contains: DYSF4PRO, DYSF4PROV2
+
+#if defined(DYSF4PRO)
+#define TARGET_BOARD_IDENTIFIER "DYS4"
+#elif defined(DYSF4PROV2)
+#define TARGET_BOARD_IDENTIFIER "DY42"
+#endif
+
+#define USBD_PRODUCT_STRING "DysF4Pro"
+
+#define LED0 PB5
+
+#define BEEPER PB4
+#define BEEPER_INVERTED
+
+#if defined(DYSF4PROV2)
+#define USE_I2C
+#define USE_I2C_DEVICE_1
+#define I2C1_SCL PB8
+#define I2C1_SDA PB9
+#define I2C_EXT_BUS BUS_I2C1
+#else
+#define USE_I2C
+#define USE_I2C_DEVICE_2
+#define I2C_DEVICE_2_SHARES_UART3
+#define I2C_EXT_BUS BUS_I2C2
+#endif
+
+#define UG2864_I2C_BUS I2C_EXT_BUS
+
+#define MPU6000_CS_PIN PA4
+#define MPU6000_SPI_BUS BUS_SPI1
+
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW180_DEG
+
+#define USE_MAG
+#define MAG_I2C_BUS I2C_EXT_BUS
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS I2C_EXT_BUS
+
+#define USE_BARO
+#define BARO_I2C_BUS I2C_EXT_BUS
+#define USE_BARO_BMP085
+#define USE_BARO_BMP280
+#define USE_BARO_MS5611
+
+#define PITOT_I2C_BUS I2C_EXT_BUS
+
+#define USE_RANGEFINDER
+#define RANGEFINDER_I2C_BUS I2C_EXT_BUS
+
+#define USE_VCP
+#define VBUS_SENSING_PIN PC5
+#define VBUS_SENSING_ENABLED
+
+
+#define USE_UART1
+#define UART1_RX_PIN PA10
+#define UART1_TX_PIN PA9
+#define UART1_AHB1_PERIPHERALS RCC_AHB1Periph_DMA2
+
+#define USE_UART3
+#define UART3_RX_PIN PB11
+#define UART3_TX_PIN PB10
+
+#define USE_UART6
+#define UART6_RX_PIN PC7
+#define UART6_TX_PIN PC6
+
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PC8
+#define SOFTSERIAL_1_TX_PIN PC9
+
+#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_SBUS
+#define SERIALRX_UART SERIAL_PORT_USART1
+
+#define USE_SPI
+
+#define USE_SPI_DEVICE_1
+
+
+#define USE_SPI_DEVICE_3
+#define SPI3_NSS_PIN PB3
+#define SPI3_SCK_PIN PC10
+#define SPI3_MISO_PIN PC11
+#define SPI3_MOSI_PIN PC12
+
+#define USE_MAX7456
+#define MAX7456_SPI_BUS BUS_SPI3
+#define MAX7456_CS_PIN PA15
+
+#define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT
+#define M25P16_CS_PIN SPI3_NSS_PIN
+#define M25P16_SPI_BUS BUS_SPI3
+#define USE_FLASHFS
+#define USE_FLASH_M25P16
+
+#define USE_ADC
+#define ADC_CHANNEL_1_PIN PC1
+#define ADC_CHANNEL_2_PIN PC2
+
+#ifdef DYSF4PRO
+ #define ADC_CHANNEL_3_PIN PC3
+#else
+ #define ADC_CHANNEL_3_PIN PA0
+#endif
+
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_1
+#define VBAT_ADC_CHANNEL ADC_CHN_2
+#define RSSI_ADC_CHANNEL ADC_CHN_3
+
+#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
+
+#define USE_LED_STRIP
+#define WS2811_PIN PA1
+
+#define DISABLE_RX_PWM_FEATURE
+#define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX | FEATURE_VBAT | FEATURE_OSD)
+
+#define USE_SPEKTRUM_BIND
+#define BIND_PIN PB11 // USART3 RX
+
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+// Number of available PWM outputs
+#define MAX_PWM_OUTPUT_PORTS 6
+#define TARGET_MOTOR_COUNT 6
+#define USE_DSHOT
+#define USE_ESC_SENSOR
+
+#define TARGET_IO_PORTA 0xffff
+#define TARGET_IO_PORTB 0xffff
+#define TARGET_IO_PORTC 0xffff
+#define TARGET_IO_PORTD 0xffff
+
diff --git a/src/main/target/JHEMCUF435/config.c b/src/main/target/JHEMCUF435/config.c
new file mode 100644
index 00000000000..bb6078293ed
--- /dev/null
+++ b/src/main/target/JHEMCUF435/config.c
@@ -0,0 +1,35 @@
+/*
+ * This file is part of INAV Project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Alternatively, the contents of this file may be used under the terms
+ * of the GNU General Public License Version 3, as described below:
+ *
+ * This file 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.
+ *
+ * This file 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 this program. If not, see http://www.gnu.org/licenses/.
+ */
+
+#include
+
+#include "platform.h"
+
+#include "io/serial.h"
+
+void targetConfiguration(void)
+{
+ serialConfigMutable()->portConfigs[findSerialPortIndexByIdentifier(SERIAL_PORT_USART2)].functionMask = FUNCTION_RX_SERIAL;
+ serialConfigMutable()->portConfigs[findSerialPortIndexByIdentifier(SERIAL_PORT_USART7)].functionMask = FUNCTION_ESCSERIAL;
+}
\ No newline at end of file
diff --git a/src/main/target/JHEMCUF435/target.h b/src/main/target/JHEMCUF435/target.h
index edbc3d0bbda..e061f67cd76 100644
--- a/src/main/target/JHEMCUF435/target.h
+++ b/src/main/target/JHEMCUF435/target.h
@@ -137,7 +137,7 @@
#define UART6_TX_PIN PC6
#define USE_UART7
-//#define USE_UART7_PIN_SWAP
+#define USE_UART7_PIN_SWAP
#define UART7_RX_PIN PC0
#define UART7_TX_PIN PC1
@@ -149,7 +149,7 @@
#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
#define SERIALRX_PROVIDER SERIALRX_CRSF
-#define SERIALRX_UART SERIAL_PORT_USART5
+#define SERIALRX_UART SERIAL_PORT_USART2
// *************** ADC *****************************
#define USE_ADC
diff --git a/src/main/target/OMNIBUSF4/CMakeLists.txt b/src/main/target/OMNIBUSF4/CMakeLists.txt
index 0e2bf2002c0..28f13c7cd5b 100644
--- a/src/main/target/OMNIBUSF4/CMakeLists.txt
+++ b/src/main/target/OMNIBUSF4/CMakeLists.txt
@@ -1,12 +1 @@
-target_stm32f405xg(DYSF4PRO)
-target_stm32f405xg(DYSF4PROV2)
target_stm32f405xg(OMNIBUSF4)
-# the OMNIBUSF4SD has an SDCARD instead of flash, a BMP280 baro and therefore a slightly different ppm/pwm and SPI mapping
-target_stm32f405xg(OMNIBUSF4PRO)
-target_stm32f405xg(OMNIBUSF4V3_S5_S6_2SS)
-target_stm32f405xg(OMNIBUSF4V3_S5S6_SS)
-target_stm32f405xg(OMNIBUSF4V3_S6_SS)
-# OMNIBUSF4V3 is a (almost identical) variant of OMNIBUSF4PRO target,
-# except for an inverter on UART6.
-target_stm32f405xg(OMNIBUSF4V3)
-target_stm32f405xg(OMNIBUSF4V3_ICM SKIP_RELEASES)
diff --git a/src/main/target/OMNIBUSF4/target.c b/src/main/target/OMNIBUSF4/target.c
index be85e9fbce3..d6de2e9bf25 100644
--- a/src/main/target/OMNIBUSF4/target.c
+++ b/src/main/target/OMNIBUSF4/target.c
@@ -31,36 +31,16 @@ timerHardware_t timerHardware[] = {
DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0), // S4_OUT D1_ST1
// { TIM9, IO_TAG(PA3), TIM_Channel_2, 1, IOCFG_AF_PP_PD, GPIO_AF_TIM9, TIM_USE_OUTPUT_AUTO }, // MOTOR_3
-#if (defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3)) && !(defined(OMNIBUSF4PRO_LEDSTRIPM5) || defined(OMNIBUSF4V3_S6_SS) || defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS))
-
- DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT
- DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0), // S6_OUT
-#elif defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS)
- DEF_TIM(TIM5, CH2, PA1, TIM_USE_ANY, 0, 0), // S5_OUT SOFTSERIAL
- DEF_TIM(TIM1, CH1, PA8, TIM_USE_ANY, 0, 0), // S6_OUT SOFTSERIAL
-#elif defined(OMNIBUSF4V3_S6_SS)
- DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT
- DEF_TIM(TIM1, CH1, PA8, TIM_USE_ANY, 0, 0), // S6_OUT SOFTSERIAL
-#else
DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT MOTOR, SERVO or LED
DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0), // S6_OUT
-#endif
-#if (defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3))
- DEF_TIM(TIM4, CH1, PB6, TIM_USE_LED, 0, 0), // LED strip for F4 V2 / F4-Pro-0X and later (RCD_CS for F4)
-#endif
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3)
- // DEF_TIM(TIM10, CH1, PB8, TIM_USE_PPM, 0, 0), // PPM
- DEF_TIM(TIM4, CH4, PB9, TIM_USE_ANY, 0, 0), // S2_IN
-#else
// DEF_TIM(TIM12, CH1, PB14, TIM_USE_PPM, 0, 0), // PPM
DEF_TIM(TIM12, CH2, PB15, TIM_USE_ANY, 0, 0), // S2_IN
-#endif
DEF_TIM(TIM8, CH1, PC6, TIM_USE_ANY, 0, 0), // S3_IN, UART6_TX
DEF_TIM(TIM8, CH2, PC7, TIM_USE_ANY, 0, 0), // S4_IN, UART6_RX
- DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // S5_IN // pad labelled CH5 on OMNIBUSF4PRO
- DEF_TIM(TIM8, CH4, PC9, TIM_USE_ANY, 0, 0), // S6_IN // pad labelled CH6 on OMNIBUSF4PRO
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // S5_IN
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_ANY, 0, 0), // S6_IN
};
diff --git a/src/main/target/OMNIBUSF4/target.h b/src/main/target/OMNIBUSF4/target.h
index fcd89fb0e46..af906bb8b12 100644
--- a/src/main/target/OMNIBUSF4/target.h
+++ b/src/main/target/OMNIBUSF4/target.h
@@ -17,86 +17,27 @@
#pragma once
-//Same target as OMNIBUSF4PRO with LED strip in M5
-#ifdef OMNIBUSF4PRO_LEDSTRIPM5
-#define OMNIBUSF4PRO
-#endif
-//Same target as OMNIBUSF4V3 with softserial in M5 and M6
-#if defined(OMNIBUSF4V3_S6_SS) || defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS)
-#define OMNIBUSF4V3
-#endif
-
-#ifdef OMNIBUSF4PRO
-#define TARGET_BOARD_IDENTIFIER "OBSD"
-#elif defined(OMNIBUSF4V3)
-#define TARGET_BOARD_IDENTIFIER "OB43"
-#elif defined(OMNIBUSF4V3_ICM)
-#define TARGET_BOARD_IDENTIFIER "OB4I"
-#elif defined(DYSF4PRO)
-#define TARGET_BOARD_IDENTIFIER "DYS4"
-#elif defined(DYSF4PROV2)
-#define TARGET_BOARD_IDENTIFIER "DY42"
-#else
#define TARGET_BOARD_IDENTIFIER "OBF4"
-#endif
-#if defined(DYSF4PRO) || defined(DYSF4PROV2)
-#define USBD_PRODUCT_STRING "DysF4Pro"
-#else
#define USBD_PRODUCT_STRING "Omnibus F4"
-#endif
#define LED0 PB5
#define BEEPER PB4
#define BEEPER_INVERTED
-#if defined(DYSF4PROV2)
-#define USE_I2C
-#define USE_I2C_DEVICE_1
-#define I2C1_SCL PB8
-#define I2C1_SDA PB9
-#define I2C_EXT_BUS BUS_I2C1
-#else
#define USE_I2C
#define USE_I2C_DEVICE_2
#define I2C_DEVICE_2_SHARES_UART3
#define I2C_EXT_BUS BUS_I2C2
-#endif
#define UG2864_I2C_BUS I2C_EXT_BUS
#define MPU6000_CS_PIN PA4
#define MPU6000_SPI_BUS BUS_SPI1
-#if defined(OMNIBUSF4V3_ICM)
- #define USE_IMU_ICM42605
- #define IMU_ICM42605_ALIGN CW180_DEG
- #define ICM42605_CS_PIN PA4
- #define ICM42605_SPI_BUS BUS_SPI1
-#endif
-
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define USE_IMU_MPU6000
- #define IMU_MPU6000_ALIGN CW270_DEG
-#else
- #define USE_IMU_MPU6000
- #define IMU_MPU6000_ALIGN CW180_DEG
-#endif
-
-// Support for OMNIBUS F4 PRO CORNER - it has MPU6500/ICM20608 instead of MPU6000
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define MPU6500_CS_PIN MPU6000_CS_PIN
- #define MPU6500_SPI_BUS MPU6000_SPI_BUS
- #define USE_IMU_MPU6500
- #define IMU_MPU6500_ALIGN IMU_MPU6000_ALIGN
-
- //BMI270
- #define USE_IMU_BMI270
- #define IMU_BMI270_ALIGN IMU_MPU6000_ALIGN
- #define BMI270_SPI_BUS MPU6000_SPI_BUS
- #define BMI270_CS_PIN MPU6000_CS_PIN
-#endif
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW180_DEG
#define USE_MAG
#define MAG_I2C_BUS I2C_EXT_BUS
@@ -105,21 +46,10 @@
#define TEMPERATURE_I2C_BUS I2C_EXT_BUS
#define USE_BARO
-
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define USE_BARO_BMP280
- #define BMP280_SPI_BUS BUS_SPI3
- #define BMP280_CS_PIN PB3 // v1
- // Support external barometers
- #define BARO_I2C_BUS I2C_EXT_BUS
- #define USE_BARO_BMP085
- #define USE_BARO_MS5611
-#else
- #define BARO_I2C_BUS I2C_EXT_BUS
- #define USE_BARO_BMP085
- #define USE_BARO_BMP280
- #define USE_BARO_MS5611
-#endif
+#define BARO_I2C_BUS I2C_EXT_BUS
+#define USE_BARO_BMP085
+#define USE_BARO_BMP280
+#define USE_BARO_MS5611
#define PITOT_I2C_BUS I2C_EXT_BUS
@@ -130,17 +60,11 @@
#define VBUS_SENSING_PIN PC5
#define VBUS_SENSING_ENABLED
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
-#define USE_UART_INVERTER
-#endif
#define USE_UART1
#define UART1_RX_PIN PA10
#define UART1_TX_PIN PA9
#define UART1_AHB1_PERIPHERALS RCC_AHB1Periph_DMA2
-#if defined(OMNIBUSF4PRO)
-#define INVERTER_PIN_UART1_RX PC0 // PC0 has never been used as inverter control on genuine OMNIBUS F4 variants, but leave it as is since some clones actually implement it.
-#endif
#define USE_UART3
#define UART3_RX_PIN PB11
@@ -149,51 +73,13 @@
#define USE_UART6
#define UART6_RX_PIN PC7
#define UART6_TX_PIN PC6
-#if defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define INVERTER_PIN_UART6_RX PC8
- #define INVERTER_PIN_UART6_TX PC9
-#endif
-
-#if (defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)) && !(defined(OMNIBUSF4V3_S6_SS) || defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS))
-#define USE_SOFTSERIAL1
-#define SOFTSERIAL_1_RX_PIN PC6 // shared with UART6 TX
-#define SOFTSERIAL_1_TX_PIN PC6 // shared with UART6 TX
-
-#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
-#elif defined(OMNIBUSF4V3_S6_SS) // one softserial on S6
#define USE_SOFTSERIAL1
-#define SOFTSERIAL_1_RX_PIN PA8 // S6 output
-#define SOFTSERIAL_1_TX_PIN PA8 // S6 output
+#define SOFTSERIAL_1_RX_PIN PC8
+#define SOFTSERIAL_1_TX_PIN PC9
#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
-#elif defined(OMNIBUSF4V3_S5S6_SS) // one softserial on S5/RX S6/TX
-#define USE_SOFTSERIAL1
-#define SOFTSERIAL_1_RX_PIN PA1 // S5 output
-#define SOFTSERIAL_1_TX_PIN PA8 // S6 output
-
-#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
-
-#elif defined(OMNIBUSF4V3_S5_S6_2SS) // two softserials, one on S5 and one on S6
-#define USE_SOFTSERIAL1
-#define SOFTSERIAL_1_RX_PIN PA1 // S5 output
-#define SOFTSERIAL_1_TX_PIN PA1 // S5 output
-
-#define USE_SOFTSERIAL2
-#define SOFTSERIAL_2_RX_PIN PA8 // S6 output
-#define SOFTSERIAL_2_TX_PIN PA8 // S6 output
-
-#define SERIAL_PORT_COUNT 6 // VCP, USART1, USART3, USART6, SOFTSERIAL1, SOFTSERIAL2
-
-#else // One softserial on versions other than OMNIBUSF4V3
-#define USE_SOFTSERIAL1
-#define SOFTSERIAL_1_RX_PIN PC8 // pad labelled CH5 on OMNIBUSF4PRO
-#define SOFTSERIAL_1_TX_PIN PC9 // pad labelled CH6 on OMNIBUSF4PRO
-
-#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
-#endif
-
#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
#define SERIALRX_PROVIDER SERIALRX_SBUS
#define SERIALRX_UART SERIAL_PORT_USART1
@@ -202,20 +88,8 @@
#define USE_SPI_DEVICE_1
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define USE_SPI_DEVICE_2
- #define SPI2_NSS_PIN PB12
- #define SPI2_SCK_PIN PB13
- #define SPI2_MISO_PIN PB14
- #define SPI2_MOSI_PIN PB15
-#endif
-
#define USE_SPI_DEVICE_3
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define SPI3_NSS_PIN PA15
-#else
- #define SPI3_NSS_PIN PB3
-#endif
+#define SPI3_NSS_PIN PB3
#define SPI3_SCK_PIN PC10
#define SPI3_MISO_PIN PC11
#define SPI3_MOSI_PIN PC12
@@ -224,33 +98,16 @@
#define MAX7456_SPI_BUS BUS_SPI3
#define MAX7456_CS_PIN PA15
-#if defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)
- #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT
- #define USE_SDCARD
- #define USE_SDCARD_SPI
-
- #define SDCARD_SPI_BUS BUS_SPI2
- #define SDCARD_CS_PIN SPI2_NSS_PIN
-
- #define SDCARD_DETECT_PIN PB7
- #define SDCARD_DETECT_INVERTED
-#else
- #define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT
- #define M25P16_CS_PIN SPI3_NSS_PIN
- #define M25P16_SPI_BUS BUS_SPI3
- #define USE_FLASHFS
- #define USE_FLASH_M25P16
-#endif
+#define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT
+#define M25P16_CS_PIN SPI3_NSS_PIN
+#define M25P16_SPI_BUS BUS_SPI3
+#define USE_FLASHFS
+#define USE_FLASH_M25P16
#define USE_ADC
#define ADC_CHANNEL_1_PIN PC1
#define ADC_CHANNEL_2_PIN PC2
-
-#ifdef DYSF4PRO
- #define ADC_CHANNEL_3_PIN PC3
-#else
- #define ADC_CHANNEL_3_PIN PA0
-#endif
+#define ADC_CHANNEL_3_PIN PA0
#define CURRENT_METER_ADC_CHANNEL ADC_CHN_1
#define VBAT_ADC_CHANNEL ADC_CHN_2
@@ -259,11 +116,7 @@
#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
#define USE_LED_STRIP
-#if (defined(OMNIBUSF4PRO) || defined(OMNIBUSF4V3) || defined(OMNIBUSF4V3_ICM)) && !defined(OMNIBUSF4PRO_LEDSTRIPM5)
- #define WS2811_PIN PB6
-#else
- #define WS2811_PIN PA1
-#endif
+#define WS2811_PIN PA1
#define DISABLE_RX_PWM_FEATURE
#define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX | FEATURE_VBAT | FEATURE_OSD)
@@ -283,7 +136,3 @@
#define TARGET_IO_PORTB 0xffff
#define TARGET_IO_PORTC 0xffff
#define TARGET_IO_PORTD 0xffff
-
-#ifdef OMNIBUSF4PRO
-#define CURRENT_METER_SCALE 265
-#endif
diff --git a/src/main/target/OMNIBUSF4PRO/CMakeLists.txt b/src/main/target/OMNIBUSF4PRO/CMakeLists.txt
new file mode 100644
index 00000000000..e09984ac0a9
--- /dev/null
+++ b/src/main/target/OMNIBUSF4PRO/CMakeLists.txt
@@ -0,0 +1,5 @@
+# OMNIBUSF4PRO has SD card, BMP280 baro
+target_stm32f405xg(OMNIBUSF4PRO)
+# OMNIBUSF4V3 is similar to PRO but with UART6 inverter
+target_stm32f405xg(OMNIBUSF4V3)
+target_stm32f405xg(OMNIBUSF4V3_ICM SKIP_RELEASES)
diff --git a/src/main/target/OMNIBUSF4PRO/target.c b/src/main/target/OMNIBUSF4PRO/target.c
new file mode 100644
index 00000000000..29cb945af85
--- /dev/null
+++ b/src/main/target/OMNIBUSF4PRO/target.c
@@ -0,0 +1,48 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#include
+
+#include
+#include "drivers/io.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+#include "drivers/bus.h"
+
+timerHardware_t timerHardware[] = {
+
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S1_OUT D1_ST7
+ DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // S2_OUT D1_ST2
+ DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 1), // S3_OUT D1_ST6
+ DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0), // S4_OUT D1_ST1
+
+ // { TIM9, IO_TAG(PA3), TIM_Channel_2, 1, IOCFG_AF_PP_PD, GPIO_AF_TIM9, TIM_USE_OUTPUT_AUTO }, // MOTOR_3
+ DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT
+ DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0), // S6_OUT
+
+ DEF_TIM(TIM4, CH1, PB6, TIM_USE_LED, 0, 0), // LED strip for F4 V2 / F4-Pro-0X and later (RCD_CS for F4)
+
+ // DEF_TIM(TIM10, CH1, PB8, TIM_USE_PPM, 0, 0), // PPM
+ DEF_TIM(TIM4, CH4, PB9, TIM_USE_ANY, 0, 0), // S2_IN
+ DEF_TIM(TIM8, CH1, PC6, TIM_USE_ANY, 0, 0), // S3_IN, UART6_TX
+ DEF_TIM(TIM8, CH2, PC7, TIM_USE_ANY, 0, 0), // S4_IN, UART6_RX
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // S5_IN // pad labelled CH5 on OMNIBUSF4PRO
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_ANY, 0, 0), // S6_IN // pad labelled CH6 on OMNIBUSF4PRO
+
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/OMNIBUSF4PRO/target.h b/src/main/target/OMNIBUSF4PRO/target.h
new file mode 100644
index 00000000000..f9d7e3af4de
--- /dev/null
+++ b/src/main/target/OMNIBUSF4PRO/target.h
@@ -0,0 +1,202 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#pragma once
+
+// This directory contains: OMNIBUSF4PRO, OMNIBUSF4V3, OMNIBUSF4V3_ICM
+// Softserial variants are in separate OMNIBUSF4V3_SS/ directory
+
+#ifdef OMNIBUSF4V3_ICM
+#define OMNIBUSF4V3
+#endif
+
+#ifdef OMNIBUSF4PRO
+#define TARGET_BOARD_IDENTIFIER "OBSD"
+#elif defined(OMNIBUSF4V3)
+#define TARGET_BOARD_IDENTIFIER "OB43"
+#endif
+
+#define USBD_PRODUCT_STRING "Omnibus F4"
+
+#define LED0 PB5
+
+#define BEEPER PB4
+#define BEEPER_INVERTED
+
+#define USE_I2C
+#define USE_I2C_DEVICE_2
+#define I2C_DEVICE_2_SHARES_UART3
+#define I2C_EXT_BUS BUS_I2C2
+
+#define UG2864_I2C_BUS I2C_EXT_BUS
+
+#define MPU6000_CS_PIN PA4
+#define MPU6000_SPI_BUS BUS_SPI1
+
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW270_DEG
+
+// Support for OMNIBUS F4 PRO CORNER - it has ICM20608 instead of MPU6000
+#define MPU6500_CS_PIN MPU6000_CS_PIN
+#define MPU6500_SPI_BUS MPU6000_SPI_BUS
+#define USE_IMU_MPU6500
+#define IMU_MPU6500_ALIGN IMU_MPU6000_ALIGN
+
+//BMI270
+#define USE_IMU_BMI270
+#define IMU_BMI270_ALIGN IMU_MPU6000_ALIGN
+#define BMI270_SPI_BUS MPU6000_SPI_BUS
+#define BMI270_CS_PIN MPU6000_CS_PIN
+
+#ifdef OMNIBUSF4V3_ICM
+#define USE_IMU_ICM42605
+#define IMU_ICM42605_ALIGN CW180_DEG
+#define ICM42605_CS_PIN PA4
+#define ICM42605_SPI_BUS BUS_SPI1
+#endif
+
+#define USE_MAG
+#define MAG_I2C_BUS I2C_EXT_BUS
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS I2C_EXT_BUS
+
+#define USE_BARO
+#define USE_BARO_BMP280
+#define BMP280_SPI_BUS BUS_SPI3
+#define BMP280_CS_PIN PB3 // v1
+// Support external barometers
+#define BARO_I2C_BUS I2C_EXT_BUS
+#define USE_BARO_BMP085
+#define USE_BARO_MS5611
+
+#define PITOT_I2C_BUS I2C_EXT_BUS
+
+#define USE_RANGEFINDER
+#define RANGEFINDER_I2C_BUS I2C_EXT_BUS
+
+#define USE_VCP
+#define VBUS_SENSING_PIN PC5
+#define VBUS_SENSING_ENABLED
+
+#define USE_UART_INVERTER
+
+#define USE_UART1
+#define UART1_RX_PIN PA10
+#define UART1_TX_PIN PA9
+#define UART1_AHB1_PERIPHERALS RCC_AHB1Periph_DMA2
+#if defined(OMNIBUSF4PRO)
+#define INVERTER_PIN_UART1_RX PC0 // PC0 has never been used as inverter control on genuine OMNIBUS F4 variants, but leave it as is since some clones actually implement it.
+#endif
+
+#define USE_UART3
+#define UART3_RX_PIN PB11
+#define UART3_TX_PIN PB10
+
+#define USE_UART6
+#define UART6_RX_PIN PC7
+#define UART6_TX_PIN PC6
+#if defined(OMNIBUSF4V3)
+ #define INVERTER_PIN_UART6_RX PC8
+ #define INVERTER_PIN_UART6_TX PC9
+#endif
+
+#if defined(OMNIBUSF4V3)
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PC6 // shared with UART6 TX
+#define SOFTSERIAL_1_TX_PIN PC6 // shared with UART6 TX
+
+#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
+
+#else // OMNIBUSF4PRO
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PC8 // pad labelled CH5 on OMNIBUSF4PRO
+#define SOFTSERIAL_1_TX_PIN PC9 // pad labelled CH6 on OMNIBUSF4PRO
+
+#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
+#endif
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_SBUS
+#define SERIALRX_UART SERIAL_PORT_USART1
+
+#define USE_SPI
+
+#define USE_SPI_DEVICE_1
+
+#define USE_SPI_DEVICE_2
+#define SPI2_NSS_PIN PB12
+#define SPI2_SCK_PIN PB13
+#define SPI2_MISO_PIN PB14
+#define SPI2_MOSI_PIN PB15
+
+#define USE_SPI_DEVICE_3
+#define SPI3_NSS_PIN PA15
+#define SPI3_SCK_PIN PC10
+#define SPI3_MISO_PIN PC11
+#define SPI3_MOSI_PIN PC12
+
+#define USE_MAX7456
+#define MAX7456_SPI_BUS BUS_SPI3
+#define MAX7456_CS_PIN PA15
+
+#define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT
+#define USE_SDCARD
+#define USE_SDCARD_SPI
+
+#define SDCARD_SPI_BUS BUS_SPI2
+#define SDCARD_CS_PIN SPI2_NSS_PIN
+
+#define SDCARD_DETECT_PIN PB7
+#define SDCARD_DETECT_INVERTED
+
+#define USE_ADC
+#define ADC_CHANNEL_1_PIN PC1
+#define ADC_CHANNEL_2_PIN PC2
+#define ADC_CHANNEL_3_PIN PA0
+
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_1
+#define VBAT_ADC_CHANNEL ADC_CHN_2
+#define RSSI_ADC_CHANNEL ADC_CHN_3
+
+#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
+
+#define USE_LED_STRIP
+#define WS2811_PIN PB6
+
+#define DISABLE_RX_PWM_FEATURE
+#define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX | FEATURE_VBAT | FEATURE_OSD)
+
+#define USE_SPEKTRUM_BIND
+#define BIND_PIN PB11 // USART3 RX
+
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+// Number of available PWM outputs
+#define MAX_PWM_OUTPUT_PORTS 6
+#define TARGET_MOTOR_COUNT 6
+#define USE_DSHOT
+#define USE_ESC_SENSOR
+
+#define TARGET_IO_PORTA 0xffff
+#define TARGET_IO_PORTB 0xffff
+#define TARGET_IO_PORTC 0xffff
+#define TARGET_IO_PORTD 0xffff
+
+#ifdef OMNIBUSF4PRO
+#define CURRENT_METER_SCALE 265
+#endif
diff --git a/src/main/target/OMNIBUSF4V3_SS/CMakeLists.txt b/src/main/target/OMNIBUSF4V3_SS/CMakeLists.txt
new file mode 100644
index 00000000000..321eeb211d1
--- /dev/null
+++ b/src/main/target/OMNIBUSF4V3_SS/CMakeLists.txt
@@ -0,0 +1,4 @@
+# OMNIBUSF4V3 softserial variants with different S5/S6 timer configurations
+target_stm32f405xg(OMNIBUSF4V3_S6_SS)
+target_stm32f405xg(OMNIBUSF4V3_S5S6_SS)
+target_stm32f405xg(OMNIBUSF4V3_S5_S6_2SS)
diff --git a/src/main/target/OMNIBUSF4V3_SS/target.c b/src/main/target/OMNIBUSF4V3_SS/target.c
new file mode 100644
index 00000000000..95b496e8bfb
--- /dev/null
+++ b/src/main/target/OMNIBUSF4V3_SS/target.c
@@ -0,0 +1,53 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#include
+
+#include
+#include "drivers/io.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+#include "drivers/bus.h"
+
+timerHardware_t timerHardware[] = {
+
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S1_OUT D1_ST7
+ DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // S2_OUT D1_ST2
+ DEF_TIM(TIM2, CH4, PA3, TIM_USE_OUTPUT_AUTO, 0, 1), // S3_OUT D1_ST6
+ DEF_TIM(TIM2, CH3, PA2, TIM_USE_OUTPUT_AUTO, 0, 0), // S4_OUT D1_ST1
+
+ // { TIM9, IO_TAG(PA3), TIM_Channel_2, 1, IOCFG_AF_PP_PD, GPIO_AF_TIM9, TIM_USE_OUTPUT_AUTO }, // MOTOR_3
+#if defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS)
+ DEF_TIM(TIM5, CH2, PA1, TIM_USE_ANY, 0, 0), // S5_OUT SOFTSERIAL
+ DEF_TIM(TIM1, CH1, PA8, TIM_USE_ANY, 0, 0), // S6_OUT SOFTSERIAL
+#elif defined(OMNIBUSF4V3_S6_SS)
+ DEF_TIM(TIM5, CH2, PA1, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT
+ DEF_TIM(TIM1, CH1, PA8, TIM_USE_ANY, 0, 0), // S6_OUT SOFTSERIAL
+#endif
+
+ DEF_TIM(TIM4, CH1, PB6, TIM_USE_LED, 0, 0), // LED strip for F4 V2 / F4-Pro-0X and later (RCD_CS for F4)
+
+ // DEF_TIM(TIM10, CH1, PB8, TIM_USE_PPM, 0, 0), // PPM
+ DEF_TIM(TIM4, CH4, PB9, TIM_USE_ANY, 0, 0), // S2_IN
+ DEF_TIM(TIM8, CH1, PC6, TIM_USE_ANY, 0, 0), // S3_IN, UART6_TX
+ DEF_TIM(TIM8, CH2, PC7, TIM_USE_ANY, 0, 0), // S4_IN, UART6_RX
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_ANY, 0, 0), // S5_IN
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_ANY, 0, 0), // S6_IN
+
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/OMNIBUSF4V3_SS/target.h b/src/main/target/OMNIBUSF4V3_SS/target.h
new file mode 100644
index 00000000000..f3cbf6c4a49
--- /dev/null
+++ b/src/main/target/OMNIBUSF4V3_SS/target.h
@@ -0,0 +1,193 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight 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.
+ *
+ * Cleanflight 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 Cleanflight. If not, see .
+ */
+
+#pragma once
+
+// This directory contains: OMNIBUSF4V3_S6_SS, OMNIBUSF4V3_S5S6_SS, OMNIBUSF4V3_S5_S6_2SS
+// Softserial variants of OMNIBUSF4V3 with different S5/S6 timer configurations
+#if defined(OMNIBUSF4V3_S6_SS) || defined(OMNIBUSF4V3_S5S6_SS) || defined(OMNIBUSF4V3_S5_S6_2SS)
+#define OMNIBUSF4V3
+#endif
+
+#define TARGET_BOARD_IDENTIFIER "OB43"
+
+#define USBD_PRODUCT_STRING "Omnibus F4"
+
+#define LED0 PB5
+
+#define BEEPER PB4
+#define BEEPER_INVERTED
+
+#define USE_I2C
+#define USE_I2C_DEVICE_2
+#define I2C_DEVICE_2_SHARES_UART3
+#define I2C_EXT_BUS BUS_I2C2
+
+#define UG2864_I2C_BUS I2C_EXT_BUS
+
+#define MPU6000_CS_PIN PA4
+#define MPU6000_SPI_BUS BUS_SPI1
+
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW270_DEG
+
+// Support for OMNIBUS F4 PRO CORNER - it has ICM20608 instead of MPU6000
+#define MPU6500_CS_PIN MPU6000_CS_PIN
+#define MPU6500_SPI_BUS MPU6000_SPI_BUS
+#define USE_IMU_MPU6500
+#define IMU_MPU6500_ALIGN IMU_MPU6000_ALIGN
+
+//BMI270
+#define USE_IMU_BMI270
+#define IMU_BMI270_ALIGN IMU_MPU6000_ALIGN
+#define BMI270_SPI_BUS MPU6000_SPI_BUS
+#define BMI270_CS_PIN MPU6000_CS_PIN
+
+#define USE_MAG
+#define MAG_I2C_BUS I2C_EXT_BUS
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS I2C_EXT_BUS
+
+#define USE_BARO
+#define USE_BARO_BMP280
+#define BMP280_SPI_BUS BUS_SPI3
+#define BMP280_CS_PIN PB3 // v1
+// Support external barometers
+#define BARO_I2C_BUS I2C_EXT_BUS
+#define USE_BARO_BMP085
+#define USE_BARO_MS5611
+
+#define PITOT_I2C_BUS I2C_EXT_BUS
+
+#define USE_RANGEFINDER
+#define RANGEFINDER_I2C_BUS I2C_EXT_BUS
+
+#define USE_VCP
+#define VBUS_SENSING_PIN PC5
+#define VBUS_SENSING_ENABLED
+
+#define USE_UART_INVERTER
+
+#define USE_UART1
+#define UART1_RX_PIN PA10
+#define UART1_TX_PIN PA9
+#define UART1_AHB1_PERIPHERALS RCC_AHB1Periph_DMA2
+
+#define USE_UART3
+#define UART3_RX_PIN PB11
+#define UART3_TX_PIN PB10
+
+#define USE_UART6
+#define UART6_RX_PIN PC7
+#define UART6_TX_PIN PC6
+#define INVERTER_PIN_UART6_RX PC8
+#define INVERTER_PIN_UART6_TX PC9
+
+#if defined(OMNIBUSF4V3_S6_SS) // one softserial on S6
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PA8 // S6 output
+#define SOFTSERIAL_1_TX_PIN PA8 // S6 output
+
+#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
+
+#elif defined(OMNIBUSF4V3_S5S6_SS) // one softserial on S5/RX S6/TX
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PA1 // S5 output
+#define SOFTSERIAL_1_TX_PIN PA8 // S6 output
+
+#define SERIAL_PORT_COUNT 5 // VCP, USART1, USART3, USART6, SOFTSERIAL1
+
+#elif defined(OMNIBUSF4V3_S5_S6_2SS) // two softserials, one on S5 and one on S6
+#define USE_SOFTSERIAL1
+#define SOFTSERIAL_1_RX_PIN PA1 // S5 output
+#define SOFTSERIAL_1_TX_PIN PA1 // S5 output
+
+#define USE_SOFTSERIAL2
+#define SOFTSERIAL_2_RX_PIN PA8 // S6 output
+#define SOFTSERIAL_2_TX_PIN PA8 // S6 output
+
+#define SERIAL_PORT_COUNT 6 // VCP, USART1, USART3, USART6, SOFTSERIAL1, SOFTSERIAL2
+#endif
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_SBUS
+#define SERIALRX_UART SERIAL_PORT_USART1
+
+#define USE_SPI
+
+#define USE_SPI_DEVICE_1
+
+#define USE_SPI_DEVICE_2
+#define SPI2_NSS_PIN PB12
+#define SPI2_SCK_PIN PB13
+#define SPI2_MISO_PIN PB14
+#define SPI2_MOSI_PIN PB15
+
+#define USE_SPI_DEVICE_3
+#define SPI3_NSS_PIN PA15
+#define SPI3_SCK_PIN PC10
+#define SPI3_MISO_PIN PC11
+#define SPI3_MOSI_PIN PC12
+
+#define USE_MAX7456
+#define MAX7456_SPI_BUS BUS_SPI3
+#define MAX7456_CS_PIN PA15
+
+#define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT
+#define USE_SDCARD
+#define USE_SDCARD_SPI
+
+#define SDCARD_SPI_BUS BUS_SPI2
+#define SDCARD_CS_PIN SPI2_NSS_PIN
+
+#define SDCARD_DETECT_PIN PB7
+#define SDCARD_DETECT_INVERTED
+
+#define USE_ADC
+#define ADC_CHANNEL_1_PIN PC1
+#define ADC_CHANNEL_2_PIN PC2
+#define ADC_CHANNEL_3_PIN PA0
+
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_1
+#define VBAT_ADC_CHANNEL ADC_CHN_2
+#define RSSI_ADC_CHANNEL ADC_CHN_3
+
+#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
+
+#define USE_LED_STRIP
+#define WS2811_PIN PB6
+
+#define DISABLE_RX_PWM_FEATURE
+#define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX | FEATURE_VBAT | FEATURE_OSD)
+
+#define USE_SPEKTRUM_BIND
+#define BIND_PIN PB11 // USART3 RX
+
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+
+// Number of available PWM outputs
+#define MAX_PWM_OUTPUT_PORTS 6
+#define TARGET_MOTOR_COUNT 6
+#define USE_DSHOT
+#define USE_ESC_SENSOR
+
+#define TARGET_IO_PORTA 0xffff
+#define TARGET_IO_PORTB 0xffff
+#define TARGET_IO_PORTC 0xffff
+#define TARGET_IO_PORTD 0xffff
+
diff --git a/src/main/target/SOLOGOODF722/CMakeLists.txt b/src/main/target/SOLOGOODF722/CMakeLists.txt
new file mode 100644
index 00000000000..59ea48d13d5
--- /dev/null
+++ b/src/main/target/SOLOGOODF722/CMakeLists.txt
@@ -0,0 +1 @@
+target_stm32f722xe(SOLOGOODF722)
diff --git a/src/main/target/SOLOGOODF722/config.c b/src/main/target/SOLOGOODF722/config.c
new file mode 100644
index 00000000000..79a0eb18472
--- /dev/null
+++ b/src/main/target/SOLOGOODF722/config.c
@@ -0,0 +1,28 @@
+/*
+ * 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"
+
+void targetConfiguration(void)
+{
+ pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
+ pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
+}
diff --git a/src/main/target/SOLOGOODF722/target.c b/src/main/target/SOLOGOODF722/target.c
new file mode 100644
index 00000000000..12b14c16b80
--- /dev/null
+++ b/src/main/target/SOLOGOODF722/target.c
@@ -0,0 +1,40 @@
+/*
+ * 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/io.h"
+
+#include "drivers/dma.h"
+#include "drivers/timer.h"
+#include "drivers/timer_def.h"
+
+timerHardware_t timerHardware[] = {
+ DEF_TIM(TIM8, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 0), // S1
+ DEF_TIM(TIM8, CH4, PC9, TIM_USE_OUTPUT_AUTO, 0, 0), // S2
+ DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 1), // S3
+ DEF_TIM(TIM1, CH2, PA9, TIM_USE_OUTPUT_AUTO, 0, 1), // S4
+ DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S5
+ DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // S6
+ DEF_TIM(TIM1, CH3, PA10, TIM_USE_OUTPUT_AUTO, 0, 1), // S7
+ DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), // S8
+
+ DEF_TIM(TIM2, CH2, PB3, TIM_USE_LED, 0, 0), // 2812LED
+};
+
+const int timerHardwareCount = sizeof(timerHardware) / sizeof(timerHardware[0]);
diff --git a/src/main/target/SOLOGOODF722/target.h b/src/main/target/SOLOGOODF722/target.h
new file mode 100644
index 00000000000..dd017691ef3
--- /dev/null
+++ b/src/main/target/SOLOGOODF722/target.h
@@ -0,0 +1,170 @@
+/*
+ * This file is part of INAV Project.
+ *
+ * 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 "SGF7"
+
+#define USBD_PRODUCT_STRING "SOLOGOODF722"
+
+#define LED0 PC15
+
+#define BEEPER PC13
+#define BEEPER_INVERTED
+
+// *************** Gyro & ACC **********************
+#define USE_SPI
+#define USE_SPI_DEVICE_1
+
+#define SPI1_NSS_PIN PA4
+#define SPI1_SCK_PIN PA5
+#define SPI1_MISO_PIN PA6
+#define SPI1_MOSI_PIN PA7
+
+
+#define USE_IMU_MPU6000
+#define IMU_MPU6000_ALIGN CW180_DEG
+#define MPU6000_CS_PIN SPI1_NSS_PIN
+#define MPU6000_SPI_BUS BUS_SPI1
+
+#define USE_IMU_BMI270
+#define IMU_BMI270_ALIGN CW180_DEG
+#define BMI270_CS_PIN SPI1_NSS_PIN
+#define BMI270_SPI_BUS BUS_SPI1
+
+#define USE_IMU_ICM42605
+#define IMU_ICM42605_ALIGN CW180_DEG
+#define ICM42605_CS_PIN SPI1_NSS_PIN
+#define ICM42605_SPI_BUS BUS_SPI1
+
+// *************** I2C/Baro/Mag *********************
+#define USE_I2C
+#define USE_I2C_DEVICE_1
+#define I2C1_SCL PB8
+#define I2C1_SDA PB9
+
+#define DEFAULT_I2C_BUS BUS_I2C1
+
+#define USE_BARO
+#define BARO_I2C_BUS DEFAULT_I2C_BUS
+#define USE_BARO_DPS310
+
+#define USE_MAG
+#define MAG_I2C_BUS DEFAULT_I2C_BUS
+#define USE_MAG_ALL
+
+#define TEMPERATURE_I2C_BUS DEFAULT_I2C_BUS
+
+#define PITOT_I2C_BUS DEFAULT_I2C_BUS
+
+#define USE_RANGEFINDER
+#define RANGEFINDER_I2C_BUS DEFAULT_I2C_BUS
+
+// *************** FLASH **************************
+#define USE_SPI_DEVICE_3
+#define SPI3_NSS_PIN PA15
+#define SPI3_SCK_PIN PC10
+#define SPI3_MISO_PIN PC11
+#define SPI3_MOSI_PIN PB5
+
+#define M25P16_CS_PIN SPI3_NSS_PIN
+#define M25P16_SPI_BUS BUS_SPI3
+#define USE_FLASHFS
+#define USE_FLASH_M25P16
+#define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT
+
+
+// *************** OSD *****************************
+#define USE_SPI_DEVICE_2
+#define SPI2_NSS_PIN PB12
+#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 SPI2_NSS_PIN
+
+// *************** UART *****************************
+#define USE_VCP
+
+#define USE_UART1
+#define UART1_RX_PIN PB7
+#define UART1_TX_PIN PB6
+
+#define USE_UART2
+#define UART2_RX_PIN PA3
+#define UART2_TX_PIN PA2
+
+#define USE_UART3
+#define UART3_RX_PIN PB11
+#define UART3_TX_PIN PB10
+
+#define USE_UART4
+#define UART4_RX_PIN PA1
+#define UART4_TX_PIN PA0
+
+#define USE_UART5
+#define UART5_RX_PIN PD2
+#define UART5_TX_PIN PC12
+
+#define USE_UART6
+#define UART6_RX_PIN PC7
+#define UART6_TX_PIN PC6
+
+#define SERIAL_PORT_COUNT 7
+
+#define DEFAULT_RX_TYPE RX_TYPE_SERIAL
+#define SERIALRX_PROVIDER SERIALRX_CRSF
+#define SERIALRX_UART SERIAL_PORT_USART2
+
+// *************** ADC *****************************
+#define USE_ADC
+#define ADC_INSTANCE ADC1
+#define ADC1_DMA_STREAM DMA2_Stream0
+#define ADC_CHANNEL_1_PIN PC1
+#define ADC_CHANNEL_2_PIN PC3
+
+#define VBAT_ADC_CHANNEL ADC_CHN_1
+#define CURRENT_METER_ADC_CHANNEL ADC_CHN_2
+
+
+#define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_CURRENT_METER | FEATURE_TELEMETRY | FEATURE_VBAT | FEATURE_OSD | FEATURE_BLACKBOX)
+// *************** PINIO ************************
+#define USE_PINIO
+#define USE_PINIOBOX
+#define PINIO1_PIN PC2
+#define PINIO2_PIN PC0
+
+// *************** LEDSTRIP ************************
+#define USE_LED_STRIP
+#define WS2811_PIN PB3
+
+// Others
+
+#define USE_SERIAL_4WAY_BLHELI_INTERFACE
+#define CURRENT_METER_SCALE 250
+
+#define TARGET_IO_PORTA 0xffff
+#define TARGET_IO_PORTB 0xffff
+#define TARGET_IO_PORTC 0xffff
+#define TARGET_IO_PORTD (BIT(2))
+
+#define MAX_PWM_OUTPUT_PORTS 8
+
+#define USE_DSHOT
+#define USE_ESC_SENSOR
diff --git a/src/test/unit/CMakeLists.txt b/src/test/unit/CMakeLists.txt
index 2cb53b64ecb..7ac93335d98 100644
--- a/src/test/unit/CMakeLists.txt
+++ b/src/test/unit/CMakeLists.txt
@@ -40,6 +40,9 @@ set_property(SOURCE osd_unittest.cc PROPERTY definitions OSD_UNIT_TEST USE_MSP_D
set_property(SOURCE gps_ublox_unittest.cc PROPERTY depends "io/gps_ublox_utils.c")
set_property(SOURCE gps_ublox_unittest.cc PROPERTY definitions GPS_UBLOX_UNIT_TEST)
+set_property(SOURCE gps_null_port_unittest.cc PROPERTY depends "io/gps.c")
+set_property(SOURCE gps_null_port_unittest.cc PROPERTY definitions GPS_NULL_PORT_UNIT_TEST USE_GPS_PROTO_UBLOX)
+
set_property(SOURCE gimbal_serial_unittest.cc PROPERTY depends "io/gimbal_serial.c" "drivers/gimbal_common.c" "common/maths.c" "drivers/headtracker_common.c")
set_property(SOURCE gimbal_serial_unittest.cc PROPERTY definitions USE_SERIAL_GIMBAL GIMBAL_UNIT_TEST USE_HEADTRACKER)
diff --git a/src/test/unit/gps_null_port_unittest.cc b/src/test/unit/gps_null_port_unittest.cc
new file mode 100644
index 00000000000..288b68995c9
--- /dev/null
+++ b/src/test/unit/gps_null_port_unittest.cc
@@ -0,0 +1,446 @@
+/*
+ * 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 .
+ */
+
+/*
+ * Reproduction test for the NULL gpsPort hard-fault bug.
+ *
+ * Scenario
+ * --------
+ * The FC boots with a driver-based GPS provider (GPS_MSP / GPS_FAKE).
+ * gpsInit() sets gpsState.gpsPort = NULL because driver-based providers
+ * do not open a serial port. If the user then changes gps_provider via
+ * the CLI to GPS_UBLOX (a serial-based provider) without rebooting, the
+ * next call to gpsUpdate() dispatches into gpsHandleUBLOX(), which calls
+ * serialRxBytesWaiting(gpsState.gpsPort) with a NULL pointer → hard fault
+ * on real hardware.
+ *
+ * There is a second exposure: gpsEnablePassthrough() dereferences
+ * gpsState.gpsPort without a NULL check, but that path requires a
+ * separate passthrough session, so only the gpsUpdate() path is exercised
+ * here.
+ *
+ * How the test encodes the fault
+ * ------------------------------
+ * The stub for serialRxBytesWaiting() calls ADD_FAILURE() whenever it is
+ * invoked with a NULL pointer. Before the fix gpsUpdate() triggers this
+ * failure; after the fix it returns false gracefully without ever calling
+ * into gpsHandleUBLOX().
+ *
+ * Expected result (after the fix)
+ * --------------------------------
+ * gpsUpdate() detects that the active provider is serial-based but
+ * gpsState.gpsPort == NULL and returns false without calling the protocol
+ * handler. The serialRxBytesWaiting stub is never called with NULL.
+ */
+
+#include "gtest/gtest.h"
+#include "unittest_macros.h"
+
+#include
+#include
+#include
+
+extern "C" {
+
+/* Pull in the types we need. */
+#include "common/time.h"
+
+#include "config/parameter_group.h"
+#include "config/parameter_group_ids.h"
+
+#include "drivers/serial.h"
+
+/* fc/config.h provides the FEATURE_GPS enum value. */
+#include "fc/config.h"
+#include "fc/runtime_config.h"
+
+#include "io/gps.h"
+#include "io/gps_private.h"
+
+/* Expose the globals defined in gps.c so the test can manipulate them. */
+extern gpsReceiverData_t gpsState;
+extern gpsSolutionData_t gpsSolDRV;
+extern gpsSolutionData_t gpsSol;
+
+/* PG_DECLARE in gps.h expands to:
+ * extern gpsConfig_t gpsConfig_System;
+ * static inline const gpsConfig_t *gpsConfig(void) { return &gpsConfig_System; }
+ * gps.c's PG_REGISTER_WITH_RESET_TEMPLATE provides the storage. */
+extern gpsConfig_t gpsConfig_System;
+
+/* ---- serial infrastructure stubs ------------------------------------ */
+/* io/serial.c normally provides these; we stub them since we do not */
+/* compile serial.c as part of this test. */
+
+/* baudRates[] maps baudRate_e enum values to integer baud rates. */
+/* Must match the baudRate_e enum in io/serial.h (BAUD_AUTO through */
+/* BAUD_2470000). */
+const uint32_t baudRates[] = { 0, 1200, 2400, 4800, 9600, 19200, 38400,
+ 57600, 115200, 230400, 250000, 460800, 921600,
+ 1000000, 1500000, 2000000, 2470000 }; /* see baudRate_e */
+
+/* serialConfig_System is the PG storage for serialConfig(). */
+/* gpsInit() accesses it; gpsUpdate() does not, but the linker needs it */
+/* because gpsInit() lives in the same translation unit as gpsUpdate(). */
+serialConfig_t serialConfig_System;
+
+/* ---- test-observable state -------------------------------------------- */
+static bool serialRxBytesWaitingCalledWithNull = false;
+
+/* ---- millis() controllable clock -------------------------------------- */
+static uint32_t fakeMillis = 0;
+
+uint32_t millis(void) { return fakeMillis; }
+uint32_t micros(void) { return fakeMillis * 1000UL; }
+
+/* ---- feature / sensors stubs ----------------------------------------- */
+static uint32_t fakeFeatureMask = 0;
+
+bool feature(uint32_t mask) { return (fakeFeatureMask & mask) != 0; }
+void featureSet(uint32_t mask) { fakeFeatureMask |= mask; }
+void featureClear(uint32_t mask) { fakeFeatureMask &= ~mask; }
+
+static uint32_t fakeSensorMask = 0;
+
+bool sensors(uint32_t mask) { return (fakeSensorMask & mask) != 0; }
+void sensorsSet(uint32_t mask) { fakeSensorMask |= mask; }
+void sensorsClear(uint32_t mask) { fakeSensorMask &= ~mask; }
+
+/* ---- serial stubs ----------------------------------------------------- */
+
+/*
+ * This is the critical mock. On real hardware, if gpsState.gpsPort is NULL
+ * the firmware hard-faults when the serial driver dereferences the pointer.
+ * In the unit test we record the call and fail the test instead.
+ *
+ * Before the fix: gpsUpdate() reaches this with a NULL pointer → failure.
+ * After the fix: gpsUpdate() returns before calling this → no failure.
+ */
+uint32_t serialRxBytesWaiting(const serialPort_t *instance)
+{
+ if (instance == NULL) {
+ serialRxBytesWaitingCalledWithNull = true;
+ ADD_FAILURE() << "serialRxBytesWaiting() called with NULL gpsPort — "
+ "this would hard-fault on real hardware";
+ }
+ return 0;
+}
+
+uint32_t serialTxBytesFree(const serialPort_t *instance)
+{
+ UNUSED(instance);
+ return 256;
+}
+
+bool isSerialTransmitBufferEmpty(const serialPort_t *instance)
+{
+ UNUSED(instance);
+ return true;
+}
+
+uint8_t serialRead(serialPort_t *instance)
+{
+ UNUSED(instance);
+ return 0;
+}
+
+void serialWrite(serialPort_t *instance, uint8_t ch)
+{
+ UNUSED(instance);
+ UNUSED(ch);
+}
+
+void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count)
+{
+ UNUSED(instance);
+ UNUSED(data);
+ UNUSED(count);
+}
+
+void serialPrint(serialPort_t *instance, const char *str)
+{
+ UNUSED(instance);
+ UNUSED(str);
+}
+
+void serialSetMode(serialPort_t *instance, portMode_t mode)
+{
+ UNUSED(instance);
+ UNUSED(mode);
+}
+
+void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate)
+{
+ UNUSED(instance);
+ UNUSED(baudRate);
+}
+
+serialPort_t *openSerialPort(serialPortIdentifier_e identifier,
+ serialPortFunction_e function,
+ serialReceiveCallbackPtr rxCallback,
+ void *rxCallbackData,
+ uint32_t baudRate, portMode_t mode,
+ portOptions_t options)
+{
+ UNUSED(identifier); UNUSED(function); UNUSED(rxCallback);
+ UNUSED(rxCallbackData); UNUSED(baudRate); UNUSED(mode); UNUSED(options);
+ return NULL;
+}
+
+void closeSerialPort(serialPort_t *serialPort) { UNUSED(serialPort); }
+
+serialPortConfig_t *findSerialPortConfig(serialPortFunction_e function)
+{
+ UNUSED(function);
+ return NULL;
+}
+
+void waitForSerialPortToFinishTransmitting(serialPort_t *serialPort)
+{
+ UNUSED(serialPort);
+}
+
+/* ---- stateFlags / armingFlags stubs ---------------------------------- */
+uint32_t stateFlags = 0;
+uint32_t armingFlags = 0;
+
+/* ---- system stubs ----------------------------------------------------- */
+bool isMPUSoftReset(void) { return false; }
+
+/* ---- LED stubs -------------------------------------------------------- */
+void LED0_ON(void) {}
+void LED0_OFF(void) {}
+void LED1_ON(void) {}
+void LED1_OFF(void) {}
+void LED1_TOGGLE(void) {}
+
+/* ---- navigation / sensor stubs --------------------------------------- */
+void onNewGPSData(void) {}
+
+bool rtcHasTime(void) { return false; }
+bool rtcSetDateTime(dateTime_t *dt) { UNUSED(dt); return true; }
+
+bool baroIsHealthy(void) { return false; }
+bool pitotIsHealthy(void) { return false; }
+
+/* ---- UBLOX protocol stubs -------------------------------------------- */
+/*
+ * gps.c references gpsRestartUBLOX() and gpsHandleUBLOX() which are
+ * normally provided by gps_ublox.c. We do NOT compile gps_ublox.c
+ * (it carries too many dependencies), so we stub them here.
+ *
+ * The stubs are intentionally minimal: gpsRestartUBLOX() does nothing
+ * (the test checks the guard before restart is called), and
+ * gpsHandleUBLOX() calls serialRxBytesWaiting() to simulate the exact
+ * operation that hard-faults in production.
+ */
+void gpsRestartUBLOX(void)
+{
+ /* In the production code this resets the protothread state and starts
+ * the receiver/state threads. For the test we do nothing: the guard
+ * under test should prevent this function from being reached when
+ * gpsPort is NULL. */
+}
+
+void gpsHandleUBLOX(void)
+{
+ /*
+ * In production this runs the protothread that immediately calls
+ * serialRxBytesWaiting(gpsState.gpsPort). Replicate that first
+ * operation here so the test observes the NULL dereference if the
+ * guard in gpsUpdate() is absent.
+ */
+ (void)serialRxBytesWaiting(gpsState.gpsPort);
+}
+
+/* ---- MSP / FAKE GPS stubs (not compiled in test target, but needed
+ * for link completeness if the linker sees the symbols) ----------- */
+/* These are guarded by USE_GPS_PROTO_MSP / USE_GPS_FAKE in gps.c so they
+ * will not appear in the compilation unit. Nothing to stub. */
+
+} // extern "C"
+
+/* ======================================================================
+ * Test fixture
+ * ====================================================================== */
+
+class GpsNullPortTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ /* Reset observable state. */
+ serialRxBytesWaitingCalledWithNull = false;
+
+ /* Enable FEATURE_GPS so gpsUpdate() does not exit early. */
+ featureSet(FEATURE_GPS);
+
+ /*
+ * Advance millis() past GPS_BOOT_DELAY (3000 ms). gpsUpdate()
+ * has a boot-delay guard: if millis() < GPS_BOOT_DELAY it returns
+ * false immediately without exercising the state machine.
+ */
+ fakeMillis = 5000;
+
+ /* Clear state/arming flags. */
+ stateFlags = 0;
+ armingFlags = 0;
+
+ /* Initialise solution structs. */
+ memset(&gpsSol, 0, sizeof(gpsSol));
+ memset(&gpsSolDRV, 0, sizeof(gpsSolDRV));
+
+ /*
+ * Set up the GPS config for a serial-based provider (GPS_UBLOX).
+ * gpsConfig_System is the storage used by the PG accessor
+ * gpsConfig() defined in gps.h and allocated by gps.c.
+ */
+ memset(&gpsConfig_System, 0, sizeof(gpsConfig_System));
+ gpsConfig_System.provider = GPS_UBLOX;
+
+ /* Point gpsState at the config and set sane defaults. */
+ gpsState.gpsConfig = &gpsConfig_System;
+ gpsState.baseTimeoutMs = 1000;
+ /* lastMessageMs == fakeMillis: no timeout yet. */
+ gpsState.lastMessageMs = fakeMillis;
+
+ /*
+ * Simulate the buggy scenario: the FC booted with a driver-based
+ * provider (e.g. GPS_MSP), so gpsInit() left gpsPort as NULL
+ * (no serial port was opened). The user then changed provider
+ * to GPS_UBLOX via CLI without rebooting.
+ */
+ gpsState.gpsPort = NULL;
+ }
+
+ void TearDown() override {
+ fakeFeatureMask = 0;
+ fakeSensorMask = 0;
+ }
+};
+
+/* ======================================================================
+ * Test 1 — Bug reproduction: GPS_RUNNING + NULL port + serial provider
+ *
+ * The state machine is placed directly into GPS_RUNNING so the protocol
+ * handler (gpsHandleUBLOX) is called on the very first gpsUpdate().
+ *
+ * Before the fix: gpsHandleUBLOX() calls serialRxBytesWaiting(NULL)
+ * → ADD_FAILURE() fires inside the stub.
+ *
+ * After the fix: gpsUpdate() detects the NULL port for a serial-based
+ * provider and returns false before reaching gpsHandleUBLOX().
+ * ====================================================================== */
+TEST_F(GpsNullPortTest, NullPortInRunningState_ShouldNotCrash)
+{
+ /* Place the state machine in GPS_RUNNING so the protocol handler
+ * is invoked immediately, skipping the INITIALIZING delay. */
+ gpsState.state = GPS_RUNNING;
+ gpsState.lastStateSwitchMs = fakeMillis - 600; /* > GPS_INIT_DELAY (500 ms) */
+
+ bool result = gpsUpdate();
+
+ /*
+ * The key assertion: serialRxBytesWaiting must NOT have been called
+ * with NULL. If it was, ADD_FAILURE() already recorded the failure
+ * in the stub; this EXPECT makes the intent explicit.
+ */
+ EXPECT_FALSE(serialRxBytesWaitingCalledWithNull)
+ << "gpsUpdate() called serialRxBytesWaiting(NULL) — "
+ "the null-port guard is missing from gpsUpdate()";
+
+ /* With the guard in place gpsUpdate() returns false (no new data). */
+ EXPECT_FALSE(result)
+ << "gpsUpdate() should return false when gpsPort is NULL "
+ "for a serial-based provider";
+}
+
+/* ======================================================================
+ * Test 2 — Bug reproduction: INITIALIZING → RUNNING transition
+ *
+ * Same scenario starting from GPS_INITIALIZING. After the init delay
+ * expires, gpsUpdate() calls restart() and transitions to GPS_RUNNING,
+ * then breaks — protocol() is NOT called in that same tick. The dangerous
+ * serialRxBytesWaiting(NULL) would only fire on the next entry when state
+ * is already GPS_RUNNING (covered by Test 1).
+ *
+ * The pre-switch guard fires before restart() is reached, so a stale
+ * GPS_INITIALIZING state left by the boot-with-driver-based scenario is
+ * also protected.
+ * ====================================================================== */
+TEST_F(GpsNullPortTest, NullPortTransitionFromInitializing_ShouldNotCrash)
+{
+ /* Start in INITIALIZING with the delay already expired. */
+ gpsState.state = GPS_INITIALIZING;
+ gpsState.lastStateSwitchMs = fakeMillis - 1000; /* >> GPS_INIT_DELAY (500 ms) */
+
+ bool result = gpsUpdate();
+
+ EXPECT_FALSE(serialRxBytesWaitingCalledWithNull)
+ << "gpsUpdate() called serialRxBytesWaiting(NULL) during "
+ "INITIALIZING→RUNNING transition";
+
+ EXPECT_FALSE(result);
+}
+
+/* ======================================================================
+ * Test 3 — Positive case: serial provider + non-NULL port
+ *
+ * When gpsPort is valid, gpsUpdate() must proceed normally. The guard
+ * must not block a legitimate call.
+ * ====================================================================== */
+TEST_F(GpsNullPortTest, NonNullPortSerialProvider_GuardDoesNotBlock)
+{
+ /* Provide a fake non-NULL port. The serial stubs accept any pointer. */
+ static serialPort_t fakePort;
+ gpsState.gpsPort = &fakePort;
+ gpsState.state = GPS_RUNNING;
+ gpsState.lastStateSwitchMs = fakeMillis - 600;
+
+ /*
+ * gpsUpdate() is allowed to call into gpsHandleUBLOX() here.
+ * gpsHandleUBLOX() passes &fakePort to serialRxBytesWaiting(), which
+ * is non-NULL, so the failure path in the stub is not triggered.
+ */
+ gpsUpdate();
+
+ EXPECT_FALSE(serialRxBytesWaitingCalledWithNull)
+ << "serialRxBytesWaiting was unexpectedly called with NULL "
+ "even though gpsPort was non-NULL";
+}
+
+/* ======================================================================
+ * Test 4 — Negative: FEATURE_GPS disabled
+ *
+ * If the GPS feature is not enabled, gpsUpdate() must return false
+ * immediately without touching gpsPort at all. This is an existing
+ * guard, but we verify it is not broken by the new guard.
+ * ====================================================================== */
+TEST_F(GpsNullPortTest, FeatureGpsDisabled_ReturnsEarlyWithoutTouchingPort)
+{
+ featureClear(FEATURE_GPS);
+
+ gpsState.state = GPS_RUNNING;
+ gpsState.lastStateSwitchMs = fakeMillis - 600;
+ /* gpsPort stays NULL — any dereference would fire the stub. */
+
+ bool result = gpsUpdate();
+
+ EXPECT_FALSE(serialRxBytesWaitingCalledWithNull)
+ << "serialRxBytesWaiting was called with NULL even though "
+ "FEATURE_GPS was disabled";
+
+ EXPECT_FALSE(result);
+}
diff --git a/src/utils/bf2inav.py b/src/utils/bf2inav.py
index 5ccbb6b4ad6..8309cb1d756 100755
--- a/src/utils/bf2inav.py
+++ b/src/utils/bf2inav.py
@@ -450,6 +450,13 @@ def writeTargetH(folder, map):
pin = findPinByFunction('MAX7456_SPI_CS', map)
file.write("#define MAX7456_CS_PIN %s\n" % (pin))
file.write("#define MAX7456_SPI_BUS BUS_%s\n" % (map['defines']['MAX7456_SPI_INSTANCE']))
+
+ pin = findPinByFunction('CAMERA_CONTROL', map)
+ if pin:
+ file.write("// CAMERA_CONTROL_PIN has no consuming driver prior to 10.0. From 10.0\n")
+ file.write("// forward this will be set up as a PWM-capable PINIO pin instead.\n")
+ file.write("#define CAMERA_CONTROL_PIN %s\n" % (pin))
+
file.write("// Blackbox\n")
# Flash:
@@ -488,9 +495,10 @@ def writeTargetH(folder, map):
file.write("#define SDCARD_SDIO_4BIT\n")
file.write("#define SDCARD_SDIO_DEVICE SDIODEV_%i\n" % (i))
- # PINIO
+ # PINIO_COUNT is 4 (BOX_PERMANENT_ID_USER1..USER4) -- don't emit more than firmware
+ # can actually wire up in writeConfigC below.
use_pinio = False
- for i in range(1, 9):
+ for i in range(1, 5):
pinio = findPinByFunction("PINIO%i" % (i), map)
if pinio != None:
if not use_pinio:
@@ -725,9 +733,13 @@ def writeConfigC(folder, map):
void targetConfiguration(void)
{
""")
- #//pinioBoxConfigMutable()->permanentId[0] = BOX_PERMANENT_ID_USER1;
- #//pinioBoxConfigMutable()->permanentId[1] = BOX_PERMANENT_ID_USER2;
- #//beeperConfigMutable()->pwmMode = true;
+ # PINIO_COUNT is 4 (BOX_PERMANENT_ID_USER1..USER4) -- don't write more than that
+ # even if Betaflight's source somehow lists more PINIO pins than INAV supports.
+ for i in range(1, 5):
+ pinio = findPinByFunction("PINIO%i" % (i), map)
+ if pinio != None:
+ file.write(" pinioBoxConfigMutable()->permanentId[%i] = BOX_PERMANENT_ID_USER%i;\n" % (i - 1, i))
+
file.write("""
}