Timers now have unique IDs (#1444)

Added `require("timer")` to access timers, removed `E.dumpTimers` and `Pin.writeAtTime` (fix #2586)
Added ability to execute JS from an a timer with `require("timer").add({type:"EXEC",...)`
This commit is contained in:
Gordon Williams 2025-12-01 14:48:26 +00:00
parent ebb469bb75
commit 43943b58ac
24 changed files with 717 additions and 362 deletions

View File

@ -2,6 +2,9 @@
Bangle.js: Updated built-in Layout.js with some minor fixes Bangle.js: Updated built-in Layout.js with some minor fixes
STM32: Ensure setDeepSleep(1) will only sleep if there are no active PWM outputs STM32: Ensure setDeepSleep(1) will only sleep if there are no active PWM outputs
Pin.getInfo().negated now set if pin negated in firmware Pin.getInfo().negated now set if pin negated in firmware
Timers now have unique IDs (#1444)
Added `require("timer")` to access timers, removed `E.dumpTimers` and `Pin.writeAtTime` (fix #2586)
Added ability to execute JS from an a timer with `require("timer").add({type:"EXEC",...)`
Bangle.js: Add Bangle.setOptions({stepCounterDisabled:bool}) to disable the step counter Bangle.js: Add Bangle.setOptions({stepCounterDisabled:bool}) to disable the step counter
Date: fix parsing of ISO8601 timezones (+HHMM worked, but +HH:MM and +HH added) (fix #2669) Date: fix parsing of ISO8601 timezones (+HHMM worked, but +HH:MM and +HH added) (fix #2669)

View File

@ -270,6 +270,7 @@ src/jswrap_json.c \
src/jswrap_number.c \ src/jswrap_number.c \
src/jswrap_object.c \ src/jswrap_object.c \
src/jswrap_regexp.c \ src/jswrap_regexp.c \
src/jswrap_timer.c \
src/jswrap_string.c \ src/jswrap_string.c \
src/jswrap_modules.c \ src/jswrap_modules.c \
src/jswrap_math.c src/jswrap_math.c

View File

@ -1,9 +1,8 @@
[ [
{"class":"OneWire","name":"*"}, {"class":"OneWire","name":"*"},
{"class":"SPI","name":"send4bit"}, {"class":"SPI","name":"send4bit"},
{"class":"E","name":"dumpTimers"},
{"class":"E","name":"enableWatchdog"}, {"class":"E","name":"enableWatchdog"},
{"class":"E","name":"getClock"}, {"class":"E","name":"getClock"},
{"class":"E","name":"setBootCode"}, {"class":"E","name":"setBootCode"},
{"class":"Pin","name":"writeAtTime"} {"class":"timer","name":"*"}
] ]

View File

@ -470,6 +470,11 @@ bool CALLED_FROM_INTERRUPT jshPushEvent(IOEventFlags evt, uint8_t *data, unsigne
return true; return true;
} }
/// Push a Custom IO event into the ioBuffer (designed to be called from IRQ), returns true on success, Calls jshHadEvent();
bool CALLED_FROM_INTERRUPT jshPushCustomEvent(IOCustomEventFlags customFlags) {
jshPushEvent(EV_CUSTOM, (uint8_t*)&customFlags, sizeof(customFlags));
}
/// Try and handle events in the IRQ itself. true if handled and shouldn't go in queue /// Try and handle events in the IRQ itself. true if handled and shouldn't go in queue
static bool jshPushIOCharEventsHandler(IOEventFlags channel, char *data, unsigned int count) { static bool jshPushIOCharEventsHandler(IOEventFlags channel, char *data, unsigned int count) {
// Check for a CTRL+C // Check for a CTRL+C

View File

@ -87,7 +87,8 @@ typedef enum {
#ifdef BLUETOOTH #ifdef BLUETOOTH
EV_BLUETOOTH_PENDING, // Tasks that came from the Bluetooth Stack in an IRQ EV_BLUETOOTH_PENDING, // Tasks that came from the Bluetooth Stack in an IRQ
#endif #endif
EV_CUSTOM, ///< Custom event (See IOCustomEventFlags) EV_RUN_INTERRUPT_JS, ///< Run some JavaScript code. See EXEC_RUN_INTERRUPT_JS. data is JsVarRef of code to run
EV_CUSTOM, ///< Custom event (First byte is IOCustomEventFlags to determine the event type)
#ifdef BANGLEJS #ifdef BANGLEJS
EV_BANGLEJS, // sent whenever Bangle.js-specific data needs to be queued EV_BANGLEJS, // sent whenever Bangle.js-specific data needs to be queued
#endif #endif
@ -131,10 +132,13 @@ typedef enum {
/** Event types for EV_CUSTOM */ /** Event types for EV_CUSTOM */
typedef enum { typedef enum {
EVC_NONE, EVC_NONE,
EVC_TIMER_FINISHED,
EVC_TIMER_BUFFER_FLIP,
#ifdef NRF52_SERIES #ifdef NRF52_SERIES
EVC_LPCOMP, // jswrap_espruino: E.setComparator / E.on("comparator" event EVC_LPCOMP, // jswrap_espruino: E.setComparator / E.on("comparator" event
#endif #endif
EVC_TYPE_MASK = 255, EVC_TYPE_MASK = 255,
EVC_DATA_SHIFT = 8,
EVC_DATA_LPCOMP_UP = 256 EVC_DATA_LPCOMP_UP = 256
} PACKED_FLAGS IOCustomEventFlags; } PACKED_FLAGS IOCustomEventFlags;
@ -185,6 +189,8 @@ typedef enum {
/// Push an IO event (max IOEVENT_MAX_LEN) into the ioBuffer (designed to be called from IRQ), returns true on success, Calls jshHadEvent(); /// Push an IO event (max IOEVENT_MAX_LEN) into the ioBuffer (designed to be called from IRQ), returns true on success, Calls jshHadEvent();
bool CALLED_FROM_INTERRUPT jshPushEvent(IOEventFlags evt, uint8_t *data, unsigned int length); bool CALLED_FROM_INTERRUPT jshPushEvent(IOEventFlags evt, uint8_t *data, unsigned int length);
/// Push a Custom IO event into the ioBuffer (designed to be called from IRQ), returns true on success, Calls jshHadEvent();
bool CALLED_FROM_INTERRUPT jshPushCustomEvent(IOCustomEventFlags customFlags);
/// Add this IO event to the IO event queue. Calls jshHadEvent(); /// Add this IO event to the IO event queue. Calls jshHadEvent();
void jshPushIOEvent(IOEventFlags channel, JsSysTime time); void jshPushIOEvent(IOEventFlags channel, JsSysTime time);
/// Signal an IO watch event as having happened. Calls jshHadEvent(); /// Signal an IO watch event as having happened. Calls jshHadEvent();
@ -196,7 +202,7 @@ void jshPushIOCharEvents(IOEventFlags channel, char *data, unsigned int count);
/// pop an IO event, returns EV_NONE on failure. data must be IOEVENT_MAX_LEN bytes /// pop an IO event, returns EV_NONE on failure. data must be IOEVENT_MAX_LEN bytes
IOEventFlags jshPopIOEvent(uint8_t *data, unsigned int *length); IOEventFlags jshPopIOEvent(uint8_t *data, unsigned int *length);
// pop an IO event of type eventType, returns true on success. data must be IOEVENT_MAX_LEN bytes // pop an IO event of type eventType, returns event type on success,EV_NONE on failure. data must be IOEVENT_MAX_LEN bytes
IOEventFlags jshPopIOEventOfType(IOEventFlags eventType, uint8_t *data, unsigned int *length); IOEventFlags jshPopIOEventOfType(IOEventFlags eventType, uint8_t *data, unsigned int *length);
/// Do we have any events pending? Will jshPopIOEvent return true? /// Do we have any events pending? Will jshPopIOEvent return true?
bool jshHasEvents(); bool jshHasEvents();

View File

@ -2204,6 +2204,20 @@ void jsiHandleIOEventForConsole(uint8_t *eventData, int eventLen) {
jsiSetBusy(BUSY_INTERACTIVE, false); jsiSetBusy(BUSY_INTERACTIVE, false);
} }
/** This is called if a EV_RUN_INTERRUPT_JS is received, or when a EXEC_RUN_INTERRUPT_JS is set.
It executes JavaScript code that was pushed to the queue by a require("timer").add({type:"EXEC", fn:myFunction... */
static void jsiOnRunInterruptJSEvent(const uint8_t *eventData, unsigned int eventLen) {
for (unsigned int i=0;i<eventLen-1;i+=2) {
JsVarRef codeRef = *(JsVarRef *)&eventData[i];
if (codeRef) {
JsVar *code = jsvLock(codeRef);
if (!jsvIsFunction(code)) return; // invalid code - maybe things got moved?
jsvUnLock(jspExecuteFunction(code, execInfo.root, 0, NULL));
jsvUnLock(code);
}
}
}
void jsiIdle() { void jsiIdle() {
// This is how many times we have been here and not done anything. // This is how many times we have been here and not done anything.
// It will be zeroed if we do stuff later // It will be zeroed if we do stuff later
@ -2248,7 +2262,10 @@ void jsiIdle() {
} }
jsvUnLock(usartClass); jsvUnLock(usartClass);
#endif #endif
} else if (eventType == EV_RUN_INTERRUPT_JS) {
jsiOnRunInterruptJSEvent(eventData, eventLen);
} else if (eventType == EV_CUSTOM) { } else if (eventType == EV_CUSTOM) {
jstOnCustomEvent(eventFlags, eventData, eventLen);
jswOnCustomEvent(eventFlags, eventData, eventLen); jswOnCustomEvent(eventFlags, eventData, eventLen);
#ifdef BLUETOOTH #ifdef BLUETOOTH
} else if (eventType == EV_BLUETOOTH_PENDING) { } else if (eventType == EV_BLUETOOTH_PENDING) {
@ -3000,3 +3017,13 @@ void jsiDebuggerLine(JsVar *line) {
jslSetLex(oldLex); jslSetLex(oldLex);
} }
#endif // USE_DEBUGGER #endif // USE_DEBUGGER
/** This is called from the parser if EXEC_RUN_INTERRUPT_JS is set.
It executes JavaScript code that was pushed to the queue by require("timer").add({type:"EXEC", fn:myFunction... */
void jsiRunInterruptingJS() {
uint8_t data[8];
memset(data, 0, sizeof(data));
if (jshPopIOEventOfType(EV_RUN_INTERRUPT_JS, data, sizeof(data)))
jsiOnRunInterruptJSEvent(data, sizeof(data));
}

View File

@ -201,5 +201,8 @@ extern void jsiTimersChanged(); // Flag timers changed so we can skip out of the
extern void jsiDebuggerLoop(); ///< Enter the debugger loop extern void jsiDebuggerLoop(); ///< Enter the debugger loop
#endif #endif
/** This is called from the parser if EXEC_RUN_INTERRUPT_JS is set.
It executes JavaScript code that was pushed to the queue by require("timer").add({type:"EXEC", fn:myFunction... */
void jsiRunInterruptingJS();
#endif /* JSINTERACTIVE_H_ */ #endif /* JSINTERACTIVE_H_ */

View File

@ -2959,6 +2959,10 @@ NO_INLINE JsVar *jspeStatement() {
jsiDebuggerLoop(); jsiDebuggerLoop();
} }
#endif #endif
if (execInfo.execute&EXEC_RUN_INTERRUPT_JS) {
execInfo.execute&=~EXEC_RUN_INTERRUPT_JS;
jsiRunInterruptingJS();
}
if (lex->tk==LEX_ID || if (lex->tk==LEX_ID ||
lex->tk==LEX_INT || lex->tk==LEX_INT ||
lex->tk==LEX_FLOAT || lex->tk==LEX_FLOAT ||

View File

@ -98,7 +98,7 @@ typedef enum {
EXEC_INTERRUPTED = 16, ///< true if execution has been interrupted EXEC_INTERRUPTED = 16, ///< true if execution has been interrupted
EXEC_EXCEPTION = 32, ///< we had an exception, so don't execute until we hit a try/catch block EXEC_EXCEPTION = 32, ///< we had an exception, so don't execute until we hit a try/catch block
EXEC_ERROR = 64, EXEC_ERROR = 64,
// 128 is free now EXEC_RUN_INTERRUPT_JS = 128, ///< when set, we should stop our execution to run some JavaScript code (used for immediate timers)
EXEC_FOR_INIT = 256, ///< when in for initialiser parsing - hack to avoid getting confused about multiple use for IN EXEC_FOR_INIT = 256, ///< when in for initialiser parsing - hack to avoid getting confused about multiple use for IN
EXEC_IN_LOOP = 512, ///< when in a loop, set this - we can then block break/continue outside it EXEC_IN_LOOP = 512, ///< when in a loop, set this - we can then block break/continue outside it

View File

@ -16,7 +16,9 @@
#include "jsinteractive.h" #include "jsinteractive.h"
/// Data for our tasks (eg when, what they are, etc) /// Data for our tasks (eg when, what they are, etc)
UtilTimerTask utilTimerTasks[UTILTIMERTASK_TASKS]; UtilTimerTask utilTimerTaskInfo[UTILTIMERTASK_TASKS];
/// List of tasks in time order (implemented as a circular buffer)
uint8_t utilTimerTasks[UTILTIMERTASK_TASKS];
/// queue beginning (push tasks on here) /// queue beginning (push tasks on here)
volatile unsigned char utilTimerTasksHead = 0; volatile unsigned char utilTimerTasksHead = 0;
/// queue end (tasks pop off here as they are complete) /// queue end (tasks pop off here as they are complete)
@ -45,7 +47,7 @@ static void jstUtilTimerSetupBuffer(UtilTimerTask *task) {
} }
} }
static void jstUtilTimerInterruptHandlerNextByte(UtilTimerTask *task) { static void jstUtilTimerInterruptHandlerNextByte(UtilTimerTask *task, uint8_t timerId) {
// move to next element in var // move to next element in var
task->data.buffer.charIdx++; task->data.buffer.charIdx++;
if (task->data.buffer.charIdx >= task->data.buffer.endIdx) { if (task->data.buffer.charIdx >= task->data.buffer.endIdx) {
@ -66,6 +68,8 @@ static void jstUtilTimerInterruptHandlerNextByte(UtilTimerTask *task) {
task->data.buffer.currentBuffer = t; task->data.buffer.currentBuffer = t;
// Setup new buffer // Setup new buffer
jstUtilTimerSetupBuffer(task); jstUtilTimerSetupBuffer(task);
// notify rest of interpreter
jshPushCustomEvent(EVC_TIMER_BUFFER_FLIP | (timerId<<EVC_DATA_SHIFT));
} else { } else {
task->data.buffer.var = 0; task->data.buffer.var = 0;
// No more data - make sure we don't repeat! // No more data - make sure we don't repeat!
@ -83,6 +87,17 @@ static inline unsigned char *jstUtilTimerInterruptHandlerByte(UtilTimerTask *tas
} }
#endif #endif
// Handle sending an event when the task is finished
static void jstUtilTimerTaskIsFinished(int timerId) {
UtilTimerTask *task = &utilTimerTaskInfo[timerId];
if (UET_EVENT_SEND_TIMER_FINISHED(task->type)) {
task->type |= UET_FINISHED;
jshPushCustomEvent(EVC_TIMER_FINISHED | (timerId<<EVC_DATA_SHIFT));
} else {
task->type = UET_NONE;
}
}
void jstUtilTimerInterruptHandler() { void jstUtilTimerInterruptHandler() {
/* Note: we're using 32 bit times here, even though the real time counter is 64 bit. We /* Note: we're using 32 bit times here, even though the real time counter is 64 bit. We
* just make sure nothing is scheduled that far in the future */ * just make sure nothing is scheduled that far in the future */
@ -93,13 +108,14 @@ void jstUtilTimerInterruptHandler() {
current time. */ current time. */
int t = utilTimerTasksTail; int t = utilTimerTasksTail;
while (t!=utilTimerTasksHead) { while (t!=utilTimerTasksHead) {
utilTimerTasks[t].time -= utilTimerPeriod; utilTimerTaskInfo[utilTimerTasks[t]].time -= utilTimerPeriod;
t = (t+1) & (UTILTIMERTASK_TASKS-1); t = (t+1) & (UTILTIMERTASK_TASKS-1);
} }
utilTimerOffset += utilTimerPeriod; utilTimerOffset += utilTimerPeriod;
// Check timers and execute any timers that are due // Check timers and execute any timers that are due
while (utilTimerTasksTail!=utilTimerTasksHead && utilTimerTasks[utilTimerTasksTail].time <= 0) { while (utilTimerTasksTail!=utilTimerTasksHead && utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time <= 0) {
UtilTimerTask *task = &utilTimerTasks[utilTimerTasksTail]; uint8_t timerId = utilTimerTasks[utilTimerTasksTail];
UtilTimerTask *task = &utilTimerTaskInfo[timerId];
void (*executeFn)(JsSysTime time, void* userdata) = 0; void (*executeFn)(JsSysTime time, void* userdata) = 0;
void *executeData = 0; void *executeData = 0;
switch (task->type) { switch (task->type) {
@ -119,15 +135,15 @@ void jstUtilTimerInterruptHandler() {
if (!task->data.buffer.var) break; if (!task->data.buffer.var) break;
int v = jshPinAnalogFast(task->data.buffer.pin); int v = jshPinAnalogFast(task->data.buffer.pin);
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)v; // LSB first *jstUtilTimerInterruptHandlerByte(task) = (unsigned char)v; // LSB first
jstUtilTimerInterruptHandlerNextByte(task); jstUtilTimerInterruptHandlerNextByte(task, timerId);
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(v >> 8); *jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(v >> 8);
jstUtilTimerInterruptHandlerNextByte(task); jstUtilTimerInterruptHandlerNextByte(task, timerId);
break; break;
} }
case UET_READ_BYTE: { case UET_READ_BYTE: {
if (!task->data.buffer.var) break; if (!task->data.buffer.var) break;
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(jshPinAnalogFast(task->data.buffer.pin) >> 8); *jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(jshPinAnalogFast(task->data.buffer.pin) >> 8);
jstUtilTimerInterruptHandlerNextByte(task); jstUtilTimerInterruptHandlerNextByte(task, timerId);
break; break;
} }
case UET_WRITE_SHORT: case UET_WRITE_SHORT:
@ -137,19 +153,19 @@ void jstUtilTimerInterruptHandler() {
int sum; int sum;
if (task->type == UET_WRITE_SHORT) { if (task->type == UET_WRITE_SHORT) {
sum = *jstUtilTimerInterruptHandlerByte(task); // LSB first sum = *jstUtilTimerInterruptHandlerByte(task); // LSB first
jstUtilTimerInterruptHandlerNextByte(task); jstUtilTimerInterruptHandlerNextByte(task, timerId);
} else { } else {
sum = 0; sum = 0;
} }
sum |= (unsigned short)(*jstUtilTimerInterruptHandlerByte(task) << 8); sum |= (unsigned short)(*jstUtilTimerInterruptHandlerByte(task) << 8);
jstUtilTimerInterruptHandlerNextByte(task); jstUtilTimerInterruptHandlerNextByte(task, timerId);
task->data.buffer.currentValue = (unsigned short)sum; task->data.buffer.currentValue = (unsigned short)sum;
// now search for other tasks writing to this pin... (polyphony) // now search for other tasks writing to this pin... (polyphony)
int t = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1); int t = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
while (t!=utilTimerTasksHead) { while (t!=utilTimerTasksHead) {
if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTasks[t].type) && if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTaskInfo[utilTimerTasks[t]].type) &&
utilTimerTasks[t].data.buffer.pin == task->data.buffer.pin) utilTimerTaskInfo[utilTimerTasks[t]].data.buffer.pin == task->data.buffer.pin)
sum += ((int)(unsigned int)utilTimerTasks[t].data.buffer.currentValue) - 32768; sum += ((int)(unsigned int)utilTimerTaskInfo[utilTimerTasks[t]].data.buffer.currentValue) - 32768;
t = (t+1) & (UTILTIMERTASK_TASKS-1); t = (t+1) & (UTILTIMERTASK_TASKS-1);
} }
// saturate // saturate
@ -194,31 +210,30 @@ void jstUtilTimerInterruptHandler() {
if (task->repeatInterval) { if (task->repeatInterval) {
// update time (we know time > task->time) // update time (we know time > task->time)
task->time += task->repeatInterval; task->time += task->repeatInterval;
// do an in-place bubble sort to ensure that times are still in the right order // do an in-place bubble sort to ensure that times are still in the right order
unsigned char ta = utilTimerTasksTail; unsigned char ta = utilTimerTasksTail;
unsigned char tb = (ta+1) & (UTILTIMERTASK_TASKS-1); unsigned char tb = (ta+1) & (UTILTIMERTASK_TASKS-1);
while (tb != utilTimerTasksHead) { while (tb != utilTimerTasksHead) {
if (utilTimerTasks[ta].time > utilTimerTasks[tb].time) { if (utilTimerTaskInfo[utilTimerTasks[ta]].time > utilTimerTaskInfo[utilTimerTasks[tb]].time) {
UtilTimerTask task = utilTimerTasks[ta]; uint8_t idx = utilTimerTasks[ta];
utilTimerTasks[ta] = utilTimerTasks[tb]; utilTimerTasks[ta] = utilTimerTasks[tb];
utilTimerTasks[tb] = task; utilTimerTasks[tb] = idx;
} }
ta = tb; ta = tb;
tb = (tb+1) & (UTILTIMERTASK_TASKS-1); tb = (tb+1) & (UTILTIMERTASK_TASKS-1);
} }
} else { } else {
// Otherwise no repeat - just go straight to the next one! // Otherwise no repeat - mark this as done and go straight to the next one!
jstUtilTimerTaskIsFinished(timerId);
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1); utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
} }
// execute the function if we had one (we do this now, because if we did it earlier we'd have to cope with everything changing) // execute the function if we had one (we do this now, because if we did it earlier we'd have to cope with everything changing)
if (executeFn) executeFn(jshGetSystemTime(), executeData); if (executeFn) executeFn(jshGetSystemTime(), executeData);
} }
// re-schedule the timer if there is something left to do // re-schedule the timer if there is something left to do
if (utilTimerTasksTail != utilTimerTasksHead) { if (utilTimerTasksTail != utilTimerTasksHead) {
utilTimerPeriod = utilTimerTasks[utilTimerTasksTail].time; utilTimerPeriod = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
if (utilTimerPeriod<0) utilTimerPeriod=0; if (utilTimerPeriod<0) utilTimerPeriod=0;
jshUtilTimerReschedule(utilTimerPeriod); jshUtilTimerReschedule(utilTimerPeriod);
} else { } else {
@ -253,32 +268,41 @@ static bool utilTimerIsFull() {
/* Restart the utility timer with the right period. This should not normally /* Restart the utility timer with the right period. This should not normally
need to be called by anything outside jstimer.c */ need to be called by anything outside jstimer.c */
void jstRestartUtilTimer() { void jstRestartUtilTimer() {
utilTimerPeriod = utilTimerTasks[utilTimerTasksTail].time; utilTimerPeriod = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
utilTimerSetTime = jshGetSystemTime(); utilTimerSetTime = jshGetSystemTime();
if (utilTimerPeriod<0) utilTimerPeriod=0; if (utilTimerPeriod<0) utilTimerPeriod=0;
jshUtilTimerStart(utilTimerPeriod); jshUtilTimerStart(utilTimerPeriod);
} }
/// Get the index of next free task slot, or -1 if none free
int utilTimerGetUnusedIndex(bool wait) {
if (wait)
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer");
for (int i=0;i<UTILTIMERTASK_TASKS;i++)
if (utilTimerTaskInfo[i].type == UET_NONE)
return i;
return -1;
}
/** Queue a task up to be executed when a timer fires... return false on failure. /** Queue a task up to be executed when a timer fires... return false on failure.
* task.time is the delay at which to execute the task. If timerOffset!==NULL then * task.time is the delay at which to execute the task. If timerOffset!==NULL then
* task.time is relative to the time at which timerOffset=jstGetUtilTimerOffset(). * task.time is relative to the time at which timerOffset=jstGetUtilTimerOffset().
* This allows pulse trains/etc to be scheduled in sync. * This allows pulse trains/etc to be scheduled in sync.
*/ */
bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset) { void utilTimerInsertTask(uint8_t taskIdx, uint32_t *timerOffset) {
// check if queue is full or not assert(!utilTimerIsFull()); // queue should not be full since we had to allocate a task to get the index
if (utilTimerIsFull()) return false; UtilTimerTask *task = &utilTimerTaskInfo[taskIdx];
jshInterruptOff();
jshInterruptOff();
// See above - keep times in sync // See above - keep times in sync
if (timerOffset) if (timerOffset)
task->time += (int)*timerOffset - (int)utilTimerOffset; task->time += (int)*timerOffset - (int)utilTimerOffset;
// How long was it since the timer was last scheduled? Update existing tasks #2575 // How long was it since the timer was last scheduled? Update existing tasks #2575
uint32_t timePassed = jshGetSystemTime() - utilTimerSetTime; int timePassed = utilTimerOn ? (jshGetSystemTime() - utilTimerSetTime) : 0;
// find out where to insert // find out where to insert
unsigned char insertPos = utilTimerTasksTail; unsigned char insertPos = utilTimerTasksTail;
while (insertPos != utilTimerTasksHead && utilTimerTasks[insertPos].time < (task->time+timePassed)) while (insertPos != utilTimerTasksHead && utilTimerTaskInfo[utilTimerTasks[insertPos]].time < (task->time+timePassed))
insertPos = (insertPos+1) & (UTILTIMERTASK_TASKS-1); insertPos = (insertPos+1) & (UTILTIMERTASK_TASKS-1);
bool haveChangedTimer = insertPos==utilTimerTasksTail; bool haveChangedTimer = insertPos==utilTimerTasksTail;
//jsiConsolePrintf("Insert at %d, Tail is %d\n",insertPos,utilTimerTasksTail); //jsiConsolePrintf("Insert at %d, Tail is %d\n",insertPos,utilTimerTasksTail);
@ -295,17 +319,16 @@ bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset) {
if (haveChangedTimer) { // timer will change - update all tasks if (haveChangedTimer) { // timer will change - update all tasks
i = utilTimerTasksTail; i = utilTimerTasksTail;
while (i != utilTimerTasksHead) { while (i != utilTimerTasksHead) {
if (utilTimerTasks[i].time > timePassed) if (utilTimerTaskInfo[utilTimerTasks[i]].time > timePassed)
utilTimerTasks[i].time -= timePassed; utilTimerTaskInfo[utilTimerTasks[i]].time -= timePassed;
else else
utilTimerTasks[i].time = 0; utilTimerTaskInfo[utilTimerTasks[i]].time = 0;
i = (i+1) & (UTILTIMERTASK_TASKS-1); i = (i+1) & (UTILTIMERTASK_TASKS-1);
} }
} else // timer hasn't changed, we have to update our task's time } else // timer hasn't changed, we have to update our task's time
task->time += timePassed; task->time += timePassed;
// add new item // add new item
utilTimerTasks[insertPos] = *task; utilTimerTasks[insertPos] = taskIdx;
//jsiConsolePrint("Head is %d\n", utilTimerTasksHead); //jsiConsolePrint("Head is %d\n", utilTimerTasksHead);
// now set up timer if not already set up... // now set up timer if not already set up...
if (!utilTimerOn || haveChangedTimer) { if (!utilTimerOn || haveChangedTimer) {
@ -313,19 +336,27 @@ bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset) {
jstRestartUtilTimer(); jstRestartUtilTimer();
} }
jshInterruptOn(); jshInterruptOn();
return true;
} }
/// Remove the task that that 'checkCallback' returns true for. Returns false if none found /// Find a task that 'checkCallback' returns true for. Returns -1 if none found
bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData) { int utilTimerFindTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData) {
for (int i=0;i<UTILTIMERTASK_TASKS;i++)
if (checkCallback(&utilTimerTaskInfo[i], checkCallbackData))
return i;
return -1;
}
/// Remove the task with the given ID. Also sets type to UET_NONE. Returns false if none found
bool utilTimerRemoveTask(int id) {
jshInterruptOff(); jshInterruptOff();
jstUtilTimerTaskIsFinished(id); // queue events for task finished
unsigned char ptr = utilTimerTasksHead; unsigned char ptr = utilTimerTasksHead;
if (ptr != utilTimerTasksTail) { if (ptr != utilTimerTasksTail) {
unsigned char endPtr = ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1)); unsigned char endPtr = ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1));
ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1); ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
// now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail // now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail
while (ptr != endPtr) { while (ptr != endPtr) {
if (checkCallback(&utilTimerTasks[ptr], checkCallbackData)) { if (utilTimerTasks[ptr]==id) {
// shift tail back along // shift tail back along
unsigned char next = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1); unsigned char next = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
while (next!=endPtr) { while (next!=endPtr) {
@ -335,6 +366,7 @@ bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data),
} }
// move 'end' pointer back // move 'end' pointer back
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1); utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
utilTimerTaskInfo[id].type = UET_NONE;
jshInterruptOn(); jshInterruptOn();
return true; return true;
} }
@ -347,14 +379,15 @@ bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data),
/// If 'checkCallback' returns true for a task, set 'task' to it and return true. Returns false if none found /// If 'checkCallback' returns true for a task, set 'task' to it and return true. Returns false if none found
bool utilTimerGetLastTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData, UtilTimerTask *task) { bool utilTimerGetLastTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData, UtilTimerTask *task) {
// FIXME: we should return the task index here, and just look over utilTimerTaskInfo outside of an IRQ
jshInterruptOff(); jshInterruptOff();
unsigned char ptr = utilTimerTasksHead; unsigned char ptr = utilTimerTasksHead;
if (ptr != utilTimerTasksTail) { if (ptr != utilTimerTasksTail) {
ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1); ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
// now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail // now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail
while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) { while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) {
if (checkCallback(&utilTimerTasks[ptr], checkCallbackData)) { if (checkCallback(&utilTimerTaskInfo[utilTimerTasks[ptr]], checkCallbackData)) {
*task = utilTimerTasks[ptr]; *task = utilTimerTaskInfo[utilTimerTasks[ptr]];
jshInterruptOn(); jshInterruptOn();
return true; return true;
} }
@ -399,18 +432,6 @@ static bool jstExecuteTaskChecker(UtilTimerTask *task, void *data) {
return memcmp(&task->data.execute, (UtilTimerTaskExec*)data, sizeof(UtilTimerTaskExec))==0; return memcmp(&task->data.execute, (UtilTimerTaskExec*)data, sizeof(UtilTimerTaskExec))==0;
} }
#ifdef ESPR_USE_STEPPER_TIMER
// data = *Pin[4]
static bool jstStepTaskChecker(UtilTimerTask *task, void *data) {
if (task->type != UET_STEP) return false;
Pin *pins = (Pin*)data;
for (int i=0;i<4;i++)
if (task->data.step.pins[i] != pins[i])
return false;
return true;
}
#endif
// -------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------
@ -429,17 +450,18 @@ bool jstGetLastBufferTimerTask(JsVar *var, UtilTimerTask *task) {
bool jstPinOutputAtTime(JsSysTime time, uint32_t *timerOffset, Pin *pins, int pinCount, uint8_t value) { bool jstPinOutputAtTime(JsSysTime time, uint32_t *timerOffset, Pin *pins, int pinCount, uint8_t value) {
assert(pinCount<=UTILTIMERTASK_PIN_COUNT); assert(pinCount<=UTILTIMERTASK_PIN_COUNT);
UtilTimerTask task; int idx = utilTimerGetUnusedIndex(true/*wait*/);
task.time = (int)time; if (idx<0) return false; // no free tasks even after waiting
task.repeatInterval = 0; UtilTimerTask *task = &utilTimerTaskInfo[idx];
task.type = UET_SET; task->type = UET_SET;
task->time = (int)time;
task->repeatInterval = 0;
int i; int i;
for (i=0;i<UTILTIMERTASK_PIN_COUNT;i++) for (i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
task.data.set.pins[i] = (Pin)((i<pinCount) ? pins[i] : PIN_UNDEFINED); task->data.set.pins[i] = (Pin)((i<pinCount) ? pins[i] : PIN_UNDEFINED);
task.data.set.value = value; task->data.set.value = value;
utilTimerInsertTask(idx, timerOffset);
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer"); return true;
return utilTimerInsertTask(&task, timerOffset);
} }
// Do software PWM on the given pin, using the timer IRQs // Do software PWM on the given pin, using the timer IRQs
@ -447,7 +469,9 @@ bool jstPinPWM(JsVarFloat freq, JsVarFloat dutyCycle, Pin pin) {
// if anything is wrong, exit now // if anything is wrong, exit now
if (dutyCycle<=0 || dutyCycle>=1 || freq<=0) { if (dutyCycle<=0 || dutyCycle>=1 || freq<=0) {
// remove any timer tasks // remove any timer tasks
while (utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin)); int taskID;
while ((taskID = utilTimerFindTask(jstPinTaskChecker, (void*)&pin)) >= 0)
utilTimerRemoveTask(taskID);
// Now set pin to the correct state and exit // Now set pin to the correct state and exit
jshPinSetValue(pin, dutyCycle >= 0.5); jshPinSetValue(pin, dutyCycle >= 0.5);
return false; return false;
@ -468,11 +492,11 @@ bool jstPinPWM(JsVarFloat freq, JsVarFloat dutyCycle, Pin pin) {
ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1); ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
// now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail // now we're at the last timer task - work back until we've just gone back past utilTimerTasksTail
while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) { while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) {
if (jstPinTaskChecker(&utilTimerTasks[ptr], (void*)&pin)) { if (jstPinTaskChecker(&utilTimerTaskInfo[utilTimerTasks[ptr]], (void*)&pin)) {
if (utilTimerTasks[ptr].data.set.value) if (utilTimerTaskInfo[utilTimerTasks[ptr]].data.set.value)
ptaskon = &utilTimerTasks[ptr]; ptaskon = &utilTimerTaskInfo[utilTimerTasks[ptr]];
else else
ptaskoff = &utilTimerTasks[ptr]; ptaskoff = &utilTimerTaskInfo[utilTimerTasks[ptr]];
} }
ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1); ptr = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
} }
@ -495,48 +519,57 @@ bool jstPinPWM(JsVarFloat freq, JsVarFloat dutyCycle, Pin pin) {
/// Remove any tasks using the given pin (if they existed) /// Remove any tasks using the given pin (if they existed)
if (ptaskon || ptaskoff) { if (ptaskon || ptaskoff) {
while (utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin)); while (jstStopPinTimerTask(pin));
} }
UtilTimerTask taskon, taskoff;
taskon.data.set.value = 1; int onidx = utilTimerGetUnusedIndex(true/*wait*/);
taskoff.data.set.value = 0; if (onidx<0) return false; // no free tasks
taskon.time = (int)period; UtilTimerTask *taskon = &utilTimerTaskInfo[onidx];
taskoff.time = (int)pulseLength; taskon->type = UET_SET;
taskon.repeatInterval = (unsigned int)period; int offidx = utilTimerGetUnusedIndex(true/*wait*/);
taskoff.repeatInterval = (unsigned int)period; if (offidx<0) {
taskon.type = UET_SET; taskon->type = UET_NONE; // free last task
taskoff.type = UET_SET; return false; // no free tasks
taskon.data.set.pins[0] = pin; }
taskoff.data.set.pins[0] = pin; UtilTimerTask *taskoff = &utilTimerTaskInfo[offidx];
taskoff->type = UET_SET;
taskon->data.set.value = 1;
taskoff->data.set.value = 0;
taskon->time = (int)period;
taskoff->time = (int)pulseLength;
taskon->repeatInterval = (unsigned int)period;
taskoff->repeatInterval = (unsigned int)period;
taskon->data.set.pins[0] = pin;
taskoff->data.set.pins[0] = pin;
int i; int i;
for (i=1;i<UTILTIMERTASK_PIN_COUNT;i++) { for (i=1;i<UTILTIMERTASK_PIN_COUNT;i++) {
taskon.data.set.pins[i] = PIN_UNDEFINED; taskon->data.set.pins[i] = PIN_UNDEFINED;
taskoff.data.set.pins[i] = PIN_UNDEFINED; taskoff->data.set.pins[i] = PIN_UNDEFINED;
} }
// first task is to turn on // first task is to turn on
jshPinSetValue(pin, 1); jshPinSetValue(pin, 1);
uint32_t timerOffset = jstGetUtilTimerOffset(); uint32_t timerOffset = jstGetUtilTimerOffset();
// now start the 2 PWM tasks // now start the 2 PWM tasks
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer"); utilTimerInsertTask(onidx, &timerOffset);
if (!utilTimerInsertTask(&taskon, &timerOffset)) return false; utilTimerInsertTask(offidx, &timerOffset);
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer"); return true;
return utilTimerInsertTask(&taskoff, &timerOffset);
} }
/** Execute the given function repeatedly after the given time period. If period=0, don't repeat. True on success or false on failure to schedule /** Execute the given function repeatedly after the given time period. If period=0, don't repeat. True on success or false on failure to schedule
* See utilTimerInsertTask for notes on timerOffset * See utilTimerInsertTask for notes on timerOffset
*/ */
bool jstExecuteFn(UtilTimerTaskExecFn fn, void *userdata, JsSysTime startTime, uint32_t period, uint32_t *timerOffset) { bool jstExecuteFn(UtilTimerTaskExecFn fn, void *userdata, JsSysTime startTime, uint32_t period, uint32_t *timerOffset) {
UtilTimerTask task; int idx = utilTimerGetUnusedIndex(true/*wait*/);
task.time = (int)startTime; if (idx<0) return false; // no free tasks even after waiting
task.repeatInterval = period; UtilTimerTask *task = &utilTimerTaskInfo[idx];
task.type = UET_EXECUTE; task->time = (int)startTime;
task.data.execute.fn = fn; task->repeatInterval = period;
task.data.execute.userdata = userdata; task->type = UET_EXECUTE;
task->data.execute.fn = fn;
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer"); task->data.execute.userdata = userdata;
return utilTimerInsertTask(&task, timerOffset); utilTimerInsertTask(idx, timerOffset);
return true;
} }
/// Stop executing the given function /// Stop executing the given function
@ -544,37 +577,39 @@ bool jstStopExecuteFn(UtilTimerTaskExecFn fn, void *userdata) {
UtilTimerTaskExec e; UtilTimerTaskExec e;
e.fn = fn; e.fn = fn;
e.userdata = userdata; e.userdata = userdata;
return utilTimerRemoveTask(jstExecuteTaskChecker, (void*)&e); return utilTimerRemoveTask(utilTimerFindTask(jstExecuteTaskChecker, (void*)&e));
} }
/// Set the utility timer so we're woken up in whatever time period /// Set the utility timer so we're woken up in whatever time period
bool jstSetWakeUp(JsSysTime period) { bool jstSetWakeUp(JsSysTime period) {
UtilTimerTask task;
task.time = (int)period;
task.repeatInterval = 0;
task.type = UET_WAKEUP;
bool hasTimer = false; bool hasTimer = false;
int wakeupTime = (int)period;
int nextTime; int nextTime;
// work out if we're waiting for a timer, // work out if we're waiting for a timer,
// and if so, when it's going to be // and if so, when it's going to be
jshInterruptOff(); jshInterruptOff();
if (utilTimerTasksTail!=utilTimerTasksHead) { if (utilTimerTasksTail!=utilTimerTasksHead) {
hasTimer = true; hasTimer = true;
nextTime = utilTimerTasks[utilTimerTasksTail].time; nextTime = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
} }
jshInterruptOn(); jshInterruptOn();
if (hasTimer && task.time >= nextTime) { if (hasTimer && wakeupTime >= nextTime) {
// we already had a timer, and it's going to wake us up sooner. // we already had a timer, and it's going to wake us up sooner.
// don't create a WAKEUP timer task // don't create a WAKEUP timer task
return true; return true;
} }
bool ok = utilTimerInsertTask(&task, NULL); int idx = utilTimerGetUnusedIndex(false/*don't wait*/);
// We wait until the timer is out of the reload event, because the reload event itself would wake us up if (idx<0) return false; // no free tasks!
return ok; UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->type = UET_WAKEUP;
task->time = wakeupTime;
task->repeatInterval = 0;
utilTimerInsertTask(idx, NULL);
return true;
} }
/** If the first timer task is a wakeup task, remove it. This stops /** If the first timer task is a wakeup task, remove it. This stops
@ -585,7 +620,7 @@ void jstClearWakeUp() {
jshInterruptOff(); jshInterruptOff();
// while the first item is a wakeup, remove it // while the first item is a wakeup, remove it
while (utilTimerTasksTail!=utilTimerTasksHead && while (utilTimerTasksTail!=utilTimerTasksHead &&
utilTimerTasks[utilTimerTasksTail].type == UET_WAKEUP) { utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].type == UET_WAKEUP) {
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1); utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
removedTimer = true; removedTimer = true;
} }
@ -597,37 +632,39 @@ void jstClearWakeUp() {
#ifndef SAVE_ON_FLASH #ifndef SAVE_ON_FLASH
bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type) { int jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type) {
assert(jsvIsString(currentData)); assert(jsvIsString(currentData));
assert(jsvIsUndefined(nextData) || jsvIsString(nextData)); assert(jsvIsUndefined(nextData) || jsvIsString(nextData));
if (!jshIsPinValid(pin)) return false; if (!jshIsPinValid(pin)) return -1;
UtilTimerTask task; int idx = utilTimerGetUnusedIndex(true/*wait*/);
task.repeatInterval = (unsigned int)period; if (idx<0) return -1; // no free tasks!
task.time = (int)(startTime + period); UtilTimerTask *task = &utilTimerTaskInfo[idx];
task.type = type; task->type = type;
task->repeatInterval = (unsigned int)period;
task->time = (int)(startTime + period);
if (UET_IS_BUFFER_WRITE_EVENT(type)) { if (UET_IS_BUFFER_WRITE_EVENT(type)) {
task.data.buffer.pin = pin; task->data.buffer.pin = pin;
task.data.buffer.npin = npin; task->data.buffer.npin = npin;
} else if (UET_IS_BUFFER_READ_EVENT(type)) { } else if (UET_IS_BUFFER_READ_EVENT(type)) {
#ifndef LINUX #ifndef LINUX
if (pinInfo[pin].analog == JSH_ANALOG_NONE) return false; // no analog... if (pinInfo[pin].analog == JSH_ANALOG_NONE) return -1; // no analog...
#endif #endif
task.data.buffer.pin = pin; task->data.buffer.pin = pin;
} else { } else {
assert(0); assert(0);
return false; return -1;
} }
task.data.buffer.currentBuffer = jsvGetRef(currentData); task->data.buffer.currentBuffer = jsvGetRef(currentData);
if (nextData) { if (nextData) {
// then we're repeating! // then we're repeating!
task.data.buffer.nextBuffer = jsvGetRef(nextData); task->data.buffer.nextBuffer = jsvGetRef(nextData);
} else { } else {
// then we're not repeating // then we're not repeating
task.data.buffer.nextBuffer = 0; task->data.buffer.nextBuffer = 0;
} }
jstUtilTimerSetupBuffer(&task); jstUtilTimerSetupBuffer(task);
utilTimerInsertTask(idx, NULL);
return utilTimerInsertTask(&task, NULL); return idx;
} }
@ -635,15 +672,15 @@ bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, Js
/// Remove the task that uses the buffer 'var' /// Remove the task that uses the buffer 'var'
bool jstStopBufferTimerTask(JsVar *var) { bool jstStopBufferTimerTask(JsVar *var) {
JsVarRef ref = jsvGetRef(var); JsVarRef ref = jsvGetRef(var);
return utilTimerRemoveTask(jstBufferTaskChecker, (void*)&ref); return utilTimerRemoveTask(utilTimerFindTask(jstBufferTaskChecker, (void*)&ref));
} }
#endif
/// Remove the task that uses the given pin /// Remove the task that uses the given pin
bool jstStopPinTimerTask(Pin pin) { bool jstStopPinTimerTask(Pin pin) {
return utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin); return utilTimerRemoveTask(utilTimerFindTask(jstPinTaskChecker, (void*)&pin));
} }
#endif
void jstReset() { void jstReset() {
jshUtilTimerDisable(); jshUtilTimerDisable();
@ -660,46 +697,13 @@ void jstSystemTimeChanged(JsSysTime diff) {
// we don't actually care since timers should hopefully just run based on delays // we don't actually care since timers should hopefully just run based on delays
} }
void jstDumpUtilityTimers() { /// Called from the idle loop if a custom event is received (could be for the timer)
int i; void jstOnCustomEvent(IOEventFlags eventFlags, uint8_t *data, int dataLen) {
UtilTimerTask uTimerTasks[UTILTIMERTASK_TASKS]; IOCustomEventFlags customFlags = *(IOCustomEventFlags*)data;
jshInterruptOff(); if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
for (i=0;i<UTILTIMERTASK_TASKS;i++) int id = customFlags >> EVC_DATA_SHIFT;
uTimerTasks[i] = utilTimerTasks[i]; if (utilTimerTaskInfo[id].type & UET_FINISHED) {
unsigned char uTimerTasksHead = utilTimerTasksHead; utilTimerTaskInfo[id].type = UET_NONE;
unsigned char uTimerTasksTail = utilTimerTasksTail;
jshInterruptOn();
jsiConsolePrintf("Util Timer %s\n", utilTimerOn?"on":"off");
unsigned char t = uTimerTasksTail;
bool hadTimers = false;
while (t!=uTimerTasksHead) {
hadTimers = true;
UtilTimerTask task = uTimerTasks[t];
jsiConsolePrintf("%08d us, repeat %08d us : ", (int)(1000*jshGetMillisecondsFromTime(task.time)), (int)(1000*jshGetMillisecondsFromTime(task.repeatInterval)));
switch (task.type) {
case UET_WAKEUP : jsiConsolePrintf("WKUP\n"); break;
case UET_SET : jsiConsolePrintf("SET ");
for (i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
if (task.data.set.pins[i] != PIN_UNDEFINED)
jsiConsolePrintf("%p=%d,", task.data.set.pins[i], (task.data.set.value>>i)&1);
jsiConsolePrintf("\n");
break;
#ifndef SAVE_ON_FLASH
case UET_WRITE_BYTE : jsiConsolePrintf("WR8\n"); break;
case UET_READ_BYTE : jsiConsolePrintf("RD8\n"); break;
case UET_WRITE_SHORT : jsiConsolePrintf("WR16\n"); break;
case UET_READ_SHORT : jsiConsolePrintf("RD16\n"); break;
#endif
case UET_EXECUTE : jsiConsolePrintf("EXEC %x(%x)\n", task.data.execute.fn, task.data.execute.userdata); break;
default : jsiConsolePrintf("?[%d]\n", task.type); break;
} }
t = (t+1) & (UTILTIMERTASK_TASKS-1);
} }
if (!hadTimers)
jsiConsolePrintf("No Timers found.\n");
} }

View File

@ -32,6 +32,7 @@ typedef enum {
#ifdef ESPR_USE_STEPPER_TIMER #ifdef ESPR_USE_STEPPER_TIMER
UET_STEP, ///< Write stepper motor UET_STEP, ///< Write stepper motor
#endif #endif
UET_FINISHED = 16, ///< OR this into a timer task to flag it as finished (it's then cleaned up outside the IRQ)
} PACKED_FLAGS UtilTimerEventType; } PACKED_FLAGS UtilTimerEventType;
#define UET_IS_SET_EVENT(T) (\ #define UET_IS_SET_EVENT(T) (\
@ -55,6 +56,23 @@ typedef enum {
#define UTILTIMERTASK_PIN_COUNT (8) #define UTILTIMERTASK_PIN_COUNT (8)
#ifdef ESPR_USE_STEPPER_TIMER
#define UET_IS_STEPPER(T) ((T)==UET_STEP)
#define UET_PIN_COUNT(T) (((T)==UET_STEP) ? 4 : UTILTIMERTASK_PIN_COUNT)
#else
#define UET_IS_STEPPER(T) ((T)==UET_SET)
#define UET_PIN_COUNT(T) UTILTIMERTASK_PIN_COUNT
#endif
#define UET_EVENT_HAS_PINS(T) (((T)==UET_SET) || UET_IS_STEPPER(T))
// Should we send EVC_TIMER_FINISHED for an event? Some like pin events are fast so we don't want to fill our buffer with them
#define UET_EVENT_SEND_TIMER_FINISHED(T) (\
((T)==UET_EXECUTE) || \
UET_IS_BUFFER_EVENT(T) || \
UET_IS_STEPPER(T) \
)
typedef struct UtilTimerTaskSet { typedef struct UtilTimerTaskSet {
Pin pins[UTILTIMERTASK_PIN_COUNT]; ///< pins to set (must be in same location as UtilTimerTaskStep.pins) Pin pins[UTILTIMERTASK_PIN_COUNT]; ///< pins to set (must be in same location as UtilTimerTaskStep.pins)
uint8_t value; ///< value to set pins to uint8_t value; ///< value to set pins to
@ -108,6 +126,10 @@ typedef struct UtilTimerTask {
UtilTimerEventType type; // the type of this task - do we set pin(s) or read/write data UtilTimerEventType type; // the type of this task - do we set pin(s) or read/write data
} PACKED_FLAGS UtilTimerTask; } PACKED_FLAGS UtilTimerTask;
/// Data for our tasks (eg when, what they are, etc)
extern UtilTimerTask utilTimerTaskInfo[UTILTIMERTASK_TASKS];
/// Called from the utility timer interrupt to handle timer events
void jstUtilTimerInterruptHandler(); void jstUtilTimerInterruptHandler();
/// Wait until the utility timer is totally empty (use with care as timers can repeat) /// Wait until the utility timer is totally empty (use with care as timers can repeat)
@ -149,8 +171,8 @@ bool jstSetWakeUp(JsSysTime period);
* before the wakeup event */ * before the wakeup event */
void jstClearWakeUp(); void jstClearWakeUp();
/// Start writing a string out at the given period between samples. 'time' is the time relative to the current time (0 = now). pin_neg is optional pin for writing opposite of signal to /// Start writing a string out at the given period between samples. 'time' is the time relative to the current time (0 = now). pin_neg is optional pin for writing opposite of signal to. Returns -1 on failure or timer ID on success
bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type); int jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type);
/// Remove the task that uses the buffer 'var' /// Remove the task that uses the buffer 'var'
bool jstStopBufferTimerTask(JsVar *var); bool jstStopBufferTimerTask(JsVar *var);
@ -165,26 +187,31 @@ void jstReset();
This should be done with interrupts off */ This should be done with interrupts off */
void jstSystemTimeChanged(JsSysTime diff); void jstSystemTimeChanged(JsSysTime diff);
/// Dump the current list of timers
void jstDumpUtilityTimers();
/* Restart the utility timer with the right period. This should not normally /* Restart the utility timer with the right period. This should not normally
need to be called by anything outside jstimer.c */ need to be called by anything outside jstimer.c */
void jstRestartUtilTimer(); void jstRestartUtilTimer();
/// Get the index of next free task slot, or -1 if none free. If 'wait=true' will wait until one is free
int utilTimerGetUnusedIndex(bool wait);
/** Queue a task up to be executed when a timer fires... return false on failure. /** Queue a task up to be executed when a timer fires... return false on failure.
* task.time is the delay at which to execute the task. If timerOffset!==NULL then * task.time is the delay at which to execute the task. If timerOffset!==NULL then
* task.time is relative to the time at which timerOffset=jstGetUtilTimerOffset(). * task.time is relative to the time at which timerOffset=jstGetUtilTimerOffset().
* This allows pulse trains/etc to be scheduled in sync. * This allows pulse trains/etc to be scheduled in sync.
*/ */
bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset); void utilTimerInsertTask(uint8_t taskIdx, uint32_t *timerOffset);
/// Remove the task that that 'checkCallback' returns true for. Returns false if none found /// Find a task that 'checkCallback' returns true for. Returns -1 if none found
bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData); int utilTimerFindTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData);
/// Remove the task with the given ID. Also sets type to UET_NONE. Returns false if none found
bool utilTimerRemoveTask(int id);
/// If 'checkCallback' returns true for a task, set 'task' to it and return true. Returns false if none found /// If 'checkCallback' returns true for a task, set 'task' to it and return true. Returns false if none found
bool utilTimerGetLastTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData, UtilTimerTask *task); bool utilTimerGetLastTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData, UtilTimerTask *task);
/// Called from the idle loop if a custom event is received (could be for the timer)
void jstOnCustomEvent(IOEventFlags eventFlags, uint8_t *data, int dataLen);
#endif /* JSTIMER_H_ */ #endif /* JSTIMER_H_ */

