Skip to content

Commit 31bea50

Browse files
Merge pull request #11564 from sensei-hacker/feature/output-assignment-api-v2
output assignment: firmware-authoritative MSP2 READ/QUERY API
2 parents 0d72c3b + 5713a12 commit 31bea50

5 files changed

Lines changed: 391 additions & 18 deletions

File tree

src/main/drivers/pwm_mapping.c

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,6 @@ enum {
5151
MAP_TO_LED_OUTPUT
5252
};
5353

54-
typedef struct {
55-
int maxTimMotorCount;
56-
int maxTimServoCount;
57-
const timerHardware_t * timMotors[MAX_PWM_OUTPUTS];
58-
const timerHardware_t * timServos[MAX_PWM_OUTPUTS];
59-
} timMotorServoHardware_t;
60-
6154
static pwmInitError_e pwmInitError = PWM_INIT_ERROR_NONE;
6255

6356
static const char * pwmInitErrorMsg[] = {
@@ -300,14 +293,31 @@ static void pwmAssignOutput(timMotorServoHardware_t *timOutputs, timerHardware_t
300293
}
301294
}
302295

303-
void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs, bool isMixerUsingServos)
296+
void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs)
304297
{
305-
UNUSED(isMixerUsingServos);
306298
timOutputs->maxTimMotorCount = 0;
307299
timOutputs->maxTimServoCount = 0;
308300

309301
const uint8_t motorCount = getMotorCount();
310302

303+
// Count servo outputs needed across all mixer profiles, matching
304+
// computeServoCount() / getServoCount() which also walk all profiles.
305+
// Using only the active profile would under-count on targets with a second
306+
// profile that has more servos, causing the simulation to diverge from
307+
// the real pwmInitServos() result.
308+
uint8_t minServo = 255, maxServo = 0;
309+
bool anyServo = false;
310+
for (int j = 0; j < MAX_MIXER_PROFILE_COUNT; j++) {
311+
for (int i = 0; i < MAX_SERVO_RULES; i++) {
312+
if (mixerServoMixersByIndex(j)[i].rate == 0) break;
313+
uint8_t ch = mixerServoMixersByIndex(j)[i].targetChannel;
314+
if (ch < minServo) minServo = ch;
315+
if (ch > maxServo) maxServo = ch;
316+
anyServo = true;
317+
}
318+
}
319+
uint8_t servoCount = anyServo ? (uint8_t)(1 + maxServo - minServo) : 0;
320+
311321
// Apply all timerOverrides upfront so flag state is stable for both passes
312322
for (int idx = 0; idx < timerHardwareCount; idx++) {
313323
timerHardwareOverride(&timerHardware[idx]);
@@ -341,7 +351,7 @@ void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs, bool isMixerUs
341351
}
342352

343353
// Servos: dedicated (OUTPUT_MODE_SERVOS) first, then auto
344-
if (TIM_IS_SERVO(timHw->usageFlags)
354+
if (TIM_IS_SERVO(timHw->usageFlags) && timOutputs->maxTimServoCount < servoCount
345355
&& !pwmHasMotorOnTimer(timOutputs, timHw->tim)
346356
&& (isDedicated ? mode == OUTPUT_MODE_SERVOS : mode != OUTPUT_MODE_SERVOS)) {
347357
pwmAssignOutput(timOutputs, timHw, MAP_TO_SERVO_OUTPUT);
@@ -459,19 +469,61 @@ static void pwmInitServos(timMotorServoHardware_t * timOutputs)
459469
}
460470

461471

472+
static timMotorServoHardware_t timOutputsStatic;
473+
462474
bool pwmMotorAndServoInit(void)
463475
{
464-
timMotorServoHardware_t timOutputs;
476+
pwmBuildTimerOutputList(&timOutputsStatic);
477+
pwmInitMotors(&timOutputsStatic);
478+
pwmInitServos(&timOutputsStatic);
479+
return (pwmInitError == PWM_INIT_ERROR_NONE);
480+
}
465481

466-
// Build temporary timer mappings for motor and servo
467-
pwmBuildTimerOutputList(&timOutputs, isMixerUsingServos());
482+
const timMotorServoHardware_t *pwmGetOutputAssignment(void)
483+
{
484+
return &timOutputsStatic;
485+
}
468486

469-
// At this point we have built tables of timers suitable for motor and servo mappings
470-
// Now we can actually initialize them according to motor/servo count from mixer
471-
pwmInitMotors(&timOutputs);
472-
pwmInitServos(&timOutputs);
487+
// Upper bound for timerHardware[] size across all supported targets.
488+
// timerHardwareCount is a runtime value; this constant prevents a VLA on the MSP
489+
// task stack. Targets with 22+ entries (e.g. OMNIBUSF4) need more than MAX_PWM_OUTPUTS.
490+
#define TIMER_HW_MAX 64
473491

474-
return (pwmInitError == PWM_INIT_ERROR_NONE);
492+
// Simulate pwmBuildTimerOutputList() with proposed overrides without modifying live state.
493+
// IMPORTANT: Must only be called from the main loop / MSP task — not ISR-safe.
494+
// timerHardware[].usageFlags are modified in-place during the simulation; this function
495+
// saves and restores them so hardware state is identical on exit.
496+
void pwmCalculateAssignment(timMotorServoHardware_t *out, const uint8_t *proposedModes)
497+
{
498+
if (timerHardwareCount > TIMER_HW_MAX) {
499+
return; // Safety guard: target exceeds the buffer — increase TIMER_HW_MAX
500+
}
501+
502+
// Snapshot timerHardware flags — pwmBuildTimerOutputList() modifies them in-place
503+
// via timerHardwareOverride() and pwmClaimTimer().
504+
uint32_t savedFlags[TIMER_HW_MAX];
505+
for (int i = 0; i < timerHardwareCount; i++) {
506+
savedFlags[i] = timerHardware[i].usageFlags;
507+
}
508+
509+
// Snapshot timerOverrides config so the proposed values can be applied temporarily.
510+
uint8_t savedModes[HARDWARE_TIMER_DEFINITION_COUNT];
511+
for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; i++) {
512+
savedModes[i] = timerOverrides(i)->outputMode;
513+
}
514+
515+
for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; i++) {
516+
timerOverridesMutable(i)->outputMode = proposedModes[i];
517+
}
518+
519+
pwmBuildTimerOutputList(out);
520+
521+
for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; i++) {
522+
timerOverridesMutable(i)->outputMode = savedModes[i];
523+
}
524+
for (int i = 0; i < timerHardwareCount; i++) {
525+
timerHardware[i].usageFlags = savedFlags[i];
526+
}
475527
}
476528

