GCC Code Coverage Report


Directory: ./
File: firmware/hw_layer/digital_input/trigger/trigger_input_adc.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 60.7% 74 0 122
Functions: 50.0% 4 0 8
Branches: 48.3% 29 0 60
Decisions: 47.8% 22 - 46

Line Branch Decision Exec Source
1 /**
2 * @file trigger_input_adc.cpp
3 * @brief Position sensor hardware layer, Using ADC and software comparator
4 *
5 * @date Jan 27, 2020
6 * @author andreika <prometheus.pcb@gmail.com>
7 * @author Andrey Belomutskiy, (c) 2012-2020
8 */
9
10 #include "pch.h"
11 #include "trigger_input_adc.h"
12
13
14 /*static*/ TriggerAdcDetector trigAdcState;
15
16 #define DELTA_THRESHOLD_CNT_LOW (GPT_FREQ_FAST / GPT_PERIOD_FAST / 32) // ~1/32 second?
17 #define DELTA_THRESHOLD_CNT_HIGH (GPT_FREQ_FAST / GPT_PERIOD_FAST / 4) // ~1/4 second?
18
19 #if HAL_USE_ADC || EFI_UNIT_TEST
20 #define triggerVoltsToAdcDivided(volts) (voltsToAdc(volts) / trigAdcState.triggerInputDividerCoefficient)
21 #endif // HAL_USE_ADC || EFI_UNIT_TEST
22
23 // hardware-dependent part
24 #if (EFI_SHAFT_POSITION_INPUT && HAL_TRIGGER_USE_ADC && HAL_USE_ADC) || defined(__DOXYGEN__)
25
26 #include "digital_input_exti.h"
27
28 #ifndef TRIGGER_ADC_DEBUG_LED
29 #define TRIGGER_ADC_DEBUG_LED FALSE
30 #endif
31 //#define DEBUG_OUTPUT_IGN1 TRUE
32 //#define TRIGGER_ADC_DUMP_BUF TRUE
33
34 #ifdef TRIGGER_ADC_DEBUG_LED
35 #define TRIGGER_ADC_DEBUG_LED1_PORT GPIOH
36 #define TRIGGER_ADC_DEBUG_LED1_PIN 9
37
38 #ifdef TRIGGER_ADC_DUMP_BUF
39 static const int dumpBufNum = 100;
40 static triggerAdcSample_t dumpBuf[dumpBufNum];
41 static int dumpBufCnt = 0;
42 #endif /* TRIGGER_ADC_DUMP_BUF */
43
44 void toggleLed(int led, int mode) {
45 #if 1
46 static uint8_t st[5] = { 0 };
47 if ((st[led] == 0 && mode == 0) || mode == 1) {
48 palClearPad(TRIGGER_ADC_DEBUG_LED1_PORT, TRIGGER_ADC_DEBUG_LED1_PIN);
49 #ifdef DEBUG_OUTPUT_IGN1
50 palClearPad(GPIOI, 8);
51 #endif
52 }
53 else if ((st[led] != 0 && mode == 0) || mode == -1) {
54 palSetPad(TRIGGER_ADC_DEBUG_LED1_PORT, TRIGGER_ADC_DEBUG_LED1_PIN);
55 #ifdef DEBUG_OUTPUT_IGN1
56 palSetPad(GPIOI, 8);
57 #endif
58 }
59 st[led] = (st[led] + 1) % 2/*10*/; //!!!!!!!!!!!
60 #endif
61 }
62 #endif /* TRIGGER_ADC_DEBUG_LED */
63
64 // used for fast pin mode switching between ADC and EXTINT
65 static ioportid_t triggerInputPort;
66 static ioportmask_t triggerInputPin;
67
68 #ifndef PAL_MODE_EXTINT
69 #define PAL_MODE_EXTINT PAL_MODE_INPUT
70 #endif /* PAL_MODE_EXTINT */
71
72 void setTriggerAdcMode(triggerAdcMode_t adcMode) {
73 trigAdcState.curAdcMode = adcMode;
74 trigAdcState.modeSwitchCnt++;
75
76 palSetPadMode(triggerInputPort, triggerInputPin,
77 (adcMode == TRIGGER_ADC_ADC) ? PAL_MODE_INPUT_ANALOG : PAL_MODE_EXTINT);
78 }
79
80 static void shaft_callback(void *arg, efitick_t stamp) {
81 // do the time sensitive things as early as possible!
82 ioline_t pal_line = (ioline_t)arg;
83 bool rise = (palReadLine(pal_line) == PAL_HIGH);
84
85 trigAdcState.digitalCallback(stamp, true, rise);
86 }
87
88 static void cam_callback(void *, efitick_t stamp) {
89 // TODO: implement...
90 }
91
92 void triggerAdcCallback(triggerAdcSample_t value) {
93 efitick_t stamp = getTimeNowNt();
94 trigAdcState.analogCallback(stamp, value);
95 }
96
97 #ifdef TRIGGER_ADC_DUMP_BUF
98 static void printDumpBuf(void) {
99 efiPrintf("------");
100 for (int i = 0; i < dumpBufNum; i++) {
101 int pos = (dumpBufCnt - i - 1 + dumpBufNum) % dumpBufNum;
102 triggerAdcSample_t v = dumpBuf[pos];
103 efiPrintf("[%d] %d", i, v);
104 }
105 }
106 #endif /* TRIGGER_ADC_DUMP_BUF */
107
108
109 int adcTriggerTurnOnInputPin(const char *msg, int index, bool isTriggerShaft) {
110 brain_pin_e brainPin = isTriggerShaft ?
111 engineConfiguration->triggerInputPins[index] : engineConfiguration->camInputs[index];
112
113 trigAdcState.init();
114
115 triggerInputPort = getHwPort("trg", brainPin);
116 triggerInputPin = getHwPin("trg", brainPin);
117
118 ioline_t pal_line = PAL_LINE(triggerInputPort, triggerInputPin);
119 efiPrintf("turnOnTriggerInputPin %s l=%ld", hwPortname(brainPin), pal_line);
120
121 if (efiExtiEnablePin(msg, brainPin, PAL_EVENT_MODE_BOTH_EDGES,
122 isTriggerShaft ? shaft_callback : cam_callback, (void *)pal_line) < 0) {
123 return -1;
124 }
125
126 // ADC mode is default, because we don't know if the wheel is already spinning
127 setTriggerAdcMode(TRIGGER_ADC_ADC);
128
129 #ifdef TRIGGER_ADC_DEBUG_LED
130 palSetPadMode(TRIGGER_ADC_DEBUG_LED1_PORT, TRIGGER_ADC_DEBUG_LED1_PIN, PAL_MODE_OUTPUT_PUSHPULL);
131 #ifdef DEBUG_OUTPUT_IGN1
132 palSetPadMode(GPIOI, 8, PAL_MODE_OUTPUT_PUSHPULL);
133 #endif
134 #endif /* TRIGGER_ADC_DEBUG_LED */
135
136 #ifdef TRIGGER_ADC_DUMP_BUF
137 addConsoleAction("trigger_adc_dump", printDumpBuf);
138 #endif /* TRIGGER_ADC_DUMP_BUF */
139
140 return 0;
141 }
142
143 void adcTriggerTurnOffInputPin(brain_pin_e brainPin) {
144 efiExtiDisablePin(brainPin);
145 }
146
147 void adcTriggerTurnOnInputPins() {
148 }
149
150 adc_channel_e getAdcChannelForTrigger(void) {
151 // todo: add other trigger or cam channels?
152 brain_pin_e brainPin = engineConfiguration->triggerInputPins[0];
153 if (!isBrainPinValid(brainPin))
154 return EFI_ADC_NONE;
155 return getAdcChannel(brainPin);
156 }
157
158 void addAdcChannelForTrigger(void) {
159 adc_channel_e channel = getAdcChannelForTrigger();
160 if (isAdcChannelValid(channel)) {
161 addFastAdcChannel("TRIG", channel);
162 }
163 }
164
165 void onTriggerChanged(efitick_t stamp, bool isPrimary, bool isRising) {
166 #ifdef TRIGGER_ADC_DEBUG_LED
167 toggleLed(0, 0);
168 #endif /* TRIGGER_ADC_DEBUG_LED */
169
170 #if 1
171 // todo: support for 3rd trigger input channel
172 // todo: start using real event time from HW event, not just software timer?
173
174 // call the main trigger handler
175 hwHandleShaftSignal(isPrimary ? 0 : 1, isRising, stamp);
176 #endif // 1
177 }
178
179 #endif // EFI_SHAFT_POSITION_INPUT && HAL_TRIGGER_USE_ADC && HAL_USE_ADC
180
181
182 1 void TriggerAdcDetector::init() {
183 #if ! EFI_SIMULATOR
184
185 // todo: move some of these to config
186
187 #if HAL_USE_ADC || EFI_UNIT_TEST
188 // 4.7k||5.1k + 4.7k
189 1 triggerInputDividerCoefficient = 1.52f; // = analogInputDividerCoefficient
190
191 // we need to make at least minNumAdcMeasurementsPerTooth for 1 tooth (i.e. between two consequent events)
192 1 const int minNumAdcMeasurementsPerTooth = 10; // for 60-2 wheel: 1/(10*2*60/10000/60) = 500 RPM
193 1 minDeltaTimeForStableAdcDetectionNt = US2NT(US_PER_SECOND_LL * minNumAdcMeasurementsPerTooth * GPT_PERIOD_FAST / GPT_FREQ_FAST);
194 // we assume that the transition occurs somewhere in the middle of the measurement period, so we take the half of it
195 1 stampCorrectionForAdc = US2NT(US_PER_SECOND_LL * GPT_PERIOD_FAST / GPT_FREQ_FAST / 2);
196
197 1 analogToDigitalTransitionCnt = 4;
198 1 digitalToAnalogTransitionCnt = 4;
199
200 // used to filter out low signals
201 1 minDeltaThresholdWeakSignal = triggerVoltsToAdcDivided(0.05f); // 50mV
202 // we need to shift the default threshold even for strong signals because of the possible loss of the first tooth (after the sync)
203 1 minDeltaThresholdStrongSignal = triggerVoltsToAdcDivided(0.04f); // 5mV
204
205 1 const triggerAdcSample_t adcDeltaThreshold = triggerVoltsToAdcDivided(0.25f);
206 1 adcDefaultThreshold = triggerVoltsToAdcDivided(2.5f); // this corresponds to VREF1 on Hellen boards
207 1 adcMinThreshold = adcDefaultThreshold - adcDeltaThreshold;
208 1 adcMaxThreshold = adcDefaultThreshold + adcDeltaThreshold;
209
210 // these thresholds allow to switch from ADC mode to EXTI mode, indicating the clamping of the signal
211 // they should exceed the MCU schmitt trigger thresholds (usually 0.3*Vdd and 0.7*Vdd)
212 1 switchingThresholdLow = triggerVoltsToAdcDivided(1.0f); // = 0.2*Vdd (<0.3*Vdd)
213 1 switchingThresholdHigh = triggerVoltsToAdcDivided(4.0f); // = 0.8*Vdd (>0.7*Vdd)
214 #endif // HAL_USE_ADC || EFI_UNIT_TEST
215
216 1 modeSwitchCnt = 0;
217
218 1 reset();
219 #endif // ! EFI_SIMULATOR
220 1 }
221
222 1 void TriggerAdcDetector::reset() {
223 1 switchingCnt = 0;
224 1 switchingTeethCnt = 0;
225 #if HAL_USE_ADC || EFI_UNIT_TEST
226 // when the strong signal becomes weak, we want to ignore the increased noise
227 // so we create a dead-zone between the pos. and neg. thresholds
228 1 zeroThreshold = minDeltaThresholdWeakSignal / 2;
229 1 triggerAdcITerm = triggerAdcITermMin;
230
231 1 adcThreshold = adcDefaultThreshold;
232
233 1 isSignalWeak = true;
234 1 integralSum = 0;
235 1 transitionCooldownCnt = 0;
236 1 minDeltaThresholdCntPos = 0;
237 1 minDeltaThresholdCntNeg = 0;
238 #endif // HAL_USE_ADC || EFI_UNIT_TEST
239
240 1 prevValue = 0; // not set
241 1 prevStamp = 0;
242 1 }
243
244 void TriggerAdcDetector::digitalCallback(efitick_t stamp, bool isPrimary, bool rise) {
245 #if !EFI_SIMULATOR && EFI_SHAFT_POSITION_INPUT
246 if (curAdcMode != TRIGGER_ADC_EXTI) {
247 return;
248 }
249
250 UNUSED(isPrimary);
251
252 onTriggerChanged(stamp, isPrimary, rise);
253
254 #if (HAL_TRIGGER_USE_ADC && HAL_USE_ADC) || EFI_UNIT_TEST
255 if ((stamp - prevStamp) > minDeltaTimeForStableAdcDetectionNt) {
256 switchingCnt++;
257 } else {
258 switchingCnt = 0;
259 switchingTeethCnt = 0;
260 }
261
262 if (switchingCnt >= digitalToAnalogTransitionCnt) {
263 switchingCnt = 0;
264 // we need at least 3 wide teeth to be certain!
265 // we don't want to confuse them with a sync.gap
266 if (switchingTeethCnt++ > 3) {
267 switchingTeethCnt = 0;
268 prevValue = rise ? 1: -1;
269 setTriggerAdcMode(TRIGGER_ADC_ADC);
270 }
271 }
272 #endif // (HAL_TRIGGER_USE_ADC && HAL_USE_ADC) || EFI_UNIT_TEST
273
274 prevStamp = stamp;
275 #endif // !EFI_SIMULATOR && EFI_SHAFT_POSITION_INPUT
276 }
277
278 60000 void TriggerAdcDetector::analogCallback(efitick_t stamp, triggerAdcSample_t value) {
279 #if ! EFI_SIMULATOR && ((HAL_TRIGGER_USE_ADC && HAL_USE_ADC) || EFI_UNIT_TEST)
280
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60000 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 60000 times.
60000 if (curAdcMode != TRIGGER_ADC_ADC) {
281 return;
282 }
283
284 #ifdef TRIGGER_ADC_DUMP_BUF
285 dumpBuf[dumpBufCnt] = value;
286 dumpBufCnt = (dumpBufCnt + 1) % dumpBufNum;
287 #endif /* TRIGGER_ADC_DUMP_BUF */
288
289 // <1V or >4V?
290
2/4
✓ Branch 0 taken 60000 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 60000 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 60000 times.
60000 if (value >= switchingThresholdHigh || value <= switchingThresholdLow) {
291 switchingCnt++;
292 } else {
293 //switchingCnt = 0;
294 60000 switchingCnt = maxI(switchingCnt - 1, 0);
295 }
296
297 60000 int delta = value - adcThreshold;
298 60000 int aDelta = absI(delta);
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60000 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 60000 times.
60000 if (isSignalWeak) {
300 // todo: detect if the sensor is disconnected (where the signal is always near 'ADC_MAX_VALUE')
301
302 // filter out low signals (noise)
303 if (delta >= minDeltaThresholdWeakSignal) {
304 minDeltaThresholdCntPos++;
305 }
306 if (delta <= -minDeltaThresholdWeakSignal) {
307 minDeltaThresholdCntNeg++;
308 }
309 } else {
310 // we just had a strong signal, let's reset the counter
311
2/2
✓ Branch 0 taken 41274 times.
✓ Branch 1 taken 18726 times.
2/2
✓ Decision 'true' taken 41274 times.
✓ Decision 'false' taken 18726 times.
60000 if (delta >= minDeltaThresholdWeakSignal) {
312 41274 minDeltaThresholdCntPos = DELTA_THRESHOLD_CNT_HIGH;
313 }
314
2/2
✓ Branch 0 taken 11332 times.
✓ Branch 1 taken 48668 times.
2/2
✓ Decision 'true' taken 11332 times.
✓ Decision 'false' taken 48668 times.
60000 if (delta <= -minDeltaThresholdWeakSignal) {
315 11332 minDeltaThresholdCntNeg = DELTA_THRESHOLD_CNT_HIGH;
316 }
317 60000 minDeltaThresholdCntPos--;
318 60000 minDeltaThresholdCntNeg--;
319 // we haven't seen the strong signal (pos or neg) for too long, maybe it's lost or too weak?
320
2/4
✓ Branch 0 taken 60000 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 60000 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 60000 times.
60000 if (minDeltaThresholdCntPos <= 0 || minDeltaThresholdCntNeg <= 0) {
321 // reset to the weak signal mode
322 reset();
323 return;
324 }
325 }
326
327 // the threshold should always correspond to the averaged signal.
328 60000 integralSum += delta;
329 // we need some limits for the integral sum
330 // we use a simple I-regulator to move the threshold
331 60000 adcThreshold += (float)integralSum * triggerAdcITerm;
332 // limit the threshold for safety
333 60000 adcThreshold = maxF(minF(adcThreshold, adcMaxThreshold), adcMinThreshold);
334
335 // now to the transition part... First, we need a cooldown to pre-filter the transition noise
336
2/2
✓ Branch 0 taken 24909 times.
✓ Branch 1 taken 35091 times.
2/2
✓ Decision 'true' taken 24909 times.
✓ Decision 'false' taken 35091 times.
60000 if (transitionCooldownCnt-- < 0)
337 24909 transitionCooldownCnt = 0;
338
339 // we need at least 2 different measurements to detect a transition
340
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 59999 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 59999 times.
60000 if (prevValue == 0) {
341 // we can take the measurement only from outside the dead-zone
342
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1 time.
✗ Decision 'false' not taken.
1 if (aDelta > minDeltaThresholdWeakSignal) {
343
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 prevValue = (delta > 0) ? 1 : -1;
344 } else {
345 return;
346 }
347 }
348
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60000 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 60000 times.
60000 if (isSignalWeak) {
350 if (minDeltaThresholdCntPos >= DELTA_THRESHOLD_CNT_LOW && minDeltaThresholdCntNeg >= DELTA_THRESHOLD_CNT_LOW) {
351 // ok, now we have a legit strong signal, let's restore the threshold
352 isSignalWeak = false;
353 integralSum = 0;
354 zeroThreshold = minDeltaThresholdStrongSignal;
355 } else {
356 // we cannot trust the weak signal!
357 return;
358 }
359 }
360
361
2/2
✓ Branch 0 taken 52608 times.
✓ Branch 1 taken 7392 times.
2/2
✓ Decision 'true' taken 52608 times.
✓ Decision 'false' taken 7392 times.
60000 if (transitionCooldownCnt <= 0) {
362 // detect the edge
363 52608 int transition = 0;
364
4/4
✓ Branch 0 taken 40745 times.
✓ Branch 1 taken 11863 times.
✓ Branch 2 taken 924 times.
✓ Branch 3 taken 39821 times.
2/2
✓ Decision 'true' taken 924 times.
✓ Decision 'false' taken 51684 times.
52608 if (delta > zeroThreshold && prevValue == -1) {
365 // a rising transition found!
366 924 transition = 1;
367 }
368
4/4
✓ Branch 0 taken 11214 times.
✓ Branch 1 taken 40470 times.
✓ Branch 2 taken 924 times.
✓ Branch 3 taken 10290 times.
2/2
✓ Decision 'true' taken 924 times.
✓ Decision 'false' taken 50760 times.
51684 else if (delta <= -zeroThreshold && prevValue == 1) {
369 // a falling transition found!
370 924 transition = -1;
371 }
372 else {
373 50760 return; // both are positive/negative/zero: not interested!
374 }
375
376 1848 onTriggerChanged(stamp - stampCorrectionForAdc, true, transition == 1);
377 // let's skip some nearest possible measurements:
378 // the transition cannot be SO fast, but the jitter can!
379 1848 transitionCooldownCnt = transitionCooldown;
380
381 // it should not accumulate too much
382 1848 integralSum = 0;
383 #if 0
384 // update triggerAdcITerm
385 efitimeus_t deltaTimeUs = NT2US(stamp - prevStamp);
386 if (deltaTimeUs > 200) { // 200 us = ~2500 RPM (we don't need this correction for large RPM)
387 triggerAdcITerm = 1.0f / (triggerAdcITermCoef * deltaTimeUs);
388 triggerAdcITerm = std::max(triggerAdcITerm, triggerAdcITermMin);
389 }
390 #endif // 0
391
392 1848 prevValue = transition;
393 }
394
395 #ifdef EFI_SHAFT_POSITION_INPUT
396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9240 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 9240 times.
9240 if (switchingCnt >= analogToDigitalTransitionCnt) {
397 switchingCnt = 0;
398 // we need at least 3 high-signal teeth to be certain!
399 if (switchingTeethCnt++ > 3) {
400 switchingTeethCnt = 0;
401
402 setTriggerAdcMode(TRIGGER_ADC_EXTI);
403
404 // we don't want to loose the signal on return
405 minDeltaThresholdCntPos = DELTA_THRESHOLD_CNT_HIGH;
406 minDeltaThresholdCntNeg = DELTA_THRESHOLD_CNT_HIGH;
407 // we want to reset the thresholds on return
408 zeroThreshold = minDeltaThresholdStrongSignal;
409 adcThreshold = adcDefaultThreshold;
410 // reset integrator
411 triggerAdcITerm = triggerAdcITermMin;
412 integralSum = 0;
413 transitionCooldownCnt = 0;
414 return;
415 }
416 } else {
417 // we don't see "big teeth" anymore
418 9240 switchingTeethCnt = 0;
419 }
420 #endif // EFI_SHAFT_POSITION_INPUT
421
422 9240 prevStamp = stamp;
423 #else
424 UNUSED(stamp); UNUSED(value);
425 #endif // ! EFI_SIMULATOR && ((HAL_TRIGGER_USE_ADC && HAL_USE_ADC) || EFI_UNIT_TEST)
426 }
427
428 1 void TriggerAdcDetector::setWeakSignal(bool isWeak) {
429 #if HAL_USE_ADC || EFI_UNIT_TEST
430 1 isSignalWeak = isWeak;
431
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 1 time.
✗ Decision 'false' not taken.
1 if (!isSignalWeak) {
432 1 minDeltaThresholdCntPos = minDeltaThresholdCntNeg = DELTA_THRESHOLD_CNT_LOW;
433 } else {
434 minDeltaThresholdCntPos = minDeltaThresholdCntNeg = 0;
435 }
436 #endif // HAL_USE_ADC || EFI_UNIT_TEST
437 1 }
438
439 triggerAdcMode_t getTriggerAdcMode(void) {
440 return trigAdcState.curAdcMode;
441 }
442
443 float getTriggerAdcThreshold(void) {
444 return trigAdcState.adcThreshold;
445 }
446
447 int getTriggerAdcModeCnt(void) {
448 return trigAdcState.modeSwitchCnt;
449 }
450