View File

@ -2684,6 +2684,10 @@ bool jsvIsStringIEqualAndUnLock(JsVar *var, const char *str) {
jsvUnLock(var); jsvUnLock(var);
return b; return b;
} }
// Also see jsvIsBasicVarEqual
bool jsvIsStringIEqual(JsVar *var, const char *str) {
return jsvIsStringEqualOrStartsWithOffset(var, str, false, 0, true);
}
/** Compare 2 strings, starting from the given character positions. equalAtEndOfString means that /** Compare 2 strings, starting from the given character positions. equalAtEndOfString means that
@ -3529,6 +3533,12 @@ void jsvSetArrayItem(JsVar *arr, JsVarInt index, JsVar *item) {
jsvUnLock(indexVar); jsvUnLock(indexVar);
} }
void jsvRemoveArrayItem(JsVar *arr, JsVarInt index) {
JsVar *indexVar = jsvGetArrayIndex(arr, index);
if (indexVar)
jsvRemoveChildAndUnLock(arr, indexVar);
}
// Get all elements from arr and put them in itemPtr (unless it'd overflow). // Get all elements from arr and put them in itemPtr (unless it'd overflow).
// Makes sure all of itemPtr either contains a JsVar or 0 // Makes sure all of itemPtr either contains a JsVar or 0
void jsvGetArrayItems(JsVar *arr, unsigned int itemCount, JsVar **itemPtr) { void jsvGetArrayItems(JsVar *arr, unsigned int itemCount, JsVar **itemPtr) {
@ -4617,6 +4627,8 @@ bool jsvReadConfigObject(JsVar *object, jsvConfigObject *configs, int nConfigs)
case JSV_STRING_0: case JSV_STRING_0:
case JSV_ARRAY: case JSV_ARRAY:
case JSV_FUNCTION: case JSV_FUNCTION:
if (*((JsVar**)configs[i].ptr))
jsvUnLock(*((JsVar**)configs[i].ptr));
*((JsVar**)configs[i].ptr) = jsvLockAgain(val); break; *((JsVar**)configs[i].ptr) = jsvLockAgain(val); break;
#ifndef ESPR_EMBED #ifndef ESPR_EMBED
case JSV_PIN: *((Pin*)configs[i].ptr) = jshGetPinFromVar(val); break; case JSV_PIN: *((Pin*)configs[i].ptr) = jshGetPinFromVar(val); break;

View File

@ -527,6 +527,7 @@ bool jsvIsStringEqualOrStartsWithOffset(JsVar *var, const char *str, bool isStar
*/ */
bool jsvIsStringEqualOrStartsWith(JsVar *var, const char *str, bool isStartsWith); bool jsvIsStringEqualOrStartsWith(JsVar *var, const char *str, bool isStartsWith);
bool jsvIsStringEqual(JsVar *var, const char *str); ///< is string equal. see jsvIsStringEqualOrStartsWith bool jsvIsStringEqual(JsVar *var, const char *str); ///< is string equal. see jsvIsStringEqualOrStartsWith
bool jsvIsStringIEqual(JsVar *var, const char *str); ///< is string equal (ignoring case). see jsvIsStringEqualOrStartsWithOffset
bool jsvIsStringIEqualAndUnLock(JsVar *var, const char *str); ///< is string equal (ignoring case). see jsvIsStringEqualOrStartsWithOffset bool jsvIsStringIEqualAndUnLock(JsVar *var, const char *str); ///< is string equal (ignoring case). see jsvIsStringEqualOrStartsWithOffset
int jsvCompareString(JsVar *va, JsVar *vb, size_t starta, size_t startb, bool equalAtEndOfString); ///< Compare 2 strings, starting from the given character positions int jsvCompareString(JsVar *va, JsVar *vb, size_t starta, size_t startb, bool equalAtEndOfString); ///< Compare 2 strings, starting from the given character positions
/// Return a new string containing just the characters that are shared between two strings. /// Return a new string containing just the characters that are shared between two strings.
@ -739,6 +740,7 @@ JsVar *jsvGetArrayIndex(const JsVar *arr, JsVarInt index); ///< Get a 'name' at
JsVar *jsvGetArrayItem(const JsVar *arr, JsVarInt index); ///< Get an item at the specified index in the array if it exists (and lock it) JsVar *jsvGetArrayItem(const JsVar *arr, JsVarInt index); ///< Get an item at the specified index in the array if it exists (and lock it)
JsVar *jsvGetLastArrayItem(const JsVar *arr); ///< Returns the last item in the given array (with string OR numeric index) JsVar *jsvGetLastArrayItem(const JsVar *arr); ///< Returns the last item in the given array (with string OR numeric index)
void jsvSetArrayItem(JsVar *arr, JsVarInt index, JsVar *item); ///< Set an array item at the specified index in the array void jsvSetArrayItem(JsVar *arr, JsVarInt index, JsVar *item); ///< Set an array item at the specified index in the array
void jsvRemoveArrayItem(JsVar *arr, JsVarInt index); ///< Remove an item from the array (does not change the array length)
void jsvGetArrayItems(JsVar *arr, unsigned int itemCount, JsVar **itemPtr); ///< Get all elements from arr and put them in itemPtr (unless it'd overflow). Makes sure all of itemPtr either contains a JsVar or 0 void jsvGetArrayItems(JsVar *arr, unsigned int itemCount, JsVar **itemPtr); ///< Get all elements from arr and put them in itemPtr (unless it'd overflow). Makes sure all of itemPtr either contains a JsVar or 0
JsVar *jsvGetIndexOfFull(JsVar *arr, JsVar *value, bool matchExact, bool matchIntegerIndices, int startIdx); ///< Get the index of the value in the array (matchExact==use pointer not equality check, matchIntegerIndices = don't check non-integers) JsVar *jsvGetIndexOfFull(JsVar *arr, JsVar *value, bool matchExact, bool matchIntegerIndices, int startIdx); ///< Get the index of the value in the array (matchExact==use pointer not equality check, matchIntegerIndices = don't check non-integers)
JsVar *jsvGetIndexOf(JsVar *arr, JsVar *value, bool matchExact); ///< Get the index of the value in the array or object (matchExact==use pointer, not equality check) JsVar *jsvGetIndexOf(JsVar *arr, JsVar *value, bool matchExact); ///< Get the index of the value in the array or object (matchExact==use pointer, not equality check)

