GCC Code Coverage Report


Directory: ./
File: firmware/controllers/math/speed_density.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 68.9% 31 0 45
Functions: 100.0% 3 0 3
Branches: 44.4% 16 0 36
Decisions: 50.0% 8 - 16

Line Branch Decision Exec Source
1 /**
2 * @file speed_density.cpp
3 *
4 * See http://rusefi.com/wiki/index.php?title=Manual:Software:Fuel_Control#Speed_Density for details
5 *
6 * @date May 29, 2014
7 * @author Andrey Belomutskiy, (c) 2012-2020
8 */
9
10 #include "pch.h"
11
12 #if defined(HAS_OS_ACCESS)
13 #error "Unexpected OS ACCESS HERE"
14 #endif
15
16 #define rpmMin 500
17 #define rpmMax 8000
18
19 ve_Map3D_t veMap{"ve"};
20
21 #define tpMin 0
22 #define tpMax 100
23
24 1126 float IFuelComputer::getTChargeCoefficient(float rpm, float tps) {
25 // First, do TPS mode since it doesn't need any of the airflow math.
26
2/2
✓ Branch 0 taken 1125 times.
✓ Branch 1 taken 1 time.
2/2
✓ Decision 'true' taken 1125 times.
✓ Decision 'false' taken 1 time.
1126 if (engineConfiguration->tChargeMode == TCHARGE_MODE_RPM_TPS) {
27 1125 float minRpmKcurrentTPS = interpolateMsg("minRpm", tpMin,
28 1125 engineConfiguration->tChargeMinRpmMinTps, tpMax,
29 1125 engineConfiguration->tChargeMinRpmMaxTps, tps);
30 1125 float maxRpmKcurrentTPS = interpolateMsg("maxRpm", tpMin,
31 1125 engineConfiguration->tChargeMaxRpmMinTps, tpMax,
32 1125 engineConfiguration->tChargeMaxRpmMaxTps, tps);
33
34 1125 return interpolateMsg("Kcurr", rpmMin, minRpmKcurrentTPS, rpmMax, maxRpmKcurrentTPS, rpm);
35 }
36
37 1 constexpr floatms_t gramsPerMsToKgPerHour = (3600.0f * 1000.0f) / 1000.0f;
38 // We're actually using an 'old' airMass calculated for the previous cycle, but it's ok, we're not having any self-excitaton issues
39 1 floatms_t airMassForEngine = sdAirMassInOneCylinder * engineConfiguration->cylindersCount;
40 // airMass is in grams per 1 cycle for 1 cyl. Convert it to airFlow in kg/h for the engine.
41 // And if the engine is stopped (0 rpm), then airFlow is also zero (avoiding NaN division)
42
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 floatms_t airFlow = (rpm == 0) ? 0 : airMassForEngine * gramsPerMsToKgPerHour / getEngineCycleDuration(rpm);
43
44
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1 time.
✗ Decision 'false' not taken.
1 if (engineConfiguration->tChargeMode == TCHARGE_MODE_AIR_INTERP) {
45 // just interpolate between user-specified min and max coefs, based on the max airFlow value
46 1 return interpolateClamped(
47 0.0, engineConfiguration->tChargeAirCoefMin,
48 engineConfiguration->tChargeAirFlowMax, engineConfiguration->tChargeAirCoefMax,
49 airFlow
50 1 );
51 } else if (engineConfiguration->tChargeMode == TCHARGE_MODE_AIR_INTERP_TABLE) {
52 return interpolate2d(
53 airFlow,
54 engineConfiguration->tchargeBins,
55 engineConfiguration->tchargeValues
56 );
57 } else {
58 criticalError("Unexpected tChargeMode: %d", engineConfiguration->tChargeMode);
59 return 0;
60 }
61 }
62
63 // http://rusefi.com/math/t_charge.html
64 /***panel:Charge Temperature*/
65 1126 temperature_t IFuelComputer::getTCharge(float rpm, float tps) {
66
1/1
✓ Branch 2 taken 1126 times.
1126 const auto clt = Sensor::get(SensorType::Clt);
67
1/1
✓ Branch 2 taken 1126 times.
1126 const auto iat = Sensor::get(SensorType::Iat);
68
69 float airTemp;
70
71 // Without either valid, return 0C. It's wrong, but it'll pretend to be nice and dense, so at least you won't go lean.
72
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1126 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1126 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1126 times.
1126 if (!iat && !clt) {
73 return 0;
74
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1126 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1126 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1126 times.
1126 } else if (!clt && iat) {
75 // Intake temperature will almost always be colder (richer) than CLT - use that
76 return iat.Value;
77
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1126 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1126 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1126 times.
1126 } else if (!iat && clt) {
78 // Without valid intake temperature, assume intake temp is 0C, and interpolate anyway
79 airTemp = 0;
80 } else {
81 // All is well - use real air temp
82 1126 airTemp = iat.Value;
83 }
84
85 1126 float coolantTemp = clt.Value;
86
87
1/1
✓ Branch 1 taken 1126 times.
1126 sdTcharge_coff = getTChargeCoefficient(rpm, tps);
88
89
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1126 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1126 times.
1126 if (std::isnan(sdTcharge_coff)) {
90 warning(ObdCode::CUSTOM_ERR_T2_CHARGE, "t2-getTCharge NaN");
91 return coolantTemp;
92 }
93
94 // Interpolate between CLT and IAT:
95 // 0.0 coefficient -> use CLT (full heat transfer)
96 // 1.0 coefficient -> use IAT (no heat transfer)
97
1/1
✓ Branch 1 taken 1126 times.
1126 float Tcharge = interpolateClamped(0.0f, coolantTemp, 1.0f, airTemp, sdTcharge_coff);
98
99
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1126 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1126 times.
1126 if (std::isnan(Tcharge)) {
100 // we can probably end up here while resetting engine state - interpolation would fail
101 warning(ObdCode::CUSTOM_ERR_TCHARGE_NOT_READY, "getTCharge NaN");
102 return coolantTemp;
103 }
104
105 1126 return Tcharge;
106 }
107
108 585 void initSpeedDensity() {
109 585 veMap.initTable(config->veTable, config->veRpmBins, config->veLoadBins);
110 585 }
111