|
- /* EventResponder - Simple event-based programming for Arduino
- * Copyright 2017 Paul Stoffregen
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- /* EventResponder is an experimental API, almost certain to
- * incompatibly change as it develops. Please understand any
- * programs you write now using EventResponder may need to be
- * updated as EventResponder develops.
- *
- * Please post your EventResponder feedback here:
- * https://forum.pjrc.com/threads/44723-Arduino-Events
- */
-
- #if !defined(EventResponder_h) && defined(__cplusplus)
- #define EventResponder_h
-
- #include <core/Arduino.h>
-
- /* EventResponder lets you control how your program responds to an event.
- * Imagine a basketball or football (American soccer) player who gets the
- * ball. Usually they will pass to another player who has the best
- * opportunity to score. Similarly in Arduino programming, events are
- * often triggered within interrupts or other timing sensitive code.
- * EventResponder can call your function a short time later, giving you
- * the ability to use Arduino functions and libraries which would not
- * be safe to use from an interrupt. However, some situations do call
- * for the most immediate response, even if doing so is more difficult.
- * EventResponder lets you choose how your function will be called,
- * without editing the timers or libraries which trigger the events.
- *
- * Event handling functions called by EventResponder should complete
- * their work quickly. Avoid delays or operations which may take
- * substantial time. While your function runs, no other event functions
- * (attached the same way) are able to run.
- *
- * If your EventResponder is triggered more than once before your
- * function can run, only the last trigger is used. Prior triggering,
- * including the status integer and data pointer, are overwritten and
- * your function is called only one time, based on the last trigger
- * event.
- */
- extern "C" void systick_isr_with_timer_events(void);
-
- class EventResponder;
- typedef EventResponder& EventResponderRef;
- typedef void (*EventResponderFunction)(EventResponderRef);
- class EventResponder
- {
- public:
- constexpr EventResponder() {
- }
- ~EventResponder() {
- detach();
- }
- enum EventType { // these are not meant for public consumption...
- EventTypeDetached = 0, // no function is called
- EventTypeYield, // function is called from yield()
- EventTypeImmediate, // function is called immediately
- EventTypeInterrupt, // function is called from interrupt
- EventTypeThread // function is run as a new thread
- };
-
- // Attach a function to be called from yield(). This should be the
- // default way to use EventResponder. Calls from yield() allow use
- // of Arduino libraries, String, Serial, etc.
- void attach(EventResponderFunction function, uint8_t priority=128) {
- bool irq = disableInterrupts();
- detachNoInterrupts();
- _function = function;
- _type = EventTypeYield;
- yield_active_check_flags |= YIELD_CHECK_EVENT_RESPONDER; // user setup a yield type...
- enableInterrupts(irq);
- }
-
- // Attach a function to be called immediately. This provides the
- // fastest possible response, but your function must be carefully
- // designed.
- void attachImmediate(EventResponderFunction function) {
- bool irq = disableInterrupts();
- detachNoInterrupts();
- _function = function;
- _type = EventTypeImmediate;
- enableInterrupts(irq);
- }
-
- // Attach a function to be called from a low priority interrupt.
- // Boards not supporting software triggered interrupts will implement
- // this as attachImmediate. On ARM and other platforms with software
- // interrupts, this allow fast interrupt-based response, but with less
- // disruption to other libraries requiring their own interrupts.
- void attachInterrupt(EventResponderFunction function, uint8_t priority=128) {
- bool irq = disableInterrupts();
- detachNoInterrupts();
- _function = function;
- _type = EventTypeInterrupt;
- SCB_SHPR3 |= 0x00FF0000; // configure PendSV, lowest priority
- // Make sure we are using the systic ISR that process this
- _VectorsRam[15] = systick_isr_with_timer_events;
- enableInterrupts(irq);
- }
-
- // Attach a function to be called as its own thread. Boards not running
- // a RTOS or pre-emptive scheduler shall implement this as attach().
- void attachThread(EventResponderFunction function, void *param=nullptr) {
- attach(function); // for non-RTOS usage, compile as default attach
- }
-
- // Do not call any function. The user's program must occasionally check
- // whether the event has occurred, or use one of the wait functions.
- void detach() {
- bool irq = disableInterrupts();
- detachNoInterrupts();
- enableInterrupts(irq);
- }
-
- // Trigger the event. An optional status code and data may be provided.
- // The code triggering the event does NOT control which of the above
- // response methods will be used.
- virtual void triggerEvent(int status=0, void *data=nullptr) {
- _status = status;
- _data = data;
- if (_type == EventTypeImmediate) {
- (*_function)(*this);
- } else {
- triggerEventNotImmediate();
- }
- }
- // Clear an event which has been triggered, but has not yet caused a
- // function to be called.
- bool clearEvent();
-
- // Get the event's status code. Typically this will indicate if the event was
- // triggered due to successful completion, or how much data was successfully
- // processed (positive numbers) or an error (negative numbers). The
- // exact meaning of this status code depends on the code or library which
- // triggers the event.
- int getStatus() { return _status; }
-
- // Get the optional data pointer associated with the event. Often this
- // will be NULL, or will be the object instance which triggered the event.
- // Some libraries may use this to pass data associated with the event.
- void * getData() { return _data; }
-
- // An optional "context" may be associated with each EventResponder.
- // When more than one EventResponder has the same function attached, these
- // may be used to allow the function to obtain extra information needed
- // depending on which EventResponder called it.
- void setContext(void *context) { _context = context; }
- void * getContext() { return _context; }
-
- // Wait for event(s) to occur. These are most likely to be useful when
- // used with a scheduler or RTOS.
- bool waitForEvent(EventResponderRef event, int timeout);
- EventResponder * waitForEvent(EventResponder *list, int listsize, int timeout);
- static void runFromYield() {
- if (!firstYield) return;
- // First, check if yield was called from an interrupt
- // never call normal handler functions from any interrupt context
- uint32_t ipsr;
- __asm__ volatile("mrs %0, ipsr\n" : "=r" (ipsr)::);
- if (ipsr != 0) return;
- // Next, check if any events have been triggered
- bool irq = disableInterrupts();
- EventResponder *first = firstYield;
- if (first == nullptr) {
- enableInterrupts(irq);
- return;
- }
- // Finally, make sure we're not being recursively called,
- // which can happen if the user's function does anything
- // that calls yield.
- if (runningFromYield) {
- enableInterrupts(irq);
- return;
- }
- // Ok, update the runningFromYield flag and process event
- runningFromYield = true;
- firstYield = first->_next;
- if (firstYield) {
- firstYield->_prev = nullptr;
- } else {
- lastYield = nullptr;
- }
- enableInterrupts(irq);
- first->_triggered = false;
- (*(first->_function))(*first);
- runningFromYield = false;
- }
- static void runFromInterrupt();
- operator bool() { return _triggered; }
- protected:
- void triggerEventNotImmediate();
- void detachNoInterrupts();
- int _status = 0;
- EventResponderFunction _function = nullptr;
- void *_data = nullptr;
- void *_context = nullptr;
- EventResponder *_next = nullptr;
- EventResponder *_prev = nullptr;
- EventType _type = EventTypeDetached;
- bool _triggered = false;
- static EventResponder *firstYield;
- static EventResponder *lastYield;
- static EventResponder *firstInterrupt;
- static EventResponder *lastInterrupt;
- static bool runningFromYield;
- private:
- static bool disableInterrupts() {
- uint32_t primask;
- __asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
- __disable_irq();
- return (primask == 0) ? true : false;
- }
- static void enableInterrupts(bool doit) {
- if (doit) __enable_irq();
- }
- };
-
- class MillisTimer
- {
- public:
- constexpr MillisTimer() {
- }
- ~MillisTimer() {
- end();
- }
- void begin(unsigned long milliseconds, EventResponderRef event);
- void beginRepeating(unsigned long milliseconds, EventResponderRef event);
- void end();
- static void runFromTimer();
- private:
- void addToWaitingList();
- void addToActiveList();
- unsigned long _ms = 0;
- unsigned long _reload = 0;
- MillisTimer *_next = nullptr;
- MillisTimer *_prev = nullptr;
- EventResponder *_event = nullptr;
- enum TimerStateType {
- TimerOff = 0,
- TimerWaiting,
- TimerActive
- };
- volatile TimerStateType _state = TimerOff;
- static MillisTimer *listWaiting; // single linked list of waiting to start timers
- static MillisTimer *listActive; // double linked list of running timers
- static bool disableTimerInterrupt() {
- uint32_t primask;
- __asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
- __disable_irq();
- return (primask == 0) ? true : false;
- }
- static void enableTimerInterrupt(bool doit) {
- if (doit) __enable_irq();
- }
- };
-
- #endif
|