1581 lines
54 KiB
C++
1581 lines
54 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Based on code named
|
|
* opentx - https://github.com/opentx/opentx
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program 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.
|
|
*/
|
|
|
|
#include "gtests.h"
|
|
#include "hal/adc_driver.h"
|
|
|
|
class TrimsTest : public EdgeTxTest {};
|
|
class MixerTest : public EdgeTxTest {};
|
|
|
|
#define CHECK_NO_MOVEMENT(channel, value, duration) \
|
|
for (int i=1; i<=(duration); i++) { \
|
|
evalFlightModeMixes(e_perout_mode_normal, 1); \
|
|
GTEST_ASSERT_EQ((value), chans[(channel)]); \
|
|
}
|
|
|
|
#define CHECK_SLOW_MOVEMENT(channel, sign, duration, max_duration) \
|
|
do { \
|
|
for (int i=1; i<=(duration); i++) { \
|
|
evalFlightModeMixes(e_perout_mode_normal, 1); \
|
|
lastAct = lastAct + (sign) * (1<<19)/max_duration; \
|
|
GTEST_ASSERT_EQ(256 * (lastAct >> 8), chans[(channel)]); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CHECK_DELAY(channel, duration) \
|
|
do { \
|
|
int32_t value = chans[(channel)]; \
|
|
for (int i=1; i<=(duration); i++) { \
|
|
evalFlightModeMixes(e_perout_mode_normal, 1); \
|
|
GTEST_ASSERT_EQ(chans[(channel)], value); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CHECK_FLIGHT_MODE_TRANSITION(channel, duration, initValue, endValue) \
|
|
do { \
|
|
uint32_t delta = 0xffff / duration; \
|
|
int32_t weightInit = 0xffff; \
|
|
int32_t weightEnd = 0; \
|
|
for (int i = 0; i <= (duration); i++) { \
|
|
evalMixes(1); \
|
|
GTEST_ASSERT_LE( abs(((initValue) * weightInit + (endValue) * weightEnd) / 0xffff - channelOutputs[(channel)]), 1); \
|
|
weightInit = weightInit - delta; \
|
|
weightEnd = weightEnd + delta; \
|
|
} \
|
|
for (int i = 0; i < 100; i++) { /* be sure the transition is finished*/ \
|
|
evalMixes(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define MIXSRC_CYC1 (MIXSRC_FIRST_HELI)
|
|
#define MIXSRC_CYC2 (MIXSRC_FIRST_HELI + 1)
|
|
#define MIXSRC_CYC3 (MIXSRC_FIRST_HELI + 2)
|
|
|
|
#if defined(SURFACE_RADIO)
|
|
#define ELE_CHAN 0
|
|
#define ELE_THRTRIMSW 1
|
|
#define THR_STICK_MIN_THR_POS 0
|
|
#define TRIM_SCALE(x) (x / 2)
|
|
#else
|
|
#define ELE_CHAN 1
|
|
#define ELE_THRTRIMSW 1
|
|
#define THR_STICK_MIN_THR_POS -1024
|
|
#define TRIM_SCALE(x) (x)
|
|
#endif
|
|
|
|
#define THR_CHAN inputMappingGetThrottle()
|
|
#define THR_STICK THR_CHAN
|
|
#define ELE_STICK ELE_CHAN
|
|
#define AIL_STICK 3
|
|
|
|
#define MIXSRC_ELE (MIXSRC_FIRST_STICK + ELE_CHAN)
|
|
#define MIXSRC_THR (MIXSRC_FIRST_STICK + THR_CHAN)
|
|
#define MIXSRC_AIL (MIXSRC_FIRST_STICK + AIL_STICK)
|
|
|
|
#define MIXSRC_TRIMELE (MIXSRC_FIRST_TRIM + ELE_CHAN)
|
|
#define MIXSRC_TRIMTHR (MIXSRC_FIRST_TRIM + THR_CHAN)
|
|
|
|
#define ELE_TRIM_SOURCE (-1 -ELE_CHAN)
|
|
#define THR_TRIM_SOURCE (-1 -THR_CHAN)
|
|
|
|
|
|
TEST_F(MixerTest, throttleInvert)
|
|
{
|
|
// Mode 1 / reversed
|
|
g_eeGeneral.stickMode = 0;
|
|
g_model.throttleReversed = 1;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
|
|
// Mode 2 / reversed
|
|
g_eeGeneral.stickMode = 1;
|
|
g_model.throttleReversed = 1;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
|
|
// Mode 2 / normal
|
|
g_eeGeneral.stickMode = 1;
|
|
g_model.throttleReversed = 0;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
}
|
|
|
|
TEST_F(TrimsTest, throttleTrim)
|
|
{
|
|
g_model.thrTrim = 1;
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(512));
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(256));
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS);
|
|
#if defined(SURFACE_RADIO)
|
|
// stick bellow 0 + trim max :no trim in reverse
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -512);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -512);
|
|
#endif
|
|
// now the same tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(2048));
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(1024));
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS);
|
|
}
|
|
|
|
TEST_F(TrimsTest, invertedThrottlePlusThrottleTrim)
|
|
{
|
|
g_model.throttleReversed = 1;
|
|
g_model.thrTrim = 1;
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[2], -1024+256);
|
|
#endif
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+512);
|
|
#endif
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+512);
|
|
#endif
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
|
|
// now the same tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// stick max + trim max : no effect since we are in reverse mode
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
#if defined(SURFACE_RADIO)
|
|
// stick max + trim mid: no effect since we are in reverse mode
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
#else
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+1024);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+2048);
|
|
#endif
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
}
|
|
|
|
TEST_F(TrimsTest, throttleTrimWithZeroWeightOnThrottle)
|
|
{
|
|
g_model.thrTrim = 1;
|
|
// the input already exists
|
|
ExpoData *expo = expoAddress(THR_STICK);
|
|
expo->weight = makeSourceNumVal(0);
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 256);
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_LE(abs(channelOutputs[THR_CHAN] - 128), 1); //can't use precise comparison here because of lower precision math on 9X
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 256);
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_LE(abs(channelOutputs[THR_CHAN] - 128), 1);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
|
|
// now some tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// trim min + various stick positions = should always be same value
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
|
|
// trim max + various stick positions = should always be same value
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
}
|
|
|
|
TEST_F(TrimsTest, invertedThrottlePlusthrottleTrimWithZeroWeightOnThrottle)
|
|
{
|
|
g_model.throttleReversed = 1;
|
|
g_model.thrTrim = 1;
|
|
// the input already exists
|
|
ExpoData *expo = expoAddress(THR_STICK);
|
|
expo->weight = makeSourceNumVal(0);
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_LE(abs(channelOutputs[THR_CHAN] - 128), 1);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 256);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, 0);
|
|
evalMixes(1);
|
|
EXPECT_LE(abs(channelOutputs[THR_CHAN] - 128), 1);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, THR_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 256);
|
|
|
|
// now some tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// trim min + various stick positions = should always be same value
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
|
|
// trim max + various stick positions = should always be same value
|
|
setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +300);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 0);
|
|
}
|
|
|
|
TEST_F(TrimsTest, CopyTrimsToOffset)
|
|
{
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, -100); // -100 on elevator/steering
|
|
evalFunctions(g_model.customFn, modelFunctionsContext); // it disables all safety channels
|
|
copyTrimsToOffset(ELE_CHAN);
|
|
EXPECT_EQ(getTrimValue(0, ELE_STICK), -100); // unchanged
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195);
|
|
}
|
|
|
|
TEST_F(TrimsTest, CopySticksToOffset)
|
|
{
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), -100);
|
|
evalMixes(1);
|
|
copySticksToOffset(ELE_CHAN);
|
|
#if defined(STICK_DEAD_ZONE)
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -93);
|
|
#else
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -97);
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TrimsTest, MoveTrimsToOffsets)
|
|
{
|
|
// No trim idle only
|
|
g_model.thrTrim = 0;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), 0);
|
|
setTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM, 100);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, -100);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], TRIM_SCALE(200)); // THR output value is reflecting 100 trim
|
|
moveTrimsToOffsets();
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
EXPECT_EQ(g_model.limitData[THR_CHAN].offset, TRIM_SCALE(195)); // value transferred
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195); // value transferred
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 99); // THR output value is still reflecting 100 trim
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 200); // THR output value is still reflecting 100 trim
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TrimsTest, MoveTrimsToOffsetsWithTrimIdle)
|
|
{
|
|
// Trim idle only
|
|
g_model.thrTrim = 1;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS); // Min stick
|
|
g_model.limitData[THR_CHAN].offset = 0;
|
|
g_model.limitData[ELE_CHAN].offset = 0;
|
|
setTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM, 100);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, -100);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 228); // THR output value is reflecting 100 trim idle
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -568); // THR output value is reflecting 100 trim idle
|
|
#endif
|
|
moveTrimsToOffsets();
|
|
|
|
// Trim affecting Throttle should not be affected
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM), 100); // unchanged
|
|
EXPECT_EQ(g_model.limitData[2].offset, 0); // unchanged
|
|
|
|
// Other trims should
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195); // value transferred
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 228); // THR output value is reflecting 100 trim idle
|
|
#else
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195); // value transferred
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -568); // THR output value is reflecting 100 trim idle
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TrimsTest, MoveTrimsToOffsetsWithCrossTrims)
|
|
{
|
|
// No trim idle only
|
|
// Cross trims
|
|
g_model.thrTrim = 0;
|
|
g_model.limitData[THR_CHAN].offset = 0;
|
|
g_model.limitData[ELE_CHAN].offset = 0;
|
|
g_model.setThrottleStickTrimSource(MIXSRC_TRIMELE);
|
|
ExpoData *expo = expoAddress(THR_CHAN);
|
|
expo->trimSource = ELE_TRIM_SOURCE;
|
|
expo = expoAddress(ELE_CHAN);
|
|
expo->trimSource = THR_TRIM_SOURCE;
|
|
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), 0);
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), 0);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 100);
|
|
setTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM, -100);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], TRIM_SCALE(200)); // THR output value is reflecting 100 Ele trim
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], -200); // ELE output value is reflecting -100 Thr trim
|
|
moveTrimsToOffsets();
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 99); // THR output value remains unchanged
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 200); // THR output value remains unchanged
|
|
#endif
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
EXPECT_EQ(g_model.limitData[THR_CHAN].offset, TRIM_SCALE(195)); // value transferred
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195); // value transferred
|
|
}
|
|
|
|
TEST_F(TrimsTest, MoveTrimsToOffsetsWithCrosstrimsAndTrimIdle)
|
|
{
|
|
// Trim idle only
|
|
// Cross trims
|
|
g_model.limitData[THR_CHAN].offset = 0;
|
|
g_model.limitData[ELE_CHAN].offset = 0;
|
|
g_model.thrTrim = 1;
|
|
g_model.thrTrimSw = ELE_THRTRIMSW;
|
|
ExpoData *expo = expoAddress(THR_CHAN);
|
|
expo->trimSource = ELE_TRIM_SOURCE;
|
|
expo = expoAddress(ELE_CHAN);
|
|
expo->trimSource = THR_TRIM_SOURCE;
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS); // Min throttle
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 100);
|
|
setTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM, -100);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 228); // THR output value is reflecting 100 ele trim idle
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -568); // THR output value is reflecting 100 ele trim idle
|
|
#endif
|
|
moveTrimsToOffsets();
|
|
|
|
// Trim affecting Throttle (now Ele because of crosstrims) should not be affected
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM), 100); // unchanged
|
|
EXPECT_EQ(g_model.limitData[2].offset, 0); // THR chan offset unchanged
|
|
|
|
// Other trims should
|
|
EXPECT_EQ(getTrimValue(0, MIXSRC_TRIMTHR - MIXSRC_FIRST_TRIM), 0); // back to neutral
|
|
EXPECT_EQ(g_model.limitData[ELE_CHAN].offset, -195); // Ele chan offset transferred
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 228); // THR output value is still reflecting 100 trim idle
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -568); // THR output value is still reflecting 100 trim idle
|
|
#endif
|
|
}
|
|
|
|
#if !defined(SURFACE_RADIO)
|
|
TEST_F(TrimsTest, InstantTrim)
|
|
{
|
|
anaSetFiltered(AIL_STICK, 50);
|
|
instantTrim();
|
|
#if defined(STICK_DEAD_ZONE)
|
|
EXPECT_EQ(23, getTrimValue(0, AIL_STICK));
|
|
#else
|
|
EXPECT_EQ(25, getTrimValue(0, AIL_STICK));
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TrimsTest, InstantTrimNegativeCurve)
|
|
{
|
|
ExpoData *expo = expoAddress(AIL_STICK);
|
|
expo->curve.type = CURVE_REF_CUSTOM;
|
|
expo->curve.value = makeSourceNumVal(1);
|
|
g_model.points[0] = -100;
|
|
g_model.points[1] = -75;
|
|
g_model.points[2] = -50;
|
|
g_model.points[3] = -25;
|
|
g_model.points[4] = 0;
|
|
anaSetFiltered(AIL_STICK, 512);
|
|
instantTrim();
|
|
#if defined(STICK_DEAD_ZONE)
|
|
EXPECT_EQ(127, getTrimValue(0, AIL_STICK));
|
|
#else
|
|
EXPECT_EQ(128, getTrimValue(0, AIL_STICK));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
TEST(Curves, LinearIntpol)
|
|
{
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
for (int8_t i=-2; i<=2; i++) {
|
|
g_model.points[2+i] = 50*i;
|
|
}
|
|
EXPECT_EQ(applyCustomCurve(-1024, 0), -1024);
|
|
EXPECT_EQ(applyCustomCurve(0, 0), 0);
|
|
EXPECT_EQ(applyCustomCurve(1024, 0), 1024);
|
|
EXPECT_EQ(applyCustomCurve(-192, 0), -192);
|
|
}
|
|
|
|
|
|
|
|
TEST_F(MixerTest, InfiniteRecursiveChannels)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_CH + 1;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].srcRaw = MIXSRC_FIRST_CH + 2;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[2].destCh = 2;
|
|
g_model.mixData[2].srcRaw = MIXSRC_FIRST_CH;
|
|
g_model.mixData[2].weight = makeSourceNumVal(100);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[2], 0);
|
|
EXPECT_EQ(chans[1], 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
}
|
|
|
|
TEST_F(MixerTest, BlockingChannel)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_CH;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
}
|
|
|
|
TEST_F(MixerTest, RecursiveAddChannel)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(50);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_FIRST_CH + 1;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[2].destCh = 1;
|
|
g_model.mixData[2].srcRaw = MIXSRC_FIRST_STICK;
|
|
g_model.mixData[2].weight = makeSourceNumVal(100);
|
|
|
|
anaSetFiltered(0, 0);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX/2);
|
|
EXPECT_EQ(chans[1], 0);
|
|
}
|
|
|
|
TEST_F(MixerTest, RecursiveAddChannelAfterInactivePhase)
|
|
{
|
|
if (switchGetMaxAllSwitches() < 4) return;
|
|
|
|
g_model.flightModeData[1].swtch = SWSRC_FIRST_SWITCH + 1;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_CH + 1;
|
|
g_model.mixData[0].flightModes = 0b11110;
|
|
g_model.mixData[0].weight = makeSourceNumVal(50);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].flightModes = 0b11101;
|
|
g_model.mixData[1].weight = makeSourceNumVal(50);
|
|
g_model.mixData[2].destCh = 1;
|
|
g_model.mixData[2].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[2].weight = makeSourceNumVal(100);
|
|
simuSetSwitch(3, -1);
|
|
evalMixes(1);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX/2);
|
|
EXPECT_EQ(chans[1], CHANNEL_MAX);
|
|
simuSetSwitch(3, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX/2);
|
|
EXPECT_EQ(chans[1], CHANNEL_MAX);
|
|
}
|
|
|
|
|
|
TEST_F(MixerTest, SlowOnPhase)
|
|
{
|
|
g_model.flightModeData[1].swtch = SWSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].flightModes = 0x2 + 0x4 + 0x8 + 0x10 /*only enabled in phase 0*/;
|
|
g_model.mixData[0].speedUp = 50;
|
|
g_model.mixData[0].speedDown = 50;
|
|
|
|
s_mixer_first_run_done = true;
|
|
mixerCurrentFlightMode = 0;
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
|
|
CHECK_SLOW_MOVEMENT(0, +1, 250, 500);
|
|
|
|
mixerCurrentFlightMode = 1;
|
|
CHECK_SLOW_MOVEMENT(0, -1, 250, 500);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowOnSwitchSource)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = sw + MIXSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].speedUp = 50;
|
|
g_model.mixData[0].speedDown = 50;
|
|
|
|
s_mixer_first_run_done = true;
|
|
|
|
simuSetSwitch(sw, -1);
|
|
CHECK_SLOW_MOVEMENT(0, -1, 250, 500);
|
|
EXPECT_EQ(chans[0], -CHANNEL_MAX);
|
|
|
|
simuSetSwitch(sw, 1);
|
|
CHECK_SLOW_MOVEMENT(0, +1, 500, 500);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowOnPhasePrec10ms)
|
|
{
|
|
g_model.flightModeData[1].swtch = SWSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].flightModes = 0x2 + 0x4 + 0x8 + 0x10 /*only enabled in phase 0*/;
|
|
g_model.mixData[0].speedUp = 50;
|
|
g_model.mixData[0].speedDown = 50;
|
|
g_model.mixData[0].speedPrec = 1;
|
|
|
|
s_mixer_first_run_done = true;
|
|
mixerCurrentFlightMode = 0;
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
|
|
CHECK_SLOW_MOVEMENT(0, +1, 25, 50);
|
|
|
|
mixerCurrentFlightMode = 1;
|
|
CHECK_SLOW_MOVEMENT(0, -1, 25, 50);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowOnSwitchSourcePrec10ms)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = sw + MIXSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].speedUp = 50;
|
|
g_model.mixData[0].speedDown = 50;
|
|
g_model.mixData[0].speedPrec = 1;
|
|
|
|
s_mixer_first_run_done = true;
|
|
|
|
simuSetSwitch(sw, -1);
|
|
CHECK_SLOW_MOVEMENT(0, -1, 25, 50);
|
|
EXPECT_EQ(chans[0], -CHANNEL_MAX);
|
|
|
|
simuSetSwitch(sw, 1);
|
|
CHECK_SLOW_MOVEMENT(0, +1, 50, 50);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowDisabledOnStartup)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].speedUp = 50;
|
|
g_model.mixData[0].speedDown = 50;
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX);
|
|
}
|
|
|
|
TEST_F(MixerTest, DelayOnSwitch)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
int swPos = (sw * 3) + SWSRC_FIRST_SWITCH + 2;
|
|
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].swtch = swPos;
|
|
g_model.mixData[0].delayUp = 50;
|
|
g_model.mixData[0].delayDown = 50;
|
|
|
|
simuSetSwitch(sw, -1);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
|
|
simuSetSwitch(sw, 1);
|
|
CHECK_DELAY(0, 500);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX);
|
|
|
|
simuSetSwitch(sw, 0);
|
|
CHECK_DELAY(0, 500);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
EXPECT_EQ(chans[0], 0);
|
|
}
|
|
|
|
TEST_F(MixerTest, DelayOnSwitch2)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = sw + MIXSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].delayUp = 50;
|
|
g_model.mixData[0].delayDown = 50;
|
|
|
|
simuSetSwitch(sw, -1);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], 0);
|
|
|
|
simuSetSwitch(sw, 1);
|
|
CHECK_DELAY(0, 500);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX);
|
|
|
|
simuSetSwitch(sw, 0);
|
|
CHECK_DELAY(0, 500);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
EXPECT_EQ(chans[0], 0);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowOnMultiply)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_MUL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].swtch = SWSRC_FIRST_SWITCH;
|
|
g_model.mixData[1].speedUp = 50;
|
|
g_model.mixData[1].speedDown = 50;
|
|
|
|
s_mixer_first_run_done = true;
|
|
|
|
simuSetSwitch(0, 1);
|
|
CHECK_SLOW_MOVEMENT(0, 1, 250, 500);
|
|
|
|
simuSetSwitch(0, -1);
|
|
CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250);
|
|
|
|
simuSetSwitch(0, 1);
|
|
CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250);
|
|
}
|
|
|
|
TEST_F(MixerTest, SlowOnMultiplyPrec10ms)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_MUL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].swtch = SWSRC_FIRST_SWITCH;
|
|
g_model.mixData[1].speedUp = 50;
|
|
g_model.mixData[1].speedDown = 50;
|
|
g_model.mixData[1].speedPrec = 1;
|
|
|
|
s_mixer_first_run_done = true;
|
|
|
|
simuSetSwitch(0, 1);
|
|
CHECK_SLOW_MOVEMENT(0, 1, 25, 50);
|
|
|
|
simuSetSwitch(0, -1);
|
|
CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250);
|
|
|
|
simuSetSwitch(0, 1);
|
|
CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250);
|
|
}
|
|
|
|
TEST_F(TrimsTest, throttleTrimEle) {
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_eeGeneral.templateSetup = 17; // WARNING: NOT RETA (TAER or TH/ST)
|
|
applyDefaultTemplate();
|
|
g_model.thrTrim = 1;
|
|
// checks ELE sticks are not affected by throttleTrim
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), +1024);
|
|
setTrimValue(0, ELE_STICK, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024 - 256);
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_eeGeneral.templateSetup = 0;
|
|
applyDefaultTemplate();
|
|
}
|
|
|
|
#if defined(HELI)
|
|
TEST(Heli, BasicTest)
|
|
{
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_model.swashR.collectiveSource = MIXSRC_THR;
|
|
g_model.swashR.elevatorSource = MIXSRC_ELE;
|
|
g_model.swashR.aileronSource = MIXSRC_AIL;
|
|
g_model.swashR.collectiveWeight = 100;
|
|
g_model.swashR.elevatorWeight = 100;
|
|
g_model.swashR.aileronWeight = 100;
|
|
g_model.swashR.type = SWASH_TYPE_120;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_CYC1;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_CYC2;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[2].destCh = 2;
|
|
g_model.mixData[2].mltpx = MLTPX_ADD;
|
|
g_model.mixData[2].srcRaw = MIXSRC_CYC3;
|
|
g_model.mixData[2].weight = makeSourceNumVal(100);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), 0);
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), 1024);
|
|
anaSetFiltered(inputMappingConvertMode(AIL_STICK), 0);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], -CHANNEL_MAX);
|
|
EXPECT_EQ(chans[1], CHANNEL_MAX/2);
|
|
EXPECT_EQ(chans[2], CHANNEL_MAX/2);
|
|
}
|
|
|
|
TEST(Heli, Mode2Test)
|
|
{
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_eeGeneral.templateSetup = 2;
|
|
applyDefaultTemplate();
|
|
g_model.swashR.collectiveSource = MIXSRC_THR;
|
|
g_model.swashR.elevatorSource = MIXSRC_ELE;
|
|
g_model.swashR.aileronSource = MIXSRC_AIL;
|
|
g_model.swashR.collectiveWeight = 100;
|
|
g_model.swashR.elevatorWeight = 100;
|
|
g_model.swashR.aileronWeight = 100;
|
|
g_model.swashR.type = SWASH_TYPE_120;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_CYC1;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_CYC2;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[2].destCh = 2;
|
|
g_model.mixData[2].mltpx = MLTPX_ADD;
|
|
g_model.mixData[2].srcRaw = MIXSRC_CYC3;
|
|
g_model.mixData[2].weight = makeSourceNumVal(100);
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), 0);
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), 1024);
|
|
anaSetFiltered(inputMappingConvertMode(AIL_STICK), 0);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], -CHANNEL_MAX);
|
|
EXPECT_EQ(chans[1], CHANNEL_MAX/2);
|
|
EXPECT_EQ(chans[2], CHANNEL_MAX/2);
|
|
SYSTEM_RESET();
|
|
}
|
|
#endif
|
|
|
|
TEST(Trainer, UnpluggedTest)
|
|
{
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_TRAINER;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].delayUp = 50;
|
|
g_model.mixData[0].delayDown = 50;
|
|
trainerSetTimer(0);
|
|
trainerInput[0] = 1024;
|
|
CHECK_DELAY(0, 5000);
|
|
}
|
|
|
|
TEST_F(MixerTest, flightModeTransition)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
int swPos = (sw * 3) + SWSRC_FIRST_SWITCH + 2;
|
|
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_model.flightModeData[1].swtch = swPos;
|
|
g_model.flightModeData[0].fadeIn = 100;
|
|
g_model.flightModeData[0].fadeOut = 100;
|
|
g_model.flightModeData[1].fadeIn = 100;
|
|
g_model.flightModeData[1].fadeOut = 100;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_REPL;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].flightModes = 0b11110;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_REPL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].flightModes = 0b11101;
|
|
g_model.mixData[1].weight = makeSourceNumVal(-10);
|
|
evalMixes(1);
|
|
simuSetSwitch(sw, 1);
|
|
CHECK_FLIGHT_MODE_TRANSITION(0, 1000, 1024, -102);
|
|
}
|
|
|
|
TEST_F(MixerTest, flightModeOverflow)
|
|
{
|
|
SYSTEM_RESET();
|
|
MODEL_RESET();
|
|
MIXER_RESET();
|
|
setModelDefaults();
|
|
g_model.flightModeData[1].swtch = SWSRC_FIRST_SWITCH + 2;
|
|
g_model.flightModeData[0].fadeIn = 100;
|
|
g_model.flightModeData[0].fadeOut = 100;
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_REPL;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].flightModes = 0;
|
|
g_model.mixData[0].weight = makeSourceNumVal(250);
|
|
evalMixes(1);
|
|
simuSetSwitch(0, 1);
|
|
CHECK_FLIGHT_MODE_TRANSITION(0, 1000, 1024, 1024);
|
|
}
|
|
|
|
TEST_F(TrimsTest, throttleTrimWithCrossTrims)
|
|
{
|
|
g_model.thrTrim = 1;
|
|
g_model.thrTrimSw = ELE_THRTRIMSW;
|
|
|
|
ExpoData *expo = expoAddress(THR_STICK);
|
|
expo->trimSource = ELE_TRIM_SOURCE;
|
|
|
|
expo = expoAddress(ELE_STICK);
|
|
expo->trimSource = THR_TRIM_SOURCE;
|
|
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), 0);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(512));
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(256));
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
|
|
// now the same tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], 1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+2048);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 0);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS + TRIM_SCALE(1024));
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), THR_STICK_MIN_THR_POS);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], THR_STICK_MIN_THR_POS);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
}
|
|
|
|
TEST_F(TrimsTest, invertedThrottlePlusThrottleTrimWithCrossTrims)
|
|
{
|
|
g_model.throttleReversed = 1;
|
|
g_model.thrTrim = 1;
|
|
g_model.thrTrimSw = ELE_THRTRIMSW;
|
|
ExpoData *expo = expoAddress(THR_STICK);
|
|
expo->trimSource = ELE_TRIM_SOURCE;
|
|
expo = expoAddress(ELE_STICK);
|
|
expo->trimSource = THR_TRIM_SOURCE;
|
|
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
anaSetFiltered(inputMappingConvertMode(ELE_STICK), 0);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 0);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+256);
|
|
#endif
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MIN);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+512);
|
|
#endif
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
|
|
// now the same tests with extended Trims
|
|
g_model.extendedTrims = 1;
|
|
// stick max + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim mid
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, 0);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+1024);
|
|
#endif
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick max + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), +1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
#if defined(SURFACE_RADIO)
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024); // no effect since we are in reverse mode
|
|
#else
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], -1024+2048);
|
|
#endif
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim max
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MAX);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
// stick min + trim min
|
|
anaSetFiltered(inputMappingConvertMode(THR_STICK), -1024);
|
|
setTrimValue(0, MIXSRC_TRIMELE - MIXSRC_FIRST_TRIM, TRIM_EXTENDED_MIN);
|
|
evalMixes(1);
|
|
EXPECT_EQ(channelOutputs[THR_CHAN], +1024);
|
|
EXPECT_EQ(channelOutputs[ELE_CHAN], 0);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// rc-soar.com documented behavior tests
|
|
// https://rc-soar.com/edgetx/index.php
|
|
// Enshrine mixer, flight-mode, and channel-cascade semantics so that
|
|
// refactoring does not silently break real-world user setups.
|
|
// ==========================================================================
|
|
|
|
// Multiplex ADD: lines accumulate additively on the same channel.
|
|
TEST_F(MixerTest, MultiplexAdd)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(60);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(40);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX); // 60% + 40% = 100%
|
|
}
|
|
|
|
// Multiplex REPL: second line replaces whatever the first produced.
|
|
TEST_F(MixerTest, MultiplexReplace)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_REPL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(-50);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], -CHANNEL_MAX / 2); // REPL overwrites to -50%
|
|
}
|
|
|
|
// Multiplex MUL: multiplies with the result of all lines above.
|
|
TEST_F(MixerTest, MultiplexMultiplyBasic)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_MUL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(50);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
EXPECT_EQ(chans[0], CHANNEL_MAX / 2); // 100% * 50% = 50%
|
|
}
|
|
|
|
// Multiplex MUL is order-sensitive: ADD+ADD+MUL differs from ADD+MUL+ADD.
|
|
TEST_F(MixerTest, MultiplexMultiplyOrderSensitive)
|
|
{
|
|
// Case A: ADD(60) + ADD(40) + MUL(50) = (60+40)*50% = 50%
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(60);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(40);
|
|
g_model.mixData[2].destCh = 0;
|
|
g_model.mixData[2].mltpx = MLTPX_MUL;
|
|
g_model.mixData[2].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[2].weight = makeSourceNumVal(50);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
int32_t caseA = chans[0];
|
|
EXPECT_EQ(caseA, CHANNEL_MAX / 2); // 100% * 50% = 50%
|
|
|
|
// Case B: ADD(60) + MUL(50) + ADD(40) = 60*50% + 40 = 30+40 = 70%
|
|
MIXER_RESET();
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(60);
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_MUL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(50);
|
|
g_model.mixData[2].destCh = 0;
|
|
g_model.mixData[2].mltpx = MLTPX_ADD;
|
|
g_model.mixData[2].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[2].weight = makeSourceNumVal(40);
|
|
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
int32_t caseB = chans[0];
|
|
|
|
EXPECT_NE(caseA, caseB); // order matters
|
|
}
|
|
|
|
// Weight-then-offset: output = (source * weight) + offset.
|
|
TEST_F(MixerTest, WeightThenOffset)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_STICK;
|
|
g_model.mixData[0].weight = makeSourceNumVal(50);
|
|
g_model.mixData[0].offset = makeSourceNumVal(50);
|
|
|
|
// Stick at +100%: 50% of 1024 + 50% offset
|
|
anaSetFiltered(inputMappingConvertMode(0), +1024);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
int32_t full = chans[0];
|
|
|
|
// Stick at 0: 0 + 50% offset
|
|
anaSetFiltered(inputMappingConvertMode(0), 0);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
int32_t mid = chans[0];
|
|
|
|
// Stick at -100%: -50% + 50% offset = 0
|
|
anaSetFiltered(inputMappingConvertMode(0), -1024);
|
|
evalFlightModeMixes(e_perout_mode_normal, 0);
|
|
int32_t low = chans[0];
|
|
|
|
// full should be ~100%, mid ~50%, low ~0%
|
|
EXPECT_NEAR(full, CHANNEL_MAX, CHANNEL_MAX / 100);
|
|
EXPECT_NEAR(mid, CHANNEL_MAX / 2, CHANNEL_MAX / 100);
|
|
EXPECT_NEAR(low, 0, CHANNEL_MAX / 100);
|
|
}
|
|
|
|
// Cascaded channels bypass output clipping: a channel used as a mix source
|
|
// carries its internal (>100%) value, not the clipped output.
|
|
TEST_F(MixerTest, CascadedChannelBypassesOutputClipping)
|
|
{
|
|
// CH0: stick at 200% weight (overdrives to 200% internally)
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_STICK;
|
|
g_model.mixData[0].weight = makeSourceNumVal(200);
|
|
// CH1: reads CH0 at 50% weight — if unclipped, 200%*50% = 100%
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].srcRaw = MIXSRC_FIRST_CH;
|
|
g_model.mixData[1].weight = makeSourceNumVal(50);
|
|
|
|
anaSetFiltered(inputMappingConvertMode(0), +1024);
|
|
evalMixes(1);
|
|
|
|
// CH0 output is clipped to 100%
|
|
EXPECT_EQ(channelOutputs[0], 1024);
|
|
// CH1 sees the unclipped CH0 (200%), applies 50% → 100%
|
|
// If CH0 were clipped before cascading, CH1 would be only 50%.
|
|
EXPECT_GT(channelOutputs[1], 512); // must be more than 50%
|
|
EXPECT_NEAR(channelOutputs[1], 1024, 2); // should be ~100%
|
|
}
|
|
|
|
// Flight mode priority: FM1 has highest priority, FM0 is fallback.
|
|
// getFlightMode() iterates FM1..FM8, first match wins; FM0 returned if none.
|
|
TEST_F(MixerTest, FlightModePriority)
|
|
{
|
|
// Find two distinct 3-pos switches
|
|
int sw1 = findHwSwitch(SWITCH_3POS);
|
|
int sw2 = findHwSwitch(SWITCH_3POS, sw1);
|
|
if (sw2 < 0) return; // not enough switches on this target
|
|
|
|
int sw1Up = (sw1 * 3) + SWSRC_FIRST_SWITCH;
|
|
int sw2Up = (sw2 * 3) + SWSRC_FIRST_SWITCH;
|
|
|
|
// FM1 activated by sw1↑, FM2 by sw2↑
|
|
g_model.flightModeData[1].swtch = sw1Up;
|
|
g_model.flightModeData[2].swtch = sw2Up;
|
|
|
|
// Three REPL mix lines, one per FM, using distinguishable weights:
|
|
// FM0-only: weight=10 FM1-only: weight=20 FM2-only: weight=30
|
|
// flightModes bitmask: bit set = DISABLED in that FM.
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_REPL;
|
|
g_model.mixData[0].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[0].weight = makeSourceNumVal(10);
|
|
g_model.mixData[0].flightModes = ~1u & 0x1FF; // enabled only in FM0
|
|
|
|
g_model.mixData[1].destCh = 0;
|
|
g_model.mixData[1].mltpx = MLTPX_REPL;
|
|
g_model.mixData[1].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[1].weight = makeSourceNumVal(20);
|
|
g_model.mixData[1].flightModes = ~2u & 0x1FF; // enabled only in FM1
|
|
|
|
g_model.mixData[2].destCh = 0;
|
|
g_model.mixData[2].mltpx = MLTPX_REPL;
|
|
g_model.mixData[2].srcRaw = MIXSRC_MAX;
|
|
g_model.mixData[2].weight = makeSourceNumVal(30);
|
|
g_model.mixData[2].flightModes = ~4u & 0x1FF; // enabled only in FM2
|
|
|
|
// Neither switch active → FM0 (fallback)
|
|
simuSetSwitch(sw1, 0);
|
|
simuSetSwitch(sw2, 0);
|
|
evalMixes(1);
|
|
EXPECT_NEAR(channelOutputs[0], 1024 * 10 / 100, 1);
|
|
|
|
// Both switches active → FM1 wins (higher priority than FM2)
|
|
simuSetSwitch(sw1, -1);
|
|
simuSetSwitch(sw2, -1);
|
|
evalMixes(1);
|
|
EXPECT_NEAR(channelOutputs[0], 1024 * 20 / 100, 1);
|
|
|
|
// Only sw2 active → FM2
|
|
simuSetSwitch(sw1, 0);
|
|
simuSetSwitch(sw2, -1);
|
|
evalMixes(1);
|
|
EXPECT_NEAR(channelOutputs[0], 1024 * 30 / 100, 1);
|
|
}
|
|
|
|
// Cumulative weight through cascaded channels:
|
|
// CH0 = stick * 80%, CH1 = CH0 * 25% → CH1 should be stick * 20%.
|
|
TEST_F(MixerTest, CascadedWeightMultiplication)
|
|
{
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].srcRaw = MIXSRC_FIRST_STICK;
|
|
g_model.mixData[0].weight = makeSourceNumVal(80);
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].srcRaw = MIXSRC_FIRST_CH;
|
|
g_model.mixData[1].weight = makeSourceNumVal(25);
|
|
|
|
anaSetFiltered(inputMappingConvertMode(0), +1024);
|
|
evalMixes(1);
|
|
|
|
// CH0 = 80% of 1024 ≈ 819
|
|
EXPECT_NEAR(channelOutputs[0], 1024 * 80 / 100, 2);
|
|
// CH1 = 25% of CH0 = 20% of 1024 ≈ 205
|
|
EXPECT_NEAR(channelOutputs[1], 1024 * 20 / 100, 2);
|
|
}
|
|
|
|
// Sequencer pattern (rc-soar.com/edgetx/setups/sequencer):
|
|
// CH0 = timebase driven by a switch with slow up/down (linear ramp).
|
|
// CH1 = servo channel reading CH0 through a custom curve.
|
|
// A flat curve segment produces a "pause" where the servo holds position
|
|
// while the timebase continues to ramp.
|
|
TEST_F(MixerTest, SequencerSlowRampThroughCurve)
|
|
{
|
|
int sw = findHwSwitch(SWITCH_3POS);
|
|
if (sw < 0) return;
|
|
|
|
// --- Curve setup: 5-point standard curve on slot 0 ---
|
|
// Points at x = -100, -50, 0, +50, +100 (standard = evenly spaced)
|
|
// y-values: -100, -100, 0, 0, +100
|
|
// Segment 1 (-100..-50): flat at -100 (pause)
|
|
// Segment 2 (-50..0): ramp from -100 to 0
|
|
// Segment 3 (0..+50): flat at 0 (pause)
|
|
// Segment 4 (+50..+100): ramp from 0 to +100
|
|
g_model.curves[0].type = CURVE_TYPE_STANDARD;
|
|
g_model.curves[0].smooth = 0; // linear interpolation, no smoothing
|
|
g_model.curves[0].points = 0; // 0 means 5 points (default)
|
|
int8_t *pts = curveAddress(0);
|
|
pts[0] = -100; // x=-100%
|
|
pts[1] = -100; // x=-50% (flat: pause)
|
|
pts[2] = 0; // x=0%
|
|
pts[3] = 0; // x=+50% (flat: pause)
|
|
pts[4] = 100; // x=+100%
|
|
|
|
// --- CH0: timebase — switch source with slow ---
|
|
// speedUp=10 → 1.0s full traversal (-100% to +100%), 100 ticks
|
|
g_model.mixData[0].destCh = 0;
|
|
g_model.mixData[0].mltpx = MLTPX_ADD;
|
|
g_model.mixData[0].srcRaw = sw + MIXSRC_FIRST_SWITCH;
|
|
g_model.mixData[0].weight = makeSourceNumVal(100);
|
|
g_model.mixData[0].speedUp = 10; // 1.0s
|
|
g_model.mixData[0].speedDown = 10; // 1.0s
|
|
|
|
// --- CH1: servo — reads CH0 through curve ---
|
|
g_model.mixData[1].destCh = 1;
|
|
g_model.mixData[1].mltpx = MLTPX_ADD;
|
|
g_model.mixData[1].srcRaw = MIXSRC_FIRST_CH;
|
|
g_model.mixData[1].weight = makeSourceNumVal(100);
|
|
g_model.mixData[1].curve.type = CURVE_REF_CUSTOM;
|
|
g_model.mixData[1].curve.value = makeSourceNumVal(1); // curve index 0 (1-based)
|
|
|
|
s_mixer_first_run_done = true;
|
|
|
|
// Start: switch up → CH0 targets -100%
|
|
simuSetSwitch(sw, -1);
|
|
for (int i = 0; i < 110; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1); // run limits to populate channelOutputs
|
|
|
|
int16_t timebaseStart = channelOutputs[0];
|
|
int16_t servoStart = channelOutputs[1];
|
|
// Timebase at -100%, curve maps -100→-100
|
|
EXPECT_NEAR(timebaseStart, -1024, 20);
|
|
EXPECT_NEAR(servoStart, -1024, 20);
|
|
|
|
// Flip switch down → ramp begins from -100% toward +100%
|
|
simuSetSwitch(sw, 1);
|
|
|
|
// Advance 25 ticks (25% of 1.0s = timebase at ~-50%)
|
|
// Curve: flat from -100% to -50%, so servo should still be near -100%
|
|
for (int i = 0; i < 25; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1);
|
|
|
|
int16_t timebaseQ1 = channelOutputs[0];
|
|
int16_t servoQ1 = channelOutputs[1];
|
|
EXPECT_GT(timebaseQ1, timebaseStart + 100) << "Timebase should have moved";
|
|
EXPECT_NEAR(servoQ1, -1024, 60) << "Servo should hold during flat segment (pause)";
|
|
|
|
// Advance to 50 ticks total (50% = timebase at ~0%)
|
|
// Curve: ramp from -100 to 0, so servo should be near 0%
|
|
for (int i = 0; i < 25; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1);
|
|
|
|
int16_t timebaseMid = channelOutputs[0];
|
|
int16_t servoMid = channelOutputs[1];
|
|
EXPECT_NEAR(timebaseMid, 0, 60);
|
|
EXPECT_NEAR(servoMid, 0, 60);
|
|
|
|
// Advance to 75 ticks (75% = timebase at ~+50%)
|
|
// Curve: flat from 0 to +50%, so servo should hold near 0%
|
|
for (int i = 0; i < 25; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1);
|
|
|
|
int16_t timebaseQ3 = channelOutputs[0];
|
|
int16_t servoQ3 = channelOutputs[1];
|
|
EXPECT_GT(timebaseQ3, timebaseMid + 100) << "Timebase should have advanced";
|
|
// Servo should be near the flat segment value (0), but may slightly
|
|
// overshoot into the final ramp segment due to discrete stepping.
|
|
EXPECT_NEAR(servoQ3, 0, 150) << "Servo should be near flat segment";
|
|
EXPECT_LT(servoQ3, 512) << "Servo must not yet reach the final ramp";
|
|
|
|
// Advance to ~100 ticks (100% = timebase at +100%)
|
|
// Curve: ramp from 0 to +100, so servo should be near +100%
|
|
for (int i = 0; i < 30; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1);
|
|
|
|
int16_t timebaseEnd = channelOutputs[0];
|
|
int16_t servoEnd = channelOutputs[1];
|
|
EXPECT_NEAR(timebaseEnd, 1024, 20);
|
|
EXPECT_NEAR(servoEnd, 1024, 40);
|
|
|
|
// Reverse: flip switch up — sequence should reverse automatically
|
|
simuSetSwitch(sw, -1);
|
|
for (int i = 0; i < 110; i++)
|
|
evalFlightModeMixes(e_perout_mode_normal, 1);
|
|
evalMixes(1);
|
|
|
|
EXPECT_NEAR(channelOutputs[0], -1024, 20) << "Timebase should return to -100%";
|
|
EXPECT_NEAR(channelOutputs[1], -1024, 40) << "Servo should return to start via reversed curve";
|
|
}
|