// Copyright (c) 2024 Oleg Kalachev // Repository: https://github.com/okalachev/flix // Parameters storage in flash memory #include #include "util.h" extern int channelZero[16], channelMax[16]; extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel; extern int rcRxPin, voltagePin; extern int wifiMode, wifiLongRange, udpLocalPort, udpRemotePort, espnowChannel; extern float rcLossTimeout, descendTime; extern float voltageScale; extern LowPassFilter voltageFilter; Preferences storage; struct Parameter { const char *name; // max length is 15 bool integer; union { float *f; int *i; }; // pointer to the variable float cache; // what's stored in flash void (*callback)(); // called after parameter change Parameter(const char *name, float *variable, void (*callback)() = nullptr) : name(name), integer(false), f(variable), callback(callback) {}; Parameter(const char *name, int *variable, void (*callback)() = nullptr) : name(name), integer(true), i(variable), callback(callback) {}; float getValue() const { return integer ? *i : *f; }; void setValue(const float value) { if (integer) *i = value; else *f = value; }; }; Parameter parameters[] = { // control {"CTL_R_RATE_P", &rollRatePID.p}, {"CTL_R_RATE_I", &rollRatePID.i}, {"CTL_R_RATE_D", &rollRatePID.d}, {"CTL_R_RATE_WU", &rollRatePID.windup}, {"CTL_R_RATE_D_A", &rollRatePID.lpf.alpha}, {"CTL_P_RATE_P", &pitchRatePID.p}, {"CTL_P_RATE_I", &pitchRatePID.i}, {"CTL_P_RATE_D", &pitchRatePID.d}, {"CTL_P_RATE_WU", &pitchRatePID.windup}, {"CTL_P_RATE_D_A", &pitchRatePID.lpf.alpha}, {"CTL_Y_RATE_P", &yawRatePID.p}, {"CTL_Y_RATE_I", &yawRatePID.i}, {"CTL_Y_RATE_D", &yawRatePID.d}, {"CTL_Y_RATE_D_A", &yawRatePID.lpf.alpha}, {"CTL_R_P", &rollPID.p}, {"CTL_R_I", &rollPID.i}, {"CTL_R_D", &rollPID.d}, {"CTL_P_P", &pitchPID.p}, {"CTL_P_I", &pitchPID.i}, {"CTL_P_D", &pitchPID.d}, {"CTL_Y_P", &yawPID.p}, {"CTL_P_RATE_MAX", &maxRate.y}, {"CTL_R_RATE_MAX", &maxRate.x}, {"CTL_Y_RATE_MAX", &maxRate.z}, {"CTL_TILT_MAX", &tiltMax}, {"CTL_FLT_MODE_0", &flightModes[0]}, {"CTL_FLT_MODE_1", &flightModes[1]}, {"CTL_FLT_MODE_2", &flightModes[2]}, // imu {"IMU_ROT_ROLL", &imuRotation.x}, {"IMU_ROT_PITCH", &imuRotation.y}, {"IMU_ROT_YAW", &imuRotation.z}, {"IMU_ACC_BIAS_X", &accBias.x}, {"IMU_ACC_BIAS_Y", &accBias.y}, {"IMU_ACC_BIAS_Z", &accBias.z}, {"IMU_ACC_SCALE_X", &accScale.x}, {"IMU_ACC_SCALE_Y", &accScale.y}, {"IMU_ACC_SCALE_Z", &accScale.z}, {"IMU_GYRO_BIAS_A", &gyroBiasFilter.alpha}, // estimate {"EST_ACC_WEIGHT", &accWeight}, {"EST_LVL_WEIGHT", &levelWeight}, {"EST_RATES_LPF_A", &ratesFilter.alpha}, // motors {"MOT_PIN_FL", &motorPins[MOTOR_FRONT_LEFT], setupMotors}, {"MOT_PIN_FR", &motorPins[MOTOR_FRONT_RIGHT], setupMotors}, {"MOT_PIN_RL", &motorPins[MOTOR_REAR_LEFT], setupMotors}, {"MOT_PIN_RR", &motorPins[MOTOR_REAR_RIGHT], setupMotors}, {"MOT_PWM_FREQ", &pwmFrequency, setupMotors}, {"MOT_PWM_RES", &pwmResolution, setupMotors}, {"MOT_PWM_STOP", &pwmStop}, {"MOT_PWM_MIN", &pwmMin}, {"MOT_PWM_MAX", &pwmMax}, // rc {"RC_RX_PIN", &rcRxPin, setupRC}, {"RC_ZERO_0", &channelZero[0]}, {"RC_ZERO_1", &channelZero[1]}, {"RC_ZERO_2", &channelZero[2]}, {"RC_ZERO_3", &channelZero[3]}, {"RC_ZERO_4", &channelZero[4]}, {"RC_ZERO_5", &channelZero[5]}, {"RC_ZERO_6", &channelZero[6]}, {"RC_ZERO_7", &channelZero[7]}, {"RC_MAX_0", &channelMax[0]}, {"RC_MAX_1", &channelMax[1]}, {"RC_MAX_2", &channelMax[2]}, {"RC_MAX_3", &channelMax[3]}, {"RC_MAX_4", &channelMax[4]}, {"RC_MAX_5", &channelMax[5]}, {"RC_MAX_6", &channelMax[6]}, {"RC_MAX_7", &channelMax[7]}, {"RC_ROLL", &rollChannel}, {"RC_PITCH", &pitchChannel}, {"RC_THROTTLE", &throttleChannel}, {"RC_YAW", &yawChannel}, {"RC_MODE", &modeChannel}, // wifi {"WIFI_MODE", &wifiMode}, {"WIFI_PORT_LOC", &udpLocalPort}, {"WIFI_PORT_REM", &udpRemotePort}, {"WIFI_LONG_RANGE", &wifiLongRange}, // espnow {"ESPNOW_CHANNEL", &espnowChannel}, // mavlink {"MAV_SYS_ID", &mavlinkSysId}, {"MAV_RATE_SLOW", &telemetrySlow.rate}, {"MAV_RATE_FAST", &telemetryFast.rate}, // power {"PWR_VOLT_PIN", &voltagePin, setupPower}, {"PWR_VOLT_SCALE", &voltageScale}, {"PWR_VOLT_LPF_A", &voltageFilter.alpha}, // safety {"SF_RC_LOSS_TIME", &rcLossTimeout}, {"SF_DESCEND_TIME", &descendTime}, }; void setupParameters() { print("Setup parameters\n"); storage.begin("flix"); // Read parameters from storage for (auto ¶meter : parameters) { if (!storage.isKey(parameter.name)) { storage.putFloat(parameter.name, parameter.getValue()); // store default value } parameter.setValue(storage.getFloat(parameter.name, 0)); parameter.cache = parameter.getValue(); } } int parametersCount() { return sizeof(parameters) / sizeof(parameters[0]); } const char *getParameterName(int index) { if (index < 0 || index >= parametersCount()) return ""; return parameters[index].name; } float getParameter(int index) { if (index < 0 || index >= parametersCount()) return NAN; return parameters[index].getValue(); } float getParameter(const char *name) { for (auto ¶meter : parameters) { if (strcasecmp(parameter.name, name) == 0) { return parameter.getValue(); } } return NAN; } bool setParameter(const char *name, const float value) { for (auto ¶meter : parameters) { if (strcasecmp(parameter.name, name) == 0) { if (parameter.integer && !isfinite(value)) return false; // can't set integer to NaN or Inf parameter.setValue(value); if (parameter.callback) parameter.callback(); return true; } } return false; } void syncParameters() { static Rate rate(1); if (!rate) return; // sync once per second if (motorsActive()) return; // don't use flash while flying, it may cause a delay for (auto ¶meter : parameters) { if (parameter.getValue() == parameter.cache) continue; // no change if (isnan(parameter.getValue()) && isnan(parameter.cache)) continue; // both are NAN storage.putFloat(parameter.name, parameter.getValue()); parameter.cache = parameter.getValue(); // update cache } } void printParameters() { for (auto ¶meter : parameters) { print("%s = %g\n", parameter.name, parameter.getValue()); } } void resetParameters() { storage.clear(); ESP.restart(); }