477529
#endif

src/main/drivers/pwm_mapping.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#pragma once
1919

2020
#include "drivers/io_types.h"
21+
#include "drivers/timer.h"
22+
#include "drivers/pwm_output.h"
2123
#include "flight/mixer.h"
2224
#include "flight/mixer_profile.h"
2325
#include "flight/servos.h"
@@ -78,7 +80,26 @@ typedef struct {
7880
bool isDSHOT;
7981
} motorProtocolProperties_t;
8082

83+
#ifndef SITL_BUILD
84+
typedef struct {
85+
int maxTimMotorCount;
86+
int maxTimServoCount;
87+
const timerHardware_t * timMotors[MAX_PWM_OUTPUTS];
88+
const timerHardware_t * timServos[MAX_PWM_OUTPUTS];
89+
} timMotorServoHardware_t;
90+
91+
// Output assignment types for MSP2_INAV_OUTPUT_ASSIGNMENT response
92+
// LED outputs are not reported here; they are already identified by TIM_USE_LED
93+
// in the MSP2_INAV_OUTPUT_MAPPING_EXT2 usageFlags response.
94+
#define OUTPUT_ASSIGNMENT_TYPE_MOTOR 1
95+
#define OUTPUT_ASSIGNMENT_TYPE_SERVO 2
96+
#endif // SITL_BUILD
97+
8198
bool pwmMotorAndServoInit(void);
8299
const motorProtocolProperties_t * getMotorProtocolProperties(motorPwmProtocolTypes_e proto);
83100
pwmInitError_e getPwmInitError(void);
84101
const char * getPwmInitErrorMessage(void);
102+
#ifndef SITL_BUILD
103+
const timMotorServoHardware_t *pwmGetOutputAssignment(void);
104+
void pwmCalculateAssignment(timMotorServoHardware_t *out, const uint8_t *proposedModes);
105+
#endif // SITL_BUILD

