/// @file /// @brief QF/C++ time events and time management services /// @ingroup qf /// @cond ///*************************************************************************** /// Last updated for version 5.9.0 /// Last updated on 2017-05-08 /// /// Q u a n t u m L e a P s /// --------------------------- /// innovating embedded systems /// /// Copyright (C) Quantum Leaps, LLC. All rights reserved. /// /// This program is open source software: you can redistribute it and/or /// modify it under the terms of the GNU General Public License as published /// by the Free Software Foundation, either version 3 of the License, or /// (at your option) any later version. /// /// Alternatively, this program may be distributed and modified under the /// terms of Quantum Leaps commercial licenses, which expressly supersede /// the GNU General Public License and are specifically designed for /// licensees interested in retaining the proprietary status of their code. /// /// This program is distributed in the hope that it will be useful, /// but WITHOUT ANY WARRANTY; without even the implied warranty of /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the /// GNU General Public License for more details. /// /// You should have received a copy of the GNU General Public License /// along with this program. If not, see . /// /// Contact information: /// http:www.state-machine.com /// mailto:info@state-machine.com ///*************************************************************************** /// @endcond #define QP_IMPL // this is QP implementation #include "qf_port.h" // QF port #include "qf_pkg.h" // QF package-scope interface #include "qassert.h" // QP embedded systems-friendly assertions #ifdef Q_SPY // QS software tracing enabled? #include "qs_port.h" // include QS port #else #include "qs_dummy.h" // disable the QS software tracing #endif // Q_SPY namespace QP { Q_DEFINE_THIS_MODULE("qf_time") // Package-scope objects ***************************************************** QTimeEvt QF::timeEvtHead_[QF_MAX_TICK_RATE]; // heads of time event lists //**************************************************************************** /// @description /// This function must be called periodically from a time-tick ISR or from /// a task so that QF can manage the timeout events assigned to the given /// system clock tick rate. /// /// @param[in] tickRate system clock tick rate serviced in this call. /// /// @note this function should be called only via the macro TICK_X() /// /// @note the calls to QP::QF::tickX_() with different tick rate argument can /// preempt each other. For example, higher clock tick rates might be /// serviced from interrupts while others from tasks (active objects). /// /// @sa QP::QTimeEvt. /// #ifndef Q_SPY void QF::tickX_(uint_fast8_t const tickRate) #else void QF::tickX_(uint_fast8_t const tickRate, void const * const sender) #endif { QTimeEvt *prev = &timeEvtHead_[tickRate]; QF_CRIT_STAT_ QF_CRIT_ENTRY_(); QS_BEGIN_NOCRIT_(QS_QF_TICK, static_cast(0), static_cast(0)) QS_TEC_(static_cast(++prev->m_ctr)); // tick ctr QS_U8_(static_cast(tickRate)); // tick rate QS_END_NOCRIT_() // scan the linked-list of time events at this rate... for (;;) { QTimeEvt *t = prev->m_next; // advance down the time evt. list // end of the list? if (t == static_cast(0)) { // any new time events armed since the last run of QF::tickX_()? if (timeEvtHead_[tickRate].m_act != static_cast(0)) { // sanity check Q_ASSERT_ID(110, prev != static_cast(0)); prev->m_next = QF::timeEvtHead_[tickRate].toTimeEvt(); timeEvtHead_[tickRate].m_act = static_cast(0); t = prev->m_next; // switch to the new list } else { break; // all currently armed time evts. processed } } // time event scheduled for removal? if (t->m_ctr == static_cast(0)) { prev->m_next = t->m_next; t->refCtr_ &= static_cast(0x7F); // mark as unlinked // do NOT advance the prev pointer QF_CRIT_EXIT_(); // exit crit. section to reduce latency // prevent merging critical sections, see NOTE1 below QF_CRIT_EXIT_NOP(); } else { --t->m_ctr; // is time evt about to expire? if (t->m_ctr == static_cast(0)) { QActive *act = t->toActive(); // temporary for volatile // periodic time evt? if (t->m_interval != static_cast(0)) { t->m_ctr = t->m_interval; // rearm the time event prev = t; // advance to this time event } // one-shot time event: automatically disarm else { prev->m_next = t->m_next; // mark as unlinked t->refCtr_ &= static_cast(0x7F); // do NOT advance the prev pointer QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_AUTO_DISARM, QS::priv_.locFilter[QS::TE_OBJ], t) QS_OBJ_(t); // this time event object QS_OBJ_(act); // the target AO QS_U8_(static_cast(tickRate)); // tick rate QS_END_NOCRIT_() } QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_POST, QS::priv_.locFilter[QS::TE_OBJ], t) QS_TIME_(); // timestamp QS_OBJ_(t); // the time event object QS_SIG_(t->sig); // signal of this time event QS_OBJ_(act); // the target AO QS_U8_(static_cast(tickRate)); // tick rate QS_END_NOCRIT_() QF_CRIT_EXIT_(); // exit crit. section before posting (void)act->POST(t, sender); // asserts if queue overflows } else { prev = t; // advance to this time event QF_CRIT_EXIT_(); // exit crit. section to reduce latency // prevent merging critical sections, see NOTE1 below QF_CRIT_EXIT_NOP(); } } QF_CRIT_ENTRY_(); // re-enter crit. section to continue } QF_CRIT_EXIT_(); } //**************************************************************************** // NOTE1: // In some QF ports the critical section exit takes effect only on the next // machine instruction. If this case, the next instruction is another entry // to a critical section, the critical section won't be really exited, but // rather the two adjacent critical sections would be merged. // // The QF_CRIT_EXIT_NOP() macro contains minimal code required // to prevent such merging of critical sections in QF ports, // in which it can occur. //**************************************************************************** /// @description /// Find out if any time events are armed at the given clock tick rate. /// /// @param[in] tickRate system clock tick rate to find out about. /// /// @returns 'true' if no time events are armed at the given tick rate and /// 'false' otherwise. /// /// @note This function should be called in critical section. /// bool QF::noTimeEvtsActiveX(uint_fast8_t const tickRate) { /// @pre the tick rate must be in range Q_REQUIRE_ID(200, tickRate < static_cast(QF_MAX_TICK_RATE)); bool inactive; if (timeEvtHead_[tickRate].m_next == static_cast(0)) { inactive = false; } else if (timeEvtHead_[tickRate].m_act == static_cast(0)) { inactive = false; } else { inactive = true; } return inactive; } //**************************************************************************** /// @description /// When creating a time event, you must commit it to a specific active object /// @p act, tick rate @p tickRate and event signal @p sgnl. You cannot change /// these attributes later. /// /// @param[in] act pointer to the active object associated with this /// time event. The time event will post itself to this AO. /// @param[in] sgnl signal to associate with this time event. /// @param[in] tickRate system tick rate to associate with this time event. /// QTimeEvt::QTimeEvt(QActive * const act, enum_t const sgnl, uint_fast8_t const tickRate) : #ifdef Q_EVT_CTOR QEvt(static_cast(sgnl)), #endif m_next(static_cast(0)), m_act(act), m_ctr(static_cast(0)), m_interval(static_cast(0)) { /// @pre The signal must be valid and the tick rate in range Q_REQUIRE_ID(300, (sgnl >= Q_USER_SIG) && (tickRate < static_cast(QF_MAX_TICK_RATE))); #ifndef Q_EVT_CTOR sig = static_cast(sgnl); // set QEvt::sig of this time event #endif // Setting the POOL_ID event attribute to zero is correct only for // events not allocated from event pools, which must be the case // for Time Events. // poolId_ = static_cast(0); // The reference counter attribute is not used in static events, // so for the Time Events it is reused to hold the tickRate in the // bits [0..6] and the linkedFlag in the MSB (bit [7]). The linkedFlag // is 0 for time events unlinked from any list and 1 otherwise. // refCtr_ = static_cast(tickRate); } //**************************************************************************** /// @note /// private default ctor for internal use only /// QTimeEvt::QTimeEvt() : #ifdef Q_EVT_CTOR QEvt(static_cast(0)), #endif // Q_EVT_CTOR m_next(static_cast(0)), m_act(static_cast(0)), m_ctr(static_cast(0)), m_interval(static_cast(0)) { #ifndef Q_EVT_CTOR sig = static_cast(0); #endif // Q_EVT_CTOR // Setting the POOL_ID event attribute to zero is correct only for // events not allocated from event pools, which must be the case // for Time Events. // poolId_ = static_cast(0); // not from any event pool // The reference counter attribute is not used in static events, // so for the Time Events it is reused to hold the tickRate in the // bits [0..6] and the linkedFlag in the MSB (bit [7]). The linkedFlag // is 0 for time events unlinked from any list and 1 otherwise. // refCtr_ = static_cast(0); // default rate 0 } //**************************************************************************** /// @description /// Arms a time event to fire in a specified number of clock ticks and with /// a specified interval. If the interval is zero, the time event is armed for /// one shot ('one-shot' time event). The time event gets directly posted /// (using the FIFO policy) into the event queue of the host active object. /// /// @param[in] nTicks number of clock ticks (at the associated rate) /// to rearm the time event with. /// @param[in] interval interval (in clock ticks) for periodic time event. /// /// @note After posting, a one-shot time event gets automatically disarmed /// while a periodic time event (interval != 0) is automatically re-armed. /// /// @note A time event can be disarmed at any time by calling /// QP::QTimeEvt::disarm(). Also, a time event can be re-armed to fire in a /// different number of clock ticks by calling the QP::QTimeEvt::rearm() /// function. /// /// @usage /// The following example shows how to arm a one-shot time event from a state /// machine of an active object: /// @include qf_state.cpp /// void QTimeEvt::armX(QTimeEvtCtr const nTicks, QTimeEvtCtr const interval) { uint_fast8_t tickRate = static_cast(refCtr_) & static_cast(0x7F); QTimeEvtCtr cntr = m_ctr; // temporary to hold volatile QF_CRIT_STAT_ /// @pre the host AO must be valid, time evnet must be disarmed, /// number of clock ticks cannot be zero, and the signal must be valid. /// Q_REQUIRE_ID(400, (m_act != static_cast(0)) && (cntr == static_cast(0)) && (nTicks != static_cast(0)) && (tickRate < static_cast(QF_MAX_TICK_RATE)) && (static_cast(sig) >= Q_USER_SIG)); QF_CRIT_ENTRY_(); m_ctr = nTicks; m_interval = interval; // is the time event unlinked? // NOTE: For the duration of a single clock tick of the specified tick // rate a time event can be disarmed and yet still linked into the list, // because un-linking is performed exclusively in the QF_tickX() function. // if ((refCtr_ & static_cast(0x80)) == static_cast(0)) { refCtr_ |= static_cast(0x80); // mark as linked // The time event is initially inserted into the separate // "freshly armed" link list based on QF::timeEvtHead_[tickRate].act. // Only later, inside the QF::tickX() function, the "freshly armed" // list is appended to the main list of armed time events based on // QF::timeEvtHead_[tickRate].next. Again, this is to keep any // changes to the main list exclusively inside the QF::tickX() // function. // m_next = QF::timeEvtHead_[tickRate].toTimeEvt(); QF::timeEvtHead_[tickRate].m_act = this; } QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_ARM, QS::priv_.locFilter[QS::TE_OBJ], this) QS_TIME_(); // timestamp QS_OBJ_(this); // this time event object QS_OBJ_(m_act); // the active object QS_TEC_(nTicks); // the number of ticks QS_TEC_(interval); // the interval QS_U8_(static_cast(tickRate)); // tick rate QS_END_NOCRIT_() QF_CRIT_EXIT_(); } //**************************************************************************** /// @description /// Disarm the time event so it can be safely reused. /// /// @returns 'true' if the time event was truly disarmed, that is, it /// was running. The return of 'false' means that the time event was /// not truly disarmed because it was not running. The 'false' return is only /// possible for one-shot time events that have been automatically disarmed /// upon expiration. In this case the 'false' return means that the time event /// has already been posted or published and should be expected in the /// active object's state machine. /// /// @note there is no harm in disarming an already disarmed time event //// bool QTimeEvt::disarm(void) { QF_CRIT_STAT_ QF_CRIT_ENTRY_(); bool wasArmed; // is the time event actually armed? if (m_ctr != static_cast(0)) { wasArmed = true; QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_DISARM, QS::priv_.locFilter[QS::TE_OBJ], this) QS_TIME_(); // timestamp QS_OBJ_(this); // this time event object QS_OBJ_(m_act); // the target AO QS_TEC_(m_ctr); // the number of ticks QS_TEC_(m_interval); // the interval // tick rate QS_U8_(static_cast(refCtr_& static_cast(0x7F))); QS_END_NOCRIT_() m_ctr = static_cast(0); // schedule removal from the list } // the time event was already not running else { wasArmed = false; QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_DISARM_ATTEMPT, QS::priv_.locFilter[QS::TE_OBJ], this) QS_TIME_(); // timestamp QS_OBJ_(this); // this time event object QS_OBJ_(m_act); // the target AO // tick rate QS_U8_(static_cast(refCtr_& static_cast(0x7F))); QS_END_NOCRIT_() } QF_CRIT_EXIT_(); return wasArmed; } /****************************************************************************/ /** * @description * Rearms a time event with a new number of clock ticks. This function can * be used to adjust the current period of a periodic time event or to * prevent a one-shot time event from expiring (e.g., a watchdog time event). * Rearming a periodic timer leaves the interval unchanged and is a convenient * method to adjust the phasing of a periodic time event. * * @param[in] nTicks number of clock ticks (at the associated rate) * to rearm the time event with. * * @returns 'true' if the time event was running as it * was re-armed. The 'false' return means that the time event was * not truly rearmed because it was not running. The 'false' return is only * possible for one-shot time events that have been automatically disarmed * upon expiration. In this case the 'false' return means that the time event * has already been posted or published and should be expected in the * active object's state machine. */ bool QTimeEvt::rearm(QTimeEvtCtr const nTicks) { uint_fast8_t tickRate = static_cast(refCtr_) & static_cast(0x7F); QF_CRIT_STAT_ /// @pre AO must be valid, tick rate must be in range, nTicks must not /// be zero, and the signal of this time event must be valid /// Q_REQUIRE_ID(600, (m_act != static_cast(0)) && (tickRate < static_cast(QF_MAX_TICK_RATE)) && (nTicks != static_cast(0)) && (static_cast(sig) >= Q_USER_SIG)); QF_CRIT_ENTRY_(); bool isArmed; // is the time evt not running? */ if (m_ctr == static_cast(0)) { isArmed = false; // is the time event unlinked? // NOTE: For a duration of a single clock tick of the specified // tick rate a time event can be disarmed and yet still linked into // the list, because unlinking is performed exclusively in the // QF::tickX() function. // if ((refCtr_ & static_cast(0x80)) == static_cast(0)) { refCtr_ |= static_cast(0x80); // mark as linked // The time event is initially inserted into the separate // "freshly armed" list based on QF_timeEvtHead_[tickRate].act. // Only later, inside the QF_tickX() function, the "freshly armed" // list is appended to the main list of armed time events based on // QF_timeEvtHead_[tickRate].next. Again, this is to keep any // changes to the main list exclusively inside the QF_tickX() // function. // m_next = QF::timeEvtHead_[tickRate].toTimeEvt(); QF::timeEvtHead_[tickRate].m_act = this; } } // the time event is armed else { isArmed = true; } m_ctr = nTicks; // re-load the tick counter (shift the phasing) QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_REARM, QS::priv_.locFilter[QS::TE_OBJ], this) QS_TIME_(); // timestamp QS_OBJ_(this); // this time event object QS_OBJ_(m_act); // the target AO QS_TEC_(m_ctr); // the number of ticks QS_TEC_(m_interval); // the interval QS_U8_(static_cast(tickRate)); // the tick rate if (isArmed) { QS_U8_(static_cast(1)); // status: armed } else { QS_U8_(static_cast(0)); // status: disarmed } QS_END_NOCRIT_() QF_CRIT_EXIT_(); return isArmed; } //**************************************************************************** /// @description /// Useful for checking how many clock ticks (at the tick rate associated /// with the time event) remain until the time event expires. /// /// @returns The current value of the down-counter for an armed time event /// or 0 for an unarmed time event. /// /// /note The function is thread-safe. /// QTimeEvtCtr QTimeEvt::ctr(void) const { QF_CRIT_STAT_ QF_CRIT_ENTRY_(); QTimeEvtCtr ret = m_ctr; QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_CTR, QS::priv_.locFilter[QS::TE_OBJ], this) QS_TIME_(); // timestamp QS_OBJ_(this); // this time event object QS_OBJ_(m_act); // the target AO QS_TEC_(ret); // the current counter QS_TEC_(m_interval); // the interval QS_U8_(refCtr_ & static_cast(0x7F)); // tick rate QS_END_NOCRIT_() QF_CRIT_EXIT_(); return ret; } } // namespace QP