View File

@ -1529,20 +1529,6 @@ int jswrap_espruino_reverseByte(int v) {
return (((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16) & 0xFF; return (((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16) & 0xFF;
} }
/*JSON{
"type" : "staticmethod",
"class" : "E",
"name" : "dumpTimers",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_espruino_dumpTimers"
}
Output the current list of Utility Timer Tasks - for debugging only
*/
void jswrap_espruino_dumpTimers() {
jstDumpUtilityTimers();
}
/*JSON{ /*JSON{
"type" : "staticmethod", "type" : "staticmethod",
"class" : "E", "class" : "E",

View File

@ -51,7 +51,6 @@ void jswrap_espruino_setConsole(JsVar *device, JsVar *options);
JsVar *jswrap_espruino_getConsole(); JsVar *jswrap_espruino_getConsole();
int jswrap_espruino_reverseByte(int v); int jswrap_espruino_reverseByte(int v);
void jswrap_espruino_dumpTimers();
void jswrap_espruino_dumpLockedVars(); void jswrap_espruino_dumpLockedVars();
void jswrap_espruino_dumpFreeList(); void jswrap_espruino_dumpFreeList();
void jswrap_e_dumpFragmentation(); void jswrap_e_dumpFragmentation();

View File

@ -269,19 +269,20 @@ void jswrap_io_digitalPulse(Pin pin, bool value, JsVar *times) {
uint32_t timerOffset = jstGetUtilTimerOffset(); uint32_t timerOffset = jstGetUtilTimerOffset();
bool hasTimer = jstGetLastPinTimerTask(pin, &task); bool hasTimer = jstGetLastPinTimerTask(pin, &task);
if (!hasTimer) task.time = 0; if (!hasTimer) task.time = 0;
// set first edge
if (!hasTimer)
jshPinOutput(pin, value);
// now start either one or a series of pulses // now start either one or a series of pulses
if (jsvIsNumeric(times)) { if (jsvIsNumeric(times)) {
JsVarFloat pulseTime = jsvGetFloat(times); JsVarFloat pulseTime = jsvGetFloat(times);
if (pulseTime<0 || isnan(pulseTime)) { if (pulseTime<0 || isnan(pulseTime)) {
jsExceptionHere(JSET_ERROR, "Pulse Time is less than 0 or not a number"); jsExceptionHere(JSET_ERROR, "Pulse Time is less than 0 or not a number");
} else if (pulseTime>0) { } else if (pulseTime>0) {
if (!hasTimer) jshPinOutput(pin, value);
task.time += jshGetTimeFromMilliseconds(pulseTime); task.time += jshGetTimeFromMilliseconds(pulseTime);
jstPinOutputAtTime(task.time, &timerOffset, &pin, 1, !value); jstPinOutputAtTime(task.time, &timerOffset, &pin, 1, !value);
} else jstUtilTimerWaitEmpty(); // time==0 } else jstUtilTimerWaitEmpty(); // time==0
} else if (jsvIsIterable(times)) { } else if (jsvIsIterable(times)) {
// iterable, so output a square wave // iterable, so output a square wave
if (!hasTimer) jshPinOutput(pin, value);
JsvIterator it; JsvIterator it;
jsvIteratorNew(&it, times, JSIF_EVERY_ARRAY_ELEMENT); jsvIteratorNew(&it, times, JSIF_EVERY_ARRAY_ELEMENT);
while (jsvIteratorHasElement(&it)) { while (jsvIteratorHasElement(&it)) {

View File

@ -131,29 +131,6 @@ void jswrap_pin_write(
jshPinOutput(pin, value); jshPinOutput(pin, value);
} }
/*JSON{
"type" : "method",
"class" : "Pin",
"name" : "writeAtTime",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_pin_writeAtTime",
"params" : [
["value", "bool", "Whether to set output high (true/1) or low (false/0)"],
["time", "float", "Time at which to write (in seconds)"]
]
}
Sets the output state of the pin to the parameter given at the specified time.
**Note:** this **doesn't** change the mode of the pin to an output. To do that,
you need to use `pin.write(0)` or `pinMode(pin, 'output')` first.
*/
void jswrap_pin_writeAtTime(JsVar *parent, bool value, JsVarFloat time) {
Pin pin = jshGetPinFromVar(parent);
JsSysTime sTime = jshGetTimeFromMilliseconds(time*1000) - jshGetSystemTime();
jstPinOutputAtTime(sTime, NULL, &pin, 1, value);
}
/*JSON{ /*JSON{
"type" : "method", "type" : "method",
"class" : "Pin", "class" : "Pin",

View File

@ -86,66 +86,51 @@ static bool jswrap_stepper_getPattern(JsVar *stepper, uint8_t *pattern) {
return ok; return ok;
} }
JsVar *_jswrap_stepper_getById(int id) {
JsVar *steppers = jsvObjectGetChild(execInfo.hiddenRoot, JSI_STEPPER_NAME, JSV_ARRAY);
if (!steppers) return 0;
return jsvGetArrayItem(steppers, id);
}
/*JSON{ /*JSON{
"type" : "idle", "type" : "EV_CUSTOM",
"generate" : "jswrap_stepper_idle", "generate" : "jswrap_stepper_eventHandler",
"ifdef" : "ESPR_USE_STEPPER_TIMER" "ifdef" : "ESPR_USE_STEPPER_TIMER"
}*/ }
bool jswrap_stepper_idle() { */
JsVar *steppers = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_STEPPER_NAME); void jswrap_stepper_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length) {
if (steppers) { IOCustomEventFlags customFlags = *(IOCustomEventFlags*)data;
JsvObjectIterator it; if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
jsvObjectIteratorNew(&it, steppers); int id = customFlags >> EVC_DATA_SHIFT;
while (jsvObjectIteratorHasValue(&it)) { JsVar *stepper = _jswrap_stepper_getById(id);
JsVar *stepper = jsvObjectIteratorGetValue(&it); if (stepper) {
bool running = jsvObjectGetBoolChild(stepper, "running"); jsiQueueObjectCallbacks(stepper, JS_EVENT_PREFIX"finish", NULL, 0);
Pin pins[4]; // Update current position
if (running) { jsvObjectSetChildAndUnLock(stepper, "pos", jsvNewFromInteger(
UtilTimerTask task; jsvObjectGetIntegerChild(stepper, "pos")+
// Search for a timer task jsvObjectGetIntegerChild(stepper, "_direction")));
if (!jswrap_stepper_getPins(stepper, pins) || !jstGetLastPinTimerTask(pins[0], &task)) { jsvObjectRemoveChild(stepper, "_direction");
// if the task is now gone... if (jsvObjectGetBoolChild(stepper, "_turnOff")) {
jsiQueueObjectCallbacks(stepper, JS_EVENT_PREFIX"finish", NULL, 0); Pin pins[4];
// Update current position int offpattern = jsvObjectGetIntegerChild(stepper, "offpattern");
jsvObjectSetChildAndUnLock(stepper, "pos", jsvNewFromInteger( if (jswrap_stepper_getPins(stepper, pins)) {
jsvObjectGetIntegerChild(stepper, "pos")+ for (int i=0;i<4;i++)
jsvObjectGetIntegerChild(stepper, "_direction"))); jshPinSetValue(pins[i], (offpattern>>i)&1);
jsvObjectRemoveChild(stepper, "_direction");
if (jsvObjectGetBoolChild(stepper, "_turnOff")) {
Pin pins[4];
int offpattern = jsvObjectGetIntegerChild(stepper, "offpattern");
if (jswrap_stepper_getPins(stepper, pins)) {
for (int i=0;i<4;i++)
jshPinSetValue(pins[i], (offpattern>>i)&1);
}
}
jsvObjectRemoveChild(stepper, "_turnOff");
// set running flag
running = false;
jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(running));
JsVar *promise = jsvObjectGetChildIfExists(stepper, "promise");
if (promise) {
jsvObjectRemoveChild(stepper, "promise");
jspromise_resolve(promise, 0);
jsvUnLock(promise);
}
} else {
// If the timer task is still there, don't do anything
// note... we could fire off a 'stepping' event?
} }
} }
jsvUnLock(stepper); jsvObjectRemoveChild(stepper, "_turnOff");
// if not running, remove stepper from this list jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(false));
if (!running) JsVar *promise = jsvObjectGetChildIfExists(stepper, "promise");
jsvObjectIteratorRemoveAndGotoNext(&it, steppers); if (promise) {
else jsvObjectRemoveChild(stepper, "promise");
jsvObjectIteratorNext(&it); jspromise_resolve(promise, 0);
jsvUnLock(promise);
}
JsVar *steppers = jsvObjectGetChild(execInfo.hiddenRoot, JSI_STEPPER_NAME, JSV_ARRAY);
if (steppers) jsvRemoveArrayItem(steppers, id);
jsvUnLock2(stepper, steppers);
} }
jsvObjectIteratorFree(&it);
jsvUnLock(steppers);
} }
return false; // no need to stay awake - an IRQ will wake us
} }
/*JSON{ /*JSON{
@ -163,7 +148,7 @@ void jswrap_stepper_kill() { // be sure to remove all stepper instances...
bool running = jsvObjectGetBoolChild(stepper, "running"); bool running = jsvObjectGetBoolChild(stepper, "running");
if (running) { if (running) {
Pin pins[4]; Pin pins[4];
if (!jswrap_stepper_getPins(stepper, pins) || !jstStopPinTimerTask(pins[0])) if (!jswrap_stepper_getPins(stepper, pins) || !jstStopPinTimerTask(pins[0])) // FIXME: use timer index
jsExceptionHere(JSET_ERROR, "Stepper couldn't be stopped"); jsExceptionHere(JSET_ERROR, "Stepper couldn't be stopped");
} }
jsvUnLock(stepper); jsvUnLock(stepper);
@ -318,32 +303,33 @@ JsVar *jswrap_stepper_moveTo(JsVar *stepper, int position, JsVar *options) {
JsVar *promise = jspromise_create(); JsVar *promise = jspromise_create();
jsvObjectSetChild(stepper, "promise", promise); jsvObjectSetChild(stepper, "promise", promise);
UtilTimerTask task; int idx = utilTimerGetUnusedIndex(true/*wait*/);
task.time = 0; if (idx<0) {
task.repeatInterval = jshGetTimeFromMilliseconds(1000.0 / freq); jsvUnLock(promise); // WAIT_UNTIL already errored
task.type = UET_STEP;
jswrap_stepper_getPins(stepper, task.data.step.pins);
task.data.step.steps = direction;
task.data.step.pIndex = currentPos&7;
task.data.step.pattern[0] = 0b00010010;
task.data.step.pattern[1] = 0b01001000;
task.data.step.pattern[2] = 0b00010010;
task.data.step.pattern[3] = 0b01001000;
jswrap_stepper_getPattern(stepper, task.data.step.pattern);
if (!utilTimerInsertTask(&task, NULL)) {
jsExceptionHere(JSET_ERROR, "Failed to add timer task");
jsvUnLock(promise);
return 0; return 0;
} }
UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->type = UET_STEP;
task->time = 0;
task->repeatInterval = jshGetTimeFromMilliseconds(1000.0 / freq);
jswrap_stepper_getPins(stepper, task->data.step.pins);
task->data.step.steps = direction;
task->data.step.pIndex = currentPos&7;
task->data.step.pattern[0] = 0b00010010;
task->data.step.pattern[1] = 0b01001000;
task->data.step.pattern[2] = 0b00010010;
task->data.step.pattern[3] = 0b01001000;
jswrap_stepper_getPattern(stepper, task->data.step.pattern);
utilTimerInsertTask(idx, NULL);
// And finally set it up // And finally set it up
jsvObjectSetChildAndUnLock(stepper, "timer", jsvNewFromInteger(idx));
jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(true)); jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(true));
jsvObjectSetChildAndUnLock(stepper, "_direction", jsvNewFromInteger(direction)); jsvObjectSetChildAndUnLock(stepper, "_direction", jsvNewFromInteger(direction));
jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff)); jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff));
// Add to our list of active steppers // Add to our list of active steppers
JsVar *steppers = jsvObjectGetChild(execInfo.hiddenRoot, JSI_STEPPER_NAME, JSV_ARRAY); JsVar *steppers = jsvObjectGetChild(execInfo.hiddenRoot, JSI_STEPPER_NAME, JSV_ARRAY);
if (steppers) { if (steppers) {
jsvArrayPush(steppers, stepper); jsvSetArrayItem(steppers, idx, stepper);
jsvUnLock(steppers); jsvUnLock(steppers);
} }
return promise; return promise;
@ -373,7 +359,7 @@ void jswrap_stepper_stop(JsVar *stepper, JsVar *options) {
bool turnOff = jsvObjectGetBoolChild(options, "turnOff"); bool turnOff = jsvObjectGetBoolChild(options, "turnOff");
if (turnOff) if (turnOff)
jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff)); jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff));
// the _idle handler will see _turnOff and will turn off the stepper // the event handler will see _turnOff and will turn off the stepper
} }
Pin pins[4]; Pin pins[4];
@ -386,12 +372,10 @@ void jswrap_stepper_stop(JsVar *stepper, JsVar *options) {
jsvObjectGetIntegerChild(stepper, "_direction") - jsvObjectGetIntegerChild(stepper, "_direction") -
task.data.step.steps)); task.data.step.steps));
} }
ok = jstStopPinTimerTask(pins[0]); ok = jstStopPinTimerTask(pins[0]); // FIXME: use timer index
} }
if (!ok) if (!ok)
jsExceptionHere(JSET_ERROR, "Stepper couldn't be stopped"); jsExceptionHere(JSET_ERROR, "Stepper couldn't be stopped");
// now run idle loop as this will issue the finish event and will clean up
jswrap_stepper_idle();
} }

