rusEFI
The most advanced open source ECU
trigger_scheduler.cpp
Go to the documentation of this file.
1 #include "pch.h"
2 
3 #include "event_queue.h"
4 
6  /* this code is just to validate state, no functional load*/
7  decltype(head) current;
8  int counter = 0;
9  LL_FOREACH2(head, current, nextToothEvent) {
10  if (++counter > QUEUE_LENGTH_LIMIT) {
12  return false;
13  }
14 
15  if (current == element) {
16  /**
17  * for example, this might happen in case of sudden RPM change if event
18  * was not scheduled by angle but was scheduled by time. In case of scheduling
19  * by time with slow RPM the whole next fast revolution might be within the wait
20  */
21  warning(ObdCode::CUSTOM_RE_ADDING_INTO_EXECUTION_QUEUE, "re-adding element into event_queue");
22  return true;
23  }
24  }
25 
26  return false;
27 }
28 
29 void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, angle_t angle, action_s action) {
30  event->setAngle(angle);
31 
32  schedule(msg, event, action);
33 }
34 
35 /**
36  * Schedules 'action' to occur at engine cycle angle 'angle'.
37  *
38  * @return true if event corresponds to current tooth and was time-based scheduler
39  * false if event was put into queue for scheduling at a later tooth
40  */
42  efitick_t edgeTimestamp,
43  angle_t angle,
44  action_s action,
45  float currentPhase, float nextPhase) {
46  event->setAngle(angle);
47 
48  // *kludge* naming mess: if (shouldSchedule) { scheduleByAngle } else { schedule } see header for more details
49  if (event->shouldSchedule(currentPhase, nextPhase)) {
50  // if we're due now, just schedule the event
52  &event->eventScheduling,
53  edgeTimestamp,
54  event->getAngleFromNow(currentPhase),
55  action
56  );
57 
58  return true;
59  } else {
60  // If not due now, add it to the queue to be scheduled later
61  schedule(msg, event, action);
62 
63  return false;
64  }
65 }
66 
67 void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, action_s action) {
68  if (event->getAngle() < 0) {
69  // at the moment we expect API consumer to wrap angle. shall we do the wrapping in the enginePhase setter?
70  // i.e. what is the best level to take care of the range constraint?
71  criticalError("Negative angle %s %f", msg, event->getAngle());
72  }
73 
74  event->action = action;
75 
76  {
77  chibios_rt::CriticalSectionLocker csl;
78 
79  // TODO: This is O(n), consider some other way of detecting if in a list,
80  // and consider doubly linked or other list tricks.
81 
83  // Use Append to retain some semblance of event ordering in case of
84  // time skew. Thus on events are always followed by off events.
85  LL_APPEND2(m_angleBasedEventsHead, event, nextToothEvent);
86  }
87  }
88 }
89 
91  efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
92 
93  if (!isValidRpm(rpm)) {
94  // this might happen for instance in case of a single trigger event after a pause
95  return;
96  }
97 
98  AngleBasedEvent *current, *tmp, *keephead;
99  AngleBasedEvent *keeptail = nullptr;
100 
101  {
102  chibios_rt::CriticalSectionLocker csl;
103 
104  keephead = m_angleBasedEventsHead;
105  m_angleBasedEventsHead = nullptr;
106  }
107 
108  LL_FOREACH_SAFE2(keephead, current, tmp, nextToothEvent)
109  {
110  if (current->shouldSchedule(currentPhase, nextPhase)) {
111  // time to fire a spark which was scheduled previously
112 
113  // Yes this looks like O(n^2), but that's only over the entire engine
114  // cycle. It's really O(mn + nn) where m = # of teeth and n = # events
115  // fired per cycle. The number of teeth outweigh the number of events, at
116  // least for 60-2.... So odds are we're only firing an event or two per
117  // tooth, which means the outer loop is really only O(n). And if we are
118  // firing many events per teeth, then it's likely the events before this
119  // one also fired and thus the call to LL_DELETE2 is closer to O(1).
120  LL_DELETE2(keephead, current, nextToothEvent);
121 
122  scheduling_s * sDown = &current->eventScheduling;
123 
124 #if SPARK_EXTREME_LOGGING
125  efiPrintf("time to invoke [%.1f, %.1f) %d %d",
126  currentPhase, nextPhase, getRevolutionCounter(), time2print(getTimeNowUs()));
127 #endif /* SPARK_EXTREME_LOGGING */
128 
129  // In case this event was scheduled by overdwell protection, cancel it so
130  // we can re-schedule at the correct time
131  // [tag:overdwell]
132  engine->executor.cancel(sDown);
133 
135  sDown,
136  edgeTimestamp,
137  current->getAngleFromNow(currentPhase),
138  current->action
139  );
140  } else {
141  keeptail = current; // Used for fast list concatenation
142  }
143  }
144 
145  if (keephead) {
146  chibios_rt::CriticalSectionLocker csl;
147 
148  // Put any new entries onto the end of the keep list
150  m_angleBasedEventsHead = keephead;
151  }
152 }
153 
154 bool AngleBasedEvent::shouldSchedule(float currentPhase, float nextPhase) const {
155  return isPhaseInRange(this->enginePhase, currentPhase, nextPhase);
156 }
157 
158 float AngleBasedEvent::getAngleFromNow(float currentPhase) const {
159  float angleOffset = this->enginePhase - currentPhase;
160  if (angleOffset < 0) {
161  angleOffset += engine->engineState.engineCycle;
162  }
163 
164  return angleOffset;
165 }
166 
167 #if EFI_UNIT_TEST
168 // todo: reduce code duplication with another 'getElementAtIndexForUnitText'
170  AngleBasedEvent * current;
171 
172  LL_FOREACH2(m_angleBasedEventsHead, current, nextToothEvent)
173  {
174  if (index == 0)
175  return current;
176  index--;
177  }
178  criticalError("getElementAtIndexForUnitText: null");
179  return nullptr;
180 }
181 #endif /* EFI_UNIT_TEST */
EngineState engineState
Definition: engine.h:312
SingleTimerExecutor executor
Definition: engine.h:241
angle_t engineCycle
Definition: engine_state.h:27
void cancel(scheduling_s *scheduling) override
Cancel the specified scheduling_s so that, if currently scheduled, it does not execute.
AngleBasedEvent * m_angleBasedEventsHead
bool assertNotInList(AngleBasedEvent *head, AngleBasedEvent *element)
AngleBasedEvent * getElementAtIndexForUnitTest(int index)
bool scheduleOrQueue(const char *msg, AngleBasedEvent *event, efitick_t edgeTimestamp, angle_t angle, action_s action, float currentPhase, float nextPhase)
void schedule(const char *msg, AngleBasedEvent *event, angle_t angle, action_s action)
void scheduleEventsUntilNextTriggerTooth(int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase)
bool isPhaseInRange(float test, float current, float next)
Definition: efilib.cpp:195
efitimeus_t getTimeNowUs()
Definition: efitime.cpp:26
int time2print(int64_t time)
Definition: efitime.h:25
Engine * engine
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
@ CUSTOM_RE_ADDING_INTO_EXECUTION_QUEUE
@ CUSTOM_ERR_LOOPED_QUEUE
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s action)
float angle_t
Definition: rusefi_types.h:59
angle_t getAngle() const
bool shouldSchedule(float currentPhase, float nextPhase) const
float getAngleFromNow(float currentPhase) const
AngleBasedEvent * nextToothEvent
scheduling_s eventScheduling