ugfx/src/gtimer/gtimer.c

232 lines
5.7 KiB
C

/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
* http://ugfx.io/license.html
*/
#include "../../gfx.h"
#if GFX_USE_GTIMER || defined(__DOXYGEN__)
#define GTIMER_FLG_PERIODIC 0x0001
#define GTIMER_FLG_INFINITE 0x0002
#define GTIMER_FLG_JABBED 0x0004
#define GTIMER_FLG_SCHEDULED 0x0008
/* Don't rework this macro to use a ternary operator - the gcc compiler stuffs it up */
#define TimeIsWithin(x, start, end) ((end >= start && x >= start && x <= end) || (end < start && (x >= start || x <= end)))
/* This mutex protects access to our tables */
static gMutex mutex;
static gThread hThread = 0;
static GTimer *pTimerHead = 0;
static gSem waitsem;
static gTicks ticks2ms;
static GFX_THREAD_STACK(waTimerThread, GTIMER_THREAD_WORKAREA_SIZE);
/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/
static GFX_THREAD_FUNCTION(GTimerThreadHandler, arg) {
GTimer *pt;
gTicks tm;
gTicks nxtTimeout;
gTicks lastTime;
GTimerFunction fn;
void *param;
(void) arg;
nxtTimeout = gDelayForever;
lastTime = 0;
while(1) {
/* Wait for work to do. */
gfxYield(); // Give someone else a go no matter how busy we are
gfxSemWait(&waitsem, nxtTimeout);
restartTimerChecks:
// Our reference time
tm = gfxSystemTicks();
nxtTimeout = gDelayForever;
/* We need to obtain the mutex */
gfxMutexEnter(&mutex);
if (pTimerHead) {
pt = pTimerHead;
do {
// Do we have something to do for this timer?
if ((pt->flags & GTIMER_FLG_JABBED) || (!(pt->flags & GTIMER_FLG_INFINITE) && TimeIsWithin(pt->when, lastTime, tm))) {
// Is this timer periodic?
if ((pt->flags & GTIMER_FLG_PERIODIC) && pt->period != gDelayNone) {
// Yes - Update ready for the next period
if (!(pt->flags & GTIMER_FLG_INFINITE)) {
// We may have skipped a period.
// We use this complicated formulae rather than a loop
// because the gcc compiler stuffs up the loop so that it
// either loops forever or doesn't get executed at all.
pt->when += ((tm + pt->period - pt->when) / pt->period) * pt->period;
}
// We are definitely no longer jabbed
pt->flags &= ~GTIMER_FLG_JABBED;
} else {
// No - get us off the timers list
if (pt->next == pt)
pTimerHead = 0;
else {
pt->next->prev = pt->prev;
pt->prev->next = pt->next;
if (pTimerHead == pt)
pTimerHead = pt->next;
}
pt->flags = 0;
}
// Call the callback function
fn = pt->fn;
param = pt->param;
gfxMutexExit(&mutex);
fn(param);
// We no longer hold the mutex, the callback function may have taken a while
// and our list may have been altered so start again!
goto restartTimerChecks;
}
// Find when we next need to wake up
if (!(pt->flags & GTIMER_FLG_INFINITE) && pt->when - tm < nxtTimeout)
nxtTimeout = (pt->when - tm)/ticks2ms;
pt = pt->next;
} while(pt != pTimerHead);
}
// Ready for the next loop
lastTime = tm;
gfxMutexExit(&mutex);
}
gfxThreadReturn(0);
}
void _gtimerInit(void)
{
gfxSemInit(&waitsem, 0, 1);
gfxMutexInit(&mutex);
ticks2ms = gfxMillisecondsToTicks(1);
}
void _gtimerDeinit(void)
{
gfxSemDestroy(&waitsem);
gfxMutexDestroy(&mutex);
// Need to destroy GTimer thread here
}
void gtimerInit(GTimer* pt)
{
pt->flags = 0;
}
void gtimerDeinit(GTimer* pt)
{
gtimerStop(pt);
}
void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, gBool periodic, gDelay millisec) {
gfxMutexEnter(&mutex);
// Start our thread if not already going
if (!hThread) {
hThread = gfxThreadCreate(waTimerThread, GTIMER_THREAD_WORKAREA_SIZE, GTIMER_THREAD_PRIORITY, GTimerThreadHandler, 0);
if (hThread) {gfxThreadClose(hThread);} // We never really need the handle again
}
// Is this already scheduled?
if (pt->flags & GTIMER_FLG_SCHEDULED) {
// Cancel it!
if (pt->next == pt)
pTimerHead = 0;
else {
pt->next->prev = pt->prev;
pt->prev->next = pt->next;
if (pTimerHead == pt)
pTimerHead = pt->next;
}
}
// Set up the timer structure
pt->fn = fn;
pt->param = param;
pt->flags = GTIMER_FLG_SCHEDULED;
if (periodic)
pt->flags |= GTIMER_FLG_PERIODIC;
if (millisec == gDelayForever) {
pt->flags |= GTIMER_FLG_INFINITE;
pt->period = gDelayForever;
} else {
pt->period = gfxMillisecondsToTicks(millisec);
pt->when = gfxSystemTicks() + pt->period;
}
// Just pop it on the end of the queue
if (pTimerHead) {
pt->next = pTimerHead;
pt->prev = pTimerHead->prev;
pt->prev->next = pt;
pt->next->prev = pt;
} else
pt->next = pt->prev = pTimerHead = pt;
// Bump the thread
if (!(pt->flags & GTIMER_FLG_INFINITE))
gfxSemSignal(&waitsem);
gfxMutexExit(&mutex);
}
void gtimerStop(GTimer *pt) {
gfxMutexEnter(&mutex);
if (pt->flags & GTIMER_FLG_SCHEDULED) {
// Cancel it!
if (pt->next == pt)
pTimerHead = 0;
else {
pt->next->prev = pt->prev;
pt->prev->next = pt->next;
if (pTimerHead == pt)
pTimerHead = pt->next;
}
// Make sure we know the structure is dead!
pt->flags = 0;
}
gfxMutexExit(&mutex);
}
gBool gtimerIsActive(GTimer *pt) {
return (pt->flags & GTIMER_FLG_SCHEDULED) ? gTrue : gFalse;
}
void gtimerJab(GTimer *pt) {
gfxMutexEnter(&mutex);
// Jab it!
pt->flags |= GTIMER_FLG_JABBED;
// Bump the thread
gfxSemSignal(&waitsem);
gfxMutexExit(&mutex);
}
void gtimerJabI(GTimer *pt) {
// Jab it!
pt->flags |= GTIMER_FLG_JABBED;
// Bump the thread
gfxSemSignalI(&waitsem);
}
#endif /* GFX_USE_GTIMER */