src/main/fc/fc_msp.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,24 @@ static bool mspFcProcessOutCommand(uint16_t cmdMSP, sbuf_t *dst, mspPostProcessF
17711771
break;
17721772

17731773

1774+
#ifndef SITL_BUILD
1775+
case MSP2_INAV_OUTPUT_ASSIGNMENT:
1776+
{
1777+
const timMotorServoHardware_t *hw = pwmGetOutputAssignment();
1778+
for (int m = 0; m < hw->maxTimMotorCount; m++) {
1779+
sbufWriteU8(dst, (uint8_t)(hw->timMotors[m] - timerHardware));
1780+
sbufWriteU8(dst, OUTPUT_ASSIGNMENT_TYPE_MOTOR);
1781+
sbufWriteU8(dst, (uint8_t)(m + 1));
1782+
}
1783+
for (int s = 0; s < hw->maxTimServoCount; s++) {
1784+
sbufWriteU8(dst, (uint8_t)(hw->timServos[s] - timerHardware));
1785+
sbufWriteU8(dst, OUTPUT_ASSIGNMENT_TYPE_SERVO);
1786+
sbufWriteU8(dst, (uint8_t)(s + 1));
1787+
}
1788+
}
1789+
break;
1790+
#endif
1791+
17741792
case MSP2_INAV_MC_BRAKING:
17751793
#ifdef USE_MR_BRAKING_MODE
17761794
sbufWriteU16(dst, navConfig()->mc.braking_speed_threshold);
@@ -4754,6 +4772,48 @@ bool mspFCProcessInOutCommand(uint16_t cmdMSP, sbuf_t *dst, sbuf_t *src, mspResu
47544772
*ret = MSP_RESULT_ERROR;
47554773
}
47564774
break;
4775+
4776+
case MSP2_INAV_QUERY_OUTPUT_ASSIGNMENT:
4777+
{
4778+
// Build proposed overrides array (defaults to current stored overrides)
4779+
uint8_t proposedModes[HARDWARE_TIMER_DEFINITION_COUNT];
4780+
for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; i++) {
4781+
proposedModes[i] = timerOverrides(i)->outputMode;
4782+
}
4783+
4784+
if (dataSize >= 1) {
4785+
uint8_t timerCount = sbufReadU8(src);
4786+
// Reject malformed payloads: must be exactly timerCount pairs.
4787+
if (timerCount > HARDWARE_TIMER_DEFINITION_COUNT ||
4788+
sbufBytesRemaining(src) != (uint32_t)(timerCount * 2)) {
4789+
*ret = MSP_RESULT_ERROR;
4790+
break;
4791+
}
4792+
for (int i = 0; i < timerCount; i++) {
4793+
uint8_t timerId = sbufReadU8(src);
4794+
uint8_t outputMode = sbufReadU8(src);
4795+
if (timerId < HARDWARE_TIMER_DEFINITION_COUNT) {
4796+
proposedModes[timerId] = outputMode;
4797+
}
4798+
}
4799+
}
4800+
4801+
timMotorServoHardware_t tempOut = {0};
4802+
pwmCalculateAssignment(&tempOut, proposedModes);
4803+
4804+
for (int m = 0; m < tempOut.maxTimMotorCount; m++) {
4805+
sbufWriteU8(dst, (uint8_t)(tempOut.timMotors[m] - timerHardware));
4806+
sbufWriteU8(dst, OUTPUT_ASSIGNMENT_TYPE_MOTOR);
4807+
sbufWriteU8(dst, (uint8_t)(m + 1));
4808+
}
4809+
for (int s = 0; s < tempOut.maxTimServoCount; s++) {
4810+
sbufWriteU8(dst, (uint8_t)(tempOut.timServos[s] - timerHardware));
4811+
sbufWriteU8(dst, OUTPUT_ASSIGNMENT_TYPE_SERVO);
4812+
sbufWriteU8(dst, (uint8_t)(s + 1));
4813+
}
4814+
*ret = MSP_RESULT_ACK;
4815+
}
4816+
break;
47574817
#endif
47584818

47594819
case MSP_VTXTABLE_POWERLEVEL: {

src/main/msp/msp_protocol_v2_inav.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#define MSP2_INAV_TIMER_OUTPUT_MODE 0x200E
3636
#define MSP2_INAV_SET_TIMER_OUTPUT_MODE 0x200F
3737
#define MSP2_INAV_OUTPUT_MAPPING_EXT2 0x210D
38+
#define MSP2_INAV_OUTPUT_ASSIGNMENT 0x210E // Read finalized post-boot output assignments
39+
#define MSP2_INAV_QUERY_OUTPUT_ASSIGNMENT 0x210F // Preview assignments for proposed timer overrides
3840

3941
#define MSP2_INAV_MIXER 0x2010
4042
#define MSP2_INAV_SET_MIXER 0x2011

0 commit comments

Comments
 (0)