345 lines
8.5 KiB
C++
345 lines
8.5 KiB
C++
/* 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 EventResponder post your feedback here:
|
|
* https://forum.pjrc.com/threads/44723-Arduino-Events
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include "EventResponder.h"
|
|
|
|
EventResponder * EventResponder::firstYield = nullptr;
|
|
EventResponder * EventResponder::lastYield = nullptr;
|
|
EventResponder * EventResponder::firstInterrupt = nullptr;
|
|
EventResponder * EventResponder::lastInterrupt = nullptr;
|
|
bool EventResponder::runningFromYield = false;
|
|
|
|
// TODO: interrupt disable/enable needed in many places!!!
|
|
|
|
void EventResponder::triggerEventNotImmediate()
|
|
{
|
|
bool irq = disableInterrupts();
|
|
if (_triggered == false) {
|
|
// not already triggered
|
|
if (_type == EventTypeYield) {
|
|
// normal type, called from yield()
|
|
if (firstYield == nullptr) {
|
|
_next = nullptr;
|
|
_prev = nullptr;
|
|
firstYield = this;
|
|
lastYield = this;
|
|
} else {
|
|
_next = nullptr;
|
|
_prev = lastYield;
|
|
_prev->_next = this;
|
|
lastYield = this;
|
|
}
|
|
} else if (_type == EventTypeInterrupt) {
|
|
// interrupt, called from software interrupt
|
|
if (firstInterrupt == nullptr) {
|
|
_next = nullptr;
|
|
_prev = nullptr;
|
|
firstInterrupt = this;
|
|
lastInterrupt = this;
|
|
} else {
|
|
_next = nullptr;
|
|
_prev = lastInterrupt;
|
|
_prev->_next = this;
|
|
lastInterrupt = this;
|
|
}
|
|
SCB_ICSR = SCB_ICSR_PENDSVSET; // set PendSV interrupt
|
|
} else {
|
|
// detached, easy :-)
|
|
}
|
|
_triggered = true;
|
|
}
|
|
enableInterrupts(irq);
|
|
}
|
|
|
|
void pendablesrvreq_isr(void)
|
|
{
|
|
EventResponder::runFromInterrupt();
|
|
}
|
|
|
|
void EventResponder::runFromInterrupt()
|
|
{
|
|
while (1) {
|
|
bool irq = disableInterrupts();
|
|
EventResponder *first = firstInterrupt;
|
|
if (first) {
|
|
firstInterrupt = first->_next;
|
|
if (firstInterrupt) {
|
|
firstInterrupt->_prev = nullptr;
|
|
} else {
|
|
lastInterrupt = nullptr;
|
|
}
|
|
enableInterrupts(irq);
|
|
first->_triggered = false;
|
|
(*(first->_function))(*first);
|
|
} else {
|
|
enableInterrupts(irq);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EventResponder::clearEvent()
|
|
{
|
|
bool ret = false;
|
|
bool irq = disableInterrupts();
|
|
if (_triggered) {
|
|
if (_type == EventTypeYield) {
|
|
if (_prev) {
|
|
_prev->_next = _next;
|
|
} else {
|
|
firstYield = _next;
|
|
}
|
|
if (_next) {
|
|
_next->_prev = _prev;
|
|
} else {
|
|
lastYield = _prev;
|
|
}
|
|
} else if (_type == EventTypeInterrupt) {
|
|
if (_prev) {
|
|
_prev->_next = _next;
|
|
} else {
|
|
firstInterrupt = _next;
|
|
}
|
|
if (_next) {
|
|
_next->_prev = _prev;
|
|
} else {
|
|
lastInterrupt = _prev;
|
|
}
|
|
}
|
|
_triggered = false;
|
|
ret = true;
|
|
}
|
|
enableInterrupts(irq);
|
|
return ret;
|
|
}
|
|
|
|
// this detach must be called with interrupts disabled
|
|
void EventResponder::detachNoInterrupts()
|
|
{
|
|
if (_type == EventTypeYield) {
|
|
if (_triggered) {
|
|
if (_prev) {
|
|
_prev->_next = _next;
|
|
} else {
|
|
firstYield = _next;
|
|
}
|
|
if (_next) {
|
|
_next->_prev = _prev;
|
|
} else {
|
|
lastYield = _prev;
|
|
}
|
|
}
|
|
_type = EventTypeDetached;
|
|
} else if (_type == EventTypeInterrupt) {
|
|
if (_triggered) {
|
|
if (_prev) {
|
|
_prev->_next = _next;
|
|
} else {
|
|
firstInterrupt = _next;
|
|
}
|
|
if (_next) {
|
|
_next->_prev = _prev;
|
|
} else {
|
|
lastInterrupt = _prev;
|
|
}
|
|
}
|
|
_type = EventTypeDetached;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------
|
|
|
|
|
|
MillisTimer * MillisTimer::listWaiting = nullptr;
|
|
MillisTimer * MillisTimer::listActive = nullptr;
|
|
|
|
void MillisTimer::begin(unsigned long milliseconds, EventResponderRef event)
|
|
{
|
|
if (_state != TimerOff) end();
|
|
if (!milliseconds) return;
|
|
_event = &event;
|
|
_ms = (milliseconds > 2)? milliseconds-2 : 0;
|
|
_reload = 0;
|
|
addToWaitingList();
|
|
}
|
|
|
|
void MillisTimer::beginRepeating(unsigned long milliseconds, EventResponderRef event)
|
|
{
|
|
if (_state != TimerOff) end();
|
|
if (!milliseconds) return;
|
|
_event = &event;
|
|
_ms = (milliseconds > 2)? milliseconds-2 : 0;
|
|
_reload = milliseconds;
|
|
addToWaitingList();
|
|
}
|
|
|
|
void MillisTimer::addToWaitingList()
|
|
{
|
|
_prev = nullptr;
|
|
bool irq = disableTimerInterrupt();
|
|
_next = listWaiting;
|
|
listWaiting = this; // TODO: use STREX to avoid interrupt disable
|
|
_state = TimerWaiting;
|
|
enableTimerInterrupt(irq);
|
|
}
|
|
|
|
void MillisTimer::addToActiveList() // only called by runFromTimer()
|
|
{
|
|
if (listActive == nullptr) {
|
|
// list is empty, easy case
|
|
_next = nullptr;
|
|
_prev = nullptr;
|
|
listActive = this;
|
|
} else if (_ms < listActive->_ms) {
|
|
// this timer triggers before any on the list
|
|
_next = listActive;
|
|
_prev = nullptr;
|
|
listActive->_prev = this;
|
|
// Decrement the next items wait time be our wait time as to properly handle waits for all other items...
|
|
listActive->_ms -= _ms;
|
|
listActive = this;
|
|
} else {
|
|
// add this timer somewhere after the first already on the list
|
|
MillisTimer *timer = listActive;
|
|
while (timer->_next) {
|
|
_ms -= timer->_ms;
|
|
timer = timer->_next;
|
|
if (_ms < timer->_ms) {
|
|
// found the right place in the middle of list
|
|
_next = timer;
|
|
_prev = timer->_prev;
|
|
timer->_prev = this;
|
|
_prev->_next = this;
|
|
timer->_ms -= _ms;
|
|
_state = TimerActive;
|
|
return;
|
|
}
|
|
}
|
|
// add this time at the end of the list
|
|
_ms -= timer->_ms;
|
|
_next = nullptr;
|
|
_prev = timer;
|
|
timer->_next = this;
|
|
}
|
|
_state = TimerActive;
|
|
}
|
|
|
|
void MillisTimer::end()
|
|
{
|
|
bool irq = disableTimerInterrupt();
|
|
TimerStateType s = _state;
|
|
if (s == TimerActive) {
|
|
if (_next) {
|
|
_next->_prev = _prev;
|
|
_next->_ms += _ms; // add in the rest of our timing to next entry...
|
|
}
|
|
if (_prev) {
|
|
_prev->_next = _next;
|
|
} else {
|
|
listActive = _next;
|
|
}
|
|
_state = TimerOff;
|
|
} else if (s == TimerWaiting) {
|
|
if (listWaiting == this) {
|
|
listWaiting = _next;
|
|
} else {
|
|
MillisTimer *timer = listWaiting;
|
|
while (timer) {
|
|
if (timer->_next == this) {
|
|
timer->_next = _next;
|
|
break;
|
|
}
|
|
timer = timer->_next;
|
|
}
|
|
}
|
|
_state = TimerOff;
|
|
}
|
|
enableTimerInterrupt(irq);
|
|
}
|
|
|
|
void MillisTimer::runFromTimer()
|
|
{
|
|
MillisTimer *timer = listActive;
|
|
while (timer) {
|
|
if (timer->_ms > 0) {
|
|
timer->_ms--;
|
|
break;
|
|
} else {
|
|
MillisTimer *next = timer->_next;
|
|
if (next) next->_prev = nullptr;
|
|
listActive = next;
|
|
timer->_state = TimerOff;
|
|
EventResponderRef event = *(timer->_event);
|
|
event.triggerEvent(0, timer);
|
|
if (timer->_reload) {
|
|
timer->_ms = timer->_reload;
|
|
timer->addToActiveList();
|
|
}
|
|
timer = listActive;
|
|
}
|
|
}
|
|
bool irq = disableTimerInterrupt();
|
|
MillisTimer *waiting = listWaiting;
|
|
listWaiting = nullptr; // TODO: use STREX to avoid interrupt disable
|
|
enableTimerInterrupt(irq);
|
|
while (waiting) {
|
|
MillisTimer *next = waiting->_next;
|
|
waiting->addToActiveList();
|
|
waiting = next;
|
|
}
|
|
}
|
|
|
|
// Long ago you could install your own systick interrupt handler by just
|
|
// creating your own systick_isr() function. No longer. But if you
|
|
// *really* want to commandeer systick, you can still do so by writing
|
|
// your function into the RAM-based vector table.
|
|
//
|
|
// _VectorsRam[15] = my_systick_function;
|
|
//
|
|
// However, for long-term portability, use a MillisTimer object to
|
|
// generate an event every millisecond, and attach your function to
|
|
// its EventResponder. You can attach as a software interrupt, so your
|
|
// code will run at lower interrupt priority for better compatibility
|
|
// with libraries using mid-to-high priority interrupts.
|
|
|
|
extern "C" volatile uint32_t systick_millis_count;
|
|
void systick_isr(void)
|
|
{
|
|
systick_millis_count++;
|
|
MillisTimer::runFromTimer();
|
|
}
|
|
|
|
|