From 24224729a3c6cfa82f830ad991620f977cae2ffd Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 3 Nov 2021 16:53:26 -0700 Subject: [PATCH] support autoscale on table axes (#3452) * scale map Y axis * allow different row/col types * scaled channel detector * interpolation * looks like this actually works * tests, no manual scaling * comment --- firmware/controllers/algo/accel_enrichment.h | 2 +- .../algo/airmass/speed_density_airmass.cpp | 2 +- firmware/controllers/core/fsio_impl.h | 4 +- .../sensors/converters/table_func.h | 5 +- firmware/init/sensor/init_fuel_level.cpp | 2 - firmware/init/sensor/init_maf.cpp | 4 +- firmware/integration/rusefi_config.txt | 4 +- firmware/util/containers/table_helper.h | 96 ++++++++++++------- firmware/util/math/interpolation.h | 14 ++- firmware/util/scaled_channel.h | 11 ++- .../test_basic_math/test_interpolation_3d.cpp | 28 ++++-- unit_tests/tests/sensor/table_func.cpp | 8 +- 12 files changed, 118 insertions(+), 62 deletions(-) diff --git a/firmware/controllers/algo/accel_enrichment.h b/firmware/controllers/algo/accel_enrichment.h index 62d167da08..2d9efb4306 100644 --- a/firmware/controllers/algo/accel_enrichment.h +++ b/firmware/controllers/algo/accel_enrichment.h @@ -14,7 +14,7 @@ #include "wall_fuel_state_generated.h" #include "tps_accel_state_generated.h" -typedef Map3D tps_tps_Map3D_t; +typedef Map3D tps_tps_Map3D_t; /** * this object is used for MAP rate-of-change and TPS rate-of-change corrections diff --git a/firmware/controllers/algo/airmass/speed_density_airmass.cpp b/firmware/controllers/algo/airmass/speed_density_airmass.cpp index db467d7280..e8a0da8a74 100644 --- a/firmware/controllers/algo/airmass/speed_density_airmass.cpp +++ b/firmware/controllers/algo/airmass/speed_density_airmass.cpp @@ -41,7 +41,7 @@ float SpeedDensityAirmass::getMap(int rpm) const { float fallbackMap; if (CONFIG(enableMapEstimationTableFallback)) { // if the map estimation table is enabled, estimate map based on the TPS and RPM - fallbackMap = m_mapEstimationTable->getValue(rpm, TPS_2_BYTE_PACKING_MULT * Sensor::getOrZero(SensorType::Tps1)); + fallbackMap = m_mapEstimationTable->getValue(rpm, Sensor::getOrZero(SensorType::Tps1)); } else { fallbackMap = CONFIG(failedMapFallback); } diff --git a/firmware/controllers/core/fsio_impl.h b/firmware/controllers/core/fsio_impl.h index 61d03c60b2..a520f82674 100644 --- a/firmware/controllers/core/fsio_impl.h +++ b/firmware/controllers/core/fsio_impl.h @@ -17,8 +17,8 @@ // see useFSIO5ForCriticalIssueEngineStop #define MAGIC_OFFSET_FOR_CRITICAL_ENGINE 5 -typedef Map3D fsio8_Map3D_f32t; -typedef Map3D fsio8_Map3D_u8t; +typedef Map3D fsio8_Map3D_f32t; +typedef Map3D fsio8_Map3D_u8t; expected getEngineValue(le_action_e action DECLARE_ENGINE_PARAMETER_SUFFIX); diff --git a/firmware/controllers/sensors/converters/table_func.h b/firmware/controllers/sensors/converters/table_func.h index 44c1e5dadf..cd646969a8 100644 --- a/firmware/controllers/sensors/converters/table_func.h +++ b/firmware/controllers/sensors/converters/table_func.h @@ -10,7 +10,7 @@ #include "interpolation.h" #include "efi_ratio.h" -template , typename TOutputScale = efi::ratio<1>> +template > class TableFunc final : public SensorConverter { public: TableFunc(TBin (&bins)[TSize], TValue (&values)[TSize]) @@ -20,9 +20,6 @@ public: } SensorResult convert(float inputValue) const override { - // Multiply by the reciprocal instead of dividing - inputValue = inputValue * TInputScale::recip::asFloat(); - return interpolate2d(inputValue, m_bins, m_values) * TOutputScale::asFloat(); } diff --git a/firmware/init/sensor/init_fuel_level.cpp b/firmware/init/sensor/init_fuel_level.cpp index 0fcce4311b..a9ccb5936a 100644 --- a/firmware/init/sensor/init_fuel_level.cpp +++ b/firmware/init/sensor/init_fuel_level.cpp @@ -14,8 +14,6 @@ using ValueType = std::remove_extent_t; static TableFunc , // Values are stored in percent efi::ratio<1>> fuelCurve(CONFIG(fuelLevelBins), CONFIG(fuelLevelValues)); diff --git a/firmware/init/sensor/init_maf.cpp b/firmware/init/sensor/init_maf.cpp index a918729ccb..1bcbf5dff4 100644 --- a/firmware/init/sensor/init_maf.cpp +++ b/firmware/init/sensor/init_maf.cpp @@ -13,9 +13,7 @@ using BinType = std::remove_extent_tmafDecodingBins)>; using ValueType = std::remove_extent_tmafDecoding)>; // This function converts volts -> kg/h -static TableFunc - - mafCurve(config->mafDecodingBins, config->mafDecoding); +static TableFunc mafCurve(config->mafDecodingBins, config->mafDecoding); void initMaf(DECLARE_CONFIG_PARAMETER_SIGNATURE) { adc_channel_e channel = CONFIG(mafAdcChannel); diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index 29e299a994..c73e78b813 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -977,7 +977,7 @@ custom maf_sensor_type_e 4 bits, S32, @OFFSET@, [0:1], @@maf_sensor_type_e_enum@ pin_output_mode_e drv8860_csPinMode; brain_pin_e drv8860_miso; - uint16_t[FUEL_LEVEL_TABLE_COUNT] fuelLevelBins;;"volt", {1/@@PACK_MULT_VOLTAGE@@}, 0, 0, 5, 3 + uint16_t[FUEL_LEVEL_TABLE_COUNT] autoscale fuelLevelBins;;"volt", {1/@@PACK_MULT_VOLTAGE@@}, 0, 0, 5, 3 output_pin_e[LUA_PWM_COUNT iterate] luaOutputPins @@ -1558,7 +1558,7 @@ tcubinary_table_t tcuSolenoidTable; float vssFilterReciprocal;+Good example: number of tooth on wheel, For Can 10 is a good number.;"Hz", 1, 0, 2, 20, 2 map_estimate_table_t mapEstimateTable; -uint16_t[FUEL_LOAD_COUNT] mapEstimateTpsBins;;"% TPS", {1/@@TPS_2_BYTE_PACKING_MULT@@}, 0, 0, 100, 1 +uint16_t[FUEL_LOAD_COUNT] autoscale mapEstimateTpsBins;;"% TPS", {1/@@TPS_2_BYTE_PACKING_MULT@@}, 0, 0, 100, 1 uint16_t[FUEL_RPM_COUNT] mapEstimateRpmBins;;"RPM", 1, 0, 0, 18000, 0 fsio_table_8x8_u8t vvtTable1; diff --git a/firmware/util/containers/table_helper.h b/firmware/util/containers/table_helper.h index a8b9aac282..8a5cf3c534 100644 --- a/firmware/util/containers/table_helper.h +++ b/firmware/util/containers/table_helper.h @@ -25,25 +25,16 @@ public: /** * this helper class brings together 3D table with two 2D axis curves */ -template> +template> class Map3D : public ValueProvider3D { public: - template - void init(scaled_channel table[TRowNum][TColNum], const kType rowBins[TRowNum], const kType columnBins[TColNum]) { - static_assert(TValueMultiplier::den == mult); - static_assert(TValueMultiplier::num == 1); - - m_values = reinterpret_cast(&table[0][0]); - - m_rowBins = rowBins; - m_columnBins = columnBins; - } - - void init(vType table[TRowNum][TColNum], const kType rowBins[TRowNum], const kType columnBins[TColNum]) { - m_values = &table[0][0]; - - m_rowBins = rowBins; - m_columnBins = columnBins; + template + void init(TValueInit table[TRowNum][TColNum], const TRowInit rowBins[TRowNum], const TColumnInit columnBins[TColNum]) { + // This splits out here so that we don't need one overload of init per possible combination of table/rows/columns types/dimensions + // Overload resolution figures out the correct versions of the functions below to call, some of which have assertions about what's allowed + initValues(table); + initRows(rowBins); + initCols(columnBins); } float getValue(float xColumn, float yRow) const override { @@ -52,8 +43,8 @@ public: return 0; } - auto row = priv::getBinPtr(yRow, m_rowBins); - auto col = priv::getBinPtr(xColumn, m_columnBins); + auto row = priv::getBinPtr(yRow * m_rowMult, m_rowBins); + auto col = priv::getBinPtr(xColumn * m_colMult, m_columnBins); // Orient the table such that (0, 0) is the bottom left corner, // then the following variable names will make sense @@ -73,7 +64,7 @@ public: return tableValue * TValueMultiplier::asFloat(); } - void setAll(vType value) { + void setAll(TValue value) { efiAssertVoid(CUSTOM_ERR_6573, m_values, "map not initialized"); for (size_t i = 0; i < TRowNum * TColNum; i++) { @@ -82,6 +73,40 @@ public: } private: + template + void initValues(scaled_channel table[TRowNum][TColNum]) { + static_assert(TValueMultiplier::den == TMult); + static_assert(TValueMultiplier::num == 1); + + m_values = reinterpret_cast(&table[0][0]); + } + + void initValues(TValue table[TRowNum][TColNum]) { + m_values = &table[0][0]; + } + + template + void initRows(const scaled_channel rowBins[TRowNum]) { + m_rowBins = reinterpret_cast(&rowBins[0]); + m_rowMult = TRowMult; + } + + void initRows(const TRow rowBins[TRowNum]) { + m_rowBins = &rowBins[0]; + m_rowMult = 1; + } + + template + void initCols(const scaled_channel columnBins[TColNum]) { + m_columnBins = reinterpret_cast(&columnBins[0]); + m_colMult = TColMult; + } + + void initCols(const TColumn columnBins[TColNum]) { + m_columnBins = &columnBins[0]; + m_colMult = 1; + } + static size_t getIndexForCoordinates(size_t row, size_t column) { // Index 0 is bottom left corner // Index TColNum - 1 is bottom right corner @@ -89,28 +114,31 @@ private: return row * TColNum + column; } - vType getValueAtPosition(size_t row, size_t column) const { + TValue getValueAtPosition(size_t row, size_t column) const { auto idx = getIndexForCoordinates(row, column); return m_values[idx]; } // TODO: should be const - /*const*/ vType* m_values = nullptr; + /*const*/ TValue* m_values = nullptr; - const kType *m_rowBins = nullptr; - const kType *m_columnBins = nullptr; + const TRow *m_rowBins = nullptr; + const TColumn *m_columnBins = nullptr; + + float m_rowMult = 1; + float m_colMult = 1; }; -typedef Map3D> lambda_Map3D_t; -typedef Map3D ign_Map3D_t; -typedef Map3D fuel_Map3D_t; -typedef Map3D baroCorr_Map3D_t; -typedef Map3D pedal2tps_t; -typedef Map3D> boostOpenLoop_Map3D_t; -typedef Map3D boostClosedLoop_Map3D_t; -typedef Map3D iacPidMultiplier_t; -typedef Map3D gppwm_Map3D_t; -typedef Map3D> mapEstimate_Map3D_t; +typedef Map3D> lambda_Map3D_t; +typedef Map3D ign_Map3D_t; +typedef Map3D fuel_Map3D_t; +typedef Map3D baroCorr_Map3D_t; +typedef Map3D pedal2tps_t; +typedef Map3D> boostOpenLoop_Map3D_t; +typedef Map3D boostClosedLoop_Map3D_t; +typedef Map3D iacPidMultiplier_t; +typedef Map3D gppwm_Map3D_t; +typedef Map3D> mapEstimate_Map3D_t; void setRpmBin(float array[], int size, float idleRpm, float topRpm); diff --git a/firmware/util/math/interpolation.h b/firmware/util/math/interpolation.h index 09af1879af..a08ae811bb 100644 --- a/firmware/util/math/interpolation.h +++ b/firmware/util/math/interpolation.h @@ -94,11 +94,23 @@ BinResult getBinPtr(float value, const TBin* bins) { return { idx, fraction }; } +template +BinResult getBinPtr(float value, const scaled_channel* bins) { + // Strip off the scaled_channel, and perform the scaling before searching the array + auto binPtrRaw = reinterpret_cast(bins); + return getBinPtr(value * TMult, binPtrRaw); +} + template BinResult getBin(float value, const TBin (&bins)[TSize]) { return getBinPtr(value, &bins[0]); } +template +BinResult getBin(float value, const scaled_channel (&bins)[TSize]) { + return getBinPtr(value, &bins[0]); +} + static float linterp(float low, float high, float frac) { return high * frac + low * (1 - frac); @@ -108,7 +120,7 @@ static float linterp(float low, float high, float frac) template float interpolate2d(const float value, const TBin (&bin)[TSize], const TValue (&values)[TSize]) { // Enforce numeric only (int, float, uintx_t, etc) - static_assert(std::is_arithmetic_v, "Table values must be an arithmetic type"); + static_assert(std::is_arithmetic_v || is_scaled_channel, "Table values must be an arithmetic type or scaled channel"); auto b = priv::getBin(value, bin); diff --git a/firmware/util/scaled_channel.h b/firmware/util/scaled_channel.h index f3f4b4a8ed..cec212f00b 100644 --- a/firmware/util/scaled_channel.h +++ b/firmware/util/scaled_channel.h @@ -16,13 +16,18 @@ #include "rusefi_generated.h" +struct scaled_channel_base { }; + +template +static constexpr bool is_scaled_channel = std::is_base_of_v; + // This class lets us transparently store something at a ratio inside an integer type // Just use it like a float - you can read and write to it, like this: // scaled_channel myVar; // myVar = 2.4f; // converts to an int, stores 24 // float x = myVar; // converts back to float, returns 2.4f template -class scaled_channel { +class scaled_channel : scaled_channel_base { using TSelf = scaled_channel; public: @@ -78,3 +83,7 @@ using scaled_voltage = scaled_channel; // 0-65v at using scaled_afr = scaled_channel; // 0-65afr at 0.001 resolution using scaled_lambda = scaled_channel; // 0-6.5 lambda at 0.0001 resolution using scaled_fuel_mass_mg = scaled_channel; // 0 - 655.35 milligrams, 0.01mg resolution + +// make sure the scaled channel detector works +static_assert(!is_scaled_channel); +static_assert(is_scaled_channel>); diff --git a/unit_tests/test_basic_math/test_interpolation_3d.cpp b/unit_tests/test_basic_math/test_interpolation_3d.cpp index 919e96c96e..a2200d2465 100644 --- a/unit_tests/test_basic_math/test_interpolation_3d.cpp +++ b/unit_tests/test_basic_math/test_interpolation_3d.cpp @@ -14,6 +14,8 @@ float rpmBins[5] = { 100, 200, 300, 400, 500 }; float mafBins[4] = { 1, 2, 3, 4 }; +scaled_channel mafBins2[4] = { 1, 2, 3, 4 }; + float map[4][5] = { { 1, 2, 3, 4, 4}, { 2, 3, 4, 200, 200 }, @@ -21,15 +23,27 @@ float map[4][5] = { { 4, 5, 300, 600, 600 }, }; -static float getValue(float rpm, float maf) { - Map3D<5, 4, float, float> x; - x.init(map, mafBins, rpmBins); - - return x.getValue(rpm, maf); -} - #define EXPECT_NEAR_M4(a, b) EXPECT_NEAR(a, b, 1e-4) +static float getValue(float rpm, float maf) { + float result1, result2; + + Map3D<5, 4, float, float, float> x1; + x1.init(map, mafBins, rpmBins); + + result1 = x1.getValue(rpm, maf); + + + Map3D<5, 4, float, float, int> x2; + x2.init(map, mafBins2, rpmBins); + + result2 = x2.getValue(rpm, maf); + + EXPECT_NEAR_M4(result1, result2); + + return result1; +} + static void newTestToComfirmInterpolation() { // here's how the table loos like: // diff --git a/unit_tests/tests/sensor/table_func.cpp b/unit_tests/tests/sensor/table_func.cpp index a0dff9f955..1445345806 100644 --- a/unit_tests/tests/sensor/table_func.cpp +++ b/unit_tests/tests/sensor/table_func.cpp @@ -16,12 +16,12 @@ TEST(TableFuncTest, basic) { } TEST(TableFuncTest, scaled) { - uint16_t in[] = { 0, 1000, 2000 }; + scaled_channel in[] = { 0, 1, 2 }; uint8_t out[] = { 70, 60, 50 }; - TableFunc, + using BinType = std::remove_extent_t; + + TableFunc> dut(in, out);