GCC Code Coverage Report


Directory: ./
File: firmware/controllers/math/throttle_model.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 96.8% 60 0 62
Functions: 100.0% 15 0 15
Branches: 77.8% 21 0 27
Decisions: 80.0% 8 - 10

Line Branch Decision Exec Source
1 #include "pch.h"
2
3 #include "throttle_model.h"
4
5 #include "fuel_math.h"
6
7 #if EFI_ENGINE_CONTROL
8
9 static const float pressureRatioCorrectionBins[] = { 0.53125, 0.546875, 0.5625, 0.578125, 0.59375, 0.609375, 0.625, 0.640625, 0.65625, 0.671875, 0.6875, 0.703125, 0.71875, 0.734375, 0.750, 0.765625, 0.78125, 0.796875, 0.8125, 0.828125, 0.84375, 0.859375, 0.875, 0.890625, 0.90625, 0.921875, 0.9375, 0.953125 };
10 static const float pressureRatioCorrectionValues[] = { 1, 0.9993, 0.998, 0.995, 0.991, 0.986, 0.979, 0.972, 0.963, 0.953, 0.942, 0.930, 0.916, 0.901, 0.884, 0.866, 0.845, 0.824, 0.800, 0.774, 0.745, 0.714, 0.679, 0.642, 0.600, 0.553, 0.449, 0.449 };
11 3064 static float pressureRatioFlowCorrection(float pr) {
12
2/2
✓ Branch 0 taken 1114 times.
✓ Branch 1 taken 1950 times.
2/2
✓ Decision 'true' taken 1114 times.
✓ Decision 'false' taken 1950 times.
3064 if (pr < 0.531) {
13 1114 return 1.0;
14 }
15
16
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1950 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1950 times.
1950 if (pr > 0.95) {
17 return 0.449f;
18 }
19
20 // float x = pr;
21 // float x2 = x * x;
22 // float x3 = x2 * x;
23 // return -6.9786 * x3 + 11.597 * x2 - 6.7227 * x + 2.3509;
24
25 1950 return interpolate2d(pr, pressureRatioCorrectionBins, pressureRatioCorrectionValues);
26 }
27
28 3064 static float flowCorrections(float pressureRatio, float p_up, float iat) {
29 // PR correction
30 3064 float prCorrectionFactor = pressureRatioFlowCorrection(pressureRatio);
31
32 // Inlet density correction
33 3064 float tempCorrection = sqrt(C_K_OFFSET / (iat + C_K_OFFSET));
34 3064 float pressureCorrection = p_up / STD_ATMOSPHERE;
35 3064 float densityCorrection = tempCorrection * pressureCorrection;
36
37 3064 return prCorrectionFactor * densityCorrection;
38 }
39
40 4926 float ThrottleModelBase::partThrottleFlow(float tps, float flowCorrection) const {
41 4926 return effectiveArea(tps) * flowCorrection;
42 }
43
44 2232 float ThrottleModelBase::partThrottleFlow(float tps, float pressureRatio, float p_up, float iat) const {
45 2232 return partThrottleFlow(tps, flowCorrections(pressureRatio, p_up, iat));
46 }
47
48 class InverseFlowSolver : public NewtonsMethodSolver {
49 public:
50 832 InverseFlowSolver(const ThrottleModelBase* model, float target, float pressureRatio, float p_up, float iat)
51 1664 : m_model(*model)
52 1664 , m_flowCorrection(flowCorrections(pressureRatio, p_up, iat))
53 832 , m_target(target)
54 {
55 832 }
56
57 private:
58 const ThrottleModelBase& m_model;
59 const float m_flowCorrection;
60 const float m_target;
61
62 2694 float fx(float x) override {
63 // Return 0 when the estimate equals the target, positive when estimate too large
64 2694 return m_model.partThrottleFlow(x, m_flowCorrection) - m_target;
65 }
66
67 898 float dfx(float x) override {
68 // The marginal flow per angle (dFlow/dTPS) is not trivially differentiable,
69 // but it is continuous, so we can use a finite difference approximation over some
70 // "small" step size (0.1 degree ~= 0 for throttle purposes)
71 // Too small a step may provoke numerical instability.
72 898 return (fx(x + 0.1) - fx(x - 0.1)) / 0.2;
73 }
74 };
75
76 // Find the throttle position that gives the specified flow
77 1132 float ThrottleModelBase::throttlePositionForFlow(float flow, float pressureRatio, float p_up, float iat) const {
78 // What does the bare throttle flow at wide open?
79
1/1
✓ Branch 1 taken 1132 times.
1132 float wideOpenFlow = partThrottleFlow(100, pressureRatio, p_up, iat);
80
81 // If the target flow is more than the throttle can flow, return 100% since the throttle
82 // can't open any further
83 // If we don't do this, trying to solve using the solver may diverge
84
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 832 times.
2/2
✓ Decision 'true' taken 300 times.
✓ Decision 'false' taken 832 times.
1132 if (flow > wideOpenFlow) {
85 300 return 100;
86 }
87
88
1/1
✓ Branch 2 taken 832 times.
832 InverseFlowSolver solver(this, flow, pressureRatio, p_up, iat);
89
1/1
✓ Branch 2 taken 832 times.
832 return solver.solve(50, 0.1).value_or(0);
90 }
91
92 1105 float ThrottleModelBase::estimateThrottleFlow(float tip, float tps, float map, float iat) {
93 // How much flow would the engine pull at 0.95 PR?
94 // The throttle won't flow much more than this in any scenario, even if the throttle could move more flow.
95 1105 constexpr float crossoverPr = 0.95f;
96 1105 float p95Flow = maxEngineFlow(tip * crossoverPr);
97
98 // What throttle position gives us that flow at 0.95 PR?
99 1105 float throttleAngle95Pr = throttlePositionForFlow(p95Flow, crossoverPr, tip, iat);
100 1105 throttleModelCrossoverAngle = throttleAngle95Pr;
101
102 1105 bool useWotModel = tps > throttleAngle95Pr;
103 1105 throttleUseWotModel = useWotModel;
104
105
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1100 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 1100 times.
1105 if (useWotModel) {
106 // "WOT" model
107
108 // Maximum flow if the throttle was removed
109 5 float maximumPossibleFlow = maxEngineFlow(tip);
110
111 // Linearly interpolate between the P95 point and wide open, where the engine flows its max
112 5 return interpolateClamped(throttleAngle95Pr, p95Flow, 100, maximumPossibleFlow, tps);
113 } else {
114 1100 float pressureRatio = map / tip;
115
116 1100 return partThrottleFlow(tps, pressureRatio, tip, iat);
117 }
118 }
119
120 // todo: migrate to proxy sensor?
121 524039 expected<float> getThrottleInletPressure() {
122 // Use TIP sensor
123 // or use Baro sensor if no TIP
124 // or use 101.325kPa (std atmosphere) if no Baro
125
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 524039 times.
524039 return Sensor::hasSensor(SensorType::ThrottleInletPressure) ? Sensor::get(SensorType::ThrottleInletPressure) :
126
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 524039 times.
524039 Sensor::hasSensor(SensorType::BarometricPressure) ? Sensor::get(SensorType::BarometricPressure) :
127 524039 SensorResult(STD_ATMOSPHERE);
128 }
129
130 522954 float getThrottlePressureRatio(float map){
131 522954 return map / getThrottleInletPressure().Value;
132 }
133
134 1085 expected<float> ThrottleModelBase::estimateThrottleFlow(float map, float tps) {
135 // Inputs
136
1/1
✓ Branch 2 taken 1085 times.
1085 auto iat = Sensor::get(SensorType::Iat);
137
138
1/1
✓ Branch 2 taken 1085 times.
1085 auto tip = getThrottleInletPressure();
139
140
3/6
✓ Branch 1 taken 1085 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1085 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1085 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1085 times.
1085 if (!tip || !iat) {
141 return unexpected;
142 }
143
144
1/1
✓ Branch 2 taken 1085 times.
1085 return estimateThrottleFlow(tip.Value, tps, map, iat.Value);
145 }
146
147 1085 void ThrottleModelBase::onSlowCallback() {
148
3/3
✓ Branch 2 taken 1085 times.
✓ Branch 5 taken 1085 times.
✓ Branch 8 taken 1085 times.
1085 throttleEstimatedFlow = estimateThrottleFlow(Sensor::getOrZero(SensorType::Map), Sensor::getOrZero(SensorType::Tps1)).value_or(0);
149 1085 }
150
151 4585 float ThrottleModel::effectiveArea(float tps) const {
152 4585 return interpolate2d(tps, config->throttleEstimateEffectiveAreaBins, config->throttleEstimateEffectiveAreaValues);
153 }
154
155 1085 float ThrottleModel::maxEngineFlow(float map) const {
156 1085 return getMaxAirflowAtMap(map);
157 }
158
159 #endif // EFI_ENGINE_CONTROL
160