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 |