View File

@ -16,8 +16,8 @@
#include "jshardware.h" #include "jshardware.h"
bool jswrap_stepper_idle();
void jswrap_stepper_kill(); void jswrap_stepper_kill();
void jswrap_stepper_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length);
JsVar *jswrap_stepper_constructor(JsVar *options); JsVar *jswrap_stepper_constructor(JsVar *options);
JsVar *jswrap_stepper_moveTo(JsVar *stepper, int position, JsVar *options); JsVar *jswrap_stepper_moveTo(JsVar *stepper, int position, JsVar *options);
void jswrap_stepper_stop(JsVar *stepper, JsVar *options); void jswrap_stepper_stop(JsVar *stepper, JsVar *options);

299
src/jswrap_timer.c Normal file
View File

@ -0,0 +1,299 @@
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* JavaScript methods for accessing the built-in timer
* ----------------------------------------------------------------------------
*/
#include "jswrap_timer.h"
#include "jsvar.h"
#include "jstimer.h"
#include "jsinteractive.h"
/*JSON{
"type" : "library",
"ifndef" : "SAVE_ON_FLASH",
"class" : "timer"
}
(2v29+ only) This class allows Espruino to control stepper motors.
```
require("timer").list()
// [ { id: 0, type: 'SET', pins: [ D2, D3, D4, D5 ], value: 0, time: 10 }, ... ]
```
This replaces `E.dumpTimers()` and `Pin.writeAtTime`
*/
static void jswrap_timer_queue_interrupt_js(JsSysTime time, void* userdata) {
JsVarRef code = (JsVarRef)(size_t)userdata;
jshPushIOCharEvents(EV_RUN_INTERRUPT_JS, (char*)&code, sizeof(code));
execInfo.execute |= EXEC_RUN_INTERRUPT_JS;
jshHadEvent();
}
/*JSON{
"type" : "staticmethod",
"class" : "timer",
"name" : "list",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_timer_list",
"return" : ["JsVar","Return a list of objects representing active timers"],
"return_object" : "Array"
}
See `require("timer").get` for details of the fields in each timer.
*/
JsVar *jswrap_timer_list() {
JsVar *arr = jsvNewEmptyArray();
for (int idx=0;idx<UTILTIMERTASK_TASKS;idx++) {
JsVar *obj = jswrap_timer_get(idx);
if (obj) {
jsvSetArrayItem(arr, idx, obj);
jsvUnLock(obj);
}
}
return arr;
}
/*JSON{
"type" : "staticmethod",
"class" : "timer",
"name" : "get",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_timer_get",
"params" : [
["timerID","int","The ID of the timer to get"]
],
"return" : ["JsVar","An object describing the added timer, with an `id` field added."]
}
Returns:
```
{
id : int, // timer ID (corresponds to array index)
type : string, // type of timer (eg 'SET/EXEC/STEP/WR8/WR16/RD8/RD16')
time : float, // time (in milliseconds) when the timer will next fire
interval : float, // (optional) if the timer repeats, the interval (in milliseconds
// the following fields are only present on devices with enough flash memory)
pins : [ Pin, ... ], // (for SET/STEP) the pins used
value : int, // (for SET) the value being set
ptr : int, // (for EXEC) pointer to the function being executed
userdata : int, // (for EXEC) userdata pointer
buffer : JsVar, // (for WR8/WR16/RD8/RD16) the buffer being used
buffer2 : JsVar // (for WR8/WR16/RD8/RD16) the second buffer being used (if any)
}
```
**Note:** `time` is set when the timer was last serviced, so if you set a 1 second timer
and then look at it after 500ms, it will still show as 1000ms (unless another timer
as been serviced before).
*/
JsVar *jswrap_timer_get(int id) {
if (id<0 || id>=UTILTIMERTASK_TASKS) {
return 0;
}
jshInterruptOff();
UtilTimerTask task = utilTimerTaskInfo[id];
jshInterruptOn();
if (task.type == UET_NONE) return 0;
JsVar *obj = jsvNewObject();
jsvObjectSetChildAndUnLock(obj, "id", jsvNewFromInteger(id));
const char *typeStr = NULL;
switch (task.type) {
case UET_NONE: assert(0); break;
case UET_WAKEUP : typeStr="WKUP"; break;
case UET_SET :
typeStr="SET";
#ifndef SAVE_ON_FLASH
jsvObjectSetChildAndUnLock(obj, "value", jsvNewFromInteger(task.data.set.value));
#endif
break;
#ifndef SAVE_ON_FLASH
case UET_WRITE_BYTE : typeStr="WR8"; break;
case UET_READ_BYTE : typeStr="RD8"; break;
case UET_WRITE_SHORT : typeStr="WR16"; break;
case UET_READ_SHORT : typeStr="RD16"; break;
#endif
#ifdef ESPR_USE_STEPPER_TIMER
case UET_STEP : typeStr="STEP"; break;
#endif
case UET_EXECUTE :
typeStr="EXEC";
#ifndef SAVE_ON_FLASH
if (task.data.execute.fn == jswrap_timer_queue_interrupt_js) {
JsVar *fn = jsvLock((JsVarRef)(size_t)task.data.execute.userdata);
jsvObjectSetChildAndUnLock(obj, "fn", fn);
} else {
jsvObjectSetChildAndUnLock(obj, "ptr", jsvNewFromInteger((size_t)task.data.execute.fn));
jsvObjectSetChildAndUnLock(obj, "userdata", jsvNewFromInteger((size_t)task.data.execute.userdata));
}
#endif
break;
}
#ifndef SAVE_ON_FLASH
if (UET_EVENT_HAS_PINS(task.type)) {
JsVar *pinsArr = jsvNewEmptyArray();
int pinCount = UET_PIN_COUNT(task.type);
for (int i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
if (task.data.set.pins[i] != PIN_UNDEFINED)
jsvArrayPushAndUnLock(pinsArr, jsvNewFromPin(task.data.set.pins[i]));
jsvObjectSetChildAndUnLock(obj, "pins", pinsArr);
}
if (UET_IS_BUFFER_EVENT(task.type)) {
jsvObjectSetChildAndUnLock(obj, "buffer", jsvLock(task.data.buffer.currentBuffer));
if (task.data.buffer.nextBuffer)
jsvObjectSetChildAndUnLock(obj, "buffer2", jsvLock(task.data.buffer.nextBuffer));
}
#endif
#ifdef ESPR_USE_STEPPER_TIMER
if (task.type == UET_STEP) {
jsvObjectSetChildAndUnLock(obj, "steps", jsvNewFromInteger(task.data.step.steps));
jsvObjectSetChildAndUnLock(obj, "stepIdx", jsvNewFromInteger(task.data.step.pIndex));
}
#endif
jsvObjectSetChildAndUnLock(obj, "type", typeStr ? jsvNewFromString(typeStr) : jsvNewFromInteger(task.type));
jsvObjectSetChildAndUnLock(obj, "time", jsvNewFromFloat(jshGetMillisecondsFromTime(task.time)));
if (task.repeatInterval)
jsvObjectSetChildAndUnLock(obj, "interval", jsvNewFromFloat(jshGetMillisecondsFromTime(task.repeatInterval)));
return obj;
}
/*JSON{
"type" : "staticmethod",
"class" : "timer",
"name" : "add",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_timer_add",
"params" : [
["timer","JsVar","An object describing the timer to add. See below."]
],
"return" : ["int","Return the ID of the added timer"]
}
To set one or more pins at a specific point in the future:
```
require("timer").add({
type : "SET",
pin : Pin, // required
pin2 : Pin, // optional
pin3 : Pin, // optional
pin4 : Pin, // optional
value : int, // required
time : float, // time (in milliseconds) when the timer will first fire
interval : float // (optional) if the timer repeats, the interval (in milliseconds)
})
// eg. set LED2 in 1 second (note: LED2 should already be an output)
require("timer").add({
type: "SET",
pin: LED2, value: 1,
time: 1000
});
```
To execute some code at a specific point in the future:
```
require("timer").add({
type : "EXEC",
ptr : int, userdata : int, // required - pointer to native function(time:uint64, userdate:int) to call, and uint32 userdata to pass to function
fn : JsVar, // alternative to ptr/userdata - a JS function to call (note: this function must be referenced elsewhere)
time : float, // time (in milliseconds) when the timer will first fire
interval : float // (optional) if the timer repeats, the interval (in milliseconds)
})
// eg. execute myFunction in 100ms, then 200ms thereafter
require("timer").add({
type:"EXEC", fn:myFunction,
time:100,
interval:200,
});
```
*/
int jswrap_timer_add(JsVar *timer) {
JsVarFloat time=0, interval=0;
JsVar *type = 0, *fn = 0;
int value=0,ptr=0,userdata=0;
Pin pins[UTILTIMERTASK_PIN_COUNT] = {PIN_UNDEFINED, PIN_UNDEFINED, PIN_UNDEFINED, PIN_UNDEFINED};
jsvConfigObject configs[] = {
{"type", JSV_STRING_0, &type},
{"time", JSV_FLOAT, &time},
{"interval", JSV_FLOAT, &interval},
{"value", JSV_INTEGER, &value},
{"fn", JSV_OBJECT, &fn},
{"ptr", JSV_INTEGER, &ptr},
{"userdata", JSV_INTEGER, &userdata},
{"pin", JSV_PIN, &pins[0]},
{"pin2", JSV_PIN, &pins[1]},
{"pin3", JSV_PIN, &pins[2]},
{"pin4", JSV_PIN, &pins[3]}
};
if (!jsvReadConfigObject(timer, configs, sizeof(configs) / sizeof(jsvConfigObject))) {
return -1;
}
UtilTimerEventType evtType = UET_NONE;
if (jsvIsStringIEqual(type, "SET")) {
if (pins[0] != PIN_UNDEFINED) evtType = UET_SET;
else jsExceptionHere(JSET_ERROR, "`pin` required for SET timer");
} else if (jsvIsStringIEqual(type, "EXEC")) {
if (ptr || jsvIsFunction(fn)) evtType = UET_EXECUTE;
else jsExceptionHere(JSET_ERROR, "`ptr` or `fn` required for EXEC timer");
} else jsExceptionHere(JSET_ERROR, "Unsupported timer type %q", type);
jsvUnLock(type);
if (evtType == UET_NONE) {
jsvUnLock(fn);
return -1;
}
int idx = utilTimerGetUnusedIndex(true/*wait*/);
if (idx<0) return -1; // no free tasks!
UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->time = (int)jshGetTimeFromMilliseconds(time);
task->repeatInterval = (unsigned int)jshGetTimeFromMilliseconds(interval);
if (evtType == UET_SET) {
for (int i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
task->data.set.pins[i] = pins[i];
task->data.set.value = value;
} else if (evtType == UET_EXECUTE) {
if (jsvIsFunction(fn)) { // if a function is passed we use EXEC_RUN_INTERRUPT_JS
if (jsvGetRefs(fn) == 0) {
jsExceptionHere(JSET_ERROR, "Function passed to timer must be referenced elsewhere");
jsvUnLock(fn);
return -1;
}
ptr = (size_t)jswrap_timer_queue_interrupt_js;
userdata = jsvGetRef(fn);
}
task->data.execute.fn = (UtilTimerTaskExecFn)(size_t)ptr;
task->data.execute.userdata = (void*)(size_t)userdata;
}
task->type = evtType; // set type here, as we may have returned with error earlier
jsvUnLock(fn);
utilTimerInsertTask(idx, NULL);
return idx;
}
/*JSON{
"type" : "staticmethod",
"class" : "timer",
"name" : "remove",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_timer_remove",
"params" : [
["timerID","int","The ID of the timer to remove"]
]
}
*/
void jswrap_timer_remove(int id) {
if (!utilTimerRemoveTask(id))
jsExceptionHere(JSET_ERROR, "No timer with ID %d", id);
}

22
src/jswrap_timer.h Normal file
View File

@ -0,0 +1,22 @@
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* JavaScript methods for accessing the built-in timer
* ----------------------------------------------------------------------------
*/
#include "jsvar.h"
JsVar *jswrap_timer_list();
JsVar *jswrap_timer_get(int id);
int jswrap_timer_add(JsVar *timer);
void jswrap_timer_remove(int id);

View File

@ -66,62 +66,6 @@ static JsVar *jswrap_waveform_getBuffer(JsVar *waveform, int bufferNumber, bool
} }
/*JSON{
"type" : "idle",
"generate" : "jswrap_waveform_idle",
"ifndef" : "SAVE_ON_FLASH"
}*/
bool jswrap_waveform_idle() {
JsVar *waveforms = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_WAVEFORM_NAME);
if (waveforms) {
JsvObjectIterator it;
jsvObjectIteratorNew(&it, waveforms);
while (jsvObjectIteratorHasValue(&it)) {
JsVar *waveform = jsvObjectIteratorGetValue(&it);
bool running = jsvObjectGetBoolChild(waveform, "running");
if (running) {
JsVar *buffer = jswrap_waveform_getBuffer(waveform,0,0);
UtilTimerTask task;
// Search for a timer task
if (!jstGetLastBufferTimerTask(buffer, &task)) {
// if the timer task is now gone...
JsVar *arrayBuffer = jsvObjectGetChildIfExists(waveform, "buffer");
jsiQueueObjectCallbacks(waveform, JS_EVENT_PREFIX"finish", &arrayBuffer, 1);
jsvUnLock(arrayBuffer);
running = false;
jsvObjectSetChildAndUnLock(waveform, "running", jsvNewFromBool(running));
} else {
// If the timer task is still there...
if (task.data.buffer.nextBuffer &&
task.data.buffer.nextBuffer != task.data.buffer.currentBuffer) {
// if it is a double-buffered task
int currentBuffer = (jsvGetRef(buffer)==task.data.buffer.currentBuffer) ? 0 : 1;
int oldBuffer = jsvGetIntegerAndUnLock(jsvObjectGetChild(waveform, "currentBuffer", JSV_INTEGER));
if (oldBuffer != currentBuffer) {
// buffers have changed - fire off a 'buffer' event with the buffer that needs to be filled
jsvObjectSetChildAndUnLock(waveform, "currentBuffer", jsvNewFromInteger(currentBuffer));
JsVar *arrayBuffer = jsvObjectGetChildIfExists(waveform, (currentBuffer==0) ? "buffer2" : "buffer");
jsiQueueObjectCallbacks(waveform, JS_EVENT_PREFIX"buffer", &arrayBuffer, 1);
jsvUnLock(arrayBuffer);
}
}
}
jsvUnLock(buffer);
}
jsvUnLock(waveform);
// if not running, remove waveform from this list
if (!running)
jsvObjectIteratorRemoveAndGotoNext(&it, waveforms);
else
jsvObjectIteratorNext(&it);
}
jsvObjectIteratorFree(&it);
jsvUnLock(waveforms);
}
return false; // no need to stay awake - an IRQ will wake us
}
/*JSON{ /*JSON{
"type" : "kill", "type" : "kill",
"generate" : "jswrap_waveform_kill", "generate" : "jswrap_waveform_kill",
@ -289,8 +233,11 @@ static void jswrap_waveform_start(JsVar *waveform, Pin pin, JsVarFloat freq, JsV
// And finally set it up // And finally set it up
if (!jstStartSignal(startTime, jshGetTimeFromMilliseconds(1000.0 / freq), pin, npin, buffer, repeat?(buffer2?buffer2:buffer):0, eventType)) int timerId = jstStartSignal(startTime, jshGetTimeFromMilliseconds(1000.0 / freq), pin, npin, buffer, repeat?(buffer2?buffer2:buffer):0, eventType);
if (timerId<0)
jsWarn("Unable to schedule a timer"); jsWarn("Unable to schedule a timer");
else
jsvObjectSetChildAndUnLock(waveform, "freq", jsvNewFromInteger(timerId));
jsvUnLock2(buffer,buffer2); jsvUnLock2(buffer,buffer2);
jsvObjectSetChildAndUnLock(waveform, "running", jsvNewFromBool(true)); jsvObjectSetChildAndUnLock(waveform, "running", jsvNewFromBool(true));
@ -298,7 +245,7 @@ static void jswrap_waveform_start(JsVar *waveform, Pin pin, JsVarFloat freq, JsV
// Add to our list of active waveforms // Add to our list of active waveforms
JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY); JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY);
if (waveforms) { if (waveforms) {
jsvArrayPush(waveforms, waveform); jsvSetArrayItem(waveforms, timerId, waveform);
jsvUnLock(waveforms); jsvUnLock(waveforms);
} }
} }
@ -378,8 +325,57 @@ void jswrap_waveform_stop(JsVar *waveform) {
jsExceptionHere(JSET_ERROR, "Waveform couldn't be stopped"); jsExceptionHere(JSET_ERROR, "Waveform couldn't be stopped");
} }
jsvUnLock(buffer); jsvUnLock(buffer);
// now run idle loop as this will issue the finish event and will clean up }
jswrap_waveform_idle();
JsVar *_jswrap_waveform_getById(int id) {
JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY);
if (!waveforms) return 0;
return jsvGetArrayItem(waveforms, id);
}
/*JSON{
"type" : "EV_CUSTOM",
"#if" : "!defined(SAVE_ON_FLASH)",
"generate" : "jswrap_waveform_eventHandler"
}
*/
void jswrap_waveform_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length) {
IOCustomEventFlags customFlags = *(IOCustomEventFlags*)data;
int id = customFlags >> EVC_DATA_SHIFT;
if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
JsVar *waveform = _jswrap_waveform_getById(id);
if (waveform) {
jsvObjectSetChildAndUnLock(waveform, "running", jsvNewFromBool(false));
JsVar *arrayBuffer = jsvObjectGetChildIfExists(waveform, "buffer");
jsiQueueObjectCallbacks(waveform, JS_EVENT_PREFIX"finish", &arrayBuffer, 1);
jsvUnLock(arrayBuffer);
JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY);
if (waveforms) jsvRemoveArrayItem(waveforms, id);
jsvUnLock2(waveform, waveforms);
}
}
if ((customFlags&EVC_TYPE_MASK) == EVC_TIMER_BUFFER_FLIP) {
JsVar *waveform = _jswrap_waveform_getById(id);
if (waveform) {
UtilTimerTask *task = &utilTimerTaskInfo[id];
if (task->data.buffer.nextBuffer &&
task->data.buffer.nextBuffer != task->data.buffer.currentBuffer) {
// if it is a double-buffered task
JsVar *buffer = jswrap_waveform_getBuffer(waveform,0,0);
int currentBuffer = (jsvGetRef(buffer)==task->data.buffer.currentBuffer) ? 0 : 1;
jsvUnLock(buffer);
int oldBuffer = jsvGetIntegerAndUnLock(jsvObjectGetChild(waveform, "currentBuffer", JSV_INTEGER));
if (oldBuffer != currentBuffer) {
// buffers have changed - fire off a 'buffer' event with the buffer that needs to be filled
jsvObjectSetChildAndUnLock(waveform, "currentBuffer", jsvNewFromInteger(currentBuffer));
JsVar *arrayBuffer = jsvObjectGetChildIfExists(waveform, (currentBuffer==0) ? "buffer2" : "buffer");
jsiQueueObjectCallbacks(waveform, JS_EVENT_PREFIX"buffer", &arrayBuffer, 1);
jsvUnLock(arrayBuffer);
}
}
jsvUnLock(waveform);
}
}
} }
#endif #endif

