rusefi/firmware/controllers/system/timer/trigger_scheduler.cpp

161 lines
4.8 KiB
C++

#include "pch.h"
#include "event_queue.h"
bool TriggerScheduler::assertNotInList(AngleBasedEvent *head, AngleBasedEvent *element) {
assertNotInListMethodBody(head, element, nextToothEvent)
}
void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, angle_t angle, action_s action) {
event->setAngle(angle);
schedule(msg, event, action);
}
/**
* Schedules 'action' to occur at engine cycle angle 'angle'.
*
* @return true if event corresponds to current tooth and was time-based scheduler
* false if event was put into queue for scheduling at a later tooth
*/
bool TriggerScheduler::scheduleOrQueue(const char *msg, AngleBasedEvent *event,
efitick_t edgeTimestamp,
angle_t angle,
action_s action,
float currentPhase, float nextPhase) {
event->setAngle(angle);
// *kludge* naming mess: if (shouldSchedule) { scheduleByAngle } else { schedule } see header for more details
if (event->shouldSchedule(currentPhase, nextPhase)) {
// if we're due now, just schedule the event
scheduleByAngle(
&event->scheduling,
edgeTimestamp,
event->getAngleFromNow(currentPhase),
action
);
return true;
} else {
// If not due now, add it to the queue to be scheduled later
schedule(msg, event, action);
return false;
}
}
void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, action_s action) {
if (event->enginePhase < 0) {
// at the moment we expect API consumer to wrap angle. shall we do the wrapping in the enginePhase setter?
// i.e. what is the best level to take care of the range constraint?
criticalError("Negative angle %s %f", msg, event->enginePhase);
}
event->action = action;
{
chibios_rt::CriticalSectionLocker csl;
// TODO: This is O(n), consider some other way of detecting if in a list,
// and consider doubly linked or other list tricks.
if (!assertNotInList(m_angleBasedEventsHead, event)) {
// Use Append to retain some semblance of event ordering in case of
// time skew. Thus on events are always followed by off events.
LL_APPEND2(m_angleBasedEventsHead, event, nextToothEvent);
}
}
}
void TriggerScheduler::scheduleEventsUntilNextTriggerTooth(int rpm,
efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
if (!isValidRpm(rpm)) {
// this might happen for instance in case of a single trigger event after a pause
return;
}
AngleBasedEvent *current, *tmp, *keephead;
AngleBasedEvent *keeptail = nullptr;
{
chibios_rt::CriticalSectionLocker csl;
keephead = m_angleBasedEventsHead;
m_angleBasedEventsHead = nullptr;
}
LL_FOREACH_SAFE2(keephead, current, tmp, nextToothEvent)
{
if (current->shouldSchedule(currentPhase, nextPhase)) {
// time to fire a spark which was scheduled previously
// Yes this looks like O(n^2), but that's only over the entire engine
// cycle. It's really O(mn + nn) where m = # of teeth and n = # events
// fired per cycle. The number of teeth outweigh the number of events, at
// least for 60-2.... So odds are we're only firing an event or two per
// tooth, which means the outer loop is really only O(n). And if we are
// firing many events per teeth, then it's likely the events before this
// one also fired and thus the call to LL_DELETE2 is closer to O(1).
LL_DELETE2(keephead, current, nextToothEvent);
scheduling_s * sDown = &current->scheduling;
#if SPARK_EXTREME_LOGGING
efiPrintf("time to invoke [%.1f, %.1f) %d %d",
currentPhase, nextPhase, getRevolutionCounter(), (int)getTimeNowUs());
#endif /* SPARK_EXTREME_LOGGING */
// In case this event was scheduled by overdwell protection, cancel it so
// we can re-schedule at the correct time
engine->executor.cancel(sDown);
scheduleByAngle(
sDown,
edgeTimestamp,
current->getAngleFromNow(currentPhase),
current->action
);
} else {
keeptail = current; // Used for fast list concatenation
}
}
if (keephead) {
chibios_rt::CriticalSectionLocker csl;
// Put any new entries onto the end of the keep list
keeptail->nextToothEvent = m_angleBasedEventsHead;
m_angleBasedEventsHead = keephead;
}
}
bool AngleBasedEvent::shouldSchedule(float currentPhase, float nextPhase) const {
return isPhaseInRange(this->enginePhase, currentPhase, nextPhase);
}
float AngleBasedEvent::getAngleFromNow(float currentPhase) const {
float angleOffset = this->enginePhase - currentPhase;
if (angleOffset < 0) {
angleOffset += engine->engineState.engineCycle;
}
return angleOffset;
}
#if EFI_UNIT_TEST
// todo: reduce code duplication with another 'getElementAtIndexForUnitText'
AngleBasedEvent * TriggerScheduler::getElementAtIndexForUnitTest(int index) {
AngleBasedEvent * current;
LL_FOREACH2(m_angleBasedEventsHead, current, nextToothEvent)
{
if (index == 0)
return current;
index--;
}
criticalError("getElementAtIndexForUnitText: null");
return nullptr;
}
#endif /* EFI_UNIT_TEST */