qpc/source/qf_time.c
Quantum Leaps 7ee7caecd6 5.4.2
2015-06-04 22:47:13 -04:00

513 lines
20 KiB
C

/**
* @file
* @brief QF time events and time management services
* @ingroup qf
* @cond
******************************************************************************
* Last updated for version 5.4.2
* Last updated on 2015-06-03
*
* Q u a n t u m L e a P s
* ---------------------------
* innovating embedded systems
*
* Copyright (C) Quantum Leaps, www.state-machine.com.
*
* 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 <http://www.gnu.org/licenses/>.
*
* Contact information:
* Web: www.state-machine.com
* Email: 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 */
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 QF_TICK_X()
*
* @note the calls to QF_tickX_() with different @a tickRate argument can
* preempt each other. For example, higher clock tick rates might be serviced
* from interrupts while others from tasks (active objects).
*
* @sa ::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 = &QF_timeEvtHead_[tickRate];
QF_CRIT_STAT_
QF_CRIT_ENTRY_();
QS_BEGIN_NOCRIT_(QS_QF_TICK, (void *)0, (void *)0)
QS_TEC_((QTimeEvtCtr)(++prev->ctr)); /* tick ctr */
QS_U8_((uint8_t)tickRate); /* tick rate */
QS_END_NOCRIT_()
/* scan the linked-list of time events at this rate... */
for (;;) {
QTimeEvt *t = prev->next; /* advance down the time evt. list */
/* end of the list? */
if (t == (QTimeEvt *)0) {
/* ny newly armed time events? */
if (QF_timeEvtHead_[tickRate].act != (void *)0) {
/* sanity check */
Q_ASSERT_ID(110, prev != (QTimeEvt *)0);
prev->next = (QTimeEvt *)QF_timeEvtHead_[tickRate].act;
QF_timeEvtHead_[tickRate].act = (void *)0;
t = prev->next; /* switch to the new list */
}
else {
break; /* all currently armed time evts. processed */
}
}
/* time evt. scheduled for removal? */
if (t->ctr == (QTimeEvtCtr)0) {
prev->next = t->next;
t->super.refCtr_ &= (uint8_t)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->ctr;
/* is time event about to expire? */
if (t->ctr == (QTimeEvtCtr)0) {
QMActive *act = (QMActive *)t->act; /* temporary for volatile */
/* periodic time evt? */
if (t->interval != (QTimeEvtCtr)0) {
t->ctr = t->interval; /* rearm the time event */
prev = t; /* advance to this time event */
}
/* one-shot time event: automatically disarm */
else {
prev->next = t->next;
t->super.refCtr_ &= (uint8_t)0x7F; /* mark as unlinked */
/* do NOT advance the prev pointer */
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_AUTO_DISARM,
QS_priv_.teObjFilter, t)
QS_OBJ_(t); /* this time event object */
QS_OBJ_(act); /* the target AO */
QS_U8_((uint8_t)tickRate); /* tick rate */
QS_END_NOCRIT_()
}
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_POST, QS_priv_.teObjFilter, t)
QS_TIME_(); /* timestamp */
QS_OBJ_(t); /* the time event object */
QS_SIG_(t->super.sig); /* signal of this time event */
QS_OBJ_(act); /* the target AO */
QS_U8_((uint8_t)tickRate); /* tick rate */
QS_END_NOCRIT_()
QF_CRIT_EXIT_(); /* exit critical section before posting */
/* QACTIVE_POST() asserts internally if the queue overflows */
QACTIVE_POST(act, &t->super, sender);
}
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) {
bool inactive;
/** @pre the tick rate must be in range */
Q_REQUIRE_ID(200, tickRate < (uint_fast8_t)QF_MAX_TICK_RATE);
if (QF_timeEvtHead_[tickRate].next != (QTimeEvt *)0) {
inactive = false;
}
else if ((QF_timeEvtHead_[tickRate].act != (void *)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 sig. You cannot change
* these attributes later.
*
* @param[in,out] me pointer (see @ref oop)
* @param[in] act pointer to the active object associated with this
* time event. The time event will post itself to this AO.
* @param[in] sig signal to associate with this time event.
* @param[in] tickRate system tick rate to associate with this time event.
*
* @note You should call the constructor exactly once for every Time Event
* object **before** arming the Time Event. The ideal place for initializing
* the time event(s) associated with a given AO is the AO's constructor.
*/
void QTimeEvt_ctorX(QTimeEvt * const me, QMActive * const act,
enum_t const sig, uint_fast8_t tickRate)
{
/** @pre The signal must be valid and the tick rate in range */
Q_REQUIRE_ID(300, (sig >= (enum_t)Q_USER_SIG)
&& (tickRate < (uint_fast8_t)QF_MAX_TICK_RATE));
me->next = (QTimeEvt *)0;
me->ctr = (QTimeEvtCtr)0;
me->interval = (QTimeEvtCtr)0;
me->super.sig = (QSignal)sig;
/* For backwards compatibility with QTimeEvt_ctor(), the active object
* pointer can be uninitialized (NULL) and is NOT validated in the
* precondition. The active object pointer is validated in preconditions
* to QTimeEvt_arm_() and QTimeEvt_rearm().
*/
me->act = act;
/* 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.
*/
me->super.poolId_ = (uint8_t)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.
*/
me->super.refCtr_ = (uint8_t)tickRate;
}
/****************************************************************************/
/**
* @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,out] me pointer (see @ref oop)
* @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 the
* QTimeEvt_disarm() function. Also, a time event can be re-armed to fire
* in a different number of clock ticks by calling the 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.c
*/
void QTimeEvt_armX(QTimeEvt * const me,
QTimeEvtCtr const nTicks, QTimeEvtCtr const interval)
{
uint_fast8_t tickRate = (uint_fast8_t)me->super.refCtr_
& (uint_fast8_t)0x7F;
QTimeEvtCtr ctr = me->ctr;
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, (me->act != (void *)0)
&& (ctr == (QTimeEvtCtr)0)
&& (nTicks != (QTimeEvtCtr)0)
&& (tickRate < (uint_fast8_t)QF_MAX_TICK_RATE)
&& (me->super.sig >= (QSignal)Q_USER_SIG));
QF_CRIT_ENTRY_();
me->ctr = nTicks;
me->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 ((me->super.refCtr_ & (uint8_t)0x80) == (uint8_t)0) {
me->super.refCtr_ |= (uint8_t)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.
*/
me->next = (QTimeEvt *)QF_timeEvtHead_[tickRate].act;
QF_timeEvtHead_[tickRate].act = me;
}
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_ARM, QS_priv_.teObjFilter, me)
QS_TIME_(); /* timestamp */
QS_OBJ_(me); /* this time event object */
QS_OBJ_(me->act); /* the active object */
QS_TEC_(nTicks); /* the number of ticks */
QS_TEC_(interval); /* the interval */
QS_U8_((uint8_t)tickRate); /* tick rate */
QS_END_NOCRIT_()
QF_CRIT_EXIT_();
}
/****************************************************************************/
/**
* @description
* Disarm the time event so it can be safely reused.
*
* @param[in,out] me pointer (see @ref oop)
*
* @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(QTimeEvt * const me) {
bool wasArmed;
QF_CRIT_STAT_
QF_CRIT_ENTRY_();
/* is the time evt running? */
if (me->ctr != (QTimeEvtCtr)0) {
wasArmed = true;
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_DISARM, QS_priv_.teObjFilter, me)
QS_TIME_(); /* timestamp */
QS_OBJ_(me); /* this time event object */
QS_OBJ_(me->act); /* the target AO */
QS_TEC_(me->ctr); /* the number of ticks */
QS_TEC_(me->interval); /* the interval */
QS_U8_((uint8_t)(me->super.refCtr_ & (uint8_t)0x7F));/*tick rate*/
QS_END_NOCRIT_()
me->ctr = (QTimeEvtCtr)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_.teObjFilter, me)
QS_TIME_(); /* timestamp */
QS_OBJ_(me); /* this time event object */
QS_OBJ_(me->act); /* the target AO */
QS_U8_((uint8_t)(me->super.refCtr_ & (uint8_t)0x7F));/*tick rate*/
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,out] me pointer (see @ref oop)
* @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(QTimeEvt * const me, QTimeEvtCtr const nTicks) {
uint_fast8_t tickRate = (uint_fast8_t)me->super.refCtr_
& (uint_fast8_t)0x7F;
bool isArmed;
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, (me->act != (void *)0)
&& (tickRate < (uint_fast8_t)QF_MAX_TICK_RATE)
&& (nTicks != (QTimeEvtCtr)0)
&& (me->super.sig >= (QSignal)Q_USER_SIG));
QF_CRIT_ENTRY_();
/* is the time evt not running? */
if (me->ctr == (QTimeEvtCtr)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 ((me->super.refCtr_ & (uint8_t)0x80) == (uint8_t)0) {
me->super.refCtr_ |= (uint8_t)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.
*/
me->next = (QTimeEvt *)QF_timeEvtHead_[tickRate].act;
QF_timeEvtHead_[tickRate].act = me;
}
}
/* the time event is armed */
else {
isArmed = true;
}
me->ctr = nTicks; /* re-load the tick counter (shift the phasing) */
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_REARM, QS_priv_.teObjFilter, me)
QS_TIME_(); /* timestamp */
QS_OBJ_(me); /* this time event object */
QS_OBJ_(me->act); /* the target AO */
QS_TEC_(me->ctr); /* the number of ticks */
QS_TEC_(me->interval); /* the interval */
QS_2U8_((uint8_t)tickRate, (uint8_t)isArmed);
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.
*
* @param[in,out] me pointer (see @ref oop)
*
* @returns For an armed time event, the function returns the current value
* of the down-counter of the given time event. If the time event is not
* armed, the function returns 0.
*
* /note The function is thread-safe.
*/
QTimeEvtCtr QTimeEvt_ctr(QTimeEvt const * const me) {
QTimeEvtCtr ret;
QF_CRIT_STAT_
QF_CRIT_ENTRY_();
ret = me->ctr;
QS_BEGIN_NOCRIT_(QS_QF_TIMEEVT_CTR, QS_priv_.teObjFilter, me)
QS_TIME_(); /* timestamp */
QS_OBJ_(me); /* this time event object */
QS_OBJ_(me->act); /* the target AO */
QS_TEC_(ret); /* the current counter */
QS_TEC_(me->interval); /* the interval */
QS_U8_((uint8_t)(me->super.refCtr_ & (uint8_t)0x7F)); /* tick rate */
QS_END_NOCRIT_()
QF_CRIT_EXIT_();
return ret;
}