View File

@ -16,8 +16,8 @@
#include "jshardware.h" #include "jshardware.h"
bool jswrap_waveform_idle();
void jswrap_waveform_kill(); void jswrap_waveform_kill();
void jswrap_waveform_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length);
JsVar *jswrap_waveform_constructor(JsVar *samples, JsVar *options); JsVar *jswrap_waveform_constructor(JsVar *samples, JsVar *options);
void jswrap_waveform_startOutput(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options); void jswrap_waveform_startOutput(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options);
void jswrap_waveform_startInput(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options); void jswrap_waveform_startInput(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options);

View File

@ -1096,7 +1096,7 @@ void jshSetSystemTime(JsSysTime time) {
/// Convert a time in Milliseconds to one in ticks. /// Convert a time in Milliseconds to one in ticks.
JsSysTime jshGetTimeFromMilliseconds(JsVarFloat ms) { JsSysTime jshGetTimeFromMilliseconds(JsVarFloat ms) {
return (JsSysTime) (ms * (SYSCLK_FREQ / 1000.0)); return (JsSysTime)((ms * (SYSCLK_FREQ / 1000.0)) + 0.5);
} }
/// Convert ticks to a time in Milliseconds. /// Convert ticks to a time in Milliseconds.
@ -2956,13 +2956,11 @@ void jsvGetProcessorPowerUsage(JsVar *devices) {
void COMP_LPCOMP_IRQHandler() { void COMP_LPCOMP_IRQHandler() {
if (nrf_lpcomp_event_check(NRF_LPCOMP_EVENT_UP) && nrf_lpcomp_int_enable_check(LPCOMP_INTENSET_UP_Msk)) { if (nrf_lpcomp_event_check(NRF_LPCOMP_EVENT_UP) && nrf_lpcomp_int_enable_check(LPCOMP_INTENSET_UP_Msk)) {
nrf_lpcomp_event_clear(NRF_LPCOMP_EVENT_UP); nrf_lpcomp_event_clear(NRF_LPCOMP_EVENT_UP);
IOCustomEventFlags customFlags = EVC_LPCOMP | EVC_DATA_LPCOMP_UP; jshPushCustomEvent(EVC_LPCOMP | EVC_DATA_LPCOMP_UP);
jshPushEvent(EV_CUSTOM, (uint8_t*)&customFlags, sizeof(customFlags));
} }
if (nrf_lpcomp_event_check(NRF_LPCOMP_EVENT_DOWN) && nrf_lpcomp_int_enable_check(LPCOMP_INTENSET_DOWN_Msk)) { if (nrf_lpcomp_event_check(NRF_LPCOMP_EVENT_DOWN) && nrf_lpcomp_int_enable_check(LPCOMP_INTENSET_DOWN_Msk)) {
nrf_lpcomp_event_clear(NRF_LPCOMP_EVENT_DOWN); nrf_lpcomp_event_clear(NRF_LPCOMP_EVENT_DOWN);
IOCustomEventFlags customFlags = EVC_LPCOMP; jshPushCustomEvent(EVC_LPCOMP);
jshPushEvent(EV_CUSTOM, (uint8_t*)&customFlags, sizeof(customFlags));
} }
} }