Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file electronic_throttle.cpp | |||
3 | * @brief Electronic Throttle driver | |||
4 | * | |||
5 | * @see test test_etb.cpp | |||
6 | * | |||
7 | * PPS=pedal position sensor=AcceleratorPedal | |||
8 | * TPS=throttle position sensor, this one is inside ETB=electronic throttle body | |||
9 | * | |||
10 | * Limited user documentation at https://github.com/rusefi/rusefi/wiki/HOWTO_electronic_throttle_body | |||
11 | * | |||
12 | * | |||
13 | * ETB is controlled according to pedal position input (pedal position sensor is a potentiometer) | |||
14 | * pedal 0% means pedal not pressed / idle | |||
15 | * pedal 100% means pedal all the way down | |||
16 | * (not TPS - not the one you can calibrate in TunerStudio) | |||
17 | * | |||
18 | * | |||
19 | * See also pid.cpp | |||
20 | * | |||
21 | * @date Dec 7, 2013 | |||
22 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
23 | * | |||
24 | * This file is part of rusEfi - see http://rusefi.com | |||
25 | * | |||
26 | * rusEfi is free software; you can redistribute it and/or modify it under the terms of | |||
27 | * the GNU General Public License as published by the Free Software Foundation; either | |||
28 | * version 3 of the License, or (at your option) any later version. | |||
29 | * | |||
30 | * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without | |||
31 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
32 | * GNU General Public License for more details. | |||
33 | * | |||
34 | * You should have received a copy of the GNU General Public License along with this program. | |||
35 | * If not, see <http://www.gnu.org/licenses/>. | |||
36 | */ | |||
37 | ||||
38 | #include "pch.h" | |||
39 | ||||
40 | #include "electronic_throttle_impl.h" | |||
41 | ||||
42 | #if EFI_ELECTRONIC_THROTTLE_BODY | |||
43 | ||||
44 | #include "dc_motor.h" | |||
45 | #include "dc_motors.h" | |||
46 | #include "defaults.h" | |||
47 | #include "tunerstudio.h" | |||
48 | #include "tunerstudio_calibration_channel.h" | |||
49 | #include "transition_events.h" | |||
50 | ||||
51 | #if defined(HAS_OS_ACCESS) | |||
52 | #error "Unexpected OS ACCESS HERE" | |||
53 | #endif | |||
54 | ||||
55 | #if HW_PROTEUS | |||
56 | #include "proteus_meta.h" | |||
57 | #endif // HW_PROTEUS | |||
58 | ||||
59 | #ifndef ETB_MAX_COUNT | |||
60 | #define ETB_MAX_COUNT 2 | |||
61 | #endif /* ETB_MAX_COUNT */ | |||
62 | ||||
63 | #ifndef ETB_INTERMITTENT_LIMIT | |||
64 | #define ETB_INTERMITTENT_LIMIT 50 | |||
65 | #endif | |||
66 | ||||
67 | static pedal2tps_t pedal2tpsMap{"p2t"}; | |||
68 | static Map3D<ETB2_TRIM_RPM_SIZE, ETB2_TRIM_SIZE, int8_t, uint8_t, uint8_t> throttle2TrimTable{"t2t"}; | |||
69 | static Map3D<TRACTION_CONTROL_ETB_DROP_SLIP_SIZE, TRACTION_CONTROL_ETB_DROP_SPEED_SIZE, int8_t, uint16_t, uint8_t> tcEtbDropTable{"tce"}; | |||
70 | ||||
71 | constexpr float etbPeriodSeconds = 1.0f / ETB_LOOP_FREQUENCY; | |||
72 | ||||
73 | //static bool startupPositionError = false; | |||
74 | ||||
75 | //#define STARTUP_NEUTRAL_POSITION_ERROR_THRESHOLD 5 | |||
76 | ||||
77 | static const float hardCodedetbHitachiBiasBins[8] = {0.0, 19.0, 21.0, 22.0, 23.0, 25.0, 30.0, 100.0}; | |||
78 | ||||
79 | static const float hardCodedetbHitachiBiasValues[8] = {-18.0, -17.0, -15.0, 0.0, 16.0, 20.0, 20.0, 20.0}; | |||
80 | ||||
81 | /* Generated by TS2C on Thu Aug 20 21:10:02 EDT 2020*/ | |||
82 | 1 | void setHitachiEtbBiasBins() { | ||
83 | 1 | copyArray(config->etbBiasBins, hardCodedetbHitachiBiasBins); | ||
84 | 1 | copyArray(config->etbBiasValues, hardCodedetbHitachiBiasValues); | ||
85 | 1 | } | ||
86 | ||||
87 | 2080 | static SensorType functionToPositionSensor(dc_function_e func) { | ||
88 |
4/5✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 1025 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
2080 | switch(func) { | |
89 |
1/1✓ Decision 'true' taken 1048 times.
|
1048 | case DC_Throttle1: return SensorType::Tps1; | |
90 |
1/1✓ Decision 'true' taken 1025 times.
|
1025 | case DC_Throttle2: return SensorType::Tps2; | |
91 |
1/1✓ Decision 'true' taken 3 times.
|
3 | case DC_IdleValve: return SensorType::IdlePosition; | |
92 |
1/1✓ Decision 'true' taken 4 times.
|
4 | case DC_Wastegate: return SensorType::WastegatePosition; | |
93 | ✗ | default: return SensorType::Invalid; | ||
94 | } | |||
95 | } | |||
96 | ||||
97 | 2073 | static SensorType functionToTpsSensor(dc_function_e func) { | ||
98 |
2/5✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 1025 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
2073 | switch(func) { | |
99 |
1/1✓ Decision 'true' taken 1048 times.
|
1048 | case DC_Throttle1: return SensorType::Tps1; | |
100 |
1/1✓ Decision 'true' taken 1025 times.
|
1025 | case DC_Throttle2: return SensorType::Tps2; | |
101 | ✗ | case DC_IdleValve: return SensorType::IdlePosition; | ||
102 | ✗ | case DC_Wastegate: return SensorType::WastegatePosition; | ||
103 | ✗ | default: return SensorType::Invalid; | ||
104 | } | |||
105 | } | |||
106 | ||||
107 | ✗ | static SensorType functionToTpsSensorPrimary(dc_function_e func) { | ||
108 | ✗ | switch(func) { | ||
109 | ✗ | case DC_Throttle1: return SensorType::Tps1Primary; | ||
110 | ✗ | case DC_Throttle2: return SensorType::Tps2Primary; | ||
111 | ✗ | case DC_IdleValve: return SensorType::IdlePosition; | ||
112 | ✗ | case DC_Wastegate: return SensorType::WastegatePosition; | ||
113 | ✗ | default: return SensorType::Invalid; | ||
114 | } | |||
115 | } | |||
116 | ||||
117 | ✗ | static SensorType functionToTpsSensorSecondary(dc_function_e func) { | ||
118 | ✗ | switch(func) { | ||
119 | ✗ | case DC_Throttle1: return SensorType::Tps1Secondary; | ||
120 | ✗ | case DC_Throttle2: return SensorType::Tps2Secondary; | ||
121 | /* No secondary sensors for Idle and EWG */ | |||
122 | ✗ | default: return SensorType::Invalid; | ||
123 | } | |||
124 | } | |||
125 | ||||
126 | #if EFI_TUNER_STUDIO | |||
127 | ✗ | static TsCalMode functionToCalModePriMin(dc_function_e func) { | ||
128 | ✗ | switch (func) { | ||
129 | ✗ | case DC_Throttle1: return TsCalMode::Tps1Min; | ||
130 | ✗ | case DC_Throttle2: return TsCalMode::Tps2Min; | ||
131 | ✗ | case DC_Wastegate: return TsCalMode::EwgPosMin; | ||
132 | ✗ | default: return TsCalMode::None; | ||
133 | } | |||
134 | } | |||
135 | ||||
136 | ✗ | static TsCalMode functionToCalModePriMax(dc_function_e func) { | ||
137 | ✗ | switch (func) { | ||
138 | ✗ | case DC_Throttle1: return TsCalMode::Tps1Max; | ||
139 | ✗ | case DC_Throttle2: return TsCalMode::Tps2Max; | ||
140 | ✗ | case DC_Wastegate: return TsCalMode::EwgPosMax; | ||
141 | ✗ | default: return TsCalMode::None; | ||
142 | } | |||
143 | } | |||
144 | ||||
145 | ✗ | static TsCalMode functionToCalModeSecMin(dc_function_e func) { | ||
146 | ✗ | switch (func) { | ||
147 | ✗ | case DC_Throttle1: return TsCalMode::Tps1SecondaryMin; | ||
148 | ✗ | case DC_Throttle2: return TsCalMode::Tps2SecondaryMin; | ||
149 | ✗ | default: return TsCalMode::None; | ||
150 | } | |||
151 | } | |||
152 | ||||
153 | ✗ | static TsCalMode functionToCalModeSecMax(dc_function_e func) { | ||
154 | ✗ | switch (func) { | ||
155 | ✗ | case DC_Throttle1: return TsCalMode::Tps1SecondaryMax; | ||
156 | ✗ | case DC_Throttle2: return TsCalMode::Tps2SecondaryMax; | ||
157 | ✗ | default: return TsCalMode::None; | ||
158 | } | |||
159 | } | |||
160 | #endif // EFI_TUNER_STUDIO | |||
161 | ||||
162 | #define ETB_DUTY_LIMIT 0.9 | |||
163 | // this macro clamps both positive and negative percentages from about -100% to 100% | |||
164 | #define ETB_PERCENT_TO_DUTY(x) (clampF(-ETB_DUTY_LIMIT, 0.01f * (x), ETB_DUTY_LIMIT)) | |||
165 | ||||
166 | 236 | PUBLIC_API_WEAK bool isBoardAllowingLackOfPps() { | ||
167 | 236 | return false; | ||
168 | } | |||
169 | ||||
170 | 2081 | bool EtbController::init(dc_function_e function, DcMotor *motor, pid_s *pidParameters, const ValueProvider3D* pedalProvider) { | ||
171 | 2081 | state = (uint8_t)EtbState::InInit; | ||
172 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2080 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 2080 times.
|
2081 | if (function == DC_None) { |
173 | // if not configured, don't init. | |||
174 | 1 | state = (uint8_t)EtbState::NotEbt; | ||
175 | 1 | etbErrorCode = (int8_t)EtbStatus::NotConfigured; | ||
176 | 1 | return false; | ||
177 | } | |||
178 | ||||
179 | 2080 | m_function = function; | ||
180 | 2080 | m_positionSensor = functionToPositionSensor(function); | ||
181 | ||||
182 | // If we are a throttle, require redundant TPS sensor | |||
183 |
2/2✓ Branch 1 taken 2073 times.
✓ Branch 2 taken 7 times.
|
2/2✓ Decision 'true' taken 2073 times.
✓ Decision 'false' taken 7 times.
|
2080 | if (isEtbMode()) { |
184 | // If no sensor is configured for this throttle, skip initialization. | |||
185 |
2/2✓ Branch 2 taken 2041 times.
✓ Branch 3 taken 32 times.
|
2/2✓ Decision 'true' taken 2041 times.
✓ Decision 'false' taken 32 times.
|
2073 | if (!Sensor::hasSensor(functionToTpsSensor(function))) { |
186 | 2041 | etbErrorCode = (int8_t)EtbStatus::TpsError; | ||
187 | 2041 | return false; | ||
188 | } | |||
189 | ||||
190 |
5/6✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 30 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 30 times.
|
32 | if (!isBoardAllowingLackOfPps() && !Sensor::isRedundant(m_positionSensor)) { |
191 | 2 | etbErrorCode = (int8_t)EtbStatus::Redundancy; | ||
192 | 2 | return false; | ||
193 | } | |||
194 | } | |||
195 | ||||
196 | 37 | m_motor = motor; | ||
197 | 37 | m_pedalProvider = pedalProvider; | ||
198 | ||||
199 | 37 | m_pid.initPidClass(pidParameters); | ||
200 | ||||
201 | #if !EFI_UNIT_TEST | |||
202 | if (isEtbMode()) { | |||
203 | m_pid.iTermMin = engineConfiguration->etb_iTermMin; | |||
204 | m_pid.iTermMax = engineConfiguration->etb_iTermMax; | |||
205 | } else { | |||
206 | // Some defaults from setDefaultEtbParameters(), find better values for EWG and Idle or add config options | |||
207 | m_pid.iTermMin = -30; | |||
208 | m_pid.iTermMax = 30; | |||
209 | } | |||
210 | #endif | |||
211 | ||||
212 | // Ignore 3% position error before complaining | |||
213 | 37 | m_targetErrorAccumulator.init(3.0f, etbPeriodSeconds); | ||
214 | ||||
215 | 37 | state = (uint8_t)EtbState::SuccessfulInit; | ||
216 | 37 | return true; | ||
217 | } | |||
218 | ||||
219 | #if EFI_UNIT_TEST | |||
220 | int ebtResetCounter; | |||
221 | #endif // EFI_UNIT_TEST | |||
222 | ||||
223 | 17 | void EtbController::reset(const char *reason) { | ||
224 | 17 | efiPrintf("ETB reset %s", reason); | ||
225 | 17 | m_shouldResetPid = true; | ||
226 | 17 | etbTpsErrorCounter = 0; | ||
227 | 17 | etbPpsErrorCounter = 0; | ||
228 | #if EFI_UNIT_TEST | |||
229 | 17 | ebtResetCounter++; | ||
230 | #endif // EFI_UNIT_TEST | |||
231 | ||||
232 | 17 | } | ||
233 | ||||
234 | // todo: document why is EtbController not engine_module? | |||
235 | 438 | void EtbController::onConfigurationChange(pid_s* previousConfiguration) { | ||
236 |
5/6✓ Branch 0 taken 16 times.
✓ Branch 1 taken 422 times.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✓ Branch 6 taken 422 times.
|
2/2✓ Decision 'true' taken 16 times.
✓ Decision 'false' taken 422 times.
|
438 | if (m_motor && !m_pid.isSame(previousConfiguration)) { |
237 | 16 | efiPrintf(" ETB m_shouldResetPid"); | ||
238 | 16 | m_shouldResetPid = true; | ||
239 | } | |||
240 | ||||
241 | 438 | doInitElectronicThrottle(/*isStartupInit*/false); | ||
242 | 438 | } | ||
243 | ||||
244 | ✗ | void EtbController::showStatus() { | ||
245 | ✗ | m_pid.showPidStatus("ETB"); | ||
246 | ✗ | } | ||
247 | ||||
248 | 208 | expected<percent_t> EtbController::observePlant() { | ||
249 |
1/1✓ Branch 2 taken 208 times.
|
208 | expected<percent_t> plant = Sensor::get(m_positionSensor); | |
250 | 208 | validPlantPosition = plant.Valid; | ||
251 | 208 | return plant; | ||
252 | } | |||
253 | ||||
254 | 2251 | void EtbController::setIdlePosition(percent_t pos) { | ||
255 | 2251 | m_idlePosition = pos; | ||
256 | 2251 | } | ||
257 | ||||
258 | 2250 | void EtbController::setWastegatePosition(percent_t pos) { | ||
259 | 2250 | m_wastegatePosition = pos; | ||
260 | 2250 | } | ||
261 | ||||
262 | 255 | expected<percent_t> EtbController::getSetpoint() { | ||
263 |
4/4✓ Branch 0 taken 244 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 time.
|
255 | switch (m_function) { | |
264 |
1/1✓ Decision 'true' taken 244 times.
|
244 | case DC_Throttle1: | |
265 | case DC_Throttle2: | |||
266 |
1/1✓ Decision 'true' taken 244 times.
|
244 | return getSetpointEtb(); | |
267 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case DC_IdleValve: | |
268 | 5 | return getSetpointIdleValve(); | ||
269 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case DC_Wastegate: | |
270 | 5 | return getSetpointWastegate(); | ||
271 |
1/1✓ Decision 'true' taken 1 time.
|
1 | default: | |
272 | 1 | return unexpected; | ||
273 | } | |||
274 | } | |||
275 | ||||
276 | 5 | expected<percent_t> EtbController::getSetpointIdleValve() const { | ||
277 | // VW ETB idle mode uses an ETB only for idle (a mini-ETB sets the lower stop, and a normal cable | |||
278 | // can pull the throttle up off the stop.), so we directly control the throttle with the idle position. | |||
279 | #if EFI_TUNER_STUDIO && (EFI_PROD_CODE || EFI_SIMULATOR) | |||
280 | // todo: where do we want to log this? engine->outputChannels.etbTarget = m_idlePosition; | |||
281 | #endif // EFI_TUNER_STUDIO | |||
282 |
1/1✓ Branch 2 taken 5 times.
|
5 | return clampPercentValue(m_idlePosition); | |
283 | } | |||
284 | ||||
285 | 5 | expected<percent_t> EtbController::getSetpointWastegate() const { | ||
286 | 5 | percent_t targetPosition = m_wastegatePosition + getLuaAdjustment(); | ||
287 | ||||
288 |
1/1✓ Branch 2 taken 5 times.
|
5 | return clampPercentValue(targetPosition); | |
289 | } | |||
290 | ||||
291 | 243 | float getSanitizedPedal() { | ||
292 |
1/1✓ Branch 2 taken 243 times.
|
243 | auto pedalPosition = Sensor::get(SensorType::AcceleratorPedal); | |
293 | // If the pedal has failed, just use 0 position. | |||
294 | // This is safer than disabling throttle control - we can at least push the throttle closed | |||
295 | // and let the engine idle. | |||
296 |
1/1✓ Branch 2 taken 243 times.
|
486 | return clampPercentValue(pedalPosition.value_or(0)); | |
297 | } | |||
298 | ||||
299 | 243 | PUBLIC_API_WEAK float boardAdjustEtbTarget(float currentEtbTarget) { | ||
300 | 243 | return currentEtbTarget; | ||
301 | } | |||
302 | ||||
303 | 244 | expected<percent_t> EtbController::getSetpointEtb() { | ||
304 | // Autotune runs with 50% target position | |||
305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 244 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 244 times.
|
244 | if (m_isAutotune) { |
306 | ✗ | return 50.0f; | ||
307 | } | |||
308 | ||||
309 | // // A few extra preconditions if throttle control is invalid | |||
310 | // if (startupPositionError) { | |||
311 | // return unexpected; | |||
312 | // } | |||
313 | ||||
314 | // If the pedal map hasn't been set, we can't provide a setpoint. | |||
315 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 243 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 243 times.
|
244 | if (!m_pedalProvider) { |
316 | 1 | state = (uint8_t)EtbState::NoPedal; | ||
317 | 1 | return unexpected; | ||
318 | } | |||
319 | ||||
320 |
1/1✓ Branch 1 taken 243 times.
|
243 | float sanitizedPedal = getSanitizedPedal(); | |
321 | ||||
322 |
1/1✓ Branch 1 taken 243 times.
|
243 | float rpm = Sensor::getOrZero(SensorType::Rpm); | |
323 |
1/1✓ Branch 1 taken 243 times.
|
243 | percent_t preBoard = m_pedalProvider->getValue(rpm, sanitizedPedal); | |
324 |
1/1✓ Branch 1 taken 243 times.
|
243 | etbCurrentTarget = boardAdjustEtbTarget(preBoard); | |
325 | 243 | boardEtbAdjustment = etbCurrentTarget - preBoard; | ||
326 | ||||
327 |
1/1✓ Branch 1 taken 243 times.
|
243 | percent_t etbIdlePosition = clampPercentValue(m_idlePosition); | |
328 | 243 | percent_t etbIdleAddition = PERCENT_DIV * engineConfiguration->etbIdleThrottleRange * etbIdlePosition; | ||
329 | ||||
330 | // Interpolate so that the idle adder just "compresses" the throttle's range upward. | |||
331 | // [0, 100] -> [idle, 100] | |||
332 | // 0% target from table -> idle position as target | |||
333 | // 100% target from table -> 100% target position | |||
334 |
1/1✓ Branch 1 taken 243 times.
|
243 | targetWithIdlePosition = interpolateClamped(0, etbIdleAddition, 100, 100, etbCurrentTarget); | |
335 | ||||
336 |
1/1✓ Branch 2 taken 243 times.
|
243 | percent_t targetPosition = targetWithIdlePosition + getLuaAdjustment(); | |
337 | // just an additional logging data point | |||
338 | 243 | adjustedEtbTarget = targetPosition; | ||
339 | ||||
340 | #if EFI_ANTILAG_SYSTEM | |||
341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 243 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 243 times.
|
243 | if (engine->antilagController.isAntilagCondition) { |
342 | ✗ | targetPosition += engineConfiguration->ALSEtbAdd; | ||
343 | } | |||
344 | #endif /* EFI_ANTILAG_SYSTEM */ | |||
345 | ||||
346 |
1/1✓ Branch 1 taken 243 times.
|
243 | float vehicleSpeed = Sensor::getOrZero(SensorType::VehicleSpeed); | |
347 |
1/1✓ Branch 1 taken 243 times.
|
243 | float wheelSlip = Sensor::getOrZero(SensorType::WheelSlipRatio); | |
348 |
1/1✓ Branch 1 taken 243 times.
|
243 | tcEtbDrop = tcEtbDropTable.getValue(wheelSlip, vehicleSpeed); | |
349 | ||||
350 | // Apply any adjustment that this throttle alone needs | |||
351 | // Clamped to +-10 to prevent anything too wild | |||
352 |
2/2✓ Branch 1 taken 243 times.
✓ Branch 4 taken 243 times.
|
243 | trim = clampF(-10, getThrottleTrim(rpm, targetPosition), 10); | |
353 | 243 | targetPosition += trim + tcEtbDrop; | ||
354 | ||||
355 | // Clamp before rev limiter to avoid ineffective rev limit due to crazy out of range position target | |||
356 |
1/1✓ Branch 1 taken 243 times.
|
243 | targetPosition = clampPercentValue(targetPosition); | |
357 | ||||
358 | // Lastly, apply ETB rev limiter | |||
359 | 243 | auto etbRpmLimit = engineConfiguration->etbRevLimitStart; | ||
360 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 238 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 238 times.
|
243 | if (etbRpmLimit != 0) { |
361 | 5 | auto fullyLimitedRpm = etbRpmLimit + engineConfiguration->etbRevLimitRange; | ||
362 | ||||
363 | 5 | float targetPositionBefore = targetPosition; | ||
364 | // Linearly taper throttle to closed from the limit across the range | |||
365 |
1/1✓ Branch 1 taken 5 times.
|
5 | targetPosition = interpolateClamped(etbRpmLimit, targetPosition, fullyLimitedRpm, 0, rpm); | |
366 | ||||
367 | // rev limit active if the position was changed by rev limiter | |||
368 | 5 | etbRevLimitActive = std::abs(targetPosition - targetPositionBefore) > 0.1f; | ||
369 | } | |||
370 | ||||
371 | 243 | float minPosition = engineConfiguration->etbMinimumPosition; | ||
372 | ||||
373 | // Keep the throttle just barely off the lower stop, and less than the user-configured maximum | |||
374 | 243 | float maxPosition = engineConfiguration->etbMaximumPosition; | ||
375 | // Don't allow max position over 100 | |||
376 | 243 | maxPosition = std::min(maxPosition, 100.0f); | ||
377 | ||||
378 |
1/1✓ Branch 1 taken 243 times.
|
243 | targetPosition = clampF(minPosition, targetPosition, maxPosition); | |
379 | 243 | m_adjustedTarget = targetPosition; | ||
380 | ||||
381 | 243 | return targetPosition; | ||
382 | } | |||
383 | ||||
384 | 6 | void EtbController::setLuaAdjustment(float adjustment) { | ||
385 | 6 | luaAdjustment = adjustment; | ||
386 | 6 | m_luaAdjustmentTimer.reset(); | ||
387 | 6 | } | ||
388 | ||||
389 | /** | |||
390 | * positive adjustment opens TPS, negative closes TPS | |||
391 | */ | |||
392 | 248 | float EtbController::getLuaAdjustment() const { | ||
393 | // If the lua position hasn't been set in 0.2 second, don't adjust! | |||
394 | // This avoids a stuck throttle due to hung/rogue/etc Lua script | |||
395 |
2/2✓ Branch 1 taken 241 times.
✓ Branch 2 taken 7 times.
|
2/2✓ Decision 'true' taken 241 times.
✓ Decision 'false' taken 7 times.
|
248 | if (m_luaAdjustmentTimer.getElapsedSeconds() > 0.2f) { |
396 | 241 | return 0; | ||
397 | } else { | |||
398 | 7 | return luaAdjustment; | ||
399 | } | |||
400 | } | |||
401 | ||||
402 | 1 | percent_t EtbController2::getThrottleTrim(float rpm, percent_t targetPosition) const { | ||
403 | 1 | return m_throttle2Trim.getValue(rpm, targetPosition); | ||
404 | } | |||
405 | ||||
406 | 169 | expected<percent_t> EtbController::getOpenLoop(percent_t target) { | ||
407 | // Don't apply open loop for idle valve, only real ETB or wastegate | |||
408 |
3/4✓ Branch 0 taken 159 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
169 | switch(m_function){ | |
409 |
1/1✓ Decision 'true' taken 159 times.
|
159 | case DC_Throttle1: | |
410 | case DC_Throttle2: { | |||
411 |
1/1✓ Decision 'true' taken 159 times.
|
159 | etbFeedForward = interpolate2d(target, config->etbBiasBins, config->etbBiasValues); | |
412 | 159 | break; | ||
413 | } | |||
414 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case DC_Wastegate: { | |
415 | 5 | etbFeedForward = interpolate2d(target, config->dcWastegateBiasBins, config->dcWastegateBiasValues); | ||
416 | 5 | break; | ||
417 | } | |||
418 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case DC_IdleValve: { | |
419 | 5 | etbFeedForward = 0; | ||
420 | 5 | break; | ||
421 | } | |||
422 | ✗ | default: { // or DC_None | ||
423 | ✗ | etbFeedForward = 0; | ||
424 | } | |||
425 | } | |||
426 | ||||
427 | 169 | return etbFeedForward; | ||
428 | } | |||
429 | ||||
430 | ✗ | expected<percent_t> EtbController::getClosedLoopAutotune(percent_t target, percent_t actualThrottlePosition) { | ||
431 | // Estimate gain at current position - this should be well away from the spring and in the linear region | |||
432 | // GetSetpoint sets this to 50% | |||
433 | ✗ | bool isPositive = actualThrottlePosition > target; | ||
434 | ||||
435 | ✗ | float autotuneAmplitude = 20; | ||
436 | ||||
437 | // End of cycle - record & reset | |||
438 | ✗ | if (!isPositive && m_lastIsPositive) { | ||
439 | // Determine period | |||
440 | ✗ | float tu = m_cycleTimer.getElapsedSecondsAndReset(getTimeNowNt()); | ||
441 | ||||
442 | // Determine amplitude | |||
443 | ✗ | float a = m_maxCycleTps - m_minCycleTps; | ||
444 | ||||
445 | // Filter - it's pretty noisy since the ultimate period is not very many loop periods | |||
446 | ✗ | constexpr float alpha = 0.05; | ||
447 | ✗ | m_a = alpha * a + (1 - alpha) * m_a; | ||
448 | ✗ | m_tu = alpha * tu + (1 - alpha) * m_tu; | ||
449 | ||||
450 | // Reset bounds | |||
451 | ✗ | m_minCycleTps = 100; | ||
452 | ✗ | m_maxCycleTps = 0; | ||
453 | ||||
454 | // Math is for Åström–Hägglund (relay) auto tuning | |||
455 | // https://warwick.ac.uk/fac/cross_fac/iatl/reinvention/archive/volume5issue2/hornsey | |||
456 | ||||
457 | // Amplitude of input (duty cycle %) | |||
458 | ✗ | float b = 2 * autotuneAmplitude; | ||
459 | ||||
460 | // Ultimate gain per A-H relay tuning rule | |||
461 | ✗ | float ku = 4 * b / (CONST_PI * m_a); | ||
462 | ||||
463 | // The multipliers below are somewhere near the "no overshoot" | |||
464 | // and "some overshoot" flavors of the Ziegler-Nichols method | |||
465 | // Kp | |||
466 | ✗ | float kp = 0.35f * ku; | ||
467 | ✗ | float ki = 0.25f * ku / m_tu; | ||
468 | ✗ | float kd = 0.08f * ku * m_tu; | ||
469 | ||||
470 | // Publish to TS state | |||
471 | #if EFI_TUNER_STUDIO | |||
472 | // Every 5 cycles (of the throttle), cycle to the next value | |||
473 | ✗ | if (m_autotuneCounter >= 5) { | ||
474 | ✗ | m_autotuneCounter = 0; | ||
475 | ✗ | m_autotuneCurrentParam = (m_autotuneCurrentParam + 1) % 3; // three ETB calibs: P-I-D | ||
476 | } | |||
477 | ||||
478 | ✗ | m_autotuneCounter++; | ||
479 | ||||
480 | ✗ | switch (m_autotuneCurrentParam) { | ||
481 | ✗ | case 0: | ||
482 | ✗ | tsCalibrationSetData(TsCalMode::EtbKp, kp); | ||
483 | ✗ | break; | ||
484 | ✗ | case 1: | ||
485 | ✗ | tsCalibrationSetData(TsCalMode::EtbKi, ki); | ||
486 | ✗ | break; | ||
487 | ✗ | case 2: | ||
488 | ✗ | tsCalibrationSetData(TsCalMode::EtbKd, kd); | ||
489 | ✗ | break; | ||
490 | } | |||
491 | ||||
492 | // Also output to debug channels if configured | |||
493 | ✗ | if (engineConfiguration->debugMode == DBG_ETB_AUTOTUNE) { | ||
494 | // a - amplitude of output (TPS %) | |||
495 | ✗ | engine->outputChannels.debugFloatField1 = m_a; | ||
496 | // b - amplitude of input (Duty cycle %) | |||
497 | ✗ | engine->outputChannels.debugFloatField2 = b; | ||
498 | // Tu - oscillation period (seconds) | |||
499 | ✗ | engine->outputChannels.debugFloatField3 = m_tu; | ||
500 | ||||
501 | ✗ | engine->outputChannels.debugFloatField4 = ku; | ||
502 | ✗ | engine->outputChannels.debugFloatField5 = kp; | ||
503 | ✗ | engine->outputChannels.debugFloatField6 = ki; | ||
504 | ✗ | engine->outputChannels.debugFloatField7 = kd; | ||
505 | } | |||
506 | #endif | |||
507 | // TODO: directly update PID settings in engineConfiguration | |||
508 | } | |||
509 | ||||
510 | ✗ | m_lastIsPositive = isPositive; | ||
511 | ||||
512 | // Find the min/max of each cycle | |||
513 | ✗ | if (actualThrottlePosition < m_minCycleTps) { | ||
514 | ✗ | m_minCycleTps = actualThrottlePosition; | ||
515 | } | |||
516 | ||||
517 | ✗ | if (actualThrottlePosition > m_maxCycleTps) { | ||
518 | ✗ | m_maxCycleTps = actualThrottlePosition; | ||
519 | } | |||
520 | ||||
521 | // Bang-bang control the output to induce oscillation | |||
522 | ✗ | return autotuneAmplitude * (isPositive ? -1 : 1); | ||
523 | } | |||
524 | ||||
525 | 158 | expected<percent_t> EtbController::getClosedLoop(percent_t target, percent_t observation) { | ||
526 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 155 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 155 times.
|
158 | if (m_shouldResetPid) { |
527 | 3 | m_pid.reset(); | ||
528 | 3 | onTransitionEvent(TransitionEvent::EtbPidReset); | ||
529 | 3 | m_shouldResetPid = false; | ||
530 | } | |||
531 | ||||
532 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 158 times.
|
158 | if (m_isAutotune) { |
533 | ✗ | state = (uint8_t)EtbState::Autotune; | ||
534 | ||||
535 | ✗ | m_targetErrorAccumulator.reset(); | ||
536 | ||||
537 | ✗ | return getClosedLoopAutotune(target, observation); | ||
538 | } else { | |||
539 | 158 | checkJam(target, observation); | ||
540 | ||||
541 | 158 | integralError = m_targetErrorAccumulator.accumulate(target - observation); | ||
542 | ||||
543 | 158 | float dt = m_cycleTimer.getElapsedSecondsAndReset(getTimeNowNt()); | ||
544 | 158 | m_lastPidDtMs = dt * 1000.0; | ||
545 | ||||
546 | // Normal case - use PID to compute closed loop part | |||
547 |
1/1✓ Branch 2 taken 158 times.
|
158 | return m_pid.getOutput(target, observation, dt); | |
548 | } | |||
549 | } | |||
550 | ||||
551 | 212 | void EtbController::setOutput(expected<percent_t> outputValue) { | ||
552 | #if EFI_TUNER_STUDIO | |||
553 | // Only report first-throttle stats | |||
554 |
2/2✓ Branch 0 taken 211 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 211 times.
✓ Decision 'false' taken 1 time.
|
212 | if (m_function == DC_Throttle1) { |
555 | 211 | engine->outputChannels.etb1DutyCycle = outputValue.value_or(0); | ||
556 | } | |||
557 | #endif | |||
558 | ||||
559 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 211 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 211 times.
|
212 | if (!m_motor) { |
560 | 1 | state = (uint8_t)EtbState::NoMotor; | ||
561 | 1 | return; | ||
562 | } | |||
563 | ||||
564 | bool isEnabled; | |||
565 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 211 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 211 times.
|
211 | if (!isEtbMode()) { |
566 | // technical debt: non-ETB usages of DC motor are still mixed into ETB controller? | |||
567 | ✗ | state = (uint8_t)EtbState::NotEbt; | ||
568 | ✗ | isEnabled = true; | ||
569 |
2/2✓ Branch 2 taken 1 time.
✓ Branch 3 taken 210 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 210 times.
|
211 | } else if (!getLimpManager()->allowElectronicThrottle()) { |
570 | 1 | state = (uint8_t)EtbState::LimpProhibited; | ||
571 | 1 | isEnabled = false; | ||
572 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 209 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 209 times.
|
210 | } else if (engineConfiguration->pauseEtbControl) { |
573 | 1 | state = (uint8_t)EtbState::Paused; | ||
574 | 1 | isEnabled = false; | ||
575 |
2/2✓ Branch 1 taken 51 times.
✓ Branch 2 taken 158 times.
|
2/2✓ Decision 'true' taken 51 times.
✓ Decision 'false' taken 158 times.
|
209 | } else if (!outputValue) { |
576 | 51 | state = (uint8_t)EtbState::NoOutput; | ||
577 | 51 | isEnabled = false; | ||
578 | } else { | |||
579 | 158 | state = (uint8_t)EtbState::Active; | ||
580 | 158 | isEnabled = true; | ||
581 | } | |||
582 | ||||
583 | // If not ETB, or ETB is allowed, output is valid, and we aren't paused, output to motor. | |||
584 |
2/2✓ Branch 0 taken 158 times.
✓ Branch 1 taken 53 times.
|
2/2✓ Decision 'true' taken 158 times.
✓ Decision 'false' taken 53 times.
|
211 | if (isEnabled) { |
585 | 158 | m_motor->enable(); | ||
586 | 158 | m_motor->set(ETB_PERCENT_TO_DUTY(outputValue.Value)); | ||
587 | } else { | |||
588 | // Otherwise disable the motor. | |||
589 | 53 | m_motor->disable("no-ETB"); | ||
590 | } | |||
591 | } | |||
592 | ||||
593 | 206 | bool EtbController::checkStatus() { | ||
594 | #if EFI_TUNER_STUDIO | |||
595 | // Only debug throttle #1 | |||
596 |
1/2✓ Branch 0 taken 206 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 206 times.
✗ Decision 'false' not taken.
|
206 | if (m_function == DC_Throttle1) { |
597 | 206 | m_pid.postState(engine->outputChannels.etbStatus); | ||
598 | ✗ | } else if (m_function == DC_Wastegate) { | ||
599 | ✗ | m_pid.postState(engine->outputChannels.wastegateDcStatus); | ||
600 | } | |||
601 | #endif /* EFI_TUNER_STUDIO */ | |||
602 | ||||
603 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 206 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 206 times.
|
206 | if (!isEtbMode()) { |
604 | // no validation for h-bridge or idle mode | |||
605 | ✗ | return true; | ||
606 | } | |||
607 | // ETB-specific code belo. The whole mix-up between DC and ETB is shameful :( | |||
608 | ||||
609 | // Only allow autotune with stopped engine, and on the first throttle | |||
610 | // Update local state about autotune | |||
611 | 206 | m_isAutotune = Sensor::getOrZero(SensorType::Rpm) == 0 | ||
612 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
|
206 | && engine->etbAutoTune | |
613 |
1/4✓ Branch 0 taken 206 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
412 | && m_function == DC_Throttle1; | |
614 | ||||
615 | 206 | bool shouldCheckSensorFunction = engine->module<SensorChecker>()->analogSensorsShouldWork(); | ||
616 | ||||
617 |
3/4✓ Branch 0 taken 206 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 204 times.
✓ Branch 3 taken 2 times.
|
2/2✓ Decision 'true' taken 204 times.
✓ Decision 'false' taken 2 times.
|
206 | if (!m_isAutotune && shouldCheckSensorFunction) { |
618 | 204 | bool isTpsError = !Sensor::get(m_positionSensor).Valid; | ||
619 | ||||
620 | // If we have an error that's new, increment the counter | |||
621 |
3/4✓ Branch 0 taken 51 times.
✓ Branch 1 taken 153 times.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
|
2/2✓ Decision 'true' taken 51 times.
✓ Decision 'false' taken 153 times.
|
204 | if (isTpsError && !hadTpsError) { |
622 | 51 | etbTpsErrorCounter++; | ||
623 | } | |||
624 | ||||
625 | 204 | hadTpsError = isTpsError; | ||
626 | ||||
627 | 204 | bool isPpsError = !Sensor::get(SensorType::AcceleratorPedal).Valid; | ||
628 | ||||
629 | // If we have an error that's new, increment the counter | |||
630 |
4/4✓ Branch 0 taken 153 times.
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 52 times.
✓ Branch 3 taken 101 times.
|
2/2✓ Decision 'true' taken 52 times.
✓ Decision 'false' taken 152 times.
|
204 | if (isPpsError && !hadPpsError) { |
631 | 52 | etbPpsErrorCounter++; | ||
632 | } | |||
633 | ||||
634 | 204 | hadPpsError = isPpsError; | ||
635 | 204 | } else { | ||
636 | // Either sensors are expected to not work, or autotune is running, so reset the error counter | |||
637 | 2 | etbTpsErrorCounter = 0; | ||
638 | 2 | etbPpsErrorCounter = 0; | ||
639 | } | |||
640 | ||||
641 | 206 | EtbStatus localReason = EtbStatus::None; | ||
642 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 205 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 205 times.
|
206 | if (etbTpsErrorCounter > ETB_INTERMITTENT_LIMIT) { |
643 | 1 | localReason = EtbStatus::IntermittentTps; | ||
644 | #if EFI_SHAFT_POSITION_INPUT | |||
645 | 410 | } else if (engineConfiguration->disableEtbWhenEngineStopped | ||
646 | ✗ | && !engine->triggerCentral.engineMovedRecently() | ||
647 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 205 times.
|
205 | && !engine->etbAutoTune) { | |
648 | ✗ | localReason = EtbStatus::EngineStopped; | ||
649 | #endif // EFI_SHAFT_POSITION_INPUT | |||
650 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 204 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 204 times.
|
205 | } else if (etbPpsErrorCounter > ETB_INTERMITTENT_LIMIT) { |
651 | 1 | localReason = EtbStatus::IntermittentPps; | ||
652 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 204 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 204 times.
|
204 | } else if (engine->engineState.lua.luaDisableEtb) { |
653 | ✗ | localReason = EtbStatus::Lua; | ||
654 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 204 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 204 times.
|
204 | } else if (!getLimpManager()->allowElectronicThrottle()) { |
655 | ✗ | localReason = EtbStatus::JamDetected; | ||
656 |
3/6✓ Branch 1 taken 204 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 204 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 204 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 204 times.
|
204 | } else if(!isBoardAllowingLackOfPps() && !Sensor::isRedundant(SensorType::AcceleratorPedal)) { |
657 | ✗ | localReason = EtbStatus::Redundancy; | ||
658 | } | |||
659 | ||||
660 | 206 | etbErrorCode = (int8_t)localReason; | ||
661 | ||||
662 | 206 | return localReason == EtbStatus::None; | ||
663 | } | |||
664 | ||||
665 | 206 | void EtbController::update() { | ||
666 | #if !EFI_UNIT_TEST | |||
667 | // If we didn't get initialized, fail fast | |||
668 | if (!m_motor) { | |||
669 | state = (uint8_t)EtbState::FailFast; | |||
670 | return; | |||
671 | } | |||
672 | #endif // EFI_UNIT_TEST | |||
673 | ||||
674 | 206 | bool isOk = checkStatus(); | ||
675 | ||||
676 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 204 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 204 times.
|
206 | if (!isOk) { |
677 | // If engine is stopped and so configured, skip the ETB update entirely | |||
678 | // This is quieter and pulls less power than leaving it on all the time | |||
679 | 2 | m_motor->disable("etb status"); | ||
680 | 2 | return; | ||
681 | } | |||
682 | ||||
683 | 204 | ClosedLoopController::update(); | ||
684 | ||||
685 |
5/6✓ Branch 1 taken 204 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 50 times.
✓ Branch 4 taken 154 times.
✓ Branch 5 taken 50 times.
✓ Branch 6 taken 154 times.
|
2/2✓ Decision 'true' taken 50 times.
✓ Decision 'false' taken 154 times.
|
204 | if (isEtbMode() && !validPlantPosition) { |
686 | 50 | etbErrorCode = (int8_t)EtbStatus::TpsError; | ||
687 | } | |||
688 | } | |||
689 | ||||
690 | 162 | void EtbController::checkJam(percent_t setpoint, percent_t observation) { | ||
691 | 162 | float absError = std::abs(setpoint - observation); | ||
692 | ||||
693 | 162 | auto jamDetectThreshold = engineConfiguration->etbJamDetectThreshold; | ||
694 | 162 | auto jamTimeout = engineConfiguration->etbJamTimeout; | ||
695 | ||||
696 |
5/6✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 158 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 158 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 158 times.
|
162 | if (jamDetectThreshold != 0 && jamTimeout != 0) { |
697 |
1/1✓ Branch 1 taken 4 times.
|
4 | auto nowNt = getTimeNowNt(); | |
698 | ||||
699 |
7/8✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 7 taken 3 times.
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 1 time.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 1 time.
|
4 | if (absError > jamDetectThreshold && engine->module<IgnitionController>()->getIgnState()) { |
700 |
3/3✓ Branch 2 taken 3 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 2 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 2 times.
|
3 | if (m_jamDetectTimer.hasElapsedSec(jamTimeout)) { |
701 |
1/1✓ Branch 1 taken 1 time.
|
1 | efiPrintf(" ************* ETB is jammed! ***************"); | |
702 | 1 | jamDetected = true; | ||
703 | ||||
704 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
|
1 | getLimpManager()->reportEtbProblem(); | |
705 | } | |||
706 | } else { | |||
707 |
1/1✓ Branch 1 taken 1 time.
|
1 | m_jamDetectTimer.reset(nowNt); | |
708 | 1 | jamDetected = false; | ||
709 | } | |||
710 | ||||
711 |
1/1✓ Branch 2 taken 4 times.
|
4 | jamTimer = m_jamDetectTimer.getElapsedSeconds(nowNt); | |
712 | } | |||
713 | 162 | } | ||
714 | ||||
715 | #if !EFI_UNIT_TEST | |||
716 | /** | |||
717 | * Things running on a timer (instead of a thread) don't participate it the RTOS's thread priority system, | |||
718 | * and operate essentially "first come first serve", which risks starvation. | |||
719 | * Since ETB is a safety critical device, we need the hard RTOS guarantee that it will be scheduled over other less important tasks. | |||
720 | */ | |||
721 | #include "periodic_thread_controller.h" | |||
722 | #endif // EFI_UNIT_TEST | |||
723 | ||||
724 | #include <utility> | |||
725 | ||||
726 | // real implementation (we mock for some unit tests) | |||
727 | EtbImpl<EtbController1> etb1; | |||
728 | EtbImpl<EtbController2> etb2(throttle2TrimTable); | |||
729 | ||||
730 | static EtbController* etbControllers[] = { &etb1, &etb2 }; | |||
731 | static_assert(ETB_COUNT == sizeof(etbControllers) / sizeof(EtbController*)); | |||
732 | ||||
733 | 522954 | void blinkEtbErrorCodes(bool blinkPhase) { | ||
734 |
2/2✓ Branch 0 taken 1045908 times.
✓ Branch 1 taken 522954 times.
|
2/2✓ Decision 'true' taken 1045908 times.
✓ Decision 'false' taken 522954 times.
|
1568862 | for (int i = 0;i<ETB_COUNT;i++) { |
735 | 1045908 | int8_t etbErrorCode = etbControllers[i]->etbErrorCode; | ||
736 |
2/4✓ Branch 0 taken 1045908 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1045908 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1045908 times.
|
1045908 | if (etbErrorCode && engine->etbAutoTune) { |
737 | ✗ | etbErrorCode = (int8_t)EtbStatus::AutoTune; | ||
738 | } | |||
739 |
2/2✓ Branch 0 taken 518662 times.
✓ Branch 1 taken 527246 times.
|
1045908 | etbControllers[i]->etbErrorCodeBlinker = blinkPhase ? 0 : etbErrorCode; | |
740 | } | |||
741 | 522954 | } | ||
742 | ||||
743 | #if !EFI_UNIT_TEST | |||
744 | ||||
745 | struct DcThread final : public PeriodicController<512> { | |||
746 | DcThread() : PeriodicController("DC", PRIO_ETB, ETB_LOOP_FREQUENCY) {} | |||
747 | ||||
748 | void PeriodicTask(efitick_t) override { | |||
749 | // Simply update all controllers | |||
750 | for (int i = 0 ; i < ETB_COUNT; i++) { | |||
751 | auto controller = engine->etbControllers[i]; | |||
752 | assertNotNullVoid(controller); | |||
753 | etbControllers[i]->update(); | |||
754 | } | |||
755 | } | |||
756 | }; | |||
757 | ||||
758 | static DcThread dcThread CCM_OPTIONAL; | |||
759 | ||||
760 | #endif // !EFI_UNIT_TEST | |||
761 | ||||
762 | #if EFI_UNIT_TEST | |||
763 | 4 | void etbPidReset() { | ||
764 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
2/2✓ Decision 'true' taken 8 times.
✓ Decision 'false' taken 4 times.
|
12 | for (int i = 0 ; i < ETB_COUNT; i++) { |
765 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
1/2✓ Decision 'true' taken 8 times.
✗ Decision 'false' not taken.
|
8 | if (auto controller = engine->etbControllers[i]) { |
766 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | assertNotNullVoid(controller); | |
767 | 8 | controller->reset("unit_test"); | ||
768 | } | |||
769 | } | |||
770 | 4 | ebtResetCounter = 0; | ||
771 | } | |||
772 | #endif // EFI_UNIT_TEST | |||
773 | ||||
774 | ✗ | void etbAutocal(dc_function_e function, bool reportToTs) { | ||
775 | ✗ | for (size_t i = 0 ; i < ETB_COUNT; i++) { | ||
776 | /* TODO: use from engine, add getFunction() to base class */ | |||
777 | //if (auto controller = engine->etbControllers[i]) { | |||
778 | ✗ | if (auto controller = etbControllers[i]) { | ||
779 | ✗ | assertNotNullVoid(controller); | ||
780 | ✗ | if (controller->getFunction() == function) { | ||
781 | /* TODO: is it possible that we have several controllers with same function? */ | |||
782 | ✗ | controller->autoCalibrateTps(reportToTs); | ||
783 | // todo fix root cause! work-around: make sure not to write bad tune since that would brick requestBurn(); | |||
784 | } | |||
785 | } | |||
786 | } | |||
787 | } | |||
788 | ||||
789 | ✗ | EtbStatus etbGetState(size_t throttleIndex) | ||
790 | { | |||
791 | ✗ | if (throttleIndex >= ETB_COUNT) { | ||
792 | ✗ | return EtbStatus::NotConfigured; | ||
793 | } | |||
794 | ||||
795 | ✗ | return (EtbStatus)etbControllers[throttleIndex]->etbErrorCode; | ||
796 | } | |||
797 | ||||
798 | /** | |||
799 | * This specific throttle has default position of about 7% open | |||
800 | */ | |||
801 | static const float boschBiasBins[] = { | |||
802 | 0, 1, 5, 7, 14, 65, 66, 100 | |||
803 | }; | |||
804 | static const float boschBiasValues[] = { | |||
805 | -15, -15, -10, 0, 19, 20, 26, 28 | |||
806 | }; | |||
807 | ||||
808 | ✗ | void setBoschVAGETB() { | ||
809 | ✗ | engineConfiguration->tpsMin = 890; // convert 12to10 bit (ADC/4) | ||
810 | ✗ | engineConfiguration->tpsMax = 70; // convert 12to10 bit (ADC/4) | ||
811 | ||||
812 | ✗ | engineConfiguration->tps1SecondaryMin = 102; | ||
813 | ✗ | engineConfiguration->tps1SecondaryMax = 891; | ||
814 | ||||
815 | ✗ | engineConfiguration->etb.pFactor = 5.12; | ||
816 | ✗ | engineConfiguration->etb.iFactor = 47; | ||
817 | ✗ | engineConfiguration->etb.dFactor = 0.088; | ||
818 | ✗ | engineConfiguration->etb.offset = 0; | ||
819 | ✗ | } | ||
820 | ||||
821 | ✗ | void setBoschVNH2SP30Curve() { | ||
822 | ✗ | copyArray(config->etbBiasBins, boschBiasBins); | ||
823 | ✗ | copyArray(config->etbBiasValues, boschBiasValues); | ||
824 | ✗ | } | ||
825 | ||||
826 | 586 | void setDefaultEtbParameters() { | ||
827 | 586 | engineConfiguration->etbIdleThrottleRange = 15; | ||
828 | ||||
829 | 586 | setLinearCurve(config->pedalToTpsPedalBins, /*from*/0, /*to*/100, 1); | ||
830 | 586 | setRpmTableBin(config->pedalToTpsRpmBins); | ||
831 | ||||
832 |
2/2✓ Branch 0 taken 4688 times.
✓ Branch 1 taken 586 times.
|
2/2✓ Decision 'true' taken 4688 times.
✓ Decision 'false' taken 586 times.
|
5274 | for (int pedalIndex = 0;pedalIndex<PEDAL_TO_TPS_SIZE;pedalIndex++) { |
833 |
2/2✓ Branch 0 taken 37504 times.
✓ Branch 1 taken 4688 times.
|
2/2✓ Decision 'true' taken 37504 times.
✓ Decision 'false' taken 4688 times.
|
42192 | for (int rpmIndex = 0;rpmIndex<PEDAL_TO_TPS_RPM_SIZE;rpmIndex++) { |
834 | 37504 | config->pedalToTpsTable[pedalIndex][rpmIndex] = config->pedalToTpsPedalBins[pedalIndex]; | ||
835 | } | |||
836 | } | |||
837 | ||||
838 | // Default is to run each throttle off its respective hbridge | |||
839 | 586 | engineConfiguration->etbFunctions[0] = DC_Throttle1; | ||
840 | 586 | engineConfiguration->etbFunctions[1] = DC_Throttle2; | ||
841 | ||||
842 | 586 | engineConfiguration->etbFreq = DEFAULT_ETB_PWM_FREQUENCY; | ||
843 | ||||
844 | // voltage, not ADC like with TPS | |||
845 | 586 | setPPSCalibration(0, 5, 5, 0); | ||
846 | ||||
847 | 586 | engineConfiguration->etb = { | ||
848 | 1, // Kp | |||
849 | 10, // Ki | |||
850 | 0.05, // Kd | |||
851 | 0, // offset | |||
852 | 0, // Update rate, unused | |||
853 | -100, 100 // min/max | |||
854 | }; | |||
855 | ||||
856 | 586 | engineConfiguration->etb_iTermMin = -30; | ||
857 | 586 | engineConfiguration->etb_iTermMax = 30; | ||
858 | ||||
859 | 586 | engineConfiguration->etbJamDetectThreshold = 10; | ||
860 | // engineConfiguration->etbJamTimeout = 1; | |||
861 | 586 | } | ||
862 | ||||
863 | 219 | void onConfigurationChangeElectronicThrottleCallback(engine_configuration_s *previousConfiguration) { | ||
864 |
2/2✓ Branch 0 taken 438 times.
✓ Branch 1 taken 219 times.
|
2/2✓ Decision 'true' taken 438 times.
✓ Decision 'false' taken 219 times.
|
657 | for (int i = 0; i < ETB_COUNT; i++) { |
865 | 438 | etbControllers[i]->onConfigurationChange(&previousConfiguration->etb); | ||
866 | } | |||
867 | 219 | } | ||
868 | ||||
869 | static const float defaultBiasBins[] = { | |||
870 | 0, 1, 2, 4, 7, 98, 99, 100 | |||
871 | }; | |||
872 | static const float defaultBiasValues[] = { | |||
873 | -20, -18, -17, 0, 20, 21, 22, 25 | |||
874 | }; | |||
875 | ||||
876 | 586 | void setDefaultEtbBiasCurve() { | ||
877 | 586 | copyArray(config->etbBiasBins, defaultBiasBins); | ||
878 | 586 | copyArray(config->etbBiasValues, defaultBiasValues); | ||
879 | 586 | copyArray(config->dcWastegateBiasBins, defaultBiasBins); | ||
880 | 586 | copyArray(config->dcWastegateBiasValues, defaultBiasValues); | ||
881 | 586 | } | ||
882 | ||||
883 | 1385 | void unregisterEtbPins() { | ||
884 | // todo: we probably need an implementation here?! | |||
885 | 1385 | } | ||
886 | ||||
887 | 2058 | static pid_s* getPidForDcFunction(dc_function_e function) { | ||
888 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2056 times.
|
2058 | switch (function) { | |
889 |
1/1✓ Decision 'true' taken 2 times.
|
2 | case DC_Wastegate: return &engineConfiguration->etbWastegatePid; | |
890 |
1/1✓ Decision 'true' taken 2056 times.
|
2056 | default: return &engineConfiguration->etb; | |
891 | } | |||
892 | } | |||
893 | ||||
894 | 2058 | PUBLIC_API_WEAK ValueProvider3D* pedal2TpsProvider() { | ||
895 | 2058 | return &pedal2tpsMap; | ||
896 | } | |||
897 | ||||
898 | 1034 | void doInitElectronicThrottle(bool isStartupInit) { | ||
899 | 1034 | bool anyEtbConfigured = false; | ||
900 | ||||
901 | // todo: technical debt: we still have DC motor code initialization in ETB-specific file while DC motors are used not just as ETB | |||
902 | // like DC motor wastegate code flow should probably NOT go through electronic_throttle.cpp right? | |||
903 | // todo: rename etbFunctions to something-without-etb for same reason? | |||
904 |
2/2✓ Branch 0 taken 2068 times.
✓ Branch 1 taken 1034 times.
|
2/2✓ Decision 'true' taken 2068 times.
✓ Decision 'false' taken 1034 times.
|
3102 | for (int i = 0 ; i < ETB_COUNT; i++) { |
905 | 2068 | auto func = engineConfiguration->etbFunctions[i]; | ||
906 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2058 times.
|
2/2✓ Decision 'true' taken 10 times.
✓ Decision 'false' taken 2058 times.
|
2068 | if (func == DC_None) { |
907 | // do not touch HW pins if function not selected, this way Lua can use DC motor hardware pins directly | |||
908 | 10 | continue; | ||
909 | } | |||
910 | 6174 | auto motor = initDcMotor("ETB disable", | ||
911 | 2058 | engineConfiguration->etbIo[i], i, engineConfiguration->etb_use_two_wires); | ||
912 | ||||
913 | 2058 | auto controller = engine->etbControllers[i]; | ||
914 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2058 times.
|
2058 | criticalAssertVoid(controller != nullptr, "null ETB"); | |
915 | ||||
916 | 2058 | auto pid = getPidForDcFunction(func); | ||
917 | ||||
918 | 2058 | bool dcConfigured = controller->init(func, motor, pid, pedal2TpsProvider()); | ||
919 |
4/4✓ Branch 0 taken 1175 times.
✓ Branch 1 taken 883 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 1166 times.
|
2/2✓ Decision 'true' taken 9 times.
✓ Decision 'false' taken 2049 times.
|
2058 | if (isStartupInit && dcConfigured) { |
920 | 9 | controller->reset("init"); | ||
921 | } | |||
922 |
4/4✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2043 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 2 times.
|
2058 | anyEtbConfigured |= dcConfigured && controller->isEtbMode(); | |
923 | } | |||
924 | ||||
925 | // It's not valid to have a PPS without any ETBs - check that at least one ETB was enabled along with the pedal | |||
926 |
6/6✓ Branch 0 taken 1023 times.
✓ Branch 1 taken 11 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1022 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1033 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1033 times.
|
1034 | if (!anyEtbConfigured && Sensor::hasSensor(SensorType::AcceleratorPedalPrimary)) { |
927 | 1 | criticalError("A pedal position sensor was configured, but no electronic throttles are configured."); | ||
928 | } | |||
929 | ||||
930 | #if 0 && ! EFI_UNIT_TEST | |||
931 | percent_t startupThrottlePosition = getTPS(); | |||
932 | if (std::abs(startupThrottlePosition - engineConfiguration->etbNeutralPosition) > STARTUP_NEUTRAL_POSITION_ERROR_THRESHOLD) { | |||
933 | /** | |||
934 | * Unexpected electronic throttle start-up position is worth a critical error | |||
935 | */ | |||
936 | firmwareError(ObdCode::OBD_Throttle_Actuator_Control_Range_Performance_Bank_1, "startup ETB position %.2f not %d", | |||
937 | startupThrottlePosition, | |||
938 | engineConfiguration->etbNeutralPosition); | |||
939 | startupPositionError = true; | |||
940 | } | |||
941 | #endif /* EFI_UNIT_TEST */ | |||
942 | ||||
943 | #if !EFI_UNIT_TEST | |||
944 | static bool started = false; | |||
945 | if (started == false) { | |||
946 | dcThread.start(); | |||
947 | started = true; | |||
948 | } | |||
949 | #endif | |||
950 | } | |||
951 | ||||
952 | 585 | void initElectronicThrottle() { | ||
953 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 585 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 585 times.
|
585 | if (hasFirmwareError()) { |
954 | ✗ | return; | ||
955 | } | |||
956 | ||||
957 |
2/2✓ Branch 0 taken 1170 times.
✓ Branch 1 taken 585 times.
|
2/2✓ Decision 'true' taken 1170 times.
✓ Decision 'false' taken 585 times.
|
1755 | for (int i = 0; i < ETB_COUNT; i++) { |
958 | 1170 | engine->etbControllers[i] = etbControllers[i]; | ||
959 | } | |||
960 | ||||
961 | #if EFI_PROD_CODE | |||
962 | addConsoleAction("etbautocal", [](){ | |||
963 | efiPrintf("etbAutocal invoked"); | |||
964 | etbAutocal(DC_Throttle1); | |||
965 | }); | |||
966 | ||||
967 | addConsoleAction("etbinfo", [](){ | |||
968 | efiPrintf("etbAutoTune=%d", engine->etbAutoTune); | |||
969 | efiPrintf("TPS=%.2f", Sensor::getOrZero(SensorType::Tps1)); | |||
970 | ||||
971 | efiPrintf("ETB1 duty=%.2f", | |||
972 | (float)engine->outputChannels.etb1DutyCycle); | |||
973 | ||||
974 | efiPrintf("ETB freq=%d", | |||
975 | engineConfiguration->etbFreq); | |||
976 | ||||
977 | for (int i = 0; i < ETB_COUNT; i++) { | |||
978 | efiPrintf("ETB%d", i); | |||
979 | efiPrintf(" dir1=%s", hwPortname(engineConfiguration->etbIo[i].directionPin1)); | |||
980 | efiPrintf(" dir2=%s", hwPortname(engineConfiguration->etbIo[i].directionPin2)); | |||
981 | efiPrintf(" control=%s", hwPortname(engineConfiguration->etbIo[i].controlPin)); | |||
982 | efiPrintf(" disable=%s", hwPortname(engineConfiguration->etbIo[i].disablePin)); | |||
983 | showDcMotorInfo(i); | |||
984 | } | |||
985 | }); | |||
986 | ||||
987 | #endif /* EFI_PROD_CODE */ | |||
988 | ||||
989 | 585 | pedal2tpsMap.initTable(config->pedalToTpsTable, config->pedalToTpsRpmBins, config->pedalToTpsPedalBins); | ||
990 | 585 | throttle2TrimTable.initTable(config->throttle2TrimTable, config->throttle2TrimRpmBins, config->throttle2TrimTpsBins); | ||
991 | 585 | tcEtbDropTable.initTable(engineConfiguration->tractionControlEtbDrop, engineConfiguration->tractionControlSlipBins, engineConfiguration->tractionControlSpeedBins); | ||
992 | ||||
993 | 585 | doInitElectronicThrottle(/*isStartupInit*/true); | ||
994 | } | |||
995 | ||||
996 | 1121 | void setEtbIdlePosition(percent_t pos) { | ||
997 |
2/2✓ Branch 0 taken 2242 times.
✓ Branch 1 taken 1121 times.
|
2/2✓ Decision 'true' taken 2242 times.
✓ Decision 'false' taken 1121 times.
|
3363 | for (int i = 0; i < ETB_COUNT; i++) { |
998 |
1/2✓ Branch 1 taken 2242 times.
✗ Branch 2 not taken.
|
1/2✓ Decision 'true' taken 2242 times.
✗ Decision 'false' not taken.
|
2242 | if (auto etb = engine->etbControllers[i]) { |
999 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2242 times.
|
2242 | assertNotNullVoid(etb); | |
1000 | 2242 | etb->setIdlePosition(pos); | ||
1001 | } | |||
1002 | } | |||
1003 | } | |||
1004 | ||||
1005 | 1123 | void setEtbWastegatePosition(percent_t pos) { | ||
1006 |
2/2✓ Branch 0 taken 2246 times.
✓ Branch 1 taken 1123 times.
|
2/2✓ Decision 'true' taken 2246 times.
✓ Decision 'false' taken 1123 times.
|
3369 | for (int i = 0; i < ETB_COUNT; i++) { |
1007 |
1/2✓ Branch 1 taken 2246 times.
✗ Branch 2 not taken.
|
1/2✓ Decision 'true' taken 2246 times.
✗ Decision 'false' not taken.
|
2246 | if (auto etb = engine->etbControllers[i]) { |
1008 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2246 times.
|
2246 | assertNotNullVoid(etb); | |
1009 | 2246 | etb->setWastegatePosition(pos); | ||
1010 | } | |||
1011 | } | |||
1012 | } | |||
1013 | ||||
1014 | ✗ | void setEtbLuaAdjustment(percent_t pos) { | ||
1015 | ✗ | for (int i = 0; i < ETB_COUNT; i++) { | ||
1016 | /* TODO: use from engine, add getFunction() to base class */ | |||
1017 | //if (auto etb = engine->etbControllers[i]) { | |||
1018 | ✗ | if (auto etb = etbControllers[i]) { | ||
1019 | ✗ | assertNotNullVoid(etb); | ||
1020 | // try to adjust all ETB | |||
1021 | ✗ | if (etb->getFunction() == DC_Throttle1 || etb->getFunction() == DC_Throttle2) { | ||
1022 | ✗ | etb->setLuaAdjustment(pos); | ||
1023 | } | |||
1024 | } | |||
1025 | } | |||
1026 | } | |||
1027 | ||||
1028 | ✗ | void setEwgLuaAdjustment(percent_t pos) { | ||
1029 | ✗ | for (int i = 0; i < ETB_COUNT; i++) { | ||
1030 | /* TODO: use from engine, add getFunction() to base class */ | |||
1031 | //if (auto etb = engine->etbControllers[i]) { | |||
1032 | ✗ | if (auto etb = etbControllers[i]) { | ||
1033 | ✗ | assertNotNullVoid(etb); | ||
1034 | // try to adjust all ETB | |||
1035 | ✗ | if (etb->getFunction() == DC_Wastegate) { | ||
1036 | ✗ | etb->setLuaAdjustment(pos); | ||
1037 | } | |||
1038 | } | |||
1039 | } | |||
1040 | } | |||
1041 | ||||
1042 | 1 | void setToyota89281_33010_pedal_position_sensor() { | ||
1043 | 1 | setPPSCalibration(0, 4.1, 0.73, 4.9); | ||
1044 | 1 | } | ||
1045 | ||||
1046 | 1 | void setHitachiEtbCalibration() { | ||
1047 | 1 | setToyota89281_33010_pedal_position_sensor(); | ||
1048 | ||||
1049 | 1 | setHitachiEtbBiasBins(); | ||
1050 | ||||
1051 | 1 | engineConfiguration->etb.pFactor = 2.7999; | ||
1052 | 1 | engineConfiguration->etb.iFactor = 25.5; | ||
1053 | 1 | engineConfiguration->etb.dFactor = 0.053; | ||
1054 | 1 | engineConfiguration->etb.offset = 0.0; | ||
1055 | 1 | engineConfiguration->etb.periodMs = 5.0; | ||
1056 | 1 | engineConfiguration->etb.minValue = -100.0; | ||
1057 | 1 | engineConfiguration->etb.maxValue = 100.0; | ||
1058 | ||||
1059 | // Nissan 60mm throttle | |||
1060 | 1 | engineConfiguration->tpsMin = engineConfiguration->tps2Min = 113; | ||
1061 | 1 | engineConfiguration->tpsMax = engineConfiguration->tps2Max = 846; | ||
1062 | 1 | engineConfiguration->tps1SecondaryMin = engineConfiguration->tps2SecondaryMin = 897; | ||
1063 | 1 | engineConfiguration->tps1SecondaryMax = engineConfiguration->tps2SecondaryMax = 161; | ||
1064 | 1 | } | ||
1065 | ||||
1066 | 1 | void setProteusHitachiEtbDefaults() { | ||
1067 | #if HW_PROTEUS | |||
1068 | 1 | setHitachiEtbCalibration(); | ||
1069 | ||||
1070 | // EFI_ADC_12: "Analog Volt 3" | |||
1071 | 1 | engineConfiguration->tps1_2AdcChannel = PROTEUS_IN_TPS1_2; | ||
1072 | // EFI_ADC_13: "Analog Volt 4" | |||
1073 | 1 | engineConfiguration->tps2_1AdcChannel = PROTEUS_IN_TPS2_1; | ||
1074 | // EFI_ADC_0: "Analog Volt 5" | |||
1075 | 1 | engineConfiguration->tps2_2AdcChannel = PROTEUS_IN_ANALOG_VOLT_5; | ||
1076 | 1 | setPPSInputs(PROTEUS_IN_ANALOG_VOLT_6, PROTEUS_IN_PPS2); | ||
1077 | #endif // HW_PROTEUS | |||
1078 | 1 | } | ||
1079 | ||||
1080 | #endif /* EFI_ELECTRONIC_THROTTLE_BODY */ | |||
1081 | ||||
1082 | // So far used by FragmentEntry (LiveData) | |||
1083 | template<> | |||
1084 | ✗ | const electronic_throttle_s* getLiveData(size_t idx) { | ||
1085 | #if EFI_ELECTRONIC_THROTTLE_BODY | |||
1086 | ✗ | if (idx >= efi::size(etbControllers)) { | ||
1087 | ✗ | return nullptr; | ||
1088 | } | |||
1089 | ||||
1090 | ✗ | return etbControllers[idx]; | ||
1091 | #else | |||
1092 | return nullptr; | |||
1093 | #endif | |||
1094 | } | |||
1095 |