GCC Code Coverage Report


Directory: ./
File: firmware/controllers/actuators/electronic_throttle_impl.h
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 32.5% 13 0 40
Functions: 46.2% 6 0 13
Branches: 17.9% 5 0 28
Decisions: -% 0 - 0

Line Branch Decision Exec Source
1 /**
2 * @file electronic_throttle_impl.h
3 *
4 * @date Dec 7, 2013
5 * @author Andrey Belomutskiy, (c) 2012-2020
6 */
7
8 #pragma once
9
10 // include the "public" ETB interface
11 #include "electronic_throttle.h"
12
13 #include "sensor.h"
14 #include "efi_pid.h"
15 #include "error_accumulator.h"
16 #include "electronic_throttle_generated.h"
17 #include "tunerstudio_calibration_channel.h"
18
19 /**
20 * Hard code ETB update speed.
21 * Since this is a safety critical system with no real reason for a user to ever need to change the update rate,
22 * it's locked to 500hz, along with the ADC.
23 * https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem
24 */
25 #define ETB_LOOP_FREQUENCY 500
26 #define DEFAULT_ETB_PWM_FREQUENCY 800
27
28 class EtbController : public IEtbController, public electronic_throttle_s {
29 public:
30 bool init(dc_function_e function, DcMotor *motor, pid_s *pidParameters, const ValueProvider3D* pedalMap) override;
31 void setIdlePosition(percent_t pos) override;
32 void setWastegatePosition(percent_t pos) override;
33 void reset(const char *reason) override;
34
35 // Update the controller's state: read sensors, send output, etc
36 void update() override;
37
38 // Called when the configuration may have changed. Controller will
39 // reset if necessary.
40 void onConfigurationChange(pid_s* previousConfiguration);
41
42 // Print this throttle's status.
43 void showStatus();
44
45 // Helpers for individual parts of throttle control
46 expected<percent_t> observePlant() override;
47
48 expected<percent_t> getSetpoint() override;
49 expected<percent_t> getSetpointEtb();
50 expected<percent_t> getSetpointWastegate() const;
51 expected<percent_t> getSetpointIdleValve() const;
52
53 expected<percent_t> getOpenLoop(percent_t target) override;
54 expected<percent_t> getClosedLoop(percent_t setpoint, percent_t observation) override;
55 expected<percent_t> getClosedLoopAutotune(percent_t setpoint, percent_t actualThrottlePosition);
56
57 dc_function_e getFunction() const { return m_function; }
58
59 void checkJam(percent_t setpoint, percent_t observation);
60
61 void setOutput(expected<percent_t> outputValue) override;
62
63 // Used to inspect the internal PID controller's state
64 const pid_state_s& getPidState() const override { return m_pid; };
65
66 // Override if this throttle needs special per-throttle adjustment (bank-to-bank trim, for example)
67 242 virtual percent_t getThrottleTrim(float /*rpm*/, percent_t /*targetPosition*/) const {
68 242 return 0;
69 }
70
71 // pedal-based part of ETB target, without idle and other interventions
72 float getCurrentTarget() const override {
73 return etbCurrentTarget;
74 }
75
76 2711 bool isEtbMode() const override {
77
4/4
✓ Branch 0 taken 1034 times.
✓ Branch 1 taken 1677 times.
✓ Branch 2 taken 1026 times.
✓ Branch 3 taken 8 times.
2711 return m_function == DC_Throttle1 || m_function == DC_Throttle2;
78 }
79
80 // Lua throttle adjustment
81 void setLuaAdjustment(percent_t adjustment) override;
82 float getLuaAdjustment() const;
83
84 float prevOutput = 0;
85
86 protected:
87 bool hadTpsError = false;
88 bool hadPpsError = false;
89
90 DcMotor* getMotor() { return m_motor; }
91
92 private:
93 dc_function_e m_function = DC_None;
94 SensorType m_positionSensor = SensorType::Invalid;
95 DcMotor *m_motor = nullptr;
96 Pid m_pid;
97 bool m_shouldResetPid = false;
98
99 ErrorAccumulator m_targetErrorAccumulator;
100
101 /**
102 * @return true if OK, false if should be disabled
103 */
104 bool checkStatus();
105
106 Timer m_jamDetectTimer;
107
108 // Pedal -> target map
109 const ValueProvider3D* m_pedalProvider = nullptr;
110
111 float m_idlePosition = 0;
112
113 // This is set if automatic PID cal should be run
114 bool m_isAutotune = false;
115
116 // Autotune helpers
117 bool m_lastIsPositive = false;
118 Timer m_cycleTimer;
119 float m_minCycleTps = 0;
120 float m_maxCycleTps = 0;
121 // Autotune measured parameters: gain and ultimate period
122 // These are set to correct order of magnitude starting points
123 // so we converge more quickly on the correct values
124 float m_a = 8;
125 float m_tu = 0.1f;
126
127 #if EFI_TUNER_STUDIO
128 uint8_t m_autotuneCounter = 0;
129 uint8_t m_autotuneCurrentParam = 0;
130 #endif
131
132 Timer m_luaAdjustmentTimer;
133
134 efitimeus_t lastTickUs;
135 };
136
137 void etbPidReset();
138
139 class EtbController1 : public EtbController { };
140
141 class EtbController2 : public EtbController {
142 public:
143 2 EtbController2(const ValueProvider3D& throttle2TrimTable)
144 2 : m_throttle2Trim(throttle2TrimTable)
145 {
146 2 }
147
148 percent_t getThrottleTrim(float rpm, percent_t /*targetPosition*/) const override;
149
150 private:
151 const ValueProvider3D& m_throttle2Trim;
152 };
153
154 template <typename TBase>
155 class EtbImpl final : public TBase {
156 private:
157 enum class ACPhase {
158 Stopped,
159
160 Start,
161
162 // Drive the motor open
163 Open,
164
165 // Drive the motor closed
166 Close,
167
168 // Write learned values to TS
169 TransmitPrimaryMax,
170 TransmitPrimaryMin,
171 TransmitSecondaryMax,
172 TransmitSecondaryMin,
173 };
174
175 public:
176 template <typename... TArgs>
177 2 EtbImpl(TArgs&&... args) : TBase(std::forward<TArgs>(args)...) { }
178
179 206 void update() override {
180 #if EFI_TUNER_STUDIO
181
1/4
EtbImpl<EtbController1>::update():
✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
EtbImpl<EtbController2>::update():
✗ Branch 0 not taken.
✗ Branch 1 not taken.
206 if (m_autocalPhase != ACPhase::Stopped) {
182 ACPhase nextPhase = doAutocal(m_autocalPhase);
183
184 // if we changed phase, reset the phase timer
185 if (m_autocalPhase != nextPhase) {
186 m_autocalTimer.reset();
187 m_autocalPhase = nextPhase;
188 }
189 } else
190 #endif /* EFI_TUNER_STUDIO */
191
192 {
193 206 TBase::update();
194 }
195 206 }
196
197 void autoCalibrateTps(bool reportToTs) override {
198 // Only auto calibrate throttles
199 if (TBase::getFunction() == DC_Throttle1 || TBase::getFunction() == DC_Throttle2 || TBase::getFunction() == DC_Wastegate) {
200 m_isAutocalTs = reportToTs;
201 m_autocalPhase = ACPhase::Start;
202 }
203 }
204
205 ACPhase doAutocal(ACPhase phase) {
206 // Don't allow if engine is running!
207 if (Sensor::getOrZero(SensorType::Rpm) > 0) {
208 efiPrintf(" ****************** ERROR: Not while RPM ********************");
209 return ACPhase::Stopped;
210 }
211
212 auto motor = TBase::getMotor();
213 if (!motor) {
214 efiPrintf(" ****************** ERROR: No DC motor ********************");
215 return ACPhase::Stopped;
216 }
217
218 TBase::etbErrorCode = (uint8_t)EtbStatus::AutoCalibrate;
219
220 auto myFunction = TBase::getFunction();
221
222 switch (phase) {
223 case ACPhase::Start:
224 // Open the throttle
225 motor->set(0.5f);
226 motor->enable();
227 return ACPhase::Open;
228 case ACPhase::Open:
229 if (m_autocalTimer.hasElapsedMs(1000)) {
230 // Capture open position
231 m_primaryMax = Sensor::getRaw(functionToTpsSensorPrimary(myFunction));
232 m_secondaryMax = Sensor::getRaw(functionToTpsSensorSecondary(myFunction));
233
234 // Next: close the throttle
235 motor->set(-0.5f);
236 return ACPhase::Close;
237 }
238 break;
239 case ACPhase::Close:
240 if (m_autocalTimer.hasElapsedMs(1000)) {
241 // Capture closed position
242 m_primaryMin = Sensor::getRaw(functionToTpsSensorPrimary(myFunction));
243 m_secondaryMin = Sensor::getRaw(functionToTpsSensorSecondary(myFunction));
244
245 // Disable the motor, we're done
246 motor->disable("autotune");
247
248 // Check that the calibrate actually moved the throttle
249 if (std::abs(m_primaryMax - m_primaryMin) < 0.5f) {
250 firmwareError(ObdCode::OBD_TPS_Configuration, "Auto calibrate failed, check your wiring!\r\nClosed voltage: %.1fv Open voltage: %.1fv", m_primaryMin, m_primaryMax);
251 return ACPhase::Stopped;
252 }
253
254 if (!m_isAutocalTs) {
255 if (myFunction == DC_Throttle1) {
256 engineConfiguration->tpsMin = convertVoltageTo10bitADC(m_primaryMin);
257 engineConfiguration->tpsMax = convertVoltageTo10bitADC(m_primaryMax);
258 engineConfiguration->tps1SecondaryMin = convertVoltageTo10bitADC(m_secondaryMin);
259 engineConfiguration->tps1SecondaryMax = convertVoltageTo10bitADC(m_secondaryMax);
260 } else if (myFunction == DC_Throttle2) {
261 engineConfiguration->tps2Min = convertVoltageTo10bitADC(m_primaryMin);
262 engineConfiguration->tps2Max = convertVoltageTo10bitADC(m_primaryMax);
263 engineConfiguration->tps2SecondaryMin = convertVoltageTo10bitADC(m_secondaryMin);
264 engineConfiguration->tps2SecondaryMax = convertVoltageTo10bitADC(m_secondaryMax);
265 } else if (myFunction == DC_Wastegate) {
266 engineConfiguration->wastegatePositionClosedVoltage = m_primaryMin;
267 engineConfiguration->wastegatePositionOpenedVoltage = m_primaryMax;
268 } else {
269 /* TODO */
270 }
271 return ACPhase::Stopped;
272 }
273
274 // Next: start transmitting results
275 tsCalibrationSetData(functionToCalModePriMax(myFunction), m_primaryMax);
276 return ACPhase::TransmitPrimaryMax;
277 }
278 break;
279 case ACPhase::TransmitPrimaryMax:
280 if (tsCalibrationIsIdle()) {
281 tsCalibrationSetData(functionToCalModePriMin(myFunction), m_primaryMin);
282 return ACPhase::TransmitPrimaryMin;
283 }
284 break;
285 case ACPhase::TransmitPrimaryMin:
286 if (tsCalibrationIsIdle()) {
287 tsCalibrationSetData(functionToCalModeSecMax(myFunction), m_secondaryMax);
288 // No secondary sensor?
289 if (tsCalibrationIsIdle())
290 return ACPhase::Stopped;
291 return ACPhase::TransmitSecondaryMax;
292 }
293 break;
294 case ACPhase::TransmitSecondaryMax:
295 if (tsCalibrationIsIdle()) {
296 tsCalibrationSetData(functionToCalModeSecMin(myFunction), m_secondaryMin);
297 return ACPhase::TransmitSecondaryMin;
298 }
299 break;
300 case ACPhase::TransmitSecondaryMin:
301 if (tsCalibrationIsIdle()) {
302 // Done!
303 return ACPhase::Stopped;
304 }
305 break;
306 case ACPhase::Stopped: break;
307 }
308
309 // by default, stay in the same phase
310 return phase;
311 }
312
313 private:
314 ACPhase m_autocalPhase = ACPhase::Stopped;
315 Timer m_autocalTimer;
316 // Report calibrated values to TS, if false - set directly to config
317 bool m_isAutocalTs;
318
319 float m_primaryMax;
320 float m_secondaryMax;
321 float m_primaryMin;
322 float m_secondaryMin;
323 };
324
325 extern EtbImpl<EtbController1> etb1;
326 extern EtbImpl<EtbController2> etb2;
327
328 static constexpr electronic_throttle_s const* etbData1_ptr = &etb1;
329 static constexpr electronic_throttle_s const* etbData2_ptr = &etb2;
330
331 // To be used by LogFields, for LiveData fragments see non constexpr getLiveData() in electronic_throttle.cpp
332 template<typename T, size_t idx>
333 consteval electronic_throttle_s const* getLiveDataConstexpr() requires std::is_same_v<T, electronic_throttle_s> {
334 #if EFI_ELECTRONIC_THROTTLE_BODY
335
336 static_assert(idx < ETB_COUNT);
337
338 if constexpr (idx == 0) {
339 return etbData1_ptr;
340 }
341
342 return etbData2_ptr;
343 #else
344 return nullptr;
345 #endif
346 }
347