diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfc355d9f4..c25bd05d1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,17 +9,37 @@ on: - '*' # repository_dispatch is a newer github-actions feature that will allow building from triggers other than code merge/PR repository_dispatch: - types: [build] + types: [tests, build] name: Build EmuFlight jobs: + test: + name: Test EmuFlight + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Toolchain + uses: fiam/arm-none-eabi-gcc@master + with: + release: '9-2020-q2' # TODO: use 10-2020-q4-major when applicable + - name: Install Prerequisites + run: | + sudo apt install clang-10 lcov libblocksruntime-dev libc6-i386 + - name : Run Tests + run: | + make test build: + name: Build EmuFlight timeout-minutes: 75 strategy: max-parallel: 4 matrix: targets: [targets-group-1, targets-group-2, targets-group-3, targets-group-rest] runs-on: ubuntu-latest + needs: test steps: # curl, by default, may timeout easily diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index 210b56426c..88e723c7d1 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -1202,7 +1202,6 @@ static bool blackboxWriteSysinfo(void) { BLACKBOX_PRINT_HEADER_LINE("acc_limit", "%d", currentPidProfile->rateAccelLimit); BLACKBOX_PRINT_HEADER_LINE("pidsum_limit", "%d", currentPidProfile->pidSumLimit); BLACKBOX_PRINT_HEADER_LINE("pidsum_limit_yaw", "%d", currentPidProfile->pidSumLimitYaw); - BLACKBOX_PRINT_HEADER_LINE("nfe_racermode", "%d", currentPidProfile->nfe_racermode); BLACKBOX_PRINT_HEADER_LINE("iterm_rotation", "%d", currentPidProfile->iterm_rotation); BLACKBOX_PRINT_HEADER_LINE("throttle_boost", "%d", currentPidProfile->throttle_boost); // End of EmuFlight controller parameters diff --git a/src/main/build/atomic.h b/src/main/build/atomic.h index 219eba98c0..90d76077ed 100644 --- a/src/main/build/atomic.h +++ b/src/main/build/atomic.h @@ -28,13 +28,15 @@ // missing versions are implemented here // set BASEPRI register, do not create memory barrier -__attribute__( ( always_inline ) ) static inline void __set_BASEPRI_nb(uint32_t basePri) { - __ASM volatile ("\tMSR basepri, %0\n" : : "r" (basePri) ); +__attribute__( ( always_inline ) ) static inline void __set_BASEPRI_nb(uint32_t basePri) +{ + __ASM volatile ("\tMSR basepri, %0\n" : : "r" (basePri) ); } // set BASEPRI_MAX register, do not create memory barrier -__attribute__( ( always_inline ) ) static inline void __set_BASEPRI_MAX_nb(uint32_t basePri) { - __ASM volatile ("\tMSR basepri_max, %0\n" : : "r" (basePri) ); +__attribute__( ( always_inline ) ) static inline void __set_BASEPRI_MAX_nb(uint32_t basePri) +{ + __ASM volatile ("\tMSR basepri_max, %0\n" : : "r" (basePri) ); } #endif @@ -44,18 +46,21 @@ __attribute__( ( always_inline ) ) static inline void __set_BASEPRI_MAX_nb(uint3 extern uint8_t atomic_BASEPRI; -static inline uint8_t __get_BASEPRI(void) { +static inline uint8_t __get_BASEPRI(void) +{ return atomic_BASEPRI; } // restore BASEPRI (called as cleanup function), with global memory barrier -static inline void __basepriRestoreMem(uint8_t *val) { +static inline void __basepriRestoreMem(uint8_t *val) +{ atomic_BASEPRI = *val; asm volatile ("": : :"memory"); // compiler memory barrier } // increase BASEPRI, with global memory barrier, returns true -static inline uint8_t __basepriSetMemRetVal(uint8_t prio) { +static inline uint8_t __basepriSetMemRetVal(uint8_t prio) +{ if(prio && (atomic_BASEPRI == 0 || atomic_BASEPRI > prio)) { atomic_BASEPRI = prio; } @@ -64,12 +69,14 @@ static inline uint8_t __basepriSetMemRetVal(uint8_t prio) { } // restore BASEPRI (called as cleanup function), no memory barrier -static inline void __basepriRestore(uint8_t *val) { +static inline void __basepriRestore(uint8_t *val) +{ atomic_BASEPRI = *val; } // increase BASEPRI, no memory barrier, returns true -static inline uint8_t __basepriSetRetVal(uint8_t prio) { +static inline uint8_t __basepriSetRetVal(uint8_t prio) +{ if(prio && (atomic_BASEPRI == 0 || atomic_BASEPRI > prio)) { atomic_BASEPRI = prio; } @@ -80,23 +87,27 @@ static inline uint8_t __basepriSetRetVal(uint8_t prio) { // ARM BASEPRI manipulation // restore BASEPRI (called as cleanup function), with global memory barrier -static inline void __basepriRestoreMem(uint8_t *val) { +static inline void __basepriRestoreMem(uint8_t *val) +{ __set_BASEPRI(*val); } // set BASEPRI_MAX, with global memory barrier, returns true -static inline uint8_t __basepriSetMemRetVal(uint8_t prio) { +static inline uint8_t __basepriSetMemRetVal(uint8_t prio) +{ __set_BASEPRI_MAX(prio); return 1; } // restore BASEPRI (called as cleanup function), no memory barrier -static inline void __basepriRestore(uint8_t *val) { +static inline void __basepriRestore(uint8_t *val) +{ __set_BASEPRI_nb(*val); } // set BASEPRI_MAX, no memory barrier, returns true -static inline uint8_t __basepriSetRetVal(uint8_t prio) { +static inline uint8_t __basepriSetRetVal(uint8_t prio) +{ __set_BASEPRI_MAX_nb(prio); return 1; } @@ -126,13 +137,6 @@ static inline uint8_t __basepriSetRetVal(uint8_t prio) { // On gcc 5 and higher, this protects only memory passed as parameter (any type can be used) // this macro can be used only ONCE PER LINE, but multiple uses per block are fine -#if (__GNUC__ > 9) -# warning "Please verify that ATOMIC_BARRIER works as intended" -// increment version number if BARRIER works -// TODO - use flag to disable ATOMIC_BARRIER and use full barrier instead -// you should check that local variable scope with cleanup spans entire block -#endif - #ifndef __UNIQL # define __UNIQL_CONCAT2(x,y) x ## y # define __UNIQL_CONCAT(x,y) __UNIQL_CONCAT2(x,y) @@ -149,14 +153,12 @@ static inline uint8_t __basepriSetRetVal(uint8_t prio) { // CLang version, using Objective C-style block // based on https://stackoverflow.com/questions/24959440/rewrite-gcc-cleanup-macro-with-nested-function-for-clang typedef void (^__cleanup_block)(void); -static inline void __do_cleanup(__cleanup_block * b) { - (*b)(); -} +static inline void __do_cleanup(__cleanup_block * b) { (*b)(); } #define ATOMIC_BARRIER(data) \ typeof(data) *__UNIQL(__barrier) = &data; \ ATOMIC_BARRIER_ENTER(__UNIQL(__barrier), #data); \ - __cleanup_block __attribute__((cleanup(__do_cleanup) __unused__)) __UNIQL(__cleanup) = \ + __cleanup_block __attribute__((cleanup(__do_cleanup), __unused__)) __UNIQL(__cleanup) = \ ^{ ATOMIC_BARRIER_LEAVE(__UNIQL(__barrier), #data); }; \ do {} while(0) \ /**/ diff --git a/src/main/cms/cms_menu_imu.c b/src/main/cms/cms_menu_imu.c index fa61196c70..0959c5b51e 100644 --- a/src/main/cms/cms_menu_imu.c +++ b/src/main/cms/cms_menu_imu.c @@ -344,7 +344,6 @@ static uint8_t cmsx_P_angle_high; static uint8_t cmsx_D_angle_high; static uint16_t cmsx_F_angle; static uint8_t cmsx_horizonTransition; -static uint8_t cmsx_nfe_racermode; static uint8_t cmsx_throttleBoost; static uint8_t cmsx_motorOutputLimit; static int8_t cmsx_autoProfileCellCount; @@ -367,7 +366,6 @@ static long cmsx_profileOtherOnEnter(void) { cmsx_D_angle_high = pidProfile->pid[PID_LEVEL_HIGH].D; cmsx_F_angle = pidProfile->pid[PID_LEVEL_LOW].F; cmsx_horizonTransition = pidProfile->horizonTransition; - cmsx_nfe_racermode = pidProfile->nfe_racermode; cmsx_throttleBoost = pidProfile->throttle_boost; cmsx_motorOutputLimit = pidProfile->motor_output_limit; cmsx_autoProfileCellCount = pidProfile->auto_profile_cell_count; @@ -393,7 +391,6 @@ static long cmsx_profileOtherOnExit(const OSD_Entry *self) { pidProfile->pid[PID_LEVEL_HIGH].D = cmsx_D_angle_high; pidProfile->pid[PID_LEVEL_LOW].F = cmsx_F_angle; pidProfile->horizonTransition = cmsx_horizonTransition; - pidProfile->nfe_racermode = cmsx_nfe_racermode; pidProfile->throttle_boost = cmsx_throttleBoost; pidProfile->motor_output_limit = cmsx_motorOutputLimit; pidProfile->auto_profile_cell_count = cmsx_autoProfileCellCount; @@ -419,7 +416,6 @@ static OSD_Entry cmsx_menuProfileOtherEntries[] = { { "ANGLE D HIGH", OME_UINT8, NULL, &(OSD_UINT8_t) { &cmsx_D_angle_high, 0, 200, 1 }, 0 }, { "ANGLE F", OME_UINT16, NULL, &(OSD_UINT16_t) { &cmsx_F_angle, 0, 2000, 1 }, 0 }, { "HORZN TRS", OME_UINT8, NULL, &(OSD_UINT8_t) { &cmsx_horizonTransition, 0, 200, 1 }, 0 }, - { "NFE RACERMODE", OME_TAB, NULL, &(OSD_TAB_t) { &cmsx_nfe_racermode, 1, cms_offOnLabels }, 0 }, #ifdef USE_THROTTLE_BOOST { "THR BOOST", OME_UINT8, NULL, &(OSD_UINT8_t) { &cmsx_throttleBoost, 0, 100, 1 }, 0 }, #endif diff --git a/src/main/common/maths.h b/src/main/common/maths.h index 377972ed57..8f1acc198b 100644 --- a/src/main/common/maths.h +++ b/src/main/common/maths.h @@ -27,6 +27,8 @@ #endif #define power3(x) ((x)*(x)*(x)) +#define SIGN(x) ((x > 0.0f) - (x < 0.0f)) + // Undefine this for use libc sinf/cosf. Keep this defined to use fast sin/cos approximations #define FAST_MATH // order 9 approximation #define VERY_FAST_MATH // order 7 approximation diff --git a/src/main/fc/config.c b/src/main/fc/config.c index 0105cd2cc2..37ec601c81 100644 --- a/src/main/fc/config.c +++ b/src/main/fc/config.c @@ -236,19 +236,6 @@ static void validateAndFixConfig(void) { ) { rxConfigMutable()->rssi_src_frame_errors = false; } - if (!rcSmoothingIsEnabled() || rxConfig()->rcInterpolationChannels == INTERPOLATION_CHANNELS_T) { - for (unsigned i = 0; i < PID_PROFILE_COUNT; i++) { - pidProfilesMutable(i)->pid[PID_ROLL].F = 0; - pidProfilesMutable(i)->pid[PID_PITCH].F = 0; - } - } - if (!rcSmoothingIsEnabled() || - (rxConfig()->rcInterpolationChannels != INTERPOLATION_CHANNELS_RPY && - rxConfig()->rcInterpolationChannels != INTERPOLATION_CHANNELS_RPYT)) { - for (unsigned i = 0; i < PID_PROFILE_COUNT; i++) { - pidProfilesMutable(i)->pid[PID_YAW].F = 0; - } - } #if defined(USE_THROTTLE_BOOST) if (!rcSmoothingIsEnabled() || !(rxConfig()->rcInterpolationChannels == INTERPOLATION_CHANNELS_RPYT @@ -280,6 +267,10 @@ static void validateAndFixConfig(void) { featureClear(FEATURE_ESC_SENSOR); } #endif + + if (currentPidProfile->horizonTransition >= currentPidProfile->horizon_tilt_effect) { + pidProfilesMutable(systemConfig()->pidProfileIndex)->horizonTransition = MAX(currentPidProfile->horizon_tilt_effect - 20, 0); + } // clear features that are not supported. // I have kept them all here in one place, some could be moved to sections of code above. #ifndef USE_PPM diff --git a/src/main/fc/fc_core.c b/src/main/fc/fc_core.c index a7a722ab0e..355ccba92e 100644 --- a/src/main/fc/fc_core.c +++ b/src/main/fc/fc_core.c @@ -681,6 +681,13 @@ bool processRx(timeUs_t currentTimeUs) { } else { DISABLE_FLIGHT_MODE(HORIZON_MODE); } + if (IS_RC_MODE_ACTIVE(BOXNFEMODE) && (FLIGHT_MODE(HORIZON_MODE) || FLIGHT_MODE(ANGLE_MODE))) { + if (!FLIGHT_MODE(NFE_RACE_MODE)) { + ENABLE_FLIGHT_MODE(NFE_RACE_MODE); + } + } else { + DISABLE_FLIGHT_MODE(NFE_RACE_MODE); + } #ifdef USE_GPS_RESCUE if (IS_RC_MODE_ACTIVE(BOXGPSRESCUE) || (failsafeIsActive() && failsafeConfig()->failsafe_procedure == FAILSAFE_PROCEDURE_GPS_RESCUE) ) { if (!FLIGHT_MODE(GPS_RESCUE_MODE)) { diff --git a/src/main/fc/rc_modes.h b/src/main/fc/rc_modes.h index 022667e568..c059fc7423 100644 --- a/src/main/fc/rc_modes.h +++ b/src/main/fc/rc_modes.h @@ -40,7 +40,8 @@ typedef enum { BOXPASSTHRU, BOXFAILSAFE, BOXGPSRESCUE, - BOXID_FLIGHTMODE_LAST = BOXGPSRESCUE, + BOXNFEMODE, + BOXID_FLIGHTMODE_LAST = BOXNFEMODE, // When new flight modes are added, the parameter group version for 'modeActivationConditions' in src/main/fc/rc_modes.c has to be incremented to ensure that the RC modes configuration is reset. diff --git a/src/main/fc/runtime_config.h b/src/main/fc/runtime_config.h index 2bd5d43520..76b2e75b2c 100644 --- a/src/main/fc/runtime_config.h +++ b/src/main/fc/runtime_config.h @@ -81,7 +81,8 @@ typedef enum { PASSTHRU_MODE = (1 << 8), // RANGEFINDER_MODE= (1 << 9), FAILSAFE_MODE = (1 << 10), - GPS_RESCUE_MODE = (1 << 11) + GPS_RESCUE_MODE = (1 << 11), + NFE_RACE_MODE = (1 << 12) } flightModeFlags_e; extern uint16_t flightModeFlags; @@ -103,6 +104,7 @@ extern uint16_t flightModeFlags; [BOXPASSTHRU] = LOG2(PASSTHRU_MODE), \ [BOXFAILSAFE] = LOG2(FAILSAFE_MODE), \ [BOXGPSRESCUE] = LOG2(GPS_RESCUE_MODE), \ + [BOXNFEMODE] = LOG2(NFE_RACE_MODE), \ } \ /**/ diff --git a/src/main/flight/pid.c b/src/main/flight/pid.c index 50d6382768..26c43a20ce 100644 --- a/src/main/flight/pid.c +++ b/src/main/flight/pid.c @@ -162,8 +162,6 @@ void resetPidProfile(pidProfile_t *pidProfile) { .crash_gthreshold = 400, // degrees/second .crash_setpoint_threshold = 350, // degrees/second .crash_recovery = PID_CRASH_RECOVERY_OFF, // off by default - .horizon_tilt_effect = 130, - .nfe_racermode = false, .crash_limit_yaw = 200, .itermLimit = 400, .throttle_boost = 5, @@ -175,7 +173,9 @@ void resetPidProfile(pidProfile_t *pidProfile) { .iterm_relax_threshold_yaw = 35, .motor_output_limit = 100, .auto_profile_cell_count = AUTO_PROFILE_CELL_COUNT_STAY, + .horizon_tilt_effect = 80, .horizonTransition = 0, + .horizonStrength = 15, ); } @@ -318,13 +318,12 @@ typedef struct pidCoefficient_s { static FAST_RAM_ZERO_INIT pidCoefficient_t pidCoefficient[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float maxVelocity[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float feathered_pids; -static FAST_RAM_ZERO_INIT uint8_t nfe_racermode; static FAST_RAM_ZERO_INIT float smart_dterm_smoothing[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float setPointPTransition[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float setPointITransition[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float setPointDTransition[XYZ_AXIS_COUNT]; static FAST_RAM_ZERO_INIT float dtermBoostMultiplier, dtermBoostLimitPercent; -static FAST_RAM_ZERO_INIT float P_angle_low, D_angle_low, P_angle_high, D_angle_high, F_angle, horizonTransition, horizonCutoffDegrees; +static FAST_RAM_ZERO_INIT float P_angle_low, D_angle_low, P_angle_high, D_angle_high, F_angle, horizonTransition, horizonCutoffDegrees, horizonStrength; static FAST_RAM_ZERO_INIT float ITermWindupPointInv; static FAST_RAM_ZERO_INIT timeDelta_t crashTimeLimitUs; static FAST_RAM_ZERO_INIT timeDelta_t crashTimeDelayUs; @@ -359,7 +358,6 @@ void pidInitConfig(const pidProfile_t *pidProfile) { smart_dterm_smoothing[axis] = pidProfile->dFilter[axis].smartSmoothing; } feathered_pids = pidProfile->feathered_pids / 100.0f; - nfe_racermode = pidProfile->nfe_racermode; dtermBoostMultiplier = (pidProfile->dtermBoost * pidProfile->dtermBoost / 1000000) * 0.003; dtermBoostLimitPercent = pidProfile->dtermBoostLimit / 100.0f; P_angle_low = pidProfile->pid[PID_LEVEL_LOW].P * 0.1f; @@ -369,6 +367,7 @@ void pidInitConfig(const pidProfile_t *pidProfile) { F_angle = pidProfile->pid[PID_LEVEL_LOW].F * 0.00000125f; horizonTransition = (float)pidProfile->horizonTransition; horizonCutoffDegrees = pidProfile->horizon_tilt_effect; + horizonStrength = pidProfile->horizonStrength / 50.0f; maxVelocity[FD_ROLL] = maxVelocity[FD_PITCH] = pidProfile->rateAccelLimit * 100 * dT; maxVelocity[FD_YAW] = pidProfile->yawRateAccelLimit * 100 * dT; ITermWindupPointInv = 0.0f; @@ -427,13 +426,11 @@ static float calcHorizonLevelStrength(void) { horizonLevelStrength = inclinationLevelRatio; } else { // if racemode_tilt_effect = 0 or horizonTransition>racemode_tilt_effect means no leveling - horizonLevelStrength = 0; + horizonLevelStrength = 1; } return constrainf(horizonLevelStrength, 0, 1); } -#define SIGN(x) ((x > 0.0f) - (x < 0.0f)) - static float pidLevel(int axis, const pidProfile_t *pidProfile, const rollAndPitchTrims_t *angleTrim, float currentPidSetpoint) { // calculate error angle and limit the angle to the max inclination // rcDeflection is in range [-1.0, 1.0] @@ -452,12 +449,12 @@ static float pidLevel(int axis, const pidProfile_t *pidProfile, const rollAndPit angle = constrainf(angle, -pidProfile->levelAngleLimit, pidProfile->levelAngleLimit); float errorAngle = angle - ((attitude.raw[axis] - angleTrim->raw[axis]) * 0.1f); errorAngle = constrainf(errorAngle, -90.0f, 90.0f); - const float errorAnglePercent = errorAngle / 90.0f; + const float errorAnglePercent = fabsf(errorAngle / 90.0f); // ANGLE mode - control is angle based - p_term_low = (1 - fabsf(errorAnglePercent)) * errorAngle * P_angle_low; - p_term_high = fabsf(errorAnglePercent) * errorAngle * P_angle_high; - d_term_low = (1 - fabsf(errorAnglePercent)) * (attitudePrevious[axis] - attitude.raw[axis]) * 0.1f * D_angle_low; - d_term_high = fabsf(errorAnglePercent) * (attitudePrevious[axis] - attitude.raw[axis]) * 0.1f * D_angle_high; + p_term_low = (1 - errorAnglePercent) * errorAngle * P_angle_low; + p_term_high = errorAnglePercent * errorAngle * P_angle_high; + d_term_low = (1 - errorAnglePercent) * (attitudePrevious[axis] - attitude.raw[axis]) * 0.1f * D_angle_low; + d_term_high = errorAnglePercent * (attitudePrevious[axis] - attitude.raw[axis]) * 0.1f * D_angle_high; attitudePrevious[axis] = attitude.raw[axis]; currentPidSetpoint = p_term_low + p_term_high; currentPidSetpoint += d_term_low + d_term_high; @@ -466,7 +463,7 @@ static float pidLevel(int axis, const pidProfile_t *pidProfile, const rollAndPit // HORIZON mode - mix of ANGLE and ACRO modes // mix in errorAngle to currentPidSetpoint to add a little auto-level feel const float horizonLevelStrength = calcHorizonLevelStrength(); - currentPidSetpoint = (((getSetpointRate(axis) * (1 - horizonLevelStrength)) + getSetpointRate(axis)) * 0.5f) + (currentPidSetpoint * horizonLevelStrength); + currentPidSetpoint = ((getSetpointRate(axis) * (1 - horizonLevelStrength)) + getSetpointRate(axis)) * 0.5f + (currentPidSetpoint * horizonLevelStrength * horizonStrength); } return currentPidSetpoint; } @@ -616,9 +613,9 @@ void pidController(const pidProfile_t *pidProfile, const rollAndPitchTrims_t *an // NFE racermode applies angle only to the roll axis if (FLIGHT_MODE(GPS_RESCUE_MODE) && axis != FD_YAW) { currentPidSetpoint = pidLevel(axis, pidProfile, angleTrim, currentPidSetpoint); - } else if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && !nfe_racermode && (axis != FD_YAW)) { + } else if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && !FLIGHT_MODE(NFE_RACE_MODE) && (axis != FD_YAW)) { currentPidSetpoint = pidLevel(axis, pidProfile, angleTrim, currentPidSetpoint); - } else if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && nfe_racermode && ((axis != FD_YAW) && (axis != FD_PITCH))) { + } else if ((FLIGHT_MODE(ANGLE_MODE) || FLIGHT_MODE(HORIZON_MODE)) && FLIGHT_MODE(NFE_RACE_MODE) && ((axis != FD_YAW) && (axis != FD_PITCH))) { currentPidSetpoint = pidLevel(axis, pidProfile, angleTrim, currentPidSetpoint); } // Handle yaw spin recovery - zero the setpoint on yaw to aid in recovery diff --git a/src/main/flight/pid.h b/src/main/flight/pid.h index fce8041a1f..d7d424c608 100755 --- a/src/main/flight/pid.h +++ b/src/main/flight/pid.h @@ -98,18 +98,18 @@ typedef struct pidProfile_s { uint8_t horizonTransition; // horizonTransition uint8_t horizon_tilt_effect; // inclination factor for Horizon mode + uint8_t horizonStrength; // boost or shrink to angle pids while in horizon mode // EmuFlight PID controller parameters uint8_t feathered_pids; // determine how feathered your pids are - uint8_t i_decay; // i-term decay (increases how quickly iterm shrinks in value) + uint8_t i_decay; // i-term decay (increases how quickly iterm shrinks in value) uint8_t i_decay_cutoff; // iterm values above which i_decay has full effect uint16_t errorBoost; // the weight of the error boost uint16_t errorBoostYaw; // the weight of the error boost for yaw uint8_t errorBoostLimit; // percentage of the error that the emu boost can boost uint8_t errorBoostLimitYaw; // percentage of the error that the emu boost can boost for yaw - uint16_t dtermBoost; // Dterm boost similar to EmuBoost + uint16_t dtermBoost; // Dterm boost similar to EmuBoost uint8_t dtermBoostLimit; // Max value allowed for the dterm boost - uint8_t nfe_racermode; // turn on or off NFE RACERMODE uint16_t yawRateAccelLimit; // yaw accel limiter for deg/sec/ms uint16_t rateAccelLimit; // accel limiter roll/pitch deg/sec/ms uint16_t crash_dthreshold; // dterm crash value diff --git a/src/main/interface/cli.c b/src/main/interface/cli.c index 3ecc49f75a..93231dbcde 100644 --- a/src/main/interface/cli.c +++ b/src/main/interface/cli.c @@ -90,6 +90,7 @@ extern uint8_t __config_end; #include "drivers/usb_msc.h" #include "drivers/vtx_common.h" + #include "fc/board_info.h" #include "fc/config.h" #include "fc/controlrate_profile.h" @@ -684,6 +685,10 @@ static void cliShowArgumentRangeError(char *name, int min, int max) { } static const char *nextArg(const char *currentArg) { + if (!currentArg) { + // if currentArg is null or empty, return to avoid segfault + return ""; + } const char *ptr = strchr(currentArg, ' '); while (ptr && *ptr == ' ') { ptr++; @@ -705,7 +710,7 @@ static const char *processChannelRangeArgs(const char *ptr, channelRange_t *rang } (*validArgumentCount)++; } - } + } } return ptr; } @@ -1978,7 +1983,7 @@ static void cliFlashRead(char *cmdline) { #ifdef USE_VTX_CONTROL static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault) { // print out vtx channel settings - const char *format = "vtx %u %u %u %u %u %u"; + const char *format = "vtx %u %u %u %u %u %u %u"; bool equalsDefault = false; for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) { const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i]; @@ -1990,6 +1995,7 @@ static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxCo cacDefault->auxChannelIndex, cacDefault->band, cacDefault->channel, + cacDefault->power, MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep), MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep) ); @@ -1999,14 +2005,15 @@ static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxCo cac->auxChannelIndex, cac->band, cac->channel, + cac->power, MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep), MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep) ); } } -static void cliVtx(char *cmdline) { - const char *format = "vtx %u %u %u %u %u %u"; +STATIC_UNIT_TESTED void cliVtx(char *cmdline) { + const char *format = "vtx %u %u %u %u %u %u %u"; int i, val = 0; const char *ptr; if (isEmpty(cmdline)) { @@ -2017,6 +2024,7 @@ static void cliVtx(char *cmdline) { if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) { vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i]; uint8_t validArgumentCount = 0; + // aux channel ptr = nextArg(ptr); if (ptr) { val = atoi(ptr); @@ -2025,6 +2033,7 @@ static void cliVtx(char *cmdline) { validArgumentCount++; } } + // band ptr = nextArg(ptr); if (ptr) { val = atoi(ptr); @@ -2035,6 +2044,7 @@ static void cliVtx(char *cmdline) { validArgumentCount++; } } + // band channel ptr = nextArg(ptr); if (ptr) { val = atoi(ptr); @@ -2045,21 +2055,43 @@ static void cliVtx(char *cmdline) { validArgumentCount++; } } + // power + ptr = nextArg(ptr); + if (ptr) { + val = atoi(ptr); + // FIXME Use VTX API to get max + // We check for the min value in final validation below + if (val >= 0 && val <= VTX_SETTINGS_POWER_COUNT) { + cac->power = val; + validArgumentCount++; + } + } + // channel range ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount); + + // consume last argument and clear it from ptr so we can check remainder is empty + ptr = nextArg(ptr); + bool parseError = false; - if (validArgumentCount != 5) { + if (validArgumentCount != 6) { + cliPrintErrorLinef("Invalid argument count, expecting exactly 6, got %d", validArgumentCount); + parseError = true; + } else if (ptr) { + cliPrintErrorLinef("Invalid argument count, expecting exactly 6, got more", validArgumentCount); parseError = true; } else { // check for an empty activation condition for reset vtxChannelActivationCondition_t emptyCac; memset(&emptyCac, 0, sizeof(emptyCac)); - if (memcmp(cac, &emptyCac, sizeof(emptyCac)) != 0 + if (memcmp(cac, &emptyCac, sizeof(emptyCac)) == 0) // FIXME Use VTX API to get min - && ((cac->band < VTX_SETTINGS_MIN_BAND) || (cac->channel < VTX_SETTINGS_MIN_CHANNEL))) { + { + cliPrintErrorLinef("Empty vtx line provided. Resetting this vtx option."); parseError = true; } } if (parseError) { + cliPrintErrorLinef("Resettting vtx condition %d", i); memset(cac, 0, sizeof(vtxChannelActivationCondition_t)); cliShowParseError(); } else { @@ -2068,6 +2100,7 @@ static void cliVtx(char *cmdline) { cac->auxChannelIndex, cac->band, cac->channel, + cac->power, MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep), MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep) ); @@ -4410,7 +4443,7 @@ const clicmd_t cmdTable[] = { #endif CLI_COMMAND_DEF("version", "show version", NULL, cliVersion), #ifdef USE_VTX_CONTROL - CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx), + CLI_COMMAND_DEF("vtx", "vtx channels on switch", " ", cliVtx), #endif }; diff --git a/src/main/interface/msp.c b/src/main/interface/msp.c index e8a4f935b5..3344ecf671 100644 --- a/src/main/interface/msp.c +++ b/src/main/interface/msp.c @@ -1275,7 +1275,7 @@ bool mspProcessOutCommand(uint8_t cmdMSP, sbuf_t *dst) { sbufWriteU8(dst, currentPidProfile->setPointPTransition[YAW]); sbufWriteU8(dst, currentPidProfile->setPointITransition[YAW]); sbufWriteU8(dst, currentPidProfile->setPointDTransition[YAW]); - sbufWriteU8(dst, currentPidProfile->nfe_racermode); + sbufWriteU8(dst, 0); // was NFE_RACE_MODE now made into a flight mode break; case MSP_SENSOR_CONFIG: sbufWriteU8(dst, accelerometerConfig()->acc_hardware); @@ -1861,7 +1861,7 @@ mspResult_e mspProcessInCommand(uint8_t cmdMSP, sbuf_t *src) { currentPidProfile->setPointPTransition[YAW] = sbufReadU8(src); currentPidProfile->setPointITransition[YAW] = sbufReadU8(src); currentPidProfile->setPointDTransition[YAW] = sbufReadU8(src); - currentPidProfile->nfe_racermode = sbufReadU8(src); + sbufReadU8(src); // was NFE_RACE_MODE now its a flight mode. } pidInitConfig(currentPidProfile); break; diff --git a/src/main/interface/msp_box.c b/src/main/interface/msp_box.c index 041e0a2276..e8358a9db4 100644 --- a/src/main/interface/msp_box.c +++ b/src/main/interface/msp_box.c @@ -94,6 +94,7 @@ static const box_t boxes[CHECKBOX_ITEM_COUNT] = { { BOXPIDAUDIO, "PID AUDIO", 44 }, { BOXPARALYZE, "PARALYZE", 45 }, { BOXGPSRESCUE, "GPS RESCUE", 46 }, + { BOXNFEMODE, "NFE RACE MODE", 47 }, }; // mask of enabled IDs, calculated on startup based on enabled features. boxId_e is used as bit index @@ -256,6 +257,7 @@ void initActiveBoxIds(void) { #if defined(USE_PID_AUDIO) BME(BOXPIDAUDIO); #endif + BME(BOXNFEMODE); #undef BME // check that all enabled IDs are in boxes array (check may be skipped when using findBoxById() functions) for (boxId_e boxId = 0; boxId < CHECKBOX_ITEM_COUNT; boxId++) diff --git a/src/main/interface/settings.c b/src/main/interface/settings.c index 51b062a3d9..d2beaf7179 100644 --- a/src/main/interface/settings.c +++ b/src/main/interface/settings.c @@ -898,7 +898,6 @@ const clivalue_t valueTable[] = { { "emu_boost_limit_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 250 }, PG_PID_PROFILE, offsetof(pidProfile_t, errorBoostLimitYaw) }, { "dterm_boost", VAR_UINT16 | PROFILE_VALUE, .config.minmax = { 0, 2000 }, PG_PID_PROFILE, offsetof(pidProfile_t, dtermBoost) }, { "dterm_boost_limit", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 250 }, PG_PID_PROFILE, offsetof(pidProfile_t, dtermBoostLimit) }, - { "nfe_racermode", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_PID_PROFILE, offsetof(pidProfile_t, nfe_racermode) }, { "p_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, pid[PID_PITCH].P) }, { "i_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, pid[PID_PITCH].I) }, @@ -921,6 +920,7 @@ const clivalue_t valueTable[] = { { "horizon_transition", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, horizonTransition) }, { "horizon_tilt_effect", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 180 }, PG_PID_PROFILE, offsetof(pidProfile_t, horizon_tilt_effect) }, + { "horizon_strength", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, horizonStrength) }, { "motor_output_limit", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { MOTOR_OUTPUT_LIMIT_PERCENT_MIN, MOTOR_OUTPUT_LIMIT_PERCENT_MAX }, PG_PID_PROFILE, offsetof(pidProfile_t, motor_output_limit) }, { "auto_profile_cell_count", VAR_INT8 | PROFILE_VALUE, .config.minmax = { AUTO_PROFILE_CELL_COUNT_CHANGE, MAX_AUTO_DETECT_CELL_COUNT }, PG_PID_PROFILE, offsetof(pidProfile_t, auto_profile_cell_count) }, diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 25d289c129..4433de5dc7 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -687,6 +687,8 @@ static bool osdDrawSingleElement(uint8_t item) { strcpy(buff, "STAB"); } else if (FLIGHT_MODE(HORIZON_MODE)) { strcpy(buff, "HOR "); + } else if (FLIGHT_MODE(NFE_RACE_MODE)) { + strcpy(buff, "NFE "); } else if (isAirmodeActive()) { strcpy(buff, "AIR "); } else { diff --git a/src/main/io/vtx_control.c b/src/main/io/vtx_control.c index 82444d8ecb..11e956180b 100644 --- a/src/main/io/vtx_control.c +++ b/src/main/io/vtx_control.c @@ -98,15 +98,25 @@ void vtxUpdateActivatedChannel(void) { if (ARMING_FLAG(ARMED)) { locked = 1; } - if (!locked && vtxCommonDevice()) { + if (vtxCommonDevice()) { static uint8_t lastIndex = -1; for (uint8_t index = 0; index < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; index++) { const vtxChannelActivationCondition_t *vtxChannelActivationCondition = &vtxConfig()->vtxChannelActivationConditions[index]; if (isRangeActive(vtxChannelActivationCondition->auxChannelIndex, &vtxChannelActivationCondition->range) && index != lastIndex) { lastIndex = index; - vtxSettingsConfigMutable()->band = vtxChannelActivationCondition->band; - vtxSettingsConfigMutable()->channel = vtxChannelActivationCondition->channel; + if (!locked) { // once armed, do not change band or channel + if (vtxChannelActivationCondition->band > 0) { + vtxSettingsConfigMutable()->band = vtxChannelActivationCondition->band; + } + if (vtxChannelActivationCondition->channel > 0) { + vtxSettingsConfigMutable()->channel = vtxChannelActivationCondition->channel; + } + } + // vtx power can be changed in flight + if (vtxChannelActivationCondition->power > 0) { + vtxSettingsConfigMutable()->power = vtxChannelActivationCondition->power; + } break; } } diff --git a/src/main/io/vtx_control.h b/src/main/io/vtx_control.h index 61372c247c..d83727fbc5 100644 --- a/src/main/io/vtx_control.h +++ b/src/main/io/vtx_control.h @@ -34,6 +34,7 @@ typedef struct vtxChannelActivationCondition_s { uint8_t auxChannelIndex; uint8_t band; uint8_t channel; + uint8_t power; channelRange_t range; } vtxChannelActivationCondition_t; diff --git a/src/main/rx/crsf.c b/src/main/rx/crsf.c index b7d800e9b2..cc76966940 100644 --- a/src/main/rx/crsf.c +++ b/src/main/rx/crsf.c @@ -129,7 +129,7 @@ typedef struct crsfPayloadLinkstatistics_s { int8_t downlink_SNR; } crsfLinkStatistics_t; -static void handleCrsfLinkStatisticsFrame(const crsfLinkStatistics_t* statsPtr, timeUs_t currentTimeUs) { +static void handleCrsfLinkStatisticsFrame(const crsfLinkStatistics_t* statsPtr) { const crsfLinkStatistics_t stats = *statsPtr; CRSFsetLQ(stats.uplink_Link_quality); CRSFsetRFMode(stats.rf_Mode); @@ -213,7 +213,7 @@ STATIC_UNIT_TESTED void crsfDataReceive(uint16_t c, void *data) { if ((crsfFrame.frame.deviceAddress == CRSF_ADDRESS_FLIGHT_CONTROLLER) && (crsfFrame.frame.frameLength == CRSF_FRAME_ORIGIN_DEST_SIZE + CRSF_FRAME_LINK_STATISTICS_PAYLOAD_SIZE)) { const crsfLinkStatistics_t* statsFrame = (const crsfLinkStatistics_t*)&crsfFrame.frame.payload; - handleCrsfLinkStatisticsFrame(statsFrame, currentTimeUs); + handleCrsfLinkStatisticsFrame(statsFrame); } break; } diff --git a/src/main/sensors/gyro.c b/src/main/sensors/gyro.c index 3c64bfdb2d..888b5bf3e9 100644 --- a/src/main/sensors/gyro.c +++ b/src/main/sensors/gyro.c @@ -1043,7 +1043,9 @@ static FAST_CODE void checkForYawSpin(gyroSensor_t *gyroSensor, timeUs_t current #undef GYRO_FILTER_FUNCTION_NAME #undef GYRO_FILTER_DEBUG_SET +#ifdef USE_GYRO_DATA_ANALYSE static FAST_CODE void dynamicGyroNotchFiltersUpdate(gyroSensor_t* gyroSensor) { + if (gyroSensor->gyroAnalyseState.filterUpdateExecute) { const uint8_t axis = gyroSensor->gyroAnalyseState.filterUpdateAxis; const uint16_t frequency = gyroSensor->gyroAnalyseState.filterUpdateFrequency; @@ -1052,6 +1054,7 @@ static FAST_CODE void dynamicGyroNotchFiltersUpdate(gyroSensor_t* gyroSensor) { biquadFilterUpdate(&gyroSensor->notchFilterDyn[2][axis], frequency, gyro.targetLooptime, gyroSensor->dynNotchQ, FILTER_NOTCH); } } +#endif static FAST_CODE_NOINLINE void gyroUpdateSensor(gyroSensor_t* gyroSensor, timeUs_t currentTimeUs) { #ifndef USE_DMA_SPI_DEVICE diff --git a/src/main/telemetry/crsf.c b/src/main/telemetry/crsf.c index 9fc2a49f85..54909e2483 100644 --- a/src/main/telemetry/crsf.c +++ b/src/main/telemetry/crsf.c @@ -193,7 +193,13 @@ void crsfFrameBatterySensor(sbuf_t *dst) { // use sbufWrite since CRC does not include frame length sbufWriteU8(dst, CRSF_FRAME_BATTERY_SENSOR_PAYLOAD_SIZE + CRSF_FRAME_LENGTH_TYPE_CRC); sbufWriteU8(dst, CRSF_FRAMETYPE_BATTERY_SENSOR); - sbufWriteU16BigEndian(dst, getBatteryVoltage()); // vbat is in units of 0.1V + + uint16_t voltage = getBatteryVoltage(); + if (telemetryConfig()->report_cell_voltage) { + voltage /= getBatteryCellCount(); + } + sbufWriteU16BigEndian(dst, voltage); // vbat is in units of 0.1V + sbufWriteU16BigEndian(dst, getAmperage() / 10); const uint32_t mAhDrawn = getMAhDrawn(); const uint8_t batteryRemainingPercentage = calculateBatteryPercentageRemaining(); diff --git a/src/test/Makefile b/src/test/Makefile index 23c24d3169..601f3ba5cc 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -1,6 +1,4 @@ -# A sample Makefile for building Google Test and using it in user -# tests. Please tweak it to suit your environment and project. You -# may want to move it to your project's root directory. +# this is the test suite of *Flight # # SYNOPSIS: # @@ -9,6 +7,13 @@ # make clean - removes all files generated by make. +# Where to find user code. +USER_DIR = ../main +TEST_DIR = unit +ROOT = ../.. +OBJECT_DIR = ../../obj/test +TARGET_DIR = $(USER_DIR)/target + # Where to find user code. USER_DIR = ../main TEST_DIR = unit @@ -16,17 +21,21 @@ ROOT = ../.. include $(ROOT)/make/system-id.mk + # specify which files that are included in the test in addition to the unittest file. # variables available: # _SRC # _DEFINES # _INCLUDE_DIRS +# _EXPAND (run for each target, call the above with target as $1) +# _BLACKLIST (targets to exclude from an expanded test's run) alignsensor_unittest_SRC := \ $(USER_DIR)/sensors/boardalignment.c \ $(USER_DIR)/common/maths.c + arming_prevention_unittest_SRC := \ $(USER_DIR)/fc/fc_core.c \ $(USER_DIR)/fc/fc_dispatch.c \ @@ -35,14 +44,11 @@ arming_prevention_unittest_SRC := \ $(USER_DIR)/fc/runtime_config.c \ $(USER_DIR)/common/bitarray.c + atomic_unittest_SRC := \ $(USER_DIR)/build/atomic.c \ $(TEST_DIR)/atomic_unittest_c.c -baro_bmp085_unittest_SRC := \ - $(USER_DIR)/drivers/barometer/barometer_bmp085.c \ - $(USER_DIR)/drivers/io.c - baro_bmp280_unittest_SRC := \ $(USER_DIR)/drivers/barometer/barometer_bmp280.c @@ -51,6 +57,7 @@ baro_bmp280_unittest_DEFINES := \ USE_BARO_BMP280 \ USE_BARO_SPI_BMP280 + baro_ms5611_unittest_SRC := \ $(USER_DIR)/drivers/barometer/barometer_ms5611.c @@ -58,10 +65,6 @@ baro_ms5611_unittest_DEFINES := \ USE_BARO_MS5611 \ USE_BARO_SPI_MS5611 -battery_unittest_SRC := \ - $(USER_DIR)/sensors/battery.c \ - $(USER_DIR)/common/maths.c - blackbox_unittest_SRC := \ $(USER_DIR)/blackbox/blackbox.c \ @@ -79,6 +82,7 @@ blackbox_encoding_unittest_SRC := \ $(USER_DIR)/common/printf.c \ $(USER_DIR)/common/typeconversion.c + cli_unittest_SRC := \ $(USER_DIR)/interface/cli.c \ $(USER_DIR)/config/feature.c \ @@ -90,6 +94,7 @@ cli_unittest_DEFINES := \ USE_CLI \ SystemCoreClock=1000000 + cms_unittest_SRC := \ $(USER_DIR)/cms/cms.c \ $(USER_DIR)/common/typeconversion.c \ @@ -161,6 +166,26 @@ osd_unittest_DEFINES := \ USE_ADC_INTERNAL +# TODO: port the test from betaflight +# link_quality_unittest_SRC := \ +# $(USER_DIR)/io/osd.c \ +# $(USER_DIR)/common/typeconversion.c \ +# $(USER_DIR)/drivers/display.c \ +# $(USER_DIR)/drivers/serial.c \ +# $(USER_DIR)/common/crc.c \ +# $(USER_DIR)/common/maths.c \ +# $(USER_DIR)/common/printf.c \ +# $(USER_DIR)/common/streambuf.c \ +# $(USER_DIR)/common/time.c \ +# $(USER_DIR)/fc/runtime_config.c \ +# $(USER_DIR)/common/bitarray.c \ +# $(USER_DIR)/fc/rc_modes.c \ +# $(USER_DIR)/rx/rx.c \ +# $(USER_DIR)/pg/pg.c \ +# $(USER_DIR)/rx/crsf.c \ +# $(USER_DIR)/rx/rx.c + + pg_unittest_SRC := \ $(USER_DIR)/pg/pg.c @@ -220,6 +245,10 @@ sensor_gyro_unittest_SRC := \ $(USER_DIR)/drivers/accgyro/gyro_sync.c \ $(USER_DIR)/pg/pg.c +sensor_gyro_unittest_DEFINES := \ + USE_GYRO_DATA_ANALYSE + + telemetry_crsf_unittest_SRC := \ $(USER_DIR)/rx/crsf.c \ $(USER_DIR)/telemetry/crsf.c \ @@ -271,13 +300,10 @@ transponder_ir_unittest_SRC := \ $(USER_DIR)/drivers/transponder_ir_arcitimer.c -type_conversion_unittest_SRC := \ - $(USER_DIR)/common/typeconversion.c - - ws2811_unittest_SRC := \ $(USER_DIR)/drivers/light_ws2811strip.c + huffman_unittest_SRC := \ $(USER_DIR)/common/huffman.c \ $(USER_DIR)/common/huffman_table.c @@ -285,12 +311,6 @@ huffman_unittest_SRC := \ huffman_unittest_DEFINES := \ USE_HUFFMAN -rcdevice_unittest_SRC := \ - $(USER_DIR)/common/crc.c \ - $(USER_DIR)/common/bitarray.c \ - $(USER_DIR)/fc/rc_modes.c \ - $(USER_DIR)/io/rcdevice.c \ - $(USER_DIR)/io/rcdevice_cam.c \ pid_unittest_SRC := \ $(USER_DIR)/common/filter.c \ @@ -298,11 +318,21 @@ pid_unittest_SRC := \ $(USER_DIR)/drivers/accgyro/gyro_sync.c \ $(USER_DIR)/flight/pid.c \ $(USER_DIR)/pg/pg.c \ - $(USER_DIR)/fc/runtime_config.c + $(USER_DIR)/fc/runtime_config.c \ + $(USER_DIR)/fc/controlrate_profile.c + + +rcdevice_unittest_SRC := \ + $(USER_DIR)/common/crc.c \ + $(USER_DIR)/common/bitarray.c \ + $(USER_DIR)/fc/rc_modes.c \ + $(USER_DIR)/io/rcdevice.c \ + $(USER_DIR)/io/rcdevice_cam.c \ rcdevice_unittest_DEFINES := \ USE_RCDEVICE + vtx_unittest_SRC := \ $(USER_DIR)/fc/fc_core.c \ $(USER_DIR)/fc/fc_dispatch.c \ @@ -333,29 +363,75 @@ USER_INCLUDE_DIR = $(USER_DIR) OBJECT_DIR = ../../obj/test -# Use clang/clang++ by default +CC := clang-10 +CXX := clang++-10 +ifeq ($(shell which $(CC) 2>/dev/null),) +$(info Falling back to 'clang'.) CC := clang CXX := clang++ +endif + #CC := gcc #CXX := g++ +# These flags are needed for clang 10 (maybe even clang 9) to make test work +# -Wno-c99-extensions \ +# -Wno-reorder + COMMON_FLAGS = \ -g \ -Wall \ -Wextra \ -Werror \ + -Wno-error=unused-command-line-argument \ -ggdb3 \ -O0 \ -DUNIT_TEST \ -isystem $(GTEST_DIR)/inc \ - -MMD -MP + -MMD -MP \ + -Wno-c99-extensions \ + -Wno-reorder + +CC_VERSION = $(shell $(CC) -dumpversion) +CXX_VERSION = $(shell $(CXX) -dumpversion) ifeq ($(shell $(CC) -v 2>&1 | grep -q "clang version" && echo "clang"),clang) + +# FIXME: Please revisit versions when new clang version arrive. Supported versions: { Linux: 7 - 10; OSX: 7- 12 } +# Travis reports CC_VERSION of 4.2.1 +CC_VERSION_MAJOR := $(firstword $(subst ., ,$(CC_VERSION))) +CC_VERSION_CHECK_MIN := 7 +CC_VERSION_CHECK_MAX := 10 +ifeq ($(OSFAMILY), macosx) +# MacOS comes with Apple's own flavour of clang that does not adhere to the official versioning +CC_VERSION_CHECK_MAX := 12 +endif +ifeq ($(shell expr $(CC_VERSION_MAJOR) \< $(CC_VERSION_CHECK_MIN) \| $(CC_VERSION_MAJOR) \> $(CC_VERSION_CHECK_MAX)),1) +$(error $(CC) $(CC_VERSION) is not supported. The officially supported version of clang is 'clang-10'. If this is not found, 'clang' is used as a fallback. The version of the compiler must be between $(CC_VERSION_CHECK_MIN) and $(CC_VERSION_CHECK_MAX).) +endif + COMMON_FLAGS += -fblocks +ifndef CYGWIN +ifneq ($(OSFAMILY), macosx) LDFLAGS += -lBlocksRuntime endif +endif +endif -ifndef MACOSX +$(info CC version: $(shell $(CC) --version)) +$(info CXX version: $(shell $(CXX) --version)) + +USE_PTHREAD = YES +USE_COVERAGE = YES +ifeq ($(OSFAMILY), macosx) + USE_PTHREAD = +endif +ifdef CYGWIN + USE_PTHREAD = + USE_COVERAGE = +endif + +ifdef USE_PTHREAD COMMON_FLAGS += -pthread endif @@ -368,7 +444,9 @@ CXX_FLAGS = $(COMMON_FLAGS) \ -std=gnu++11 # Compiler flags for coverage instrumentation +ifdef USE_COVERAGE COVERAGE_FLAGS := --coverage +endif C_FLAGS += $(COVERAGE_FLAGS) CXX_FLAGS += $(COVERAGE_FLAGS) @@ -376,15 +454,23 @@ CXX_FLAGS += $(COVERAGE_FLAGS) C_FLAGS += -D_GNU_SOURCE # Set up the parameter group linker flags according to OS -ifdef MACOSX +ifeq ($(OSFAMILY), macosx) LDFLAGS += -Wl,-map,$(OBJECT_DIR)/$@.map else LDFLAGS += -Wl,-T,$(TEST_DIR)/pg.ld -Wl,-Map,$(OBJECT_DIR)/$@.map endif # Gather up all of the tests. -TEST_SRC = $(sort $(wildcard $(TEST_DIR)/*.cc)) -TESTS = $(TEST_SRC:$(TEST_DIR)/%.cc=%) +TEST_SRCS = $(sort $(wildcard $(TEST_DIR)/*.cc)) +TEST_BASENAMES = $(TEST_SRCS:$(TEST_DIR)/%.cc=%) +TESTS_TARGET_SPECIFIC = $(foreach test,$(TEST_BASENAMES),$(if $($(test)_EXPAND),$(test))) +TESTS_TARGET_SPECIFIC_EXPANDED = $(foreach test,$(TESTS_TARGET_SPECIFIC),$(foreach \ + target,$(filter-out $($(test)_BLACKLIST),$(VALID_TARGETS)),$(test).$(target))) + +TESTS = $(foreach test,$(TEST_BASENAMES),$(if $($(test)_EXPAND),,$(test))) +TESTS_ALL = $(TESTS) $(TESTS_TARGET_SPECIFIC_EXPANDED) +TESTS_REPRESENTATIVE = $(TESTS) $(foreach test,$(TESTS_TARGET_SPECIFIC), \ + $(test).$(word 1,$(filter-out $($(test)_BLACKLIST),$(VALID_TARGETS)))) # All Google Test headers. Usually you shouldn't change this # definition. @@ -397,15 +483,19 @@ include ../../make/build_verbosity.mk # House-keeping build targets. -## test : Build and run the Unit Tests (default goal) +## test : Build and run the non target specific Unit Tests (default goal) test: $(TESTS:%=test_%) +## test-all : Build and run all Unit Tests +test-all: $(TESTS_ALL:%=test_%) + +## test-representative : Build and run a representative subset of the Unit Tests (i.e. run every expanded test only for the first target) +test-representative: $(TESTS_REPRESENTATIVE:%=test_%) + ## junittest : Build and run the Unit Tests, producing Junit XML result files." junittest: EXEC_OPTS = "--gtest_output=xml:$<_results.xml" junittest: $(TESTS:%=test_%) - - ## help : print this help message and exit ## what : print this help message and exit ## usage : print this help message and exit @@ -423,14 +513,18 @@ help what usage: Makefile @echo "Any of the Unit Test programs can be used as goals to build:" @$(foreach test, $(TESTS), echo " $(OBJECT_DIR)/$(test)/$(test)";) @echo "" - @echo "Any of the Unit Test programs can be used as goals to build and run:" + @echo "Any of the Unit Test programs (except for target specific unit tests) can be used as goals to build and run:" @$(foreach test, $(TESTS), echo " test_$(test)";) + +versions: + @echo "C compiler: $(CC): $(CC_VERSION)" + @echo "C++ compiler: $(CXX): $(CXX_VERSION)" + ## clean : Cleanup the UnitTest binaries. clean : rm -rf $(OBJECT_DIR) - # Builds gtest.a and gtest_main.a. # Usually you shouldn't tweak such internal variables, indicated by a @@ -465,69 +559,126 @@ $(OBJECT_DIR)/gtest_main.a : $(OBJECT_DIR)/gtest-all.o $(OBJECT_DIR)/gtest_main. $(OBJECT_DIR)/gtest_main.d -# includes in test dir must override includes in user dir -TEST_INCLUDE_DIRS := $(TEST_DIR) \ - $(USER_INCLUDE_DIR) - -TEST_CFLAGS = $(addprefix -I,$(TEST_INCLUDE_DIRS)) - +# includes in test dir must override includes in user dir, unless the user +# specifies a list of endorsed directories in ${target}_INCLUDE_DIRS. +test_include_dirs = $1 $(TEST_DIR) $(USER_DIR) +test_cflags = $(addprefix -I,$(call test_include_dirs,$1)) +# target name extractor +# param $1 = expanded test name in the form of test.target +target = $(1:$(basename $1).%=%) # canned recipe for all test builds -# param $1 = testname +# +# variable expansion rules of thumb (number of $'s): +# * parameters: one $, e.g. $1 +# * statically accessed variables set elsewhere: one $, e.g. $(C_FLAGS) +# * dynamically accessed variables set elsewhere: one $, e.g. $($1_SRC) +# * make functions accessing only the above: one $, e.g. $(basename $1) +# * dynamically set and accessed variables: two $, e.g. $$($1_OBJS) +# * make functions accessing dynamically set variables: two $, +# e.g. $$(call test_cflags,$$($1_INCLUDE_DIRS)) +# +# param $1 = plain test name for global tests, test.target for per-target tests define test-specific-stuff -$$1_OBJS = $$(patsubst $$(TEST_DIR)%,$$(OBJECT_DIR)/$1%, $$(patsubst $$(USER_DIR)%,$$(OBJECT_DIR)/$1%,$$($1_SRC:=.o))) +ifeq ($1,$(basename $1)) +# standard global test +$1_OBJS = $(patsubst \ + $(TEST_DIR)/%,$(OBJECT_DIR)/$1/%,$(patsubst \ + $(USER_DIR)/%,$(OBJECT_DIR)/$1/%,$($1_SRC:=.o))) +else +# test executed for each target, $1 has the form of test.target +$1_SRC = $(addsuffix .o,$(call $(basename $1)_SRC,$(call target,$1))) +$1_OBJS = $$(patsubst \ + $(TEST_DIR)/%,$(OBJECT_DIR)/$1/%,$$(patsubst \ + $(USER_DIR)/%,$(OBJECT_DIR)/$1/%,$$(patsubst \ + $(TARGET_DIR)/$(call get_base_target,$(call target,$1))/%,$(OBJECT_DIR)/$1/%,$$($1_SRC)))) +$1_DEFINES = $(call $(basename $1)_DEFINES,$(call target,$1)) +$1_INCLUDE_DIRS = $(call $(basename $1)_INCLUDE_DIRS,$(call target,$1)) +endif # $$(info $1 -v-v-------) -# $$(info $1_SRC: $($1_SRC)) -# $$(info $1_OBJS: $$($$1_OBJS)) +# $$(info $1_SRC: $$($1_SRC)) +# $$(info $1_OBJS: $$($1_OBJS)) # $$(info $1 -^-^-------) - -#include generated dependencies --include $$($$1_OBJS:.o=.d) --include $(OBJECT_DIR)/$1/$1.d - +# include generated dependencies +-include $$($1_OBJS:.o=.d) +-include $(OBJECT_DIR)/$1/$(basename $1).d $(OBJECT_DIR)/$1/%.c.o: $(USER_DIR)/%.c @echo "compiling $$<" "$(STDOUT)" $(V1) mkdir -p $$(dir $$@) - $(V1) $(CC) $(C_FLAGS) $(TEST_CFLAGS) \ - $(foreach def,$($1_INCLUDE_DIRS),-I $(def)) \ - $(foreach def,$($1_DEFINES),-D $(def)) \ + $(V1) $(CC) $(C_FLAGS) $$(call test_cflags,$$($1_INCLUDE_DIRS)) \ + $$(foreach def,$$($1_DEFINES),-D $$(def)) \ -c $$< -o $$@ $(OBJECT_DIR)/$1/%.c.o: $(TEST_DIR)/%.c @echo "compiling test c file: $$<" "$(STDOUT)" $(V1) mkdir -p $$(dir $$@) - $(V1) $(CC) $(C_FLAGS) $(TEST_CFLAGS) \ - $(foreach def,$($1_INCLUDE_DIRS),-I $(def)) \ - $(foreach def,$($1_DEFINES),-D $(def)) \ + $(V1) $(CC) $(C_FLAGS) $$(call test_cflags,$$($1_INCLUDE_DIRS)) \ + $$(foreach def,$$($1_DEFINES),-D $$(def)) \ -c $$< -o $$@ -$(OBJECT_DIR)/$1/$1.o: $(TEST_DIR)/$1.cc - @echo "compiling $$<" "$(STDOUT)" +ifneq ($1,$(basename $1)) +# per-target tests may compile files from the target directory +$(OBJECT_DIR)/$1/%.c.o: $(TARGET_DIR)/$(call get_base_target,$(call target,$1))/%.c + @echo "compiling target c file: $$<" "$(STDOUT)" $(V1) mkdir -p $$(dir $$@) - $(V1) $(CXX) $(CXX_FLAGS) $(TEST_CFLAGS) \ - $(foreach def,$($1_INCLUDE_DIRS),-I $(def)) \ - $(foreach def,$($1_DEFINES),-D $(def)) \ + $(V1) $(CC) $(C_FLAGS) $$(call test_cflags,$$($1_INCLUDE_DIRS)) \ + $$(foreach def,$$($1_DEFINES),-D $$(def)) \ -c $$< -o $$@ +endif +$(OBJECT_DIR)/$1/$(basename $1).o: $(TEST_DIR)/$(basename $1).cc + @echo "compiling $$<" "$(STDOUT)" + $(V1) mkdir -p $$(dir $$@) + $(V1) $(CXX) $(CXX_FLAGS) $$(call test_cflags,$$($1_INCLUDE_DIRS)) \ + $$(foreach def,$$($1_DEFINES),-D $$(def)) \ + -c $$< -o $$@ -$(OBJECT_DIR)/$1/$1 : $$($$1_OBJS) \ - $(OBJECT_DIR)/$1/$1.o \ +$(OBJECT_DIR)/$1/$(basename $1): $$($1_OBJS) \ + $(OBJECT_DIR)/$1/$(basename $1).o \ $(OBJECT_DIR)/gtest_main.a @echo "linking $$@" "$(STDOUT)" $(V1) mkdir -p $(dir $$@) $(V1) $(CXX) $(CXX_FLAGS) $(LDFLAGS) $$^ -o $$@ -test_$1: $(OBJECT_DIR)/$1/$1 +test_$1: $(OBJECT_DIR)/$1/$(basename $1) $(V1) $$< $$(EXEC_OPTS) "$(STDOUT)" && echo "running $$@: PASS" endef -#apply the canned recipe above to all tests -$(eval $(foreach test,$(TESTS),$(call test-specific-stuff,$(test)))) + +ifeq ($(MAKECMDGOALS),test-all) + $(eval $(foreach test,$(TESTS_ALL),$(call test-specific-stuff,$(test)))) +else + ifeq ($(MAKECMDGOALS),test-representative) + $(eval $(foreach test,$(TESTS_REPRESENTATIVE),$(call test-specific-stuff,$(test)))) + else + $(eval $(foreach test,$(TESTS),$(call test-specific-stuff,$(test)))) + endif +endif + +$(foreach test,$(TESTS_ALL),$(if $($(basename $(test))_SRC),,$(error \ + Test 'unit/$(basename $(test)).cc' has no '$(basename $(test))_SRC' variable defined))) +$(foreach var,$(filter-out TARGET_SRC,$(filter %_SRC,$(.VARIABLES))),$(if $(filter $(var:_SRC=)%,$(TESTS_ALL)),,$(error \ + Variable '$(var)' has no 'unit/$(var:_SRC=).cc' test))) + + +target_list: + @echo ========== BASE TARGETS ========== + @echo $(BASE_TARGETS) + @echo ========== ALT TARGETS ========== + @echo $(ALT_TARGETS) + @echo ========== VALID_TARGETS ========== + @echo $(VALID_TARGETS) + @echo ========== BASE/ALT PAIRS ========== + @echo $(BASE_ALT_PAIRS) + @echo ========== ALT/BASE MAPPING ========== + @echo $(foreach target,$(ALT_TARGETS),$(target)\>$(call get_base_target,$(target))) + @echo ========== ALT/BASE FULL MAPPING ========== + @echo $(foreach target,$(VALID_TARGETS),$(target)\>$(call get_base_target,$(target))) \ No newline at end of file diff --git a/src/test/unit/arming_prevention_unittest.cc b/src/test/unit/arming_prevention_unittest.cc index bc754b9026..2dd829372e 100644 --- a/src/test/unit/arming_prevention_unittest.cc +++ b/src/test/unit/arming_prevention_unittest.cc @@ -848,6 +848,7 @@ extern "C" { void dashboardDisablePageCycling(void) {} bool imuQuaternionHeadfreeOffsetSet(void) { return true; } void rescheduleTask(cfTaskId_e, uint32_t) {} + void updateRcRefreshRate(timeUs_t) {} bool usbCableIsInserted(void) { return false; } bool usbVcpIsConnected(void) { return false; } void pinioBoxTaskControl(void) {} diff --git a/src/test/unit/atomic_unittest_c.c b/src/test/unit/atomic_unittest_c.c index a709b28593..a502e0de46 100644 --- a/src/test/unit/atomic_unittest_c.c +++ b/src/test/unit/atomic_unittest_c.c @@ -4,41 +4,33 @@ struct barrierTrace { int enter, leave; }; -int testAtomicBarrier_C(struct barrierTrace *b0, struct barrierTrace *b1, struct barrierTrace sample[][2]) { +int testAtomicBarrier_C(struct barrierTrace *b0, struct barrierTrace *b1, struct barrierTrace sample[][2]) +{ int sIdx = 0; + // replace barrier macros to track barrier invocation // pass known struct as barrier variable, keep track inside it #undef ATOMIC_BARRIER_ENTER #undef ATOMIC_BARRIER_LEAVE #define ATOMIC_BARRIER_ENTER(ptr, refStr) do {(ptr)->enter++; } while(0) #define ATOMIC_BARRIER_LEAVE(ptr, refStr) do {(ptr)->leave++; } while(0) - b0->enter = 0; - b0->leave = 0; - b1->enter = 0; - b1->leave = 0; - sample[sIdx][0] = *b0; - sample[sIdx][1] = *b1; - sIdx++; + + b0->enter = 0; b0->leave = 0; + b1->enter = 0; b1->leave = 0; + sample[sIdx][0]=*b0; sample[sIdx][1]=*b1; sIdx++; do { ATOMIC_BARRIER(*b0); ATOMIC_BARRIER(*b1); - sample[sIdx][0] = *b0; - sample[sIdx][1] = *b1; - sIdx++; + sample[sIdx][0]=*b0; sample[sIdx][1]=*b1; sIdx++; do { ATOMIC_BARRIER(*b0); - sample[sIdx][0] = *b0; - sample[sIdx][1] = *b1; - sIdx++; + sample[sIdx][0]=*b0; sample[sIdx][1]=*b1; sIdx++; } while(0); - sample[sIdx][0] = *b0; - sample[sIdx][1] = *b1; - sIdx++; + sample[sIdx][0]=*b0; sample[sIdx][1]=*b1; sIdx++; } while(0); - sample[sIdx][0] = *b0; - sample[sIdx][1] = *b1; - sIdx++; + sample[sIdx][0]=*b0; sample[sIdx][1]=*b1; sIdx++; return sIdx; + // ATOMIC_BARRIER is broken in rest of this file #undef ATOMIC_BARRIER_ENTER #undef ATOMIC_BARRIER_LEAVE diff --git a/src/test/unit/cli_unittest.cc b/src/test/unit/cli_unittest.cc index b0f1975dff..742097b74b 100644 --- a/src/test/unit/cli_unittest.cc +++ b/src/test/unit/cli_unittest.cc @@ -47,14 +47,17 @@ extern "C" { #include "io/ledstrip.h" #include "io/osd.h" #include "io/serial.h" + #include "io/vtx_control.h" #include "io/vtx.h" #include "pg/beeper.h" + #include "pg/pg.h" #include "rx/rx.h" #include "scheduler/scheduler.h" #include "sensors/battery.h" void cliSet(char *cmdline); void cliGet(char *cmdline); + void cliVtx(char *cmdline); const clivalue_t valueTable[] = { { "array_unit_test", VAR_INT8 | MODE_ARRAY | MASTER_VALUE, .config.array.length = 3, PG_RESERVED_FOR_TESTING_1, 0 } @@ -80,6 +83,10 @@ extern "C" { PG_REGISTER_ARRAY(rxChannelRangeConfig_t, NON_AUX_CHANNEL_COUNT, rxChannelRangeConfigs, PG_RX_CHANNEL_RANGE_CONFIG, 0); PG_REGISTER_ARRAY(rxFailsafeChannelConfig_t, MAX_SUPPORTED_RC_CHANNEL_COUNT, rxFailsafeChannelConfigs, PG_RX_FAILSAFE_CHANNEL_CONFIG, 0); PG_REGISTER(pidConfig_t, pidConfig, PG_PID_CONFIG, 0); + PG_REGISTER_WITH_RESET_TEMPLATE(vtxConfig_t, vtxConfig, PG_VTX_CONFIG, 1); + PG_RESET_TEMPLATE(vtxConfig_t, vtxConfig, + .halfDuplex = true + ); PG_REGISTER_WITH_RESET_FN(int8_t, unitTestData, PG_RESERVED_FOR_TESTING_1, 0); } @@ -110,11 +117,92 @@ TEST(CLIUnittest, TestCliSet) EXPECT_EQ( -3, data[1]); EXPECT_EQ( 1, data[2]); - //cliGet((char *)"osd_item_vbat"); //EXPECT_EQ(false, false); } +TEST(CLIUnittest, TestCliVtx) +{ + // check condition 6 is empty to start with + const vtxChannelActivationCondition_t *cac6 = &vtxConfig()->vtxChannelActivationConditions[6]; + EXPECT_EQ(0, cac6->auxChannelIndex); + EXPECT_EQ(0, cac6->band); + EXPECT_EQ(0, cac6->channel); + EXPECT_EQ(0, cac6->power); + EXPECT_EQ(0, cac6->power); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac6->range.startStep)); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac6->range.endStep)); + + // set condition 6 + char *str = (char *)"6 6 1 2 3 925 1300"; + cliVtx(str); + + EXPECT_EQ(6, cac6->auxChannelIndex); + EXPECT_EQ(1, cac6->band); + EXPECT_EQ(2, cac6->channel); + EXPECT_EQ(3, cac6->power); + EXPECT_EQ(3, cac6->power); + EXPECT_EQ(925, MODE_STEP_TO_CHANNEL_VALUE(cac6->range.startStep)); + EXPECT_EQ(1300, MODE_STEP_TO_CHANNEL_VALUE(cac6->range.endStep)); + + // set condition 2 + char *str2 = (char *)"2 1 2 3 4 1500 2100"; + cliVtx(str2); + + const vtxChannelActivationCondition_t *cac2 = &vtxConfig()->vtxChannelActivationConditions[2]; + EXPECT_EQ(1, cac2->auxChannelIndex); + EXPECT_EQ(2, cac2->band); + EXPECT_EQ(3, cac2->channel); + EXPECT_EQ(4, cac2->power); + EXPECT_EQ(1500, MODE_STEP_TO_CHANNEL_VALUE(cac2->range.startStep)); + EXPECT_EQ(2100, MODE_STEP_TO_CHANNEL_VALUE(cac2->range.endStep)); + + + // test we can reset existing condition + // by providing 0 vaues, the condition should be reset (and error shown to the user) + char *str3 = (char *)"2 0 0 0 0 0 0"; + cliVtx(str3); + + EXPECT_EQ(0, cac2->auxChannelIndex); + EXPECT_EQ(0, cac2->band); + EXPECT_EQ(0, cac2->channel); + EXPECT_EQ(0, cac2->power); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac2->range.startStep)); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac2->range.endStep)); +} +TEST(CLIUnittest, TestCliVtxInvalidArgumentCount) +{ + // cli expects 7 total arguments (condition index + 6 parameters) + char *correctCmd = (char *) "1 1 2 3 4 1000 2000"; + cliVtx(correctCmd); // load some data into condition 1 + const vtxChannelActivationCondition_t *cac1 = &vtxConfig()->vtxChannelActivationConditions[1]; + EXPECT_EQ(1, cac1->auxChannelIndex); + EXPECT_EQ(2, cac1->band); + EXPECT_EQ(3, cac1->channel); + EXPECT_EQ(4, cac1->power); + EXPECT_EQ(1000, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.startStep)); + EXPECT_EQ(2000, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.endStep)); + char *str = (char *)"1 0 0 0 0"; // 5 arguments, should throw an error and reset the line 1 + cliVtx(str); + EXPECT_EQ(0, cac1->auxChannelIndex); + EXPECT_EQ(0, cac1->band); + EXPECT_EQ(0, cac1->channel); + EXPECT_EQ(0, cac1->power); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.startStep)); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.endStep)); + + + cliVtx(correctCmd); // load some more data into condition 1 so we have something to reset + + char *tooManyArgs = (char *)"1 0 0 0 0 100 200 300"; // 7 arguments, expects 6 + cliVtx(tooManyArgs); //should throw an cli error and reset the line 1 + EXPECT_EQ(0, cac1->auxChannelIndex); + EXPECT_EQ(0, cac1->band); + EXPECT_EQ(0, cac1->channel); + EXPECT_EQ(0, cac1->power); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.startStep)); + EXPECT_EQ(900, MODE_STEP_TO_CHANNEL_VALUE(cac1->range.endStep)); +} // STUBS extern "C" { diff --git a/src/test/unit/osd_unittest.cc b/src/test/unit/osd_unittest.cc index 6a60edccc7..9b44960666 100644 --- a/src/test/unit/osd_unittest.cc +++ b/src/test/unit/osd_unittest.cc @@ -62,6 +62,12 @@ extern "C" { int osdConvertTemperatureToSelectedUnit(int tempInDegreesCelcius); bool usbCableIsInserted(void) { return false; } bool usbVcpIsConnected(void) { return false; } + + uint16_t CRSFgetLQ(void) { return 0;} + uint8_t CRSFgetRSSI(void) { return 0;} + uint8_t CRSFgetSnR(void) { return 0;} + uint8_t CRSFgetRFMode(void) { return 0;} + uint16_t CRSFgetTXPower(void) {return 0;} uint16_t rssi; attitudeEulerAngles_t attitude; diff --git a/src/test/unit/pid_unittest.cc b/src/test/unit/pid_unittest.cc index ad394c340f..5b6509ac25 100644 --- a/src/test/unit/pid_unittest.cc +++ b/src/test/unit/pid_unittest.cc @@ -28,7 +28,11 @@ bool simulateMixerSaturated = false; float simulatedSetpointRate[3] = { 0,0,0 }; float simulatedRcDeflection[3] = { 0,0,0 }; float simulatedThrottlePIDAttenuation = 1.0f; +float simulatedThrottlePAttenuation = 1.0f; +float simulatedThrottleIAttenuation = 1.0f; +float simulatedThrottleDAttenuation = 1.0f; float simulatedMotorMixRange = 0.0f; +float simulatedVbatCompensation = 1.0f; int16_t debug[DEBUG16_VALUE_COUNT]; uint8_t debugMode; @@ -46,11 +50,13 @@ extern "C" { #include "drivers/sound_beeper.h" #include "drivers/time.h" + #include "fc/config.h" #include "fc/fc_core.h" #include "fc/fc_rc.h" #include "fc/rc_controls.h" #include "fc/runtime_config.h" + #include "fc/controlrate_profile.h" #include "flight/pid.h" #include "flight/imu.h" @@ -64,7 +70,12 @@ extern "C" { gyro_t gyro; attitudeEulerAngles_t attitude; + /* controlRateConfig_t *currentControlRateProfile; */ float getThrottlePIDAttenuation(void) { return simulatedThrottlePIDAttenuation; } + float getThrottlePAttenuation(void) { return simulatedThrottlePAttenuation; } + float getThrottleIAttenuation(void) { return simulatedThrottleIAttenuation; } + float getThrottleDAttenuation(void) { return simulatedThrottleDAttenuation; } + float calculateVbatCompensation(uint8_t, uint8_t) { return simulatedVbatCompensation; } float getMotorMixRange(void) { return simulatedMotorMixRange; } float getSetpointRate(int axis) { return simulatedSetpointRate[axis]; } bool mixerIsOutputSaturated(int, float) { return simulateMixerSaturated; } @@ -73,6 +84,12 @@ extern "C" { bool gyroOverflowDetected(void) { return false; } float getRcDeflection(int axis) { return simulatedRcDeflection[axis]; } void beeperConfirmationBeeps(uint8_t) { } + controlRateConfig_t *currentControlRateProfile = controlRateProfilesMutable(0); + + void initRcProcessing(void) {} + + PG_REGISTER(systemConfig_t, systemConfig, PG_SYSTEM_CONFIG, 0); + } pidProfile_t *pidProfile; @@ -117,6 +134,7 @@ void setDefaultTestSettings(void) { pidProfile->throttle_boost_cutoff = 15; pidProfile->iterm_rotation = false; + gyro.targetLooptime = 4000; } @@ -125,6 +143,7 @@ timeUs_t currentTestTime(void) { } void resetTest(void) { + printf("resetTest:begining\n"); loopIter = 0; simulateMixerSaturated = false; simulatedThrottlePIDAttenuation = 1.0f; @@ -133,6 +152,7 @@ void resetTest(void) { pidStabilisationState(PID_STABILISATION_OFF); DISABLE_ARMING_FLAG(ARMED); + setDefaultTestSettings(); for (int axis = FD_ROLL; axis <= FD_YAW; axis++) { pidData[axis].P = 0; @@ -148,12 +168,13 @@ void resetTest(void) { attitude.values.yaw = 0; flightModeFlags = 0; - pidInit(pidProfile); - + printf("initialising profile\n"); // Run pidloop for a while after reset + printf("configuring pid controller\n"); for (int loop = 0; loop < 20; loop++) { pidController(pidProfile, &rollAndPitchTrims, currentTestTime()); } + printf("resetTest: end\n"); } void setStickPosition(int axis, float stickRatio) { @@ -168,7 +189,10 @@ float calculateTolerance(float input) { TEST(pidControllerTest, testInitialisation) { + printf("start\n"); resetTest(); + printf("test reset\n"); + fflush(stdout); // In initial state PIDsums should be 0 for (int axis = 0; axis <= FD_YAW; axis++) { diff --git a/src/test/unit/rc_controls_unittest.cc b/src/test/unit/rc_controls_unittest.cc index cff4a2a463..2e0d804576 100644 --- a/src/test/unit/rc_controls_unittest.cc +++ b/src/test/unit/rc_controls_unittest.cc @@ -327,7 +327,9 @@ TEST_F(RcControlsAdjustmentsTest, processRcAdjustmentsWithRcRateFunctionSwitchUp .thrMid8 = 0, .thrExpo8 = 0, .rates = {0,0,0}, - .dynThrPID = 0, + .dynThrP = 0, + .dynThrI = 0, + .dynThrD = 0, .rcExpo[FD_YAW] = 0, .tpa_breakpoint = 0 }; diff --git a/src/test/unit/rx_crsf_unittest.cc b/src/test/unit/rx_crsf_unittest.cc index 459348d090..4854c21d84 100644 --- a/src/test/unit/rx_crsf_unittest.cc +++ b/src/test/unit/rx_crsf_unittest.cc @@ -296,4 +296,10 @@ void crsfScheduleMspResponse(void) {}; bool bufferMspFrame(uint8_t *, int) {return true;} bool isBatteryVoltageAvailable(void) { return true; } bool isAmperageAvailable(void) { return true; } + +void CRSFsetLQ(uint16_t) {} +void CRSFsetRFMode(uint8_t) {} +void CRSFsetRSSI(uint8_t) {} +void CRSFsetSnR(uint16_t) {} +void CRSFsetTXPower(uint16_t) {} } diff --git a/src/test/unit/rx_ranges_unittest.cc b/src/test/unit/rx_ranges_unittest.cc index d547f6bbd7..b474bd4896 100644 --- a/src/test/unit/rx_ranges_unittest.cc +++ b/src/test/unit/rx_ranges_unittest.cc @@ -223,4 +223,6 @@ void failsafeOnValidDataFailed(void) { } +void setCrsfRssi(void) {} + } diff --git a/src/test/unit/rx_rx_unittest.cc b/src/test/unit/rx_rx_unittest.cc index 2454b26b1b..2e81311e93 100644 --- a/src/test/unit/rx_rx_unittest.cc +++ b/src/test/unit/rx_rx_unittest.cc @@ -231,5 +231,6 @@ extern "C" { void rxPwmInit(const rxConfig_t *, rxRuntimeConfig_t *) {} void pinioBoxTaskControl(void) {} -} + + void setCrsfRssi(bool) {} } diff --git a/src/test/unit/sensor_gyro_unittest.cc b/src/test/unit/sensor_gyro_unittest.cc index fc2dbc20e4..8984b98717 100644 --- a/src/test/unit/sensor_gyro_unittest.cc +++ b/src/test/unit/sensor_gyro_unittest.cc @@ -17,6 +17,7 @@ #include #include +#include "arm_math.h" #include #include @@ -32,6 +33,7 @@ extern "C" { #include "drivers/accgyro/accgyro_fake.h" #include "drivers/accgyro/accgyro_mpu.h" #include "drivers/sensor.h" + #include "fc/rc_controls.h" #include "io/beeper.h" #include "pg/pg.h" #include "pg/pg_ids.h" diff --git a/src/test/unit/telemetry_crsf_msp_unittest.cc b/src/test/unit/telemetry_crsf_msp_unittest.cc index 4a87eb4db1..fc573277c0 100644 --- a/src/test/unit/telemetry_crsf_msp_unittest.cc +++ b/src/test/unit/telemetry_crsf_msp_unittest.cc @@ -297,4 +297,9 @@ extern "C" { int32_t getMAhDrawn(void) { return testmAhDrawn; } + void CRSFsetLQ(uint16_t) {} + void CRSFsetRFMode(uint8_t) {} + void CRSFsetRSSI(uint8_t) {} + void CRSFsetSnR(uint16_t) {} + void CRSFsetTXPower(uint16_t) {} } diff --git a/src/test/unit/telemetry_crsf_unittest.cc b/src/test/unit/telemetry_crsf_unittest.cc index 6da6cb6b9c..26460bb37d 100644 --- a/src/test/unit/telemetry_crsf_unittest.cc +++ b/src/test/unit/telemetry_crsf_unittest.cc @@ -341,4 +341,10 @@ void crsfScheduleMspResponse(void) {}; bool isBatteryVoltageConfigured(void) { return true; } bool isAmperageConfigured(void) { return true; } +void CRSFsetLQ(uint16_t) {} +void CRSFsetRFMode(uint8_t) {} +void CRSFsetRSSI(uint8_t) {} +void CRSFsetSnR(uint16_t) {} +void CRSFsetTXPower(uint16_t) {} + } diff --git a/src/test/unit/vtx_unittest.cc b/src/test/unit/vtx_unittest.cc index 1b01154752..f4f871574a 100644 --- a/src/test/unit/vtx_unittest.cc +++ b/src/test/unit/vtx_unittest.cc @@ -39,6 +39,8 @@ extern "C" { #include "io/beeper.h" #include "io/gps.h" #include "io/vtx.h" + #include "io/vtx_control.h" + #include "io/vtx_string.h" #include "rx/rx.h" #include "scheduler/scheduler.h" #include "sensors/acceleration.h" @@ -72,6 +74,8 @@ extern "C" { bool cmsInMenu = false; float axisPID_P[3], axisPID_I[3], axisPID_D[3], axisPIDSum[3]; rxRuntimeConfig_t rxRuntimeConfig = {}; + + void vtxUpdateActivatedChannel(void); } uint32_t simulationFeatureFlags = 0; @@ -79,6 +83,16 @@ uint32_t simulationTime = 0; bool gyroCalibDone = false; bool simulationHaveRx = false; +const char * const powerNames[4] ={ "---", "25", "100", "200"} ; +static vtxDevice_t testVtxDevice = { + .capability.bandCount = 5, + .capability.channelCount = 8, + .capability.powerCount = 3, + .bandNames = (char **)vtx58BandNames, + .channelNames = (char **)vtx58ChannelNames, + .powerNames = (char **)powerNames +}; + #include "gtest/gtest.h" TEST(VtxTest, PitMode) @@ -109,6 +123,137 @@ TEST(VtxTest, PitMode) EXPECT_EQ(5300, vtxGetSettings().freq); } +void ResetVtxActivationConditions(void) { + for (uint8_t index = 0; index < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; index++) { + const vtxChannelActivationCondition_t *cac = &vtxConfig()->vtxChannelActivationConditions[index]; + memset(&cac, 0, sizeof(vtxChannelActivationCondition_t)); + } +} + +void ResetVtxConfig(void) { + vtxSettingsConfig_s *vtxConfig = vtxSettingsConfigMutable(); + vtxConfig->band = 0; + vtxConfig->channel = 0; + vtxConfig->power = 0; +} + + + +TEST(VtxTest, VtxCanUpdateVtxWithActivationCondition) +{ + vtxCommonSetDevice(&testVtxDevice); + ResetVtxActivationConditions(); + ResetVtxConfig(); + + const vtxSettingsConfig_s *actualVtxConfig = vtxSettingsConfig(); + vtxUpdateActivatedChannel(); + + EXPECT_EQ(0, actualVtxConfig->band); + EXPECT_EQ(0, actualVtxConfig->channel); + EXPECT_EQ(0, actualVtxConfig->power); + + + // let's set condition number 1 + vtxChannelActivationCondition_t *cac1 = &vtxConfigMutable()->vtxChannelActivationConditions[1]; + cac1->auxChannelIndex = 0; // zero indexed, 0 is aux1 + cac1->band = 5; + cac1->channel = 2; + cac1->power = 3; + (&cac1->range)->startStep = CHANNEL_VALUE_TO_STEP(1900); + (&cac1->range)->endStep = CHANNEL_VALUE_TO_STEP(2000); + // setup condition number 2 + vtxChannelActivationCondition_t *cac2 = &vtxConfigMutable()->vtxChannelActivationConditions[2]; + cac2->auxChannelIndex = 0; // zero indexed, 0 is aux1 + cac2->band = 4; + cac2->channel = 1; + cac2->power = 1; + (&cac2->range)->startStep = CHANNEL_VALUE_TO_STEP(1500); + (&cac2->range)->endStep = CHANNEL_VALUE_TO_STEP(1800); + + // set actual channel to low "inactive" value + rcData[AUX1] = 1000; + vtxUpdateActivatedChannel(); // band, channel and power should remain at 0 as aux channel not active + + EXPECT_EQ(0, actualVtxConfig->band); + EXPECT_EQ(0, actualVtxConfig->channel); + EXPECT_EQ(0, actualVtxConfig->power); + + // set AUX1 to match condition 1 + rcData[AUX1] = 1950; + // actualVtxConfig should be updated + vtxUpdateActivatedChannel(); + + EXPECT_EQ(cac1->band, actualVtxConfig->band); + EXPECT_EQ(cac1->channel, actualVtxConfig->channel); + EXPECT_EQ(cac1->power, actualVtxConfig->power); + + // set AUX1 to match condition 2 + rcData[AUX1] = 1650; + // actualVtxConfig should be updated + vtxUpdateActivatedChannel(); + + EXPECT_EQ(cac2->band, actualVtxConfig->band); + EXPECT_EQ(cac2->channel, actualVtxConfig->channel); + EXPECT_EQ(cac2->power, actualVtxConfig->power); +} + +TEST(VtxTest, VtxShouldNotUpdateBandAndChannelOnceArmed) +{ + vtxCommonSetDevice(&testVtxDevice); + + ResetVtxActivationConditions(); + ResetVtxConfig(); + + const vtxSettingsConfig_s *actualVtxConfig = vtxSettingsConfig(); + vtxUpdateActivatedChannel(); + + EXPECT_EQ(0, actualVtxConfig->band); + EXPECT_EQ(0, actualVtxConfig->channel); + EXPECT_EQ(0, actualVtxConfig->power); + + // let's set condition number 1 + vtxChannelActivationCondition_t *cac1 = &vtxConfigMutable()->vtxChannelActivationConditions[1]; + + cac1->auxChannelIndex = 0; + cac1->band = 5; + cac1->channel = 2; + cac1->power = 3; + (&cac1->range)->startStep = CHANNEL_VALUE_TO_STEP(1000); + (&cac1->range)->endStep = CHANNEL_VALUE_TO_STEP(1500); + + // setup condition number 2 + vtxChannelActivationCondition_t *cac2 = &vtxConfigMutable()->vtxChannelActivationConditions[2]; + cac2->auxChannelIndex = 0; // zero indexed, 0 is aux1 + cac2->band = 4; + cac2->channel = 1; + cac2->power = 1; + (&cac2->range)->startStep = CHANNEL_VALUE_TO_STEP(1500); + (&cac2->range)->endStep = CHANNEL_VALUE_TO_STEP(2000); + + rcData[AUX1] = 1200; + // set actual channel to low "inactive" value + vtxUpdateActivatedChannel(); // band, channel and power should remain at 0 as aux channel not active + + EXPECT_EQ(cac1->band, actualVtxConfig->band); + EXPECT_EQ(cac1->channel, actualVtxConfig->channel); + EXPECT_EQ(cac1->power, actualVtxConfig->power); + + // Arm the quad. this should lock band and channel settings + ENABLE_ARMING_FLAG(ARMED); + + // set AUX1 to condition2 state + rcData[AUX1] = 1800; + vtxUpdateActivatedChannel(); + + // band and channel should stay as condition1 because quad has been armed + EXPECT_EQ(cac1->band, actualVtxConfig->band); + EXPECT_EQ(cac1->channel, actualVtxConfig->channel); + // power can change in flight, it should change to condition2 + EXPECT_EQ(cac2->power, actualVtxConfig->power); +} + + + // STUBS extern "C" { uint32_t micros(void) { return simulationTime; } @@ -168,6 +313,7 @@ extern "C" { void changeControlRateProfile(uint8_t) {} void dashboardEnablePageCycling(void) {} void dashboardDisablePageCycling(void) {} + void updateRcRefreshRate(timeUs_t) {}; bool imuQuaternionHeadfreeOffsetSet(void) { return true; } void rescheduleTask(cfTaskId_e, uint32_t) {} bool usbCableIsInserted(void) { return false; }