Merge remote-tracking branch 'origin/master' into master
This commit is contained in:
commit
015becd751
|
@ -96,7 +96,7 @@ analog_inputs:
|
|||
EFI_ADC_8: "Analog Temp 3"
|
||||
# PB1, pin #31
|
||||
EFI_ADC_9: "Analog Temp 4"
|
||||
# MAP = Analog volt 1 = PC0
|
||||
# MAP = Analog volt 1 = PC0, pin #13
|
||||
EFI_ADC_10: "Analog Volt 1"
|
||||
# TPS = Analog volt 2 = PC1
|
||||
EFI_ADC_11: "Analog Volt 2"
|
||||
|
|
|
@ -176,77 +176,6 @@ void setMiata1990(DECLARE_CONFIG_PARAMETER_SIGNATURE) {
|
|||
// todo: idleValvePin
|
||||
}
|
||||
|
||||
|
||||
static void setMiata1994_common(DECLARE_CONFIG_PARAMETER_SIGNATURE) {
|
||||
commonMiataNa(PASS_CONFIG_PARAMETER_SIGNATURE);
|
||||
engineConfiguration->specs.displacement = 1.839;
|
||||
|
||||
// set cranking_timing_angle 0
|
||||
engineConfiguration->crankingTimingAngle = 0;
|
||||
|
||||
engineConfiguration->crankingChargeAngle = 70;
|
||||
|
||||
#if IGN_LOAD_COUNT == DEFAULT_IGN_LOAD_COUNT
|
||||
MEMCPY(config->ignitionTable, miataNA8_maf_advance_table);
|
||||
#endif
|
||||
|
||||
// engineConfiguration->triggerSimulatorPins[0] = GPIOD_2; // 2G - YEL/BLU
|
||||
// engineConfiguration->triggerSimulatorPins[1] = GPIOB_3; // 2E - WHT - four times
|
||||
// engineConfiguration->triggerSimulatorPinModes[0] = OM_OPENDRAIN;
|
||||
// engineConfiguration->triggerSimulatorPinModes[1] = OM_OPENDRAIN;
|
||||
//
|
||||
// engineConfiguration->triggerInputPins[0] = GPIO_UNASSIGNED;
|
||||
// engineConfiguration->triggerInputPins[1] = GPIO_UNASSIGNED;
|
||||
//
|
||||
// engineConfiguration->is_enabled_spi_1 = false;
|
||||
// engineConfiguration->is_enabled_spi_2 = false;
|
||||
// engineConfiguration->is_enabled_spi_3 = false;
|
||||
|
||||
/**
|
||||
* Outputs
|
||||
*/
|
||||
// Frankenso low out #: PE6
|
||||
// Frankenso low out #: PE5
|
||||
// Frankenso low out #:
|
||||
// Frankenso low out #:
|
||||
// Frankenso low out #5: PE3
|
||||
// Frankenso low out #6: PE4
|
||||
// Frankenso low out #7: PE1 (do not use with discovery!)
|
||||
// Frankenso low out #8:
|
||||
// Frankenso low out #9: PB9
|
||||
// Frankenso low out #10: PE0 (do not use with discovery!)
|
||||
// Frankenso low out #11: PB8
|
||||
// Frankenso low out #12: PB7
|
||||
engineConfiguration->fanPin = GPIOE_6;
|
||||
|
||||
engineConfiguration->o2heaterPin = GPIO_UNASSIGNED;
|
||||
|
||||
engineConfiguration->fuelPumpPin = GPIOE_4;
|
||||
|
||||
engineConfiguration->injectionPins[4] = GPIO_UNASSIGNED;
|
||||
engineConfiguration->injectionPins[5] = GPIO_UNASSIGNED;
|
||||
engineConfiguration->injectionPinMode = OM_DEFAULT;
|
||||
|
||||
engineConfiguration->idle.solenoidPin = GPIOB_9;
|
||||
|
||||
engineConfiguration->ignitionPins[0] = GPIOE_14; // Frankenso high side - pin 1G
|
||||
engineConfiguration->ignitionPins[1] = GPIO_UNASSIGNED;
|
||||
engineConfiguration->ignitionPins[2] = GPIOC_7; // Frankenso high side - pin 1H
|
||||
engineConfiguration->ignitionPins[3] = GPIO_UNASSIGNED;
|
||||
engineConfiguration->ignitionPinMode = OM_DEFAULT;
|
||||
|
||||
setFrankenso_01_LCD(engineConfiguration);
|
||||
|
||||
commonFrankensoAnalogInputs(engineConfiguration);
|
||||
|
||||
engineConfiguration->tps1_1AdcChannel = EFI_ADC_2; // PA2
|
||||
engineConfiguration->map.sensor.hwChannel = EFI_ADC_4;
|
||||
engineConfiguration->mafAdcChannel = EFI_ADC_0;
|
||||
engineConfiguration->clt.adcChannel = EFI_ADC_12;
|
||||
engineConfiguration->iat.adcChannel = EFI_ADC_11;
|
||||
// end of 1994 commond
|
||||
}
|
||||
|
||||
/**
|
||||
* Tom tomiata, Frankenstein board
|
||||
*/
|
||||
|
|
|
@ -793,7 +793,7 @@ void setMiataNB2_ProteusEngineConfiguration(DECLARE_CONFIG_PARAMETER_SIGNATURE)
|
|||
// 13.8
|
||||
engineConfiguration->knockBandCustom = 2 * BAND(engineConfiguration->cylinderBore);
|
||||
|
||||
engineConfiguration->malfunctionIndicatorPin = GPIOB_6;
|
||||
engineConfiguration->malfunctionIndicatorPin = GPIOB_6; // "Lowside 10" # pin 20/black35
|
||||
|
||||
engineConfiguration->map.sensor.hwChannel = EFI_ADC_10;
|
||||
|
||||
|
|
|
@ -119,12 +119,13 @@ static void sayHello(void) {
|
|||
}
|
||||
|
||||
void validateStack(const char*msg, obd_code_e code, int desiredStackUnusedSize) {
|
||||
|
||||
#if CH_DBG_THREADS_PROFILING && CH_DBG_FILL_THREADS
|
||||
int unusedStack = CountFreeStackSpace(chThdGetSelfX()->wabase);
|
||||
if (unusedStack < desiredStackUnusedSize) {
|
||||
warning(code, "Stack low on %s: %d", msg, unusedStack);
|
||||
}
|
||||
#else
|
||||
(void)msg; (void)code; (void)desiredStackUnusedSize;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -157,6 +157,8 @@ void writeLogLine(Writer& buffer) {
|
|||
}
|
||||
|
||||
binaryLogCount++;
|
||||
#else
|
||||
(void)buffer;
|
||||
#endif /* EFI_FILE_LOGGING */
|
||||
}
|
||||
|
||||
|
|
|
@ -565,6 +565,10 @@ float getIdleTimingAdjustment(int rpm) {
|
|||
return idleControllerInstance.getIdleTimingAdjustment(rpm);
|
||||
}
|
||||
|
||||
bool isIdling() {
|
||||
return idleControllerInstance.isIdling();
|
||||
}
|
||||
|
||||
static void applyPidSettings(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
||||
getIdlePid(PASS_ENGINE_PARAMETER_SIGNATURE)->updateFactors(engineConfiguration->idleRpmPid.pFactor, engineConfiguration->idleRpmPid.iFactor, engineConfiguration->idleRpmPid.dFactor);
|
||||
iacPidMultMap.init(CONFIG(iacPidMultTable), CONFIG(iacPidMultLoadBins), CONFIG(iacPidMultRpmBins));
|
||||
|
|
|
@ -53,6 +53,11 @@ public:
|
|||
float getIdleTimingAdjustment(int rpm);
|
||||
float getIdleTimingAdjustment(int rpm, int targetRpm, Phase phase);
|
||||
|
||||
// Allow querying state from outside
|
||||
bool isIdling() {
|
||||
return m_lastPhase == Phase::Idling;
|
||||
}
|
||||
|
||||
private:
|
||||
// These are stored by getIdlePosition() and used by getIdleTimingAdjustment()
|
||||
Phase m_lastPhase = Phase::Cranking;
|
||||
|
@ -66,6 +71,8 @@ percent_t getIdlePosition();
|
|||
|
||||
float getIdleTimingAdjustment(int rpm);
|
||||
|
||||
bool isIdling();
|
||||
|
||||
void applyIACposition(percent_t position DECLARE_ENGINE_PARAMETER_SUFFIX);
|
||||
void setManualIdleValvePosition(int positionPercent);
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ static angle_t getRunningAdvance(int rpm, float engineLoad DECLARE_ENGINE_PARAME
|
|||
float advanceAngle = advanceMap.getValue((float) rpm, engineLoad);
|
||||
|
||||
// get advance from the separate table for Idle
|
||||
if (CONFIG(useSeparateAdvanceForIdle)) {
|
||||
if (CONFIG(useSeparateAdvanceForIdle) && isIdling()) {
|
||||
float idleAdvance = interpolate2d("idleAdvance", rpm, config->idleAdvanceBins, config->idleAdvance);
|
||||
|
||||
auto [valid, tps] = Sensor::get(SensorType::DriverThrottleIntent);
|
||||
|
@ -92,7 +92,6 @@ static angle_t getRunningAdvance(int rpm, float engineLoad DECLARE_ENGINE_PARAME
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#if EFI_LAUNCH_CONTROL
|
||||
if (engine->isLaunchCondition && CONFIG(enableLaunchRetard)) {
|
||||
if (CONFIG(launchSmoothRetard)) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "airmass.h"
|
||||
#include "sensor.h"
|
||||
#include "idle_thread.h"
|
||||
|
||||
EXTERN_ENGINE;
|
||||
|
||||
|
@ -23,8 +24,8 @@ float AirmassModelBase::getVe(int rpm, float load) const {
|
|||
float ve = m_veTable->getValue(rpm, load);
|
||||
|
||||
auto tps = Sensor::get(SensorType::Tps1);
|
||||
// get VE from the separate table for Idle
|
||||
if (tps.Valid && CONFIG(useSeparateVeForIdle)) {
|
||||
// get VE from the separate table for Idle if idling
|
||||
if (isIdling() && tps && CONFIG(useSeparateVeForIdle)) {
|
||||
float idleVe = interpolate2d("idleVe", rpm, config->idleVeBins, config->idleVe);
|
||||
// interpolate between idle table and normal (running) table using TPS threshold
|
||||
ve = interpolateClamped(0.0f, idleVe, CONFIG(idlePidDeactivationTpsThreshold), ve, tps.Value);
|
||||
|
|
|
@ -58,6 +58,9 @@ class FuelSchedule {
|
|||
public:
|
||||
FuelSchedule();
|
||||
|
||||
// Call this function if something happens that requires a rebuild, like a change to the trigger pattern
|
||||
void invalidate();
|
||||
|
||||
// Call this every trigger tooth. It will schedule all required injector events.
|
||||
void onTriggerTooth(size_t toothIndex, int rpm, efitick_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX);
|
||||
|
||||
|
@ -73,10 +76,7 @@ public:
|
|||
* injection events, per cylinder
|
||||
*/
|
||||
InjectionEvent elements[MAX_INJECTION_OUTPUT_COUNT];
|
||||
bool isReady;
|
||||
|
||||
private:
|
||||
void clear();
|
||||
bool isReady = false;
|
||||
};
|
||||
|
||||
class AngleBasedEvent {
|
||||
|
|
|
@ -14,14 +14,13 @@ EXTERN_ENGINE;
|
|||
#if EFI_ENGINE_CONTROL
|
||||
|
||||
FuelSchedule::FuelSchedule() {
|
||||
clear();
|
||||
for (int cylinderIndex = 0; cylinderIndex < MAX_INJECTION_OUTPUT_COUNT; cylinderIndex++) {
|
||||
InjectionEvent *ev = &elements[cylinderIndex];
|
||||
ev->ownIndex = cylinderIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void FuelSchedule::clear() {
|
||||
void FuelSchedule::invalidate() {
|
||||
isReady = false;
|
||||
}
|
||||
|
||||
|
@ -142,19 +141,26 @@ bool FuelSchedule::addFuelEventsForCylinder(int i DECLARE_ENGINE_PARAMETER_SUFF
|
|||
}
|
||||
|
||||
void FuelSchedule::addFuelEvents(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
||||
clear();
|
||||
|
||||
for (int cylinderIndex = 0; cylinderIndex < CONFIG(specs.cylindersCount); cylinderIndex++) {
|
||||
InjectionEvent *ev = &elements[cylinderIndex];
|
||||
ev->ownIndex = cylinderIndex; // todo: is this assignment needed here? we now initialize in constructor
|
||||
bool result = addFuelEventsForCylinder(cylinderIndex PASS_ENGINE_PARAMETER_SUFFIX);
|
||||
if (!result)
|
||||
if (!result) {
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We made it through all cylinders, mark the schedule as ready so it can be used
|
||||
isReady = true;
|
||||
}
|
||||
|
||||
void FuelSchedule::onTriggerTooth(size_t toothIndex, int rpm, efitick_t nowNt DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
||||
// Wait for schedule to be built - this happens the first time we get RPM
|
||||
if (!isReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CONFIG(specs.cylindersCount); i++) {
|
||||
elements[i].onTriggerTooth(toothIndex, rpm, nowNt);
|
||||
}
|
||||
|
|
|
@ -360,6 +360,7 @@ static int getIgnitionPinForIndex(int cylinderIndex DECLARE_ENGINE_PARAMETER_SUF
|
|||
}
|
||||
|
||||
void prepareIgnitionPinIndices(ignition_mode_e ignitionMode DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
||||
(void)ignitionMode;
|
||||
#if EFI_ENGINE_CONTROL
|
||||
for (int cylinderIndex = 0; cylinderIndex < CONFIG(specs.cylindersCount); cylinderIndex++) {
|
||||
ENGINE(ignitionPin[cylinderIndex]) = getIgnitionPinForIndex(cylinderIndex PASS_ENGINE_PARAMETER_SUFFIX);
|
||||
|
@ -412,6 +413,9 @@ void prepareOutputSignals(DECLARE_ENGINE_PARAMETER_SIGNATURE) {
|
|||
prepareIgnitionPinIndices(CONFIG(ignitionMode) PASS_ENGINE_PARAMETER_SUFFIX);
|
||||
|
||||
TRIGGER_WAVEFORM(prepareShape(&ENGINE(triggerCentral.triggerFormDetails) PASS_ENGINE_PARAMETER_SUFFIX));
|
||||
|
||||
// Fuel schedule may now be completely wrong, force a reset
|
||||
ENGINE(injectionEvents).invalidate();
|
||||
}
|
||||
|
||||
void setTimingRpmBin(float from, float to DECLARE_CONFIG_PARAMETER_SUFFIX) {
|
||||
|
|
|
@ -118,6 +118,8 @@ const ADCConversionGroup* getConversionGroup(uint8_t cylinderIndex) {
|
|||
if (cylinderUsesChannel2(cylinderIndex)) {
|
||||
return &adcConvGroupCh2;
|
||||
}
|
||||
#else
|
||||
(void)cylinderIndex;
|
||||
#endif // KNOCK_HAS_CH2
|
||||
|
||||
return &adcConvGroupCh1;
|
||||
|
|
|
@ -418,8 +418,13 @@ void findTriggerPosition(TriggerWaveform *triggerShape,
|
|||
return;
|
||||
}
|
||||
|
||||
position->triggerEventIndex = triggerEventIndex;
|
||||
position->angleOffsetFromTriggerEvent = angle - triggerEventAngle;
|
||||
{
|
||||
// This must happen under lock so that the tooth and offset don't get partially read and mismatched
|
||||
chibios_rt::CriticalSectionLocker csl;
|
||||
|
||||
position->triggerEventIndex = triggerEventIndex;
|
||||
position->angleOffsetFromTriggerEvent = angle - triggerEventAngle;
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerWaveform::prepareShape(TriggerFormDetails *details DECLARE_ENGINE_PARAMETER_SUFFIX) {
|
||||
|
|
|
@ -359,7 +359,8 @@ void handleTsW(ts_channel_s *tsChannel, char *input) {
|
|||
if (isLogFile(fileName)) {
|
||||
int dotIndex = indexOf(fileName, DOT);
|
||||
if (0 == strncmp(input + 6, &fileName[dotIndex - 4], 4)) {
|
||||
FRESULT err = f_open(&uploading, fileName, FA_READ);// This file has the index for next log file name
|
||||
/* FRESULT err = */
|
||||
f_open(&uploading, fileName, FA_READ);// This file has the index for next log file name
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,8 @@ int getPortPinIndex(ioportid_t port, ioportmask_t pin) {
|
|||
}
|
||||
|
||||
ioportid_t getHwPort(const char *msg, brain_pin_e brainPin) {
|
||||
(void)msg;
|
||||
|
||||
if (!isBrainPinValid(brainPin)) {
|
||||
/*
|
||||
* https://github.com/dron0gus please help
|
||||
|
|
|
@ -406,6 +406,7 @@ void HAL_FLASH_IRQHandler(void)
|
|||
*/
|
||||
__weak void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue)
|
||||
{
|
||||
(void)ReturnValue;
|
||||
/* NOTE : This function Should not be modified, when the callback is needed,
|
||||
the HAL_FLASH_EndOfOperationCallback could be implemented in the user file
|
||||
*/
|
||||
|
@ -421,6 +422,7 @@ __weak void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue)
|
|||
*/
|
||||
__weak void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue)
|
||||
{
|
||||
(void)ReturnValue;
|
||||
/* NOTE : This function Should not be modified, when the callback is needed,
|
||||
the HAL_FLASH_OperationErrorCallback could be implemented in the user file
|
||||
*/
|
||||
|
@ -565,9 +567,9 @@ uint32_t HAL_FLASH_GetError(void)
|
|||
* @retval HAL Status
|
||||
*/
|
||||
HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout)
|
||||
{
|
||||
{
|
||||
(void)Timeout;
|
||||
|
||||
|
||||
/* Clear Error Code */
|
||||
pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;
|
||||
|
||||
|
|
|
@ -1001,6 +1001,9 @@ static HAL_StatusTypeDef FLASH_OB_DisablePCROP(uint32_t SectorBank1, uint32_t Se
|
|||
*/
|
||||
static void FLASH_MassErase(uint8_t VoltageRange, uint32_t Banks)
|
||||
{
|
||||
(void)VoltageRange;
|
||||
(void)Banks;
|
||||
|
||||
uint32_t tmp_psize = 0;
|
||||
|
||||
/* Check the parameters */
|
||||
|
@ -1083,6 +1086,8 @@ void FLASH_Erase_Sector(uint32_t Sector, uint8_t VoltageRange)
|
|||
*/
|
||||
static HAL_StatusTypeDef FLASH_OB_EnableWRP(uint32_t WRPSector, uint32_t Banks)
|
||||
{
|
||||
(void)Banks;
|
||||
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
|
||||
/* Check the parameters */
|
||||
|
@ -1119,6 +1124,8 @@ static HAL_StatusTypeDef FLASH_OB_EnableWRP(uint32_t WRPSector, uint32_t Banks)
|
|||
*/
|
||||
static HAL_StatusTypeDef FLASH_OB_DisableWRP(uint32_t WRPSector, uint32_t Banks)
|
||||
{
|
||||
(void)Banks;
|
||||
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
|
||||
/* Check the parameters */
|
||||
|
|
|
@ -454,7 +454,7 @@ void HAL_FLASH_IRQHandler(void)
|
|||
__weak void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue)
|
||||
{
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
//UNUSED(ReturnValue);
|
||||
(void)ReturnValue;
|
||||
/* NOTE : This function Should not be modified, when the callback is needed,
|
||||
the HAL_FLASH_EndOfOperationCallback could be implemented in the user file
|
||||
*/
|
||||
|
@ -472,7 +472,7 @@ __weak void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue)
|
|||
__weak void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue)
|
||||
{
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
//UNUSED(ReturnValue);
|
||||
(void)ReturnValue;
|
||||
/* NOTE : This function Should not be modified, when the callback is needed,
|
||||
the HAL_FLASH_OperationErrorCallback could be implemented in the user file
|
||||
*/
|
||||
|
@ -616,8 +616,9 @@ uint32_t HAL_FLASH_GetError(void)
|
|||
* @retval HAL Status
|
||||
*/
|
||||
HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout)
|
||||
{
|
||||
uint32_t tickstart = 0;
|
||||
{
|
||||
(void)Timeout;
|
||||
//uint32_t tickstart = 0;
|
||||
|
||||
/* Clear Error Code */
|
||||
pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;
|
||||
|
|
|
@ -191,30 +191,6 @@ int findIndex(const float array[], int size, float value) {
|
|||
return findIndexMsg("", array, size, value);
|
||||
}
|
||||
|
||||
namespace priv
|
||||
{
|
||||
/**
|
||||
* @brief One-dimensional table lookup with linear interpolation
|
||||
*
|
||||
* @see setLinearCurve()
|
||||
*/
|
||||
float interpolate2d(const char *msg, float value, const float bin[], const float values[], int size) {
|
||||
if (isnan(value)) {
|
||||
// this unfortunately sometimes happens during functional tests on real hardware
|
||||
warning(CUSTOM_INTERPOLATE_NAN, "NaN in interpolate2d %s", msg);
|
||||
return NAN;
|
||||
}
|
||||
int index = findIndexMsg(msg, bin, size, value);
|
||||
|
||||
if (index == -1)
|
||||
return values[0];
|
||||
if (index == size - 1)
|
||||
return values[size - 1];
|
||||
|
||||
return interpolateMsg(msg, bin[index], values[index], bin[index + 1], values[index + 1], value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets specified value for specified key in a correction curve
|
||||
* see also setLinearCurve()
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "obd_error_codes.h"
|
||||
#include "error_handling.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef DEBUG_INTERPOLATION
|
||||
#define DEBUG_INTERPOLATION FALSE
|
||||
#endif
|
||||
|
@ -27,12 +29,87 @@ float interpolateClamped(float x1, float y1, float x2, float y2, float x);
|
|||
float interpolateMsg(const char *msg, float x1, float y1, float x2, float y2, float x);
|
||||
|
||||
namespace priv {
|
||||
float interpolate2d(const char *msg, float value, const float bin[], const float values[], int size);
|
||||
struct BinResult
|
||||
{
|
||||
size_t Idx;
|
||||
float Frac;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Finds the location of a value in the bin array.
|
||||
*
|
||||
* @param value The value to find in the bins.
|
||||
* @return A result containing the index to the left of the value,
|
||||
* and how far from (idx) to (idx + 1) the value is located.
|
||||
*/
|
||||
template<class TBin, int TSize>
|
||||
BinResult getBin(float value, const TBin (&bins)[TSize]) {
|
||||
// Enforce numeric only (int, float, uintx_t, etc)
|
||||
static_assert(std::is_arithmetic_v<TBin>, "Table bins must be an arithmetic type");
|
||||
|
||||
// Enforce that there are enough bins to make sense (what does one bin even mean?)
|
||||
static_assert(TSize >= 2);
|
||||
|
||||
// Handle NaN
|
||||
if (cisnan(value)) {
|
||||
return { 0, 0.0f };
|
||||
}
|
||||
|
||||
// Handle off-scale low
|
||||
if (value <= bins[0]) {
|
||||
return { 0, 0.0f };
|
||||
}
|
||||
|
||||
// Handle off-scale high
|
||||
if (value >= bins[TSize - 1]) {
|
||||
return { TSize - 2, 1.0f };
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
// Find the last index less than the searched value
|
||||
// Linear search for now, maybe binary search in future
|
||||
// after collecting real perf data
|
||||
for (idx = 0; idx < TSize - 1; idx++) {
|
||||
if (bins[idx + 1] > value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float low = bins[idx];
|
||||
float high = bins[idx + 1];
|
||||
|
||||
// Compute how far along the bin we are
|
||||
// (0.0f = left side, 1.0f = right side)
|
||||
float fraction = (value - low) / (high - low);
|
||||
|
||||
return { idx, fraction };
|
||||
}
|
||||
|
||||
template <int TSize>
|
||||
float interpolate2d(const char *msg, const float value, const float (&bin)[TSize], const float (&values)[TSize]) {
|
||||
return priv::interpolate2d(msg, value, bin, values, TSize);
|
||||
static float linterp(float low, float high, float frac)
|
||||
{
|
||||
return high * frac + low * (1 - frac);
|
||||
}
|
||||
} // namespace priv
|
||||
|
||||
template <class TBin, class TValue, int TSize>
|
||||
float interpolate2d(const char *msg, 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<TBin>, "Table values must be an arithmetic type");
|
||||
|
||||
auto b = priv::getBin(value, bin);
|
||||
|
||||
// Convert to float as we read it out
|
||||
float low = static_cast<float>(values[b.Idx]);
|
||||
float high = static_cast<float>(values[b.Idx + 1]);
|
||||
float frac = b.Frac;
|
||||
|
||||
return priv::linterp(low, high, frac);
|
||||
}
|
||||
|
||||
template <class TBin, class TValue, int TSize>
|
||||
float interpolate2d(const float value, const TBin (&bin)[TSize], const TValue (&values)[TSize]) {
|
||||
return interpolate2d("", value, bin, values);
|
||||
}
|
||||
|
||||
int needInterpolationLogging(void);
|
||||
|
|
|
@ -110,3 +110,171 @@ TEST(misc, testSetTableValue) {
|
|||
ASSERT_FLOAT_EQ(1.4, config.cltFuelCorr[0]);
|
||||
|
||||
}
|
||||
|
||||
class TestTable2dSmall : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
float bins[2];
|
||||
float values[2];
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
// This test maps [20,30] -> [100,200]
|
||||
copyArray(bins, { 20.0f, 30.0f });
|
||||
copyArray(values, { 100.0f, 200.0f });
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TestTable2dSmall, OffScaleLow)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(10, bins, values), 100);
|
||||
}
|
||||
|
||||
TEST_F(TestTable2dSmall, OffScaleHigh)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(40, bins, values), 200);
|
||||
}
|
||||
|
||||
TEST_F(TestTable2dSmall, EdgeLeft)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(20, bins, values), 100);
|
||||
}
|
||||
|
||||
TEST_F(TestTable2dSmall, EdgeRight)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(30, bins, values), 200);
|
||||
}
|
||||
|
||||
TEST_F(TestTable2dSmall, Middle)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(25, bins, values), 150);
|
||||
}
|
||||
|
||||
TEST_F(TestTable2dSmall, NanInput)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(interpolate2d(NAN, bins, values), 100);
|
||||
}
|
||||
|
||||
class Test2dTableMassive : public ::testing::Test
|
||||
{
|
||||
static constexpr int Count = 2500;
|
||||
|
||||
protected:
|
||||
float bins[Count];
|
||||
float values[Count];
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
float x = 0;
|
||||
|
||||
for (size_t i = 0; i < std::size(bins); i++)
|
||||
{
|
||||
x += 0.1f;
|
||||
bins[i] = x;
|
||||
values[i] = x * x;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(Test2dTableMassive, t)
|
||||
{
|
||||
float x = 0;
|
||||
float maxErr = -1;
|
||||
|
||||
for (size_t i = 0; i < 25000; i++)
|
||||
{
|
||||
x += 0.01f;
|
||||
|
||||
float actual = x * x;
|
||||
float lookup = interpolate2d(x, bins, values);
|
||||
|
||||
float err = std::abs(actual - lookup);
|
||||
|
||||
if (err > maxErr)
|
||||
{
|
||||
maxErr = err;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_LT(maxErr, 0.01);
|
||||
}
|
||||
|
||||
// Helper for BinResult type
|
||||
#define EXPECT_BINRESULT(actual, expectedIdx, expectedFrac) \
|
||||
{ \
|
||||
auto ___temp___ = actual; \
|
||||
EXPECT_EQ(___temp___.Idx, expectedIdx); \
|
||||
EXPECT_NEAR(___temp___.Frac, expectedFrac, expectedFrac / 1e4); \
|
||||
}
|
||||
|
||||
// Test with small bins: only two values
|
||||
static const float smallBins[] = { 10, 20 };
|
||||
|
||||
TEST(TableBinsSmall, OffScaleLeft)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(5, smallBins), 0, 0);
|
||||
}
|
||||
|
||||
TEST(TableBinsSmall, OffScaleRight)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(25, smallBins), 0, 1);
|
||||
}
|
||||
|
||||
TEST(TableBinsSmall, EdgeLeft)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(10, smallBins), 0, 0);
|
||||
}
|
||||
|
||||
TEST(TableBinsSmall, EdgeRight)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(10, smallBins), 0, 0);
|
||||
}
|
||||
|
||||
TEST(TableBinsSmall, Middle)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(15, smallBins), 0, 0.5f);
|
||||
}
|
||||
|
||||
TEST(TableBinsSmall, NanInput)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(NAN, smallBins), 0, 0);
|
||||
}
|
||||
|
||||
// Test with medium bins, 3 items
|
||||
static const float bigBins[] = { 10, 20, 30 };
|
||||
|
||||
TEST(TableBinsBig, OffScaleLow)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(5, bigBins), 0, 0);
|
||||
}
|
||||
|
||||
TEST(TableBinsBig, OffScaleHigh)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(35, bigBins), 1, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
TEST(TableBinsBig, NearMiddleLow)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(19.99f, bigBins), 0, 0.999f);
|
||||
}
|
||||
|
||||
TEST(TableBinsBig, NearMiddleExact)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(20.0f, bigBins), 1, 0);
|
||||
}
|
||||
|
||||
TEST(TableBinsBig, NearMiddleHigh)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(20.01f, bigBins), 1, 0.001f);
|
||||
}
|
||||
|
||||
TEST(TableBinsBig, LeftMiddle)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(15.0f, bigBins), 0, 0.5f);
|
||||
}
|
||||
|
||||
TEST(TableBinsBig, RightMiddle)
|
||||
{
|
||||
EXPECT_BINRESULT(priv::getBin(25.0f, bigBins), 1, 0.5f);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue