Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a6ba116
WIP: Attempt circular DShot DMA during settings save
sensei-hacker Jan 18, 2026
d32cf25
Fix ESC spinup during MSP settings save (#10913)
sensei-hacker Jan 18, 2026
4249b55
Address code review feedback for ESC spinup fix
sensei-hacker Jan 18, 2026
ebcd802
Fix H7 circular DMA implementation - wait for stream disable
sensei-hacker Jan 19, 2026
9c2c1f4
Fix SITL build: Guard DShot circular DMA calls with SITL_BUILD check
sensei-hacker Jan 19, 2026
eda9f22
Add DMA disable timeout handling to all platforms
sensei-hacker Jan 19, 2026
c7b342d
Fix build for targets without DShot: Add USE_DSHOT guard
sensei-hacker Jan 20, 2026
e7075c5
Remove unnecessary comments
sensei-hacker Jan 20, 2026
31a5b38
pid/filter: add FAST_CODE to pidController callees
sensei-hacker Feb 18, 2026
81d596a
fc/pid: avoid recomputing values that change at lower rates
sensei-hacker Feb 18, 2026
2be65e0
drivers: wait for DMA EN bit to clear before reconfiguring DShot DMA
sensei-hacker Mar 2, 2026
52ba52b
drivers: fix DMA EN-bit timeout path — set dmaState IDLE and avoid co…
sensei-hacker Mar 2, 2026
0735c57
drivers: clarify EN-bit timeout comment — skipped frame, not hardware…
sensei-hacker Mar 2, 2026
76cc064
drivers: tighten EN-bit timeout comment — concise and accurate
sensei-hacker Mar 2, 2026
7801a8e
Fix circular DMA reliability: IRQ handler guard, data length reload, …
sensei-hacker Mar 19, 2026
6af160d
Fix forward declaration guards for loadDmaBufferDshot/Stride
sensei-hacker Mar 20, 2026
2ed1096
Flash supports MX35LF2G model
RunnyCow Apr 17, 2026
52f0d68
Clean up and fix OSD stats
breadoven Apr 17, 2026
6bdc98b
Update osd.c
breadoven Apr 18, 2026
7e824ee
fix Save Setting message for stats screen
breadoven Apr 18, 2026
79ebed8
Merge pull request #11506 from breadoven/abo_osd_stats_fixes
sensei-hacker Apr 26, 2026
ebf10d7
allow yaw reset to 0 with no mag
breadoven May 2, 2026
50f8353
fixes
breadoven May 2, 2026
73d9e07
BLUEBERRYF405: add PWM beeper timer entry for PB9
sensei-hacker May 3, 2026
28ab300
Merge pull request #11505 from RunnyCow/master
sensei-hacker May 3, 2026
c220198
Merge pull request #11531 from sensei-hacker/fix-pwm-beeper-mode-regr…
sensei-hacker May 3, 2026
3303405
drivers: use NAND-standard opcodes for W25N register read/write
sensei-hacker May 3, 2026
36c323f
Merge pull request #11533 from sensei-hacker/fix-nand-register-write-…
sensei-hacker May 4, 2026
8bd85d7
lucid: add more baros, enable led strip, align gyo orientations
bkleiner Apr 21, 2026
e309823
Update Controls.md
breadoven May 4, 2026
49ef6fc
Fix DMA disable timeout: uint32_t wraparound made abort path unreachable
sensei-hacker May 6, 2026
7f0a225
Merge pull request #11260 from sensei-hacker/fix/esc-spinup-circular-…
sensei-hacker May 6, 2026
cf28e0f
multifunction improvement
breadoven May 7, 2026
8bd9fdd
tweaks
breadoven May 8, 2026
2c4aa6d
Update osd.c
breadoven May 9, 2026
eb86f4e
Merge pull request #11543 from breadoven/abo_multifunc_improvement
breadoven May 10, 2026
c5be817
Merge branch 'maintenance-9.x' into abo_magless_heading_zero
breadoven May 10, 2026
71f9ec1
Merge pull request #11390 from sensei-hacker/fix/f7-dshot-dma-en-bit-…
sensei-hacker May 11, 2026
ca803eb
Merge pull request #11357 from sensei-hacker/perf/pid-fast-code-and-c…
sensei-hacker May 11, 2026
f5bd7be
multifunction fixes
breadoven May 11, 2026
451c95e
Merge pull request #11549 from breadoven/abo_multifunction_fixes
breadoven May 11, 2026
bffd6f3
Merge branch 'maintenance-9.x' into abo_magless_heading_zero
breadoven May 11, 2026
07cd33f
Add mag calibration to multifunction
breadoven May 11, 2026
9cf6945
Update imu.c
breadoven May 11, 2026
0b92214
Merge pull request #11529 from breadoven/abo_magless_heading_zero
breadoven May 12, 2026
49e2776
multifunction refactor
breadoven May 12, 2026
308b243
Merge pull request #11552 from breadoven/abo_multifunction_refactor
breadoven May 14, 2026
5837d2c
Revert "use explicitly assign motor and servo timers before auto time…
sensei-hacker May 15, 2026
824f582
Merge pull request #11559 from sensei-hacker/revert/pwm-timer-priority
sensei-hacker May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/Controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The stick positions are combined to activate different functions:
| Battery profile 3 | HIGH | LOW | CENTER | HIGH |
| Calibrate Gyro | LOW | LOW | LOW | CENTER |
| Calibrate Acc | HIGH | LOW | LOW | CENTER |
| Calibrate Mag/Compass | HIGH | HIGH | LOW | CENTER |
| Calibrate Compass/Zero Yaw | HIGH | HIGH | LOW | CENTER |
| Trim Acc Left | HIGH | CENTER | CENTER | LOW |
| Trim Acc Right | HIGH | CENTER | CENTER | HIGH |
| Trim Acc Forwards | HIGH | CENTER | HIGH | CENTER |
Expand All @@ -52,6 +52,14 @@ The stick positions are combined to activate different functions:
For graphical stick position in all transmitter modes, check out [this page](https://www.mrd-rc.com/tutorials-tools-and-testing/inav-flight/inav-stick-commands-for-all-transmitter-modes/).
![Stick Positions](assets/images/StickPositions.png)

## Compass Calibration and Yaw Zero Reset

The stick function `Calibrate Compass/Zero Yaw` provides 2 functions depending on whether or not a compass is available.

If a compass is available the stick function initiates the compass calibration routine.

If no compass is available the stick function will reset the current yaw/heading estimate to zero (North) and also set the heading as trusted. This is useful on multirotors, allowing the craft yaw/heading to be correctly aligned to actual North simply by physically pointing the craft North then using the stick function to zero the yaw estimate. Since this also sets the heading as trusted Nav modes reliant on heading will be available immediately after arming without the need to fly fast enough to obtain a valid heading from GPS ground course.

## Yaw control

While arming/disarming with sticks, your yaw stick will be moving to extreme values. In order to prevent your craft from trying to yaw during arming/disarming while on the ground, your yaw input will not cause the craft to yaw when the throttle is LOW (i.e. below the `min_check` setting).
Expand Down
2 changes: 1 addition & 1 deletion src/main/common/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ float nullFilterApply4(void *filter, float input, float f_cut, float dt)

// PT1 Low Pass filter

static float pt1ComputeRC(const float f_cut)
static float FAST_CODE pt1ComputeRC(const float f_cut)
{
return 1.0f / (2.0f * M_PIf * f_cut);
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/common/maths.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ int32_t applyDeadbandRescaled(int32_t value, int32_t deadband, int32_t min, int3
return value;
}

int32_t constrain(int32_t amt, int32_t low, int32_t high)
int32_t FAST_CODE constrain(int32_t amt, int32_t low, int32_t high)
{
if (amt < low)
return low;
Expand All @@ -175,7 +175,7 @@ int32_t constrain(int32_t amt, int32_t low, int32_t high)
return amt;
}

float constrainf(float amt, float low, float high)
float FAST_CODE constrainf(float amt, float low, float high)
{
if (amt < low)
return low;
Expand Down Expand Up @@ -225,7 +225,7 @@ int scaleRange(int x, int srcMin, int srcMax, int destMin, int destMax) {
return ((a / b) + destMin);
}

float scaleRangef(float x, float srcMin, float srcMax, float destMin, float destMax) {
float FAST_CODE scaleRangef(float x, float srcMin, float srcMax, float destMin, float destMax) {
float a = (destMax - destMin) * (x - srcMin);
float b = srcMax - srcMin;
return ((a / b) + destMin);
Expand Down
12 changes: 12 additions & 0 deletions src/main/config/config_eeprom.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "drivers/system.h"
#include "drivers/flash.h"
#include "drivers/pwm_output.h"

#include "fc/config.h"

Expand Down Expand Up @@ -321,6 +322,13 @@ static bool writeSettingsToEEPROM(void)

void writeConfigToEEPROM(void)
{
#if !defined(SITL_BUILD) && defined(USE_DSHOT)
// Enable circular DMA so hardware keeps repeating zero-throttle DShot
// packets during flash writes (which block the CPU for 20-200ms).
// Without this, ESCs lose signal and may spin up or reboot.
pwmSetMotorDMACircular(true);
#endif

bool success = false;
// write it
for (int attempt = 0; attempt < 3 && !success; attempt++) {
Expand All @@ -333,6 +341,10 @@ void writeConfigToEEPROM(void)
}
}

#if !defined(SITL_BUILD) && defined(USE_DSHOT)
pwmSetMotorDMACircular(false);
#endif

if (success && isEEPROMContentValid()) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/drivers/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ typedef struct flashGeometry_s {
uint32_t totalSize; // This is just sectorSize * sectors
uint16_t pagesPerSector;
flashType_e flashType;
int32_t bbReplacementBlocks;
uint8_t bblutTableEntryCount; // Used by W25N_BBLUT_TABLE_ENTRY_COUNT for bad-block scanning
} flashGeometry_t;

typedef struct
Expand Down
37 changes: 24 additions & 13 deletions src/main/drivers/flash_w25n.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

#include "platform.h"

#if defined(USE_FLASH_W25N01G) || defined(USE_FLASH_W25N02K)
#if defined(USE_FLASH_W25N01G) || defined(USE_FLASH_W25N02K) || defined(USE_FLASH_MX35LF2G)

#include "drivers/bus.h"
#include "drivers/io.h"
Expand All @@ -37,26 +37,24 @@

#define W25N01GV_BLOCKS_PER_DIE 1024
#define W25N02KV_BLOCKS_PER_DIE 2048

#define MX35LF2G_BLOCKS_PER_DIE 2048


// BB replacement area
#define W25N_BB_MARKER_BLOCKS 1
#define W25N_BB_REPLACEMENT_BLOCKS 20
#define W25N_BB_REPLACEMENT_BLOCKS (geometry.bbReplacementBlocks)
#define W25N_BB_MANAGEMENT_BLOCKS (W25N_BB_REPLACEMENT_BLOCKS + W25N_BB_MARKER_BLOCKS)

// blocks are zero-based index
#define W25N_BB_REPLACEMENT_START_BLOCK (W25N_BLOCKS_PER_DIE - W25N_BB_REPLACEMENT_BLOCKS)
#define W25N_BB_MANAGEMENT_START_BLOCK (W25N_BLOCKS_PER_DIE - W25N_BB_MANAGEMENT_BLOCKS)
#define W25N_BB_REPLACEMENT_START_BLOCK (geometry.sectors - W25N_BB_REPLACEMENT_BLOCKS)
#define W25N_BB_MANAGEMENT_START_BLOCK (geometry.sectors - W25N_BB_MANAGEMENT_BLOCKS)
#define W25N_BB_MARKER_BLOCK (W25N_BB_REPLACEMENT_START_BLOCK - W25N_BB_MARKER_BLOCKS)

// Instructions
#define W25N_INSTRUCTION_RDID 0x9F
#define W25N_INSTRUCTION_DEVICE_RESET 0xFF
#define W25N_INSTRUCTION_READ_STATUS_REG 0x05
#define W25N_INSTRUCTION_READ_STATUS_ALTERNATE_REG 0x0F
#define W25N_INSTRUCTION_WRITE_STATUS_REG 0x01
#define W25N_INSTRUCTION_WRITE_STATUS_ALTERNATE_REG 0x1F
#define W25N_INSTRUCTION_READ_STATUS_REG 0x0F
#define W25N_INSTRUCTION_WRITE_STATUS_REG 0x1F
#define W25N_INSTRUCTION_WRITE_ENABLE 0x06
#define W25N_INSTRUCTION_DIE_SELECT 0xC2
#define W25N_INSTRUCTION_BLOCK_ERASE 0xD8
Expand Down Expand Up @@ -99,7 +97,7 @@
#define W25N_STATUS_ERASE_FAIL (1 << 2)
#define W25N_STATUS_FLAG_WRITE_ENABLED (1 << 1)
#define W25N_STATUS_FLAG_BUSY (1 << 0)
#define W25N_BBLUT_TABLE_ENTRY_COUNT 20
#define W25N_BBLUT_TABLE_ENTRY_COUNT geometry.bblutTableEntryCount
#define W25N_BBLUT_TABLE_ENTRY_SIZE 4 // in bytes

// Bits in LBA for BB LUT
Expand All @@ -110,8 +108,8 @@
// Some useful defs and macros
#define W25N_LINEAR_TO_COLUMN(laddr) ((laddr) % W25N_PAGE_SIZE)
#define W25N_LINEAR_TO_PAGE(laddr) ((laddr) / W25N_PAGE_SIZE)
#define W25N_LINEAR_TO_BLOCK(laddr) (W25N_LINEAR_TO_PAGE(laddr) / W25N_PAGES_PER_BLOCK)
#define W25N_BLOCK_TO_PAGE(block) ((block) * W25N_PAGES_PER_BLOCK)
#define W25N_LINEAR_TO_BLOCK(laddr) (W25N_LINEAR_TO_PAGE(laddr) / geometry.pagesPerSector)
#define W25N_BLOCK_TO_PAGE(block) ((block) * geometry.pagesPerSector)
#define W25N_BLOCK_TO_LINEAR(block) (W25N_BLOCK_TO_PAGE(block) * W25N_PAGE_SIZE)

// IMPORTANT: Timeout values are currently required to be set to the highest value required by any of the supported flash chips by this driver
Expand All @@ -129,6 +127,7 @@
// JEDEC ID
#define JEDEC_ID_WINBOND_W25N01GV 0xEFAA21
#define JEDEC_ID_WINBOND_W25N02KV 0xEFAA22
#define JEDEC_ID_MACRONIX_MX35LF2G 0xC22603

static busDevice_t *busDev = NULL;
static flashGeometry_t geometry;
Expand Down Expand Up @@ -242,19 +241,31 @@ bool w25n_detect(uint32_t chipID)
geometry.sectors = W25N01GV_BLOCKS_PER_DIE; // Blocks
geometry.pagesPerSector = W25N_PAGES_PER_BLOCK; // Pages/Blocks
geometry.pageSize = W25N_PAGE_SIZE;
geometry.bbReplacementBlocks = 20;
geometry.bblutTableEntryCount = 20;
break;
case JEDEC_ID_WINBOND_W25N02KV:
geometry.sectors = W25N02KV_BLOCKS_PER_DIE; // Blocks
geometry.pagesPerSector = W25N_PAGES_PER_BLOCK; // Pages/Blocks
geometry.pageSize = W25N_PAGE_SIZE;
geometry.bbReplacementBlocks = 20;
geometry.bblutTableEntryCount = 20;
break;
case JEDEC_ID_MACRONIX_MX35LF2G:
geometry.sectors = MX35LF2G_BLOCKS_PER_DIE; // Blocks
geometry.pagesPerSector = W25N_PAGES_PER_BLOCK; // Pages/Blocks
geometry.pageSize = W25N_PAGE_SIZE;
geometry.bbReplacementBlocks = 40;
geometry.bblutTableEntryCount = 40;
break;

default:
// Unsupported chip
geometry.sectors = 0;
geometry.pagesPerSector = 0;
geometry.sectorSize = 0;
geometry.totalSize = 0;
geometry.bbReplacementBlocks = 0;
geometry.bblutTableEntryCount = 0;
return false;
}

Expand Down
134 changes: 77 additions & 57 deletions src/main/drivers/pwm_mapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,23 +270,43 @@ uint8_t pwmClaimTimer(HAL_Timer_t *tim, uint32_t usageFlags) {
return changed;
}

static void pwmAssignOutput(timMotorServoHardware_t *timOutputs, timerHardware_t *timHw, int type)
void pwmEnsureEnoughtMotors(uint8_t motorCount)
{
switch (type) {
case MAP_TO_MOTOR_OUTPUT:
timHw->usageFlags &= TIM_USE_MOTOR;
timOutputs->timMotors[timOutputs->maxTimMotorCount++] = timHw;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
case MAP_TO_SERVO_OUTPUT:
timHw->usageFlags &= TIM_USE_SERVO;
timOutputs->timServos[timOutputs->maxTimServoCount++] = timHw;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
case MAP_TO_LED_OUTPUT:
timHw->usageFlags &= TIM_USE_LED;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
uint8_t motorOnlyOutputs = 0;

for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardware_t *timHw = &timerHardware[idx];

timerHardwareOverride(timHw);

if (checkPwmTimerConflicts(timHw)) {
continue;
}

if (TIM_IS_MOTOR_ONLY(timHw->usageFlags)) {
motorOnlyOutputs++;
motorOnlyOutputs += pwmClaimTimer(timHw->tim, timHw->usageFlags);
}
}

for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardware_t *timHw = &timerHardware[idx];

if (checkPwmTimerConflicts(timHw)) {
continue;
}

if (TIM_IS_MOTOR(timHw->usageFlags) && !TIM_IS_MOTOR_ONLY(timHw->usageFlags)) {
if (motorOnlyOutputs < motorCount) {
timHw->usageFlags &= ~TIM_USE_SERVO;
timHw->usageFlags |= TIM_USE_MOTOR;
motorOnlyOutputs++;
motorOnlyOutputs += pwmClaimTimer(timHw->tim, timHw->usageFlags);
} else {
timHw->usageFlags &= ~TIM_USE_MOTOR;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
}
}
}
}

Expand All @@ -296,54 +316,54 @@ void pwmBuildTimerOutputList(timMotorServoHardware_t * timOutputs, bool isMixerU
timOutputs->maxTimMotorCount = 0;
timOutputs->maxTimServoCount = 0;

const uint8_t motorCount = getMotorCount();
uint8_t motorCount = getMotorCount();
uint8_t motorIdx = 0;

// Apply configurator overrides to all timer outputs
for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardwareOverride(&timerHardware[idx]);
}
pwmEnsureEnoughtMotors(motorCount);

// Assign outputs in priority order: dedicated first, then auto.
// Within each pass, array order (S1, S2, ...) is preserved.
// Motors and servos cannot share a timer, enforced by pwmHasMotorOnTimer/pwmHasServoOnTimer.
for (int priority = 0; priority < 2; priority++) {
uint8_t motorIdx = timOutputs->maxTimMotorCount;
for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardware_t *timHw = &timerHardware[idx];

for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardware_t *timHw = &timerHardware[idx];
outputMode_e mode = timerOverrides(timer2id(timHw->tim))->outputMode;
bool isDedicated = (priority == 0);
int type = MAP_TO_NONE;

if (checkPwmTimerConflicts(timHw)) {
if (priority == 0) {
LOG_WARNING(PWM, "Timer output %d skipped", idx);
}
continue;
}
// Check for known conflicts (i.e. UART, LEDSTRIP, Rangefinder and ADC)
if (checkPwmTimerConflicts(timHw)) {
LOG_WARNING(PWM, "Timer output %d skipped", idx);
continue;
}

// Motors: dedicated (OUTPUT_MODE_MOTORS) first, then auto
if (TIM_IS_MOTOR(timHw->usageFlags) && motorIdx < motorCount
&& !pwmHasServoOnTimer(timOutputs, timHw->tim)
&& (isDedicated ? mode == OUTPUT_MODE_MOTORS : mode != OUTPUT_MODE_MOTORS)) {
pwmAssignOutput(timOutputs, timHw, MAP_TO_MOTOR_OUTPUT);
motorIdx++;
continue;
}
// Make sure first motorCount motor outputs get assigned to motor
if (TIM_IS_MOTOR(timHw->usageFlags) && (motorIdx < motorCount)) {
timHw->usageFlags &= ~TIM_USE_SERVO;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
motorIdx += 1;
}

// Servos: dedicated (OUTPUT_MODE_SERVOS) first, then auto
if (TIM_IS_SERVO(timHw->usageFlags)
&& !pwmHasMotorOnTimer(timOutputs, timHw->tim)
&& (isDedicated ? mode == OUTPUT_MODE_SERVOS : mode != OUTPUT_MODE_SERVOS)) {
pwmAssignOutput(timOutputs, timHw, MAP_TO_SERVO_OUTPUT);
continue;
}
if (TIM_IS_SERVO(timHw->usageFlags) && !pwmHasMotorOnTimer(timOutputs, timHw->tim)) {
type = MAP_TO_SERVO_OUTPUT;
} else if (TIM_IS_MOTOR(timHw->usageFlags) && !pwmHasServoOnTimer(timOutputs, timHw->tim)) {
type = MAP_TO_MOTOR_OUTPUT;
} else if (TIM_IS_LED(timHw->usageFlags) && !pwmHasMotorOnTimer(timOutputs, timHw->tim) && !pwmHasServoOnTimer(timOutputs, timHw->tim)) {
type = MAP_TO_LED_OUTPUT;
}

// LEDs: only on the auto pass, and only if timer is uncontested
if (!isDedicated && TIM_IS_LED(timHw->usageFlags)
&& !pwmHasMotorOnTimer(timOutputs, timHw->tim)
&& !pwmHasServoOnTimer(timOutputs, timHw->tim)) {
pwmAssignOutput(timOutputs, timHw, MAP_TO_LED_OUTPUT);
}
switch(type) {
case MAP_TO_MOTOR_OUTPUT:
timHw->usageFlags &= TIM_USE_MOTOR;
timOutputs->timMotors[timOutputs->maxTimMotorCount++] = timHw;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
case MAP_TO_SERVO_OUTPUT:
timHw->usageFlags &= TIM_USE_SERVO;
timOutputs->timServos[timOutputs->maxTimServoCount++] = timHw;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
case MAP_TO_LED_OUTPUT:
timHw->usageFlags &= TIM_USE_LED;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
default:
break;
}
}
}
Expand Down
Loading
Loading