GCC Code Coverage Report


Directory: ./
File: firmware/controllers/actuators/electronic_throttle.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 69.6% 330 0 474
Functions: 72.7% 40 0 55
Branches: 59.5% 175 0 294
Decisions: 57.4% 105 - 183

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