diff --git a/.gitignore b/.gitignore index 34dd269dd..245d33be9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.dep *.bak *.uvgui.* +*.swp +*.save .project .settings .cproject diff --git a/src/main/config/config.c b/src/main/config/config.c index e18f3dba8..db8a7425d 100755 --- a/src/main/config/config.c +++ b/src/main/config/config.c @@ -75,7 +75,12 @@ #include "config/config_master.h" #define BRUSHED_MOTORS_PWM_RATE 16000 +#ifdef STM32F4 +#define BRUSHLESS_MOTORS_PWM_RATE 2600 +#else #define BRUSHLESS_MOTORS_PWM_RATE 400 +#endif // STM32F4 + void useRcControlsConfig(modeActivationCondition_t *modeActivationConditions, escAndServoConfig_t *escAndServoConfigToUse, pidProfile_t *pidProfileToUse); @@ -200,7 +205,7 @@ void resetPidProfile(pidProfile_t *pidProfile) pidProfile->dterm_lpf_hz = 0; // filtering ON by default pidProfile->deltaMethod = DELTA_FROM_MEASUREMENT; -#if defined(STM32F411xE) || defined(STM32F40_41xxx) +#if defined(STM32F4) pidProfile->dterm_lpf_hz = 60; // filtering ON by default pidProfile->P_f[ROLL] = 5.012f; // new PID for raceflight. test carefully pidProfile->I_f[ROLL] = 1.021f; @@ -451,6 +456,10 @@ static void resetConf(void) featureSet(FEATURE_FAILSAFE); featureSet(FEATURE_ONESHOT125); featureSet(FEATURE_SBUS_INVERTER); + +#ifdef STM32F4 + featureSet(FEATURE_USE_PWM_RATE); +#endif // global settings masterConfig.current_profile_index = 0; // default profile diff --git a/src/main/drivers/light_ws2811strip_stm32f30x.c b/src/main/drivers/light_ws2811strip_stm32f30x.c index 853f81b81..1685dcb1f 100644 --- a/src/main/drivers/light_ws2811strip_stm32f30x.c +++ b/src/main/drivers/light_ws2811strip_stm32f30x.c @@ -39,7 +39,6 @@ #define WS2811_IRQ DMA1_Channel3_IRQn #define WS2811_DMA_TC_FLAG DMA1_FLAG_TC3 #define WS2811_DMA_HANDLER_IDENTIFER DMA1_CH3_HANDLER - #endif void ws2811DMAHandler(DMA_Channel_TypeDef *channel) { diff --git a/src/main/drivers/pwm_mapping.h b/src/main/drivers/pwm_mapping.h index ab6b58893..fade65b1c 100644 --- a/src/main/drivers/pwm_mapping.h +++ b/src/main/drivers/pwm_mapping.h @@ -36,8 +36,8 @@ #define MAX_INPUTS 8 #define PWM_TIMER_MHZ 1 -#define ONESHOT_TIMER_MHZ 24 //these three have to be the same because of the ppmAvoidPWMTimerClash functions +#define ONESHOT_TIMER_MHZ 12 #define MULTISHOT_TIMER_MHZ 12 #define PWM_BRUSHED_TIMER_MHZ 12 diff --git a/src/main/drivers/pwm_output.c b/src/main/drivers/pwm_output.c index 6f95ef59a..ba1ec28cf 100644 --- a/src/main/drivers/pwm_output.c +++ b/src/main/drivers/pwm_output.c @@ -28,7 +28,6 @@ #include "flight/failsafe.h" // FIXME dependency into the main code from a driver #include "pwm_mapping.h" - #include "pwm_output.h" typedef void (*pwmWriteFuncPtr)(uint8_t index, uint16_t value); // function pointer used to write motors @@ -99,7 +98,6 @@ static pwmOutputPort_t *pwmOutConfig(const timerHardware_t *timerHardware, uint8 configTimeBase(timerHardware->tim, period, mhz); pwmGPIOConfig(timerHardware->gpio, timerHardware->pin, Mode_AF_PP); - pwmOCConfig(timerHardware->tim, timerHardware->channel, value); if (timerHardware->outputEnable) TIM_CtrlPWMOutputs(timerHardware->tim, ENABLE); @@ -134,6 +132,7 @@ static void pwmWriteStandard(uint8_t index, uint16_t value) { *motors[index]->ccr = value; } + #if defined(STM32F10X) && !defined(CC3D) static void pwmWriteOneshot125(uint8_t index, uint16_t value) { @@ -147,7 +146,7 @@ static void pwmWriteOneshot42(uint8_t index, uint16_t value) #else static void pwmWriteOneshot125(uint8_t index, uint16_t value) { - *motors[index]->ccr = value * 3; // 24Mhz -> 8Mhz + *motors[index]->ccr = value * 3; } static void pwmWriteOneshot42(uint8_t index, uint16_t value) @@ -158,7 +157,7 @@ static void pwmWriteOneshot42(uint8_t index, uint16_t value) static void pwmWriteMultiShot(uint8_t index, uint16_t value) { - *motors[index]->ccr = (uint16_t)((float)(value-1000) / 4.1666f)+ 60; + *motors[index]->ccr = (uint16_t)((float)(value-1000) / 4.1666f) + 60; } void pwmWriteMotor(uint8_t index, uint16_t value) @@ -235,7 +234,6 @@ void pwmOneshotMotorConfig(const timerHardware_t *timerHardware, uint8_t motorIn } else { motors[motorIndex]->pwmWritePtr = pwmWriteOneshot125; } - } void pwmMultiShotPwmRateMotorConfig(const timerHardware_t *timerHardware, uint8_t motorIndex, uint16_t motorPwmRate, uint16_t idlePulse) diff --git a/src/main/drivers/timer.h b/src/main/drivers/timer.h index 14c937155..31aed125b 100644 --- a/src/main/drivers/timer.h +++ b/src/main/drivers/timer.h @@ -17,6 +17,13 @@ #pragma once +#include +#include + +#include "platform.h" +#include "gpio.h" +#include "system.h" + #if !defined(USABLE_TIMER_CHANNEL_COUNT) #define USABLE_TIMER_CHANNEL_COUNT 14 #endif diff --git a/src/main/flight/mixer.c b/src/main/flight/mixer.c index a11832bbe..27c0b4679 100755 --- a/src/main/flight/mixer.c +++ b/src/main/flight/mixer.c @@ -828,18 +828,18 @@ void mixTable(void) int16_t throttleMin, throttleMax; static int16_t throttlePrevious = 0; // Store the last throttle direction for deadband transitions - // Find min and max throttle based on condition. Use rcData for 3D to prevent loss of power due to min_check + // Find min and max throttle based on condition. if (feature(FEATURE_3D)) { if (!ARMING_FLAG(ARMED)) throttlePrevious = rxConfig->midrc; // When disarmed set to mid_rc. It always results in positive direction after arming. - if ((rcData[THROTTLE] <= (rxConfig->midrc - flight3DConfig->deadband3d_throttle))) { // Out of band handling + if ((rcCommand[THROTTLE] <= (rxConfig->midrc - flight3DConfig->deadband3d_throttle))) { // Out of band handling throttleMax = flight3DConfig->deadband3d_low; throttleMin = escAndServoConfig->minthrottle; - throttlePrevious = throttle = rcData[THROTTLE]; - } else if (rcData[THROTTLE] >= (rxConfig->midrc + flight3DConfig->deadband3d_throttle)) { // Positive handling + throttlePrevious = throttle = rcCommand[THROTTLE]; + } else if (rcCommand[THROTTLE] >= (rxConfig->midrc + flight3DConfig->deadband3d_throttle)) { // Positive handling throttleMax = escAndServoConfig->maxthrottle; throttleMin = flight3DConfig->deadband3d_high; - throttlePrevious = throttle = rcData[THROTTLE]; + throttlePrevious = throttle = rcCommand[THROTTLE]; } else if ((throttlePrevious <= (rxConfig->midrc - flight3DConfig->deadband3d_throttle))) { // Deadband handling from negative to positive throttle = throttleMax = flight3DConfig->deadband3d_low; throttleMin = escAndServoConfig->minthrottle; diff --git a/src/main/io/rc_controls.c b/src/main/io/rc_controls.c index c89c50bef..03a108d6f 100644 --- a/src/main/io/rc_controls.c +++ b/src/main/io/rc_controls.c @@ -34,6 +34,7 @@ #include "drivers/system.h" #include "drivers/sensor.h" #include "drivers/accgyro.h" +#include "drivers/gyro_sync.h" #include "sensors/barometer.h" #include "sensors/battery.h" @@ -215,7 +216,7 @@ void processRcStickPositions(rxConfig_t *rxConfig, throttleStatus_e throttleStat if (rcSticks == THR_LO + YAW_LO + PIT_LO + ROL_CE) { // GYRO calibration - gyroSetCalibrationCycles(CALIBRATING_GYRO_CYCLES); + gyroSetCalibrationCycles(calculateCalibratingCycles()); #ifdef GPS if (feature(FEATURE_GPS)) { diff --git a/src/main/io/rc_controls.h b/src/main/io/rc_controls.h index d89f59f33..50b1cff30 100644 --- a/src/main/io/rc_controls.h +++ b/src/main/io/rc_controls.h @@ -49,12 +49,13 @@ typedef enum { BOXBLACKBOX, BOXFAILSAFE, BOXAIRMODE, - BOXACROPLUS, - BOXALWAYSSTABILIZED, - BOXTEST1, - BOXTEST2, - BOXTEST3, - CHECKBOX_ITEM_COUNT + BOXACROPLUS, + BOX3DDISABLESWITCH, + BOXALWAYSSTABILIZED, + BOXTEST1, + BOXTEST2, + BOXTEST3, + CHECKBOX_ITEM_COUNT } boxId_e; extern uint32_t rcModeActivationMask; diff --git a/src/main/io/rc_curves.c b/src/main/io/rc_curves.c index 9429d59ea..2068c9fba 100644 --- a/src/main/io/rc_curves.c +++ b/src/main/io/rc_curves.c @@ -24,6 +24,8 @@ #include "io/rc_curves.h" +#include "config/config.h" + int16_t lookupPitchRollRC[PITCH_LOOKUP_LENGTH]; // lookup table for expo & RC rate PITCH+ROLL int16_t lookupYawRC[YAW_LOOKUP_LENGTH]; // lookup table for expo & RC rate YAW int16_t lookupThrottleRC[THROTTLE_LOOKUP_LENGTH]; // lookup table for expo & mid THROTTLE @@ -48,6 +50,7 @@ void generateYawCurve(controlRateConfig_t *controlRateConfig) void generateThrottleCurve(controlRateConfig_t *controlRateConfig, escAndServoConfig_t *escAndServoConfig) { uint8_t i; + uint16_t minThrottle = (feature(FEATURE_3D && IS_RC_MODE_ACTIVE(BOX3DDISABLESWITCH)) ? PWM_RANGE_MIN : escAndServoConfig->minthrottle); for (i = 0; i < THROTTLE_LOOKUP_LENGTH; i++) { int16_t tmp = 10 * i - controlRateConfig->thrMid8; @@ -57,6 +60,6 @@ void generateThrottleCurve(controlRateConfig_t *controlRateConfig, escAndServoCo if (tmp < 0) y = controlRateConfig->thrMid8; lookupThrottleRC[i] = 10 * controlRateConfig->thrMid8 + tmp * (100 - controlRateConfig->thrExpo8 + (int32_t) controlRateConfig->thrExpo8 * (tmp * tmp) / (y * y)) / 10; - lookupThrottleRC[i] = escAndServoConfig->minthrottle + (int32_t) (escAndServoConfig->maxthrottle - escAndServoConfig->minthrottle) * lookupThrottleRC[i] / 1000; // [MINTHROTTLE;MAXTHROTTLE] + lookupThrottleRC[i] = minThrottle + (int32_t) (escAndServoConfig->maxthrottle - minThrottle) * lookupThrottleRC[i] / 1000; // [MINTHROTTLE;MAXTHROTTLE] } } diff --git a/src/main/io/serial_cli.c b/src/main/io/serial_cli.c index f758f61c7..9e88e8178 100644 --- a/src/main/io/serial_cli.c +++ b/src/main/io/serial_cli.c @@ -110,6 +110,8 @@ static void cliAdjustmentRange(char *cmdline); static void cliMotorMix(char *cmdline); static void cliDefaults(char *cmdline); static void cliDump(char *cmdLine); +void cliDumpProfile(uint8_t profileIndex); +void cliDumpRateProfile(uint8_t rateProfileIndex) ; static void cliExit(char *cmdline); static void cliFeature(char *cmdline); static void cliMotor(char *cmdline); @@ -1841,8 +1843,6 @@ typedef enum { DUMP_RATES = (1 << 2), } dumpFlags_e; -#define DUMP_ALL (DUMP_MASTER | DUMP_PROFILE | DUMP_RATES) - static const char* const sectionBreak = "\r\n"; @@ -1858,7 +1858,7 @@ static void cliDump(char *cmdline) float thr, roll, pitch, yaw; #endif - uint8_t dumpMask = DUMP_ALL; + uint8_t dumpMask = DUMP_MASTER; if (strcasecmp(cmdline, "master") == 0) { dumpMask = DUMP_MASTER; // only } @@ -1953,7 +1953,7 @@ static void cliDump(char *cmdline) if (mask & (1 << i)) cliPrintf("beeper -%s\r\n", beeperNameForTableIndex(i)); else - cliPrintf("beeper %s\r\n", beeperNameForTableIndex(i)); + cliPrintf("beeper %s\r\n", beeperNameForTableIndex(i)); } #endif @@ -2010,27 +2010,57 @@ static void cliDump(char *cmdline) cliPrint("\r\n# rxfail\r\n"); cliRxFail(""); + + uint8_t activeProfile = masterConfig.current_profile_index; + uint8_t i; + for (i=0; iactiveRateProfile); + } + +} +void cliDumpProfile(uint8_t profileIndex) { + if (profileIndex >= MAX_PROFILE_COUNT) // Faulty values + return; + + changeProfile(profileIndex); cliPrint("\r\n# profile\r\n"); cliProfile(""); - printSectionBreak(); - dumpValues(PROFILE_VALUE); - - cliPrint("\r\n# rateprofile\r\n"); - cliRateProfile(""); - - printSectionBreak(); - - dumpValues(PROFILE_RATE_VALUE); - } + uint8_t currentRateIndex = currentProfile->activeRateProfile; + uint8_t i; + for (i=0; i= MAX_RATEPROFILES) // Faulty values + return; + + changeControlRateProfile(rateProfileIndex); + cliPrint("\r\n# rateprofile\r\n"); + cliRateProfile(""); + printSectionBreak(); + + dumpValues(PROFILE_RATE_VALUE); + +} void cliEnter(serialPort_t *serialPort) { cliMode = 1; diff --git a/src/main/io/serial_msp.c b/src/main/io/serial_msp.c index 53fa65a73..11113add3 100644 --- a/src/main/io/serial_msp.c +++ b/src/main/io/serial_msp.c @@ -223,9 +223,10 @@ static const box_t boxes[CHECKBOX_ITEM_COUNT + 1] = { { BOXFAILSAFE, "FAILSAFE;", 27 }, { BOXAIRMODE, "AIR MODE;", 28 }, { BOXACROPLUS, "ACRO PLUS;", 29 }, - { BOXALWAYSSTABILIZED, "ALWAYS STABILIZED;", 30 }, - { BOXTEST1, "TEST 1;", 31 }, - { BOXTEST2, "TEST 2;", 32 }, + { BOX3DDISABLESWITCH, "DISABLE 3D SWITCH;", 30}, + { BOXALWAYSSTABILIZED, "ALWAYS STABILIZED;", 31 }, + { BOXTEST1, "TEST 1;", 32 }, + { BOXTEST2, "TEST 2;", 33 }, { CHECKBOX_ITEM_COUNT, NULL, 0xFF } }; @@ -554,7 +555,7 @@ void mspInit(serialConfig_t *serialConfig) #ifdef BARO activeBoxIds[activeBoxIdCount++] = BOXACROPLUS; - + activeBoxIds[activeBoxIdCount++] = BOX3DDISABLESWITCH; if (sensors(SENSOR_BARO)) { activeBoxIds[activeBoxIdCount++] = BOXBARO; } @@ -670,8 +671,7 @@ static uint32_t packFlightModeFlags(void) IS_ENABLED(IS_RC_MODE_ACTIVE(BOXAIRMODE)) << BOXAIRMODE| IS_ENABLED(IS_RC_MODE_ACTIVE(BOXACROPLUS)) << BOXACROPLUS| IS_ENABLED(IS_RC_MODE_ACTIVE(BOXALWAYSSTABILIZED)) << BOXALWAYSSTABILIZED| - IS_ENABLED(IS_RC_MODE_ACTIVE(BOXTEST1)) << BOXTEST1| - IS_ENABLED(IS_RC_MODE_ACTIVE(BOXTEST2)) << BOXTEST2; + IS_ENABLED(IS_RC_MODE_ACTIVE(BOXTEST1)) << BOXTEST1; for (i = 0; i < activeBoxIdCount; i++) { int flag = (tmp & (1 << activeBoxIds[i])); diff --git a/src/main/main.c b/src/main/main.c index 3642fc1b8..cd6ed909d 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -629,7 +629,7 @@ void init(void) if (masterConfig.mixerMode == MIXER_GIMBAL) { accSetCalibrationCycles(CALIBRATING_ACC_CYCLES); } - gyroSetCalibrationCycles(CALIBRATING_GYRO_CYCLES); + gyroSetCalibrationCycles(calculateCalibratingCycles()); #ifdef BARO baroSetCalibrationCycles(CALIBRATING_BARO_CYCLES); #endif diff --git a/src/main/mw.c b/src/main/mw.c index ff6250646..7b4235f01 100644 --- a/src/main/mw.c +++ b/src/main/mw.c @@ -288,11 +288,21 @@ void annexCode(void) rcCommand[axis] = -rcCommand[axis]; } - tmp = constrain(rcData[THROTTLE], masterConfig.rxConfig.mincheck, PWM_RANGE_MAX); - tmp = (uint32_t)(tmp - masterConfig.rxConfig.mincheck) * PWM_RANGE_MIN / (PWM_RANGE_MAX - masterConfig.rxConfig.mincheck); + if (feature(FEATURE_3D)) { + tmp = constrain(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX); + tmp = (uint32_t)(tmp - PWM_RANGE_MIN); + } else { + tmp = constrain(rcData[THROTTLE], masterConfig.rxConfig.mincheck, PWM_RANGE_MAX); + tmp = (uint32_t)(tmp - masterConfig.rxConfig.mincheck) * PWM_RANGE_MIN / (PWM_RANGE_MAX - masterConfig.rxConfig.mincheck); + } tmp2 = tmp / 100; rcCommand[THROTTLE] = lookupThrottleRC[tmp2] + (tmp - tmp2 * 100) * (lookupThrottleRC[tmp2 + 1] - lookupThrottleRC[tmp2]) / 100; // [0;1000] -> expo -> [MINTHROTTLE;MAXTHROTTLE] + if (feature(FEATURE_3D) && IS_RC_MODE_ACTIVE(BOX3DDISABLESWITCH) && !failsafeIsActive()) { + fix12_t throttleScaler = qConstruct(rcCommand[THROTTLE] - 1000, 1000); + rcCommand[THROTTLE] = masterConfig.rxConfig.midrc + qMultiply(throttleScaler, PWM_RANGE_MAX - masterConfig.rxConfig.midrc); + } + if (FLIGHT_MODE(HEADFREE_MODE)) { float radDiff = degreesToRadians(DECIDEGREES_TO_DEGREES(attitude.values.yaw) - headFreeModeHold); float cosDiff = cos_approx(radDiff); @@ -763,6 +773,14 @@ void taskMotorUpdate(void) { } +uint8_t setPidUpdateCountDown(void) { + if (masterConfig.gyro_soft_lpf_hz) { + return masterConfig.pid_process_denom - 1; + } else { + return 1; + } +} + // Check for oneshot125 protection. With fast looptimes oneshot125 pulse duration gets more near the pid looptime bool shouldUpdateMotorsAfterPIDLoop(void) { if (targetPidLooptime > 375 ) { @@ -804,7 +822,7 @@ void taskMainPidLoopCheck(void) { if (pidUpdateCountdown) { pidUpdateCountdown--; } else { - pidUpdateCountdown = masterConfig.pid_process_denom - 1; + pidUpdateCountdown = setPidUpdateCountDown(); taskMainPidLoop(); if (shouldUpdateMotorsAfterPIDLoop()) taskMotorUpdate(); runTaskMainSubprocesses = true; diff --git a/src/main/sensors/gyro.c b/src/main/sensors/gyro.c index 5fe75d281..de3412760 100644 --- a/src/main/sensors/gyro.c +++ b/src/main/sensors/gyro.c @@ -77,9 +77,13 @@ bool isOnFinalGyroCalibrationCycle(void) return calibratingG == 1; } +uint16_t calculateCalibratingCycles(void) { + return (CALIBRATING_GYRO_CYCLES / targetLooptime) * CALIBRATING_GYRO_CYCLES; +} + bool isOnFirstGyroCalibrationCycle(void) { - return calibratingG == CALIBRATING_GYRO_CYCLES; + return calibratingG == calculateCalibratingCycles(); } static void performAcclerationCalibration(uint8_t gyroMovementCalibrationThreshold) @@ -108,10 +112,10 @@ static void performAcclerationCalibration(uint8_t gyroMovementCalibrationThresho float dev = devStandardDeviation(&var[axis]); // check deviation and startover in case the model was moved if (gyroMovementCalibrationThreshold && dev > gyroMovementCalibrationThreshold) { - gyroSetCalibrationCycles(CALIBRATING_GYRO_CYCLES); + gyroSetCalibrationCycles(calculateCalibratingCycles()); return; } - gyroZero[axis] = (g[axis] + (CALIBRATING_GYRO_CYCLES / 2)) / CALIBRATING_GYRO_CYCLES; + gyroZero[axis] = (g[axis] + (calculateCalibratingCycles() / 2)) / calculateCalibratingCycles(); } } diff --git a/src/main/sensors/gyro.h b/src/main/sensors/gyro.h index 065a06f65..d801f0cf0 100644 --- a/src/main/sensors/gyro.h +++ b/src/main/sensors/gyro.h @@ -44,4 +44,5 @@ void useGyroConfig(gyroConfig_t *gyroConfigToUse, float gyro_lpf_hz); void gyroSetCalibrationCycles(uint16_t calibrationCyclesRequired); void gyroUpdate(void); bool isGyroCalibrationComplete(void); +uint16_t calculateCalibratingCycles(void); diff --git a/src/main/target/NAZE/target.h b/src/main/target/NAZE/target.h index 32255ac6b..4b3a7b816 100644 --- a/src/main/target/NAZE/target.h +++ b/src/main/target/NAZE/target.h @@ -149,9 +149,9 @@ #define LED_STRIP -#define LED_STRIP_TIMER TIM3 -#define WS2811_DMA_TC_FLAG DMA1_FLAG_TC6 -#define WS2811_DMA_HANDLER_IDENTIFER DMA1_CH6_HANDLER +#define LED_STRIP_TIMER TIM3 +#define WS2811_DMA_TC_FLAG DMA1_FLAG_TC6 +#define WS2811_DMA_HANDLER_IDENTIFER DMA1_CH6_HANDLER //#define GPS //#define GTUNE diff --git a/src/main/version.h b/src/main/version.h index 6a2daac27..574cc8896 100644 --- a/src/main/version.h +++ b/src/main/version.h @@ -15,6 +15,12 @@ * along with Cleanflight. If not, see . */ +#define FC_VERSION_MAJOR 16 // build year +#define FC_VERSION_MINOR 03 // build month +#define FC_VERSION_PATCH_LEVEL 15 // build day +#define FC_VERSION_LETTER "a" // build of day + +#define FC_VERSION_COMMENT "RaceFlight Release 1 RC6.2 - New filters" // Example of __DATE__ string: "Jul 27 2012" @@ -63,14 +69,6 @@ #define BUILD_DAY_CH0 ((__DATE__[4] >= '0') ? (__DATE__[4]) : '0') #define BUILD_DAY_CH1 (__DATE__[ 5]) - -#define FC_VERSION_MAJOR 16 // build year -#define FC_VERSION_MINOR 02 // build month -#define FC_VERSION_PATCH_LEVEL 15 // build day -#define FC_VERSION_LETTER "a" // build of day - -#define FC_VERSION_COMMENT "RaceFlight Release 1 RC6.2 - New filters" - #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #define FC_VERSION_STRING STR(FC_VERSION_MAJOR) "." STR(FC_VERSION_MINOR) "." STR(FC_VERSION_PATCH_LEVEL)