diff --git a/gfx.mk b/gfx.mk index 5a7e8610..6f199fb0 100644 --- a/gfx.mk +++ b/gfx.mk @@ -5,6 +5,9 @@ endif GFXSRC += $(GFXLIB)/src/gdisp.c \ $(GFXLIB)/src/gdisp_fonts.c \ + $(GFXLIB)/src/gevent.c \ + $(GFXLIB)/src/gtimer.c \ + $(GFXLIB)/src/ginput.c \ $(GFXLIB)/src/gwin.c \ $(GFXLIB)/src/touchscreen.c \ $(GFXLIB)/src/graph.c \ diff --git a/include/gevent.h b/include/gevent.h new file mode 100644 index 00000000..e7c5dcbf --- /dev/null +++ b/include/gevent.h @@ -0,0 +1,222 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ +/** + * @file gevent.h + * @brief GEVENT GFX User Event subsystem header file. + * + * @addtogroup GEVENT + * @{ + */ +#ifndef _GEVENT_H +#define _GEVENT_H + +#ifndef GFX_USE_GEVENT + #define GFX_USE_GEVENT FALSE +#endif + +#if GFX_USE_GEVENT || defined(__DOXYGEN__) + +/** + * @name GEVENT macros and more complex functionality to be compiled + * @{ + */ + /** + * @brief Data part of a static GListener initializer. + */ + #define _GLISTENER_DATA(name) { _SEMAPHORE_DATA(name.waitqueue, 0), _BSEMAPHORE_DATA(name.eventlock, FALSE), {0} } + /** + * @brief Static GListener initializer. + */ + #define GLISTENER_DECL(name) GListener name = _GLISTENER_DATA(name) + /** + * @brief Defines the maximum size of an event status variable. + * @details Defaults to 32 bytes + */ + #ifndef GEVENT_MAXIMUM_STATUS_SIZE + #define GEVENT_MAXIMUM_STATUS_SIZE 32 + #endif + /** + * @brief Should routines assert() if they run out of resources. + * @details Defaults to FALSE. + * @details If FALSE the application must be prepared to handle these + * failures. + */ + #ifndef GEVENT_ASSERT_NO_RESOURCE + #define GEVENT_ASSERT_NO_RESOURCE FALSE + #endif + /** + * @brief Defines the maximum Source/Listener pairs in the system. + * @details Defaults to 32 + */ + #ifndef MAX_SOURCE_LISTENERS + #define MAX_SOURCE_LISTENERS 32 + #endif +/** @} */ + +/*===========================================================================*/ +/* Low Level Driver details and error checks. */ +/*===========================================================================*/ + +#if !CH_USE_MUTEXES || !CH_USE_SEMAPHORES + #error "GEVENT: CH_USE_MUTEXES and CH_USE_SEMAPHORES must be defined in chconf.h" +#endif + +/*===========================================================================*/ +/* Type definitions */ +/*===========================================================================*/ + +typedef uint16_t GEventType; + #define GEVENT_NULL 0x0000 // Null Event - Do nothing + #define GEVENT_EXIT 0x0001 // The listener is being forced to exit (someone is destroying the listener) + + /* Other event types are allocated in ranges in their respective include files */ + #define GEVENT_GINPUT_FIRST 0x0100 // GINPUT events range from 0x0100 to 0x01FF + #define GEVENT_GWIN_FIRST 0x0200 // GWIN events range from 0x0200 to 0x02FF + #define GEVENT_USER_FIRST 0x8000 // Any application defined events start at 0x8000 + +// This object can be typecast to any GEventXxxxx type to allow any sub-system (or the application) to create events. +// The prerequisite is that the new status structure type starts with a field named 'type' of type 'GEventType'. +// The total status structure also must not exceed GEVENT_MAXIMUM_STATUS_SIZE bytes. +// For example, this is used by GWIN button events, GINPUT data streams etc. +typedef union GEvent_u { + GEventType type; // The type of this event + char pad[GEVENT_MAXIMUM_STATUS_SIZE]; // This is here to allow static initialisation of GEventObject's in the application. + } GEvent; + +// The Listener Object +typedef struct GListener { + Semaphore waitqueue; // Private: Semaphore for the listener to wait on. + BinarySemaphore eventlock; // Private: Protect against more than one sources trying to use this event lock at the same time + GEvent event; // Public: The event object into which the event information is stored. + } GListener; + +// The Source Object +typedef struct GSource_t GSource, *GSourceHandle; + +// This structure is passed to a source to describe a contender listener for sending the current event. +typedef struct GSourceListener_t { + GListener *pListener; // The listener + GSource *pSource; // The source + unsigned listenflags; // The flags the listener passed when the source was assigned to it. + unsigned srcflags; // For the source's exclusive use. Initialised as 0 for a new listener source assignment. + } GSourceListener; + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* How to listen for events (act as a Listener)... + 1. Get handles for all the event sources you are interested in. + 2. Initialise a listener + 3. Attach sources to your listener. + - Sources can be attached or detached from a listener at any time. + - A source can be attached to more than one listener. + 4. Loop on getting listener events + 5. When finished detach all sources from the listener + + How to create events (act as a Source)... + 1. Provide a funtion to the application that returns a GSourceHandle (which can be a pointer to whatever the source wants) + 2. Whenever a possible event occurs call geventGetSourceListener to get a pointer to a GSourceListener. + This will return NULL when there are no more listeners. + For each listener - check the flags to see if an event should be sent. + - use geventGetEvent() to get the event buffer supplied by the listener + and then call geventSendEvent to send the event. + - Note: geventGetEvent() may return FALSE to indicate the listener is currently not listening and + therefore no event should be sent. This situation enables the source to (optionally) flag + to the listener on its next wait that there have been missed events. + - Note: The GSourceListener pointer (and the GEvent buffer) are only valid between + the geventGetSourceListener call and either the geventSendEvent call or the next + geventGetSourceListener call. + - Note: All listeners must be processed for this event before anything else is processed. +*/ + +/*---------- Listener Functions --------------------------------------------*/ + +/* Initialise a Listener. + */ +void geventListenerInit(GListener *pl); + +/* Attach a source to a listener. + * Flags are interpreted by the source when generating events for each listener. + * If this source is already assigned to the listener it will update the flags. + * If insufficient resources are available it will either assert or return FALSE + * depending on the value of GEVENT_ASSERT_NO_RESOURCE. + */ +bool_t geventAttachSource(GListener *pl, GSourceHandle gsh, unsigned flags); + +/* Detach a source from a listener + * If gsh is NULL detach all sources from this listener and if there is still + * a thread waiting for events on this listener, it is sent the exit event. + */ +void geventDetachSource(GListener *pl, GSourceHandle gsh); + +/* Wait for an event on a listener from an assigned source. + * The type of the event should be checked (pevent->type) and then pevent should be typecast to the + * actual event type if it needs to be processed. + * timeout specifies the time to wait in system ticks. + * TIME_INFINITE means no timeout - wait forever for an event. + * TIME_IMMEDIATE means return immediately + * Returns NULL on timeout. + * Note: The GEvent buffer is staticly allocated within the GListener so the event does not + * need to be dynamicly freed however it will get overwritten by the next call to + * this routine. + */ +GEvent *geventEventWait(GListener *pl, systime_t timeout); + +/*---------- Source Functions --------------------------------------------*/ + +/* Sources create their own GSourceHandles which are pointers to any arbitrary structure + typecast to a GSourceHandle. +*/ + +/* Called by a source with a possible event to get a listener record. + * 'lastlr' should be NULL on the first call and thereafter the result of the previous call. + * It will return NULL when there are no more listeners for this source. + */ +GSourceListener *geventGetSourceListener(GSourceHandle gsh, GSourceListener *lastlr); + +/* Get the event buffer from the GSourceListener. + * Returns NULL if the listener is not currently listening. + * A NULL return allows the source to record (perhaps in glr->scrflags) that the listener has missed events. + * This can then be notified as part of the next event for the listener. + * The buffer can only be accessed untill the next call to geventGetSourceListener or geventSendEvent + */ +GEvent *geventGetEventBuffer(GSourceListener *psl); + +/* Called by a source to indicate the listener's event buffer has been filled. + * After calling this function the source must not reference in fields in the GSourceListener or the event buffer. + */ +void geventSendEvent(GSourceListener *psl); + +/* Detach any listener that has this source attached */ +void geventDetachSourceListeners(GSourceHandle gsh); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GEVENT */ + +#endif /* _GEVENT_H */ +/** @} */ diff --git a/include/ginput.h b/include/ginput.h new file mode 100644 index 00000000..de2c617b --- /dev/null +++ b/include/ginput.h @@ -0,0 +1,339 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ +/** + * @file ginput.h + * @brief GINPUT GFX User Input subsystem header file. + * + * @addtogroup GINPUT + * @{ + */ +#ifndef _GINPUT_H +#define _GINPUT_H + +#ifndef GFX_USE_GINPUT + #define GFX_USE_GINPUT FALSE +#endif + +#if GFX_USE_GINPUT || defined(__DOXYGEN__) + +/** + * @name GINPUT more complex functionality to be compiled + * @{ + */ + /** + * @brief Should mouse functions be included. + * @details Defaults to FALSE + */ + #ifndef GINPUT_NEED_MOUSE + #define GINPUT_NEED_MOUSE FALSE + #endif + /** + * @brief Should touch functions be included. + * @details Defaults to FALSE + */ + #ifndef GINPUT_NEED_TOUCH + #define GINPUT_NEED_TOUCH FALSE + #endif + /** + * @brief Should keyboard functions be included. + * @details Defaults to FALSE + */ + #ifndef GINPUT_NEED_KEYBOARD + #define GINPUT_NEED_KEYBOARD FALSE + #endif + /** + * @brief Should hardware toggle/switch/button (pio) functions be included. + * @details Defaults to FALSE + */ + #ifndef GINPUT_NEED_TOGGLE + #define GINPUT_NEED_TOGGLE FALSE + #endif + /** + * @brief Should analog dial functions be included. + * @details Defaults to FALSE + */ + #ifndef GINPUT_NEED_DIAL + #define GINPUT_NEED_DIAL FALSE + #endif +/** @} */ + +/*===========================================================================*/ +/* Low Level Driver details and error checks. */ +/*===========================================================================*/ + +#ifndef GFX_USE_GDISP + #define GFX_USE_GDISP FALSE +#endif +#if GINPUT_NEED_TOUCH || !GFX_USE_GDISP + #error "GINPUT: GFX_USE_GDISP must be defined for touch functions" +#endif + +#if GFX_USE_GDISP + #include "gdisp.h" +#else + // We require some basic type definitions normally kept in gdisp.h + typedef int16_t coord_t; +#endif + +#ifndef GFX_USE_GEVENT + #define GFX_USE_GEVENT TRUE + #include "gevent.h" +#elif !GFX_USE_GEVENT + #error "GINPUT: GFX_USE_GEVENT must be defined" +#endif + +#ifndef GFX_USE_GTIMER + #define GFX_USE_GTIMER TRUE + #include "gtimer.h" +#elif !GFX_USE_GTIMER + #error "GINPUT: GFX_USE_GTIMER must be defined" +#endif + +/*===========================================================================*/ +/* Type definitions */ +/*===========================================================================*/ + +// Event types for various ginput sources +#define GEVENT_MOUSE (GEVENT_GINPUT_FIRST+0) +#define GEVENT_TOUCH (GEVENT_GINPUT_FIRST+1) +#define GEVENT_KEYBOARD (GEVENT_GINPUT_FIRST+2) +#define GEVENT_TOGGLE (GEVENT_GINPUT_FIRST+3) +#define GEVENT_DIAL (GEVENT_GINPUT_FIRST+4) + +#if GINPUT_NEED_MOUSE || GINPUT_NEED_TOUCH + typedef struct GEventMouse_t { + GEventType type; // The type of this event (GEVENT_MOUSE or GEVENT_TOUCH) + uint16_t instance; // The mouse/touch instance + coord_t x, y, z; // The position of the mouse. + // - For touch devices, Z is the current pressure if supported (otherwise 0) + // - For mice, Z is the 3rd dimension if supported (otherwise 0) + uint16_t current_buttons; // A bit is set if the button is down. + // - For touch only bit 0 is relevant + // - For mice the order of the buttons is (from 0 to n) left, right, middle, any other buttons + // - Bit 15 being set indicates that an important mouse event has been missed. + #define GINPUT_TOUCH_PRESSED 0x0001 + #define GINPUT_MOUSE_BTN_LEFT 0x0001 + #define GINPUT_MOUSE_BTN_RIGHT 0x0002 + #define GINPUT_MOUSE_BTN_MIDDLE 0x0004 + #define GINPUT_MOUSE_BTN_4 0x0008 + #define GINPUT_MISSED_MOUSE_EVENT 0x8000 + uint16_t last_buttons; // The value of current_buttons on the last event + enum GMouseMeta_e { + GMETA_NONE, // There is no meta event currently happenning + GMETA_DOWN, GMETA_UP, // Button 0 has just gone up or down + GMETA_CLICK, // Button 0 has just gone through a short down - up cycle + GMETA_CXTCLICK // For mice - The right button has just been depressed + // For touch - a long press has just occurred + } meta; + } GEventMouse, GEventTouch; + + // Mouse/Touch Listen Flags - passed to geventAddSourceToListener() + #define GLISTEN_MOUSEMETA 0x0001 // Create events for meta events such as CLICK and CXTCLICK + #define GLISTEN_MOUSEDOWNMOVES 0x0002 // Creates mouse move events when the primary mouse button is down (touch is on the surface) + #define GLISTEN_MOUSEUPMOVES 0x0004 // Creates mouse move events when the primary mouse button is up (touch is off the surface - if the hardware allows). + #define GLISTEN_TOUCHMETA 0x0001 // Ditto for touch + #define GLISTEN_TOUCHDOWNMOVES 0x0002 + #define GLISTEN_TOUCHUPMOVES 0x0004 +#endif + +#if GINPUT_NEED_KEYBOARD + typedef struct GEventKeyboard_t { + GEventType type; // The type of this event (GEVENT_KEYBOARD) + uint16_t instance; // The keyboard instance + char c; // The Ascii code for the current key press. + // The only possible values are 0(NUL), 8(BS), 9(TAB), 13(CR), 27(ESC), 32(SPACE) to 126(~), 127(DEL) + // 0 indicates an extended only key. + uint16_t code; // An extended keyboard code. Codes less than 128 match their ascii equivelent. + #define GKEY_NULL 0 + #define GKEY_BACKSPACE 8 + #define GKEY_TAB 9 + #define GKEY_CR 13 + #define GKEY_ESC 27 + #define GKEY_SPACE 32 + #define GKEY_DEL 127 + #define GKEY_UP 0x0101 + #define GKEY_DOWN 0x0102 + #define GKEY_LEFT 0x0103 + #define GKEY_RIGHT 0x0104 + #define GKEY_HOME 0x0105 + #define GKEY_END 0x0106 + #define GKEY_PAGEUP 0x0107 + #define GKEY_PAGEDOWN 0x0108 + #define GKEY_INSERT 0x0109 + #define GKEY_DELETE 0x010A + #define GKEY_SHIFT 0x0201 + #define GKEY_CNTRL 0x0202 + #define GKEY_ALT 0x0203 + #define GKEY_WINKEY 0x0204 + #define GKEY_RCLKEY 0x0205 + #define GKEY_FNKEY 0x0206 + #define GKEY_FN1 0x0301 + #define GKEY_FN2 0x0302 + #define GKEY_FN3 0x0303 + #define GKEY_FN4 0x0304 + #define GKEY_FN5 0x0305 + #define GKEY_FN6 0x0306 + #define GKEY_FN7 0x0307 + #define GKEY_FN8 0x0308 + #define GKEY_FN9 0x0309 + #define GKEY_FN10 0x030A + #define GKEY_FN11 0x030B + #define GKEY_FN12 0x030C + uint16_t current_buttons; // A bit is set to indicate various meta status. + #define GMETA_KEYDN 0x0001 + #define GMETA_SHIFT 0x0002 + #define GMETA_CNTRL 0x0004 + #define GMETA_ALT 0x0008 + #define GMETA_WINKEY 0x0010 + #define GMETA_RCLKKEY 0x0020 + #define GMETA_FNKEY 0x0040 + #define GMETA_MISSED_EVENT 0x8000 + uint16_t last_buttons; // The value of current_buttons on the last event + } GEventKeyboard; + + // Keyboard Listen Flags - passed to geventAddSourceToListener() + #define GLISTEN_KEYREPEATS 0x0001 // Return key repeats (where the key is held down to get a repeat character) + #define GLISTEN_KEYCODES 0x0002 // Return all key presses including extended code key presses (not just ascii codes) + #define GLISTEN_KEYALL 0x0004 // Return keyup's, keydown's and everything in between (but not repeats unless GLISTEN_KEYREPEATS is set). + #define GLISTEN_KEYSINGLE 0x8000 // Return only when one particular extended code key is pressed or released. The particular extended code is OR'd into this value + // eg. (GLISTEN_KEYSINGLE | GKEY_CR) + // No other flags may be set with this flag. +#endif + +#if GINPUT_NEED_TOGGLE + typedef struct GEventToggle_t { + GEventType type; // The type of this event (GEVENT_TOGGLE) + uint16_t instance; // The toggle instance + BOOL on; // True if the toggle/button is on + } GEventToggle; +#endif + +#if GINPUT_NEED_DIAL + typedef struct GEventDial_t { + GEventType type; // The type of this event (GEVENT_DIAL) + uint16_t instance; // The dial instance + uint16_t value; // The dial value + } GEventDial; +#endif + + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* How to use... + + 1. Get source handles for all the inputs you are interested in. + - Attempting to get a handle for one instance of an input more than once will return the same handle + 2. Create a listener + 3. Assign inputs to your listener. + - Inputs can be assigned or released from a listener at any time. + - An input can be assigned to more than one listener. + 4. Loop on getting listener events + 5. When complete destroy the listener +*/ + +#if GINPUT_NEED_MOUSE + /* Mouse Functions */ + GSourceHandle ginputGetMouse(uint16_t instance); // Instance = 0 to n-1 + + /* Get the current mouse position and button status. + * Unlike a listener event, this status cannot record meta events such as "CLICK" + * Returns FALSE on error (eg invalid instance) + */ + BOOL ginputGetMouseStatus(uint16_t instance, GEventMouse *pmouse); +#endif + +#if GINPUT_NEED_TOUCH + /* Touch Functions */ + GSourceHandle ginputGetTouch(uint16_t instance); // Instance = 0 to n-1 + + /* Get the current touch position and button status. + * Unlike a listener event, this status cannot record meta events such as "CLICK" + * Returns FALSE on error (eg invalid instance) + */ + BOOL ginputGetTouchStatus(uint16_t instance, GEventTouch *ptouch); + + /* Run a touch calibration. + * Returns FALSE if the driver doesn't support it or if the handle is invalid. + */ + BOOL ginputCalibrateTouch(uint16_t instance); + + /* Set the routines to save and fetch calibration data. + * This function should be called before first calling ginputGetTouch() for a particular instance + * as the gdispGetTouch() routine may attempt to fetch calibration data and perform a startup calibration if there is no way to get it. + * If this is called after gdispGetTouch() has been called and the driver requires calibration storage, it will immediately save the data is has already obtained. + * The 'requireFree' parameter indicates if the fetch buffer must be free()'d to deallocate the buffer provided by the Fetch routine. + */ + typedef void (*)(uint16_t instance, const uint8_t *calbuf, size_t sz) GTouchCalibrationSaveRoutine; // Save calibration data + typedef const char * (*)(uint16_t instance) GTouchCalibrationFetchRoutine; // Fetch calibration data (returns NULL if not data saved) + void ginputSetTouchCalibrationRoutines(uint16_t instance, GTouchCalibrationSaveRoutine fnsave, GTouchCalibrationFetchRoutine fnfetch, BOOL requireFree); + + /* Test if a particular touch instance requires routines to save its calibration data. */ + BOOL ginputRequireTouchCalibrationStorage(uint16_t instance); +#endif + +#if GINPUT_NEED_KEYBOARD + /* Keyboard Functions */ + GSourceHandle ginputGetKeyboard(uint16_t instance); // Instance = 0 to n-1 + + /* Get the current keyboard button status. + * Returns FALSE on error (eg invalid instance) + */ + BOOL ginputGetKeyboardStatus(uint16_t instance, GEventKeyboard *pkeyboard); +#endif + +#if GINPUT_NEED_TOGGLE + /* Hardware Toggle/Switch/Button Functions */ + GSourceHandle ginputGetToggle(uint16_t instance); // Instance = 0 to n-1 + void ginputInvertToggle(uint16_t instance, BOOL invert); // If invert is true, invert the on/off sense for the toggle + + /* Get the current toggle status. + * Returns FALSE on error (eg invalid instance) + */ + BOOL ginputGetToggleStatus(uint16_t instance, GEventToggle *ptoggle); +#endif + +#if GINPUT_NEED_DIAL + /* Dial Functions */ + GSourceHandle ginputGetDial(uint16_t instance); // Instance = 0 to n-1 + void ginputResetDialRange(uint16_t instance); // Reset the maximum value back to the hardware default. + uint16_t ginputGetDialRange(uint16_t instance); // Get the maximum value. The readings are scaled to be 0...max-1. 0 means over the full uint16_t range. + void ginputSetDialRange(uint16_t instance, uint16_t max); // Set the maximum value. + void ginputSetDialSensitivity(uint16_t instance, uint16_t diff); // Set the level change required before a dial event is generated. + // - This is done after range scaling + /* Get the current keyboard button status. + * Returns FALSE on error (eg invalid instance) + */ + BOOL ginputGetDialStatus(uint16_t instance, GEventDial *pdial); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GINPUT */ + +#endif /* _GINPUT_H */ +/** @} */ diff --git a/include/gtimer.h b/include/gtimer.h new file mode 100644 index 00000000..2946e0ea --- /dev/null +++ b/include/gtimer.h @@ -0,0 +1,179 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ +/** + * @file gtimer.h + * @brief GTIMER GFX User Timer subsystem header file. + * + * @addtogroup GEVENT + * @{ + */ +#ifndef _GTIMER_H +#define _GTIMER_H + +#ifndef GFX_USE_GTIMER + #define GFX_USE_GTIMER FALSE +#endif + +#if GFX_USE_GTIMER || defined(__DOXYGEN__) + +/** + * @name GTIMER macros and more complex functionality to be compiled + * @{ + */ + /** + * @brief Data part of a static GTimer initializer. + */ + #define _GTIMER_DATA() {0} + /** + * @brief Static GTimer initializer. + */ + #define GTIMER_DECL(name) GTimer name = _GTIMER_DATA() + /** + * @brief Defines the size of the timer threads work area (stack+structures). + * @details Defaults to 512 bytes + */ + #ifndef GTIMER_THREAD_STACK_SIZE + #define GTIMER_THREAD_STACK_SIZE 512 + #endif +/** @} */ + +/*===========================================================================*/ +/* Low Level Driver details and error checks. */ +/*===========================================================================*/ + +#if !CH_USE_MUTEXES || !CH_USE_SEMAPHORES + #error "GTIMER: CH_USE_MUTEXES and CH_USE_SEMAPHORES must be defined in chconf.h" +#endif + +/*===========================================================================*/ +/* Type definitions */ +/*===========================================================================*/ + +// A callback function (executed in a thread context) +typedef void (*GTimerFunction)(void *param); + +// A GTimer structure. +typedef struct GTimer_t { + GTimerFunction fn; + void *param; + systime_t when; + systime_t period; + uint16_t flags; + struct GTimer_t *next; + struct GTimer_t *prev; + } GTimer; + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialise a timer. + * + * @param[in] pt pointer to a GTimer structure + * + * @api + */ +void gtimerInit(GTimer *pt); + +/** + * @brief Set a timer going or alter its properties if it is already going. + * + * @param[in] pt Pointer to a GTimer structure + * @param[in] fn The callback function + * @param[in] param The parameter to pass to the callback function + * @param[in] periodic Is the timer a periodic timer? FALSE is a once-only timer. + * @param[in] millisec The timer period. The following special values are allowed: + * TIME_IMMEDIATE causes the callback function to be called asap. + * A periodic timer with this value will fire once only. + * TIME_INFINITE never timeout (unless triggered by gtimerJab or gtimerJabI) + * + * @note If the timer is already active its properties are updated with the new parameters. + * The current period will be immediately canceled (without the callback function being + * called) and the timer will be restart with the new timer properties. + * @note The callback function should be careful not to over-run the thread stack. + * Define a new value for the macro GTIME_THREAD_STACK_SIZE if you want to + * change the default size. + * @note The callback function should return as quickly as possible as all + * timer callbacks are performed by a single thread. If a callback function + * takes too long it could affect the timer response for other timers. + * @note A timer callback function is not a replacement for a dedicated thread if the + * function wants to perform computationally expensive stuff. + * @note As the callback function is called on GTIMER's thread, the function must make sure it uses + * appropriate synchronisation controls such as semaphores or mutexes around any data + * structures it shares with other threads such as the main application thread. + * + * @api + */ +void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, bool_t periodic, systime_t millisec); + +/** + * @brief Stop a timer (periodic or otherwise) + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * + * @api + */ +void gtimerStop(GTimer *pt); + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from a normal thread context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJab(GTimer *pt); + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from an interrupt routine context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJabI(GTimer *pt); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GTIMER */ + +#endif /* _GTIMER_H */ +/** @} */ diff --git a/releases.txt b/releases.txt index 83680ae1..caadade2 100644 --- a/releases.txt +++ b/releases.txt @@ -14,6 +14,9 @@ FIX: gdisp Win32 driver fixes DEPRECATE: console deprecated - replaced with gwin functionality FEATURE: ILI9320 GDISP driver FEATURE: gdisp Win32 driver - full orientation support +FEATURE: GEVENT - for passing event structures from Sources to Listeners +FEATURE: GTIMER - thread context based once-off and periodic timers. +FEATURE: GINPUT - extensible, multiple device-type, input sub-system. *** changes after 1.3 *** diff --git a/src/gevent.c b/src/gevent.c new file mode 100644 index 00000000..bfda6d53 --- /dev/null +++ b/src/gevent.c @@ -0,0 +1,216 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ + +/** + * @file src/gevent.c + * @brief GEVENT Driver code. + * + * @addtogroup GEVENT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gevent.h" + +#ifndef _GEVENT_C +#define _GEVENT_C + +#if GFX_USE_GEVENT || defined(__DOXYGEN__) + +#if GEVENT_ASSERT_NO_RESOURCE + #define GEVENT_ASSERT(x) assert(x) +#else + #define GEVENT_ASSERT(x) +#endif + +// This mutex protects access to our tables +static MUTEX_DECL(geventMutex); + +// Our table of listener/source pairs +static GSourceListener Assignments[MAX_SOURCE_LISTENERS]; + +// Loop through the assignment table deleting this listener/source pair. +// Null is treated as a wildcard. +static void deleteAssignments(GListener *pl, GSourceHandle gsh) { + GSourceListener *psl; + + for(psl = Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + if ((!pl || psl->pListener == pl) && (!gsh || psl->pSource == gsh)) { + if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) { + chBSemWait(&psl->pListener->eventlock); // Obtain the buffer lock + psl->pListener->event.type = GEVENT_EXIT; // Set up the EXIT event + chSemSignal(&psl->pListener->waitqueue); // Wake up the listener + chBSemSignal(&psl->pListener->eventlock); // Release the buffer lock + } + psl->pListener = 0; + } + } +} + +/* Create a Listener. + * If insufficient resources are available it will either assert or return NULL + * depending on the value of GEVENT_ASSERT_NO_RESOURCE. + */ +void geventListenerInit(GListener *pl) { + chSemInit(&pl->waitqueue, 0); // Next wait'er will block + chBSemInit(&pl->eventlock, FALSE); // Only one thread at a time looking at the event buffer + pl->event.type = GEVENT_NULL; // Always safety +} + +/* Attach a source to a listener. + * Flags are interpreted by the source when generating events for each listener. + * If this source is already assigned to the listener it will update the flags. + * If insufficient resources are available it will either assert or return FALSE + * depending on the value of GEVENT_ASSERT_NO_RESOURCE. + */ +bool_t geventAttachSource(GListener *pl, GSourceHandle gsh, unsigned flags) { + GSourceListener *psl, *pslfree; + + // Safety first + if (!pl || !gsh) { + GEVENT_ASSERT(FALSE); + return FALSE; + } + + chMtxLock(&geventMutex); + + // Check if this pair is already in the table (scan for a free slot at the same time) + pslfree = 0; + for(psl = Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + + if (pl == psl->pListener && gsh == psl->pSource) { + // Just update the flags + chBSemWait(&pl->eventlock); // Safety first - just in case a source is using it + psl->listenflags = flags; + chBSemSignal(&pl->eventlock); // Release this lock + chMtxUnlock(); + return TRUE; + } + if (!pslfree && !psl->pListener) + pslfree = psl; + } + + // A free slot was found - allocate it + if (pslfree) { + pslfree->pListener = pl; + pslfree->pSource = gsh; + pslfree->listenflags = flags; + pslfree->srcflags = 0; + } + chMtxUnlock(); + GEVENT_ASSERT(pslfree != 0); + return pslfree != 0; +} + +/* Detach a source from a listener + * If gsh is NULL detach all sources from this listener and if there is still + * a thread waiting for events on this listener, it is sent the exit event. + */ +void geventDetachSource(GListener *pl, GSourceHandle gsh) { + if (pl && gsh) { + chMtxLock(&geventMutex); + deleteAssignments(pl, gsh); + if (!gsh && chSemGetCounterI(&pl->waitqueue) < 0) { + chBSemWait(&pl->eventlock); // Obtain the buffer lock + pl->event.type = GEVENT_EXIT; // Set up the EXIT event + chSemSignal(&pl->waitqueue); // Wake up the listener + chBSemSignal(&pl->eventlock); // Release the buffer lock + } + chMtxUnlock(); + } +} + +/* Wait for an event on a listener from an assigned source. + * The type of the event should be checked (pevent->type) and then pevent should be typecast to the + * actual event type if it needs to be processed. + * timeout specifies the time to wait in system ticks. + * TIME_INFINITE means no timeout - wait forever for an event. + * TIME_IMMEDIATE means return immediately + * Returns NULL on timeout. + * Note: The GEvent buffer is staticly allocated within the GListener so the event does not + * need to be dynamicly freed however it will get overwritten by the next call to + * this routine. + */ +GEvent *geventEventWait(GListener *pl, systime_t timeout) { + return chSemWaitTimeout(&pl->waitqueue, timeout) == RDY_OK ? &pl->event : 0; +} + +/* Called by a source with a possible event to get a listener record. + * 'lastlr' should be NULL on the first call and thereafter the result of the previous call. + * It will return NULL when there are no more listeners for this source. + */ +GSourceListener *geventGetSourceListener(GSourceHandle gsh, GSourceListener *lastlr) { + GSourceListener *psl; + + // Safety first + if (!gsh) + return 0; + + chMtxLock(&geventMutex); + + // Unlock the last listener event buffer + if (lastlr) + chBSemSignal(&lastlr->pListener->eventlock); + + // Loop through the table looking for attachments to this source + for(psl = lastlr ? (lastlr+1) : Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + if (gsh == psl->pSource) { + chBSemWait(&psl->pListener->eventlock); // Obtain a lock on the listener event buffer + chMtxUnlock(); + return psl; + } + } + chMtxUnlock(); + return 0; +} + +/* Get the event buffer from the GSourceListener. + * Returns NULL if the listener is not currently listening. + * A NULL return allows the source to record (perhaps in glr->scrflags) that the listener has missed events. + * This can then be notified as part of the next event for the listener. + * The buffer can only be accessed untill the next call to geventGetSourceListener or geventSendEvent + */ +GEvent *geventGetEventBuffer(GSourceListener *psl) { + // We already know we have the event lock + return chSemGetCounterI(&psl->pListener->waitqueue) < 0 ? &psl->pListener->event : 0; +} + +/* Called by a source to indicate the listener's event buffer has been filled. + * After calling this function the source must not reference in fields in the GSourceListener or the event buffer. + */ +void geventSendEvent(GSourceListener *psl) { + chMtxLock(&geventMutex); + // Wake up the listener + if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) + chSemSignal(&psl->pListener->waitqueue); + chMtxUnlock(); +} + +/* Detach any listener that has this source attached */ +void geventDetachSourceListeners(GSourceHandle gsh) { + chMtxLock(&geventMutex); + deleteAssignments(0, gsh); + chMtxUnlock(); +} + +#endif /* GFX_USE_GEVENT */ + +#endif /* _GEVENT_C */ +/** @} */ diff --git a/src/ginput.c b/src/ginput.c new file mode 100644 index 00000000..d12bef2b --- /dev/null +++ b/src/ginput.c @@ -0,0 +1,42 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ + +/** + * @file src/ginput.c + * @brief GINPUT Driver code. + * + * @addtogroup GINPUT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "ginput.h" + +#ifndef _GINPUT_C +#define _GINPUT_C + +#if GFX_USE_GINPUT || defined(__DOXYGEN__) + +#error "GINPUT: Not Implemented Yet" + +#endif /* GFX_USE_GINPUT */ + +#endif /* _GINPUT_C */ +/** @} */ diff --git a/src/gtimer.c b/src/gtimer.c new file mode 100644 index 00000000..84518a23 --- /dev/null +++ b/src/gtimer.c @@ -0,0 +1,302 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free 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. + + ChibiOS/GFX 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 . +*/ + +/** + * @file src/gtimer.c + * @brief GTIMER Driver code. + * + * @addtogroup GTIMER + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gtimer.h" + +#ifndef _GTIMER_C +#define _GTIMER_C + +#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 + +#define TimeIsWithin(time, start, end) (end > start ? (time >= start && time <= end) : (time >= start || time <= end)) + +// This mutex protects access to our tables +static MUTEX_DECL(mutex); +static Thread *pThread = 0; +static GTimer *pTimerHead = 0; +static systime_t lastTime = 0; +static SEMAPHORE_DECL(waitsem, 0); +static WORKING_AREA(waTimerThread, GTIMER_THREAD_STACK_SIZE); + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static msg_t GTimerThreadHandler(void *arg) { + (void)arg; + GTimer *pt; + systime_t tm; + systime_t nxtTimeout; + systime_t tmptime; + GTimerFunction fn; + void *param; + + #if CH_USE_REGISTRY + chRegSetThreadName("GTimer"); + #endif + + nxtTimeout = TIME_INFINITE; + while(1) { + /* Wait for work to do. */ + chSemWaitTimeout(&waitsem, nxtTimeout); + + restartTimerChecks: + + // Our reference time + tm = chTimeNow(); + nxtTimeout = TIME_INFINITE; + + /* We need to obtain the mutex */ + chMtxLock(&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 != TIME_IMMEDIATE) { + // Yes - Update ready for the next period + if (!(pt->flags & GTIMER_FLG_INFINITE)) { + do { + pt->when += pt->period; // We may have skipped a period + } while (TimeIsWithin(pt->when, lastTime, tm)); + } + + // We are definitely no longer jabbed + pt->flags &= ~GTIMER_FLG_JABBED; + + } else { + // No - get us off the timers list + if (pt->next == pt->prev) + 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; + chMtxUnlock(); + 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)) { + tmptime = pt->when - tm; + if (tmptime < nxtTimeout) nxtTimeout = tmptime; + } + pt = pt->next; + } while(pt != pTimerHead); + } + + // Ready for the next loop + lastTime = tm; + chMtxUnlock(); + } + return 0; +} + +/** + * @brief Initialise a timer. + * + * @param[in] pt pointer to a GTimer structure + * + * @api + */ +void gtimerInit(GTimer *pt) { + pt->flags = 0; +} + +/** + * @brief Set a timer going or alter its properties if it is already going. + * + * @param[in] pt Pointer to a GTimer structure + * @param[in] fn The callback function + * @param[in] param The parameter to pass to the callback function + * @param[in] periodic Is the timer a periodic timer? FALSE is a once-only timer. + * @param[in] millisec The timer period. The following special values are allowed: + * TIME_IMMEDIATE causes the callback function to be called asap. + * A periodic timer with this value will fire once only. + * TIME_INFINITE never timeout (unless triggered by gtimerJab or gtimerJabI) + * + * @note If the timer is already active its properties are updated with the new parameters. + * The current period will be immediately canceled (without the callback function being + * called) and the timer will be restart with the new timer properties. + * @note The callback function should be careful not to over-run the thread stack. + * Define a new value for the macro GTIME_THREAD_STACK_SIZE if you want to + * change the default size. + * @note The callback function should return as quickly as possible as all + * timer callbacks are performed by a single thread. If a callback function + * takes too long it could affect the timer response for other timers. + * @note A timer callback function is not a replacement for a dedicated thread if the + * function wants to perform computationally expensive stuff. + * @note As the callback function is called on GTIMER's thread, the function must make sure it uses + * appropriate synchronisation controls such as semaphores or mutexes around any data + * structures it shares with other threads such as the main application thread. + * + * @api + */ +void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, bool_t periodic, systime_t millisec) { + chMtxLock(&mutex); + + // Start our thread if not already going + if (!pThread) + pThread = chThdCreateStatic(waTimerThread, sizeof(waTimerThread), HIGHPRIO, GTimerThreadHandler, NULL); + + // Is this already scheduled? + if (pt->flags & GTIMER_FLG_SCHEDULED) { + // Cancel it! + if (pt->next == pt->prev) + 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 != TIME_INFINITE) { + pt->period = MS2ST(millisec); + pt->when = chTimeNow() + pt->period; + } else + pt->flags |= GTIMER_FLG_INFINITE; + + // 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 + chSemSignal(&waitsem); + chMtxUnlock(); +} + +/** + * @brief Stop a timer (periodic or otherwise) + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * + * @api + */ +void gtimerStop(GTimer *pt) { + chMtxLock(&mutex); + if (pt->flags & GTIMER_FLG_SCHEDULED) { + // Cancel it! + if (pt->next == pt->prev) + 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; + } + chMtxUnlock(); +} + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from a normal thread context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJab(GTimer *pt) { + chMtxLock(&mutex); + + // Jab it! + pt->flags |= GTIMER_FLG_JABBED; + + // Bump the thread + chSemSignal(&waitsem); + chMtxUnlock(); +} + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from an interrupt routine context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJabI(GTimer *pt) { + // Jab it! + pt->flags |= GTIMER_FLG_JABBED; + + // Bump the thread + chSemSignalI(&waitsem); +} + +#endif /* GFX_USE_GTIMER */ + +#endif /* _GTIMER_C */ +/** @} */