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
STM32: Ensure setDeepSleep(1) will only sleep if there are no active PWM outputs
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
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_object.c \
src/jswrap_regexp.c \
src/jswrap_timer.c \
src/jswrap_string.c \
src/jswrap_modules.c \
src/jswrap_math.c

View File

@ -1,9 +1,8 @@
[
{"class":"OneWire","name":"*"},
{"class":"SPI","name":"send4bit"},
{"class":"E","name":"dumpTimers"},
{"class":"E","name":"enableWatchdog"},
{"class":"E","name":"getClock"},
{"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;
}
/// 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
static bool jshPushIOCharEventsHandler(IOEventFlags channel, char *data, unsigned int count) {
// Check for a CTRL+C

View File

@ -87,7 +87,8 @@ typedef enum {
#ifdef BLUETOOTH
EV_BLUETOOTH_PENDING, // Tasks that came from the Bluetooth Stack in an IRQ
#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
EV_BANGLEJS, // sent whenever Bangle.js-specific data needs to be queued
#endif
@ -131,10 +132,13 @@ typedef enum {
/** Event types for EV_CUSTOM */
typedef enum {
EVC_NONE,
EVC_TIMER_FINISHED,
EVC_TIMER_BUFFER_FLIP,
#ifdef NRF52_SERIES
EVC_LPCOMP, // jswrap_espruino: E.setComparator / E.on("comparator" event
#endif
EVC_TYPE_MASK = 255,
EVC_DATA_SHIFT = 8,
EVC_DATA_LPCOMP_UP = 256
} 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();
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();
void jshPushIOEvent(IOEventFlags channel, JsSysTime time);
/// 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
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);
/// Do we have any events pending? Will jshPopIOEvent return true?
bool jshHasEvents();

View File

@ -2204,6 +2204,20 @@ void jsiHandleIOEventForConsole(uint8_t *eventData, int eventLen) {
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() {
// This is how many times we have been here and not done anything.
// It will be zeroed if we do stuff later
@ -2248,7 +2262,10 @@ void jsiIdle() {
}
jsvUnLock(usartClass);
#endif
} else if (eventType == EV_RUN_INTERRUPT_JS) {
jsiOnRunInterruptJSEvent(eventData, eventLen);
} else if (eventType == EV_CUSTOM) {
jstOnCustomEvent(eventFlags, eventData, eventLen);
jswOnCustomEvent(eventFlags, eventData, eventLen);
#ifdef BLUETOOTH
} else if (eventType == EV_BLUETOOTH_PENDING) {
@ -3000,3 +3017,13 @@ void jsiDebuggerLine(JsVar *line) {
jslSetLex(oldLex);
}
#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
#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_ */

View File

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

View File

@ -98,7 +98,7 @@ typedef enum {
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_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_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"
/// 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)
volatile unsigned char utilTimerTasksHead = 0;
/// 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
task->data.buffer.charIdx++;
if (task->data.buffer.charIdx >= task->data.buffer.endIdx) {
@ -66,6 +68,8 @@ static void jstUtilTimerInterruptHandlerNextByte(UtilTimerTask *task) {
task->data.buffer.currentBuffer = t;
// Setup new buffer
jstUtilTimerSetupBuffer(task);
// notify rest of interpreter
jshPushCustomEvent(EVC_TIMER_BUFFER_FLIP | (timerId<<EVC_DATA_SHIFT));
} else {
task->data.buffer.var = 0;
// No more data - make sure we don't repeat!
@ -83,6 +87,17 @@ static inline unsigned char *jstUtilTimerInterruptHandlerByte(UtilTimerTask *tas
}
#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() {
/* 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 */
@ -93,13 +108,14 @@ void jstUtilTimerInterruptHandler() {
current time. */
int t = utilTimerTasksTail;
while (t!=utilTimerTasksHead) {
utilTimerTasks[t].time -= utilTimerPeriod;
utilTimerTaskInfo[utilTimerTasks[t]].time -= utilTimerPeriod;
t = (t+1) & (UTILTIMERTASK_TASKS-1);
}
utilTimerOffset += utilTimerPeriod;
// Check timers and execute any timers that are due
while (utilTimerTasksTail!=utilTimerTasksHead && utilTimerTasks[utilTimerTasksTail].time <= 0) {
UtilTimerTask *task = &utilTimerTasks[utilTimerTasksTail];
while (utilTimerTasksTail!=utilTimerTasksHead && utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time <= 0) {
uint8_t timerId = utilTimerTasks[utilTimerTasksTail];
UtilTimerTask *task = &utilTimerTaskInfo[timerId];
void (*executeFn)(JsSysTime time, void* userdata) = 0;
void *executeData = 0;
switch (task->type) {
@ -119,15 +135,15 @@ void jstUtilTimerInterruptHandler() {
if (!task->data.buffer.var) break;
int v = jshPinAnalogFast(task->data.buffer.pin);
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)v; // LSB first
jstUtilTimerInterruptHandlerNextByte(task);
jstUtilTimerInterruptHandlerNextByte(task, timerId);
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(v >> 8);
jstUtilTimerInterruptHandlerNextByte(task);
jstUtilTimerInterruptHandlerNextByte(task, timerId);
break;
}
case UET_READ_BYTE: {
if (!task->data.buffer.var) break;
*jstUtilTimerInterruptHandlerByte(task) = (unsigned char)(jshPinAnalogFast(task->data.buffer.pin) >> 8);
jstUtilTimerInterruptHandlerNextByte(task);
jstUtilTimerInterruptHandlerNextByte(task, timerId);
break;
}
case UET_WRITE_SHORT:
@ -137,19 +153,19 @@ void jstUtilTimerInterruptHandler() {
int sum;
if (task->type == UET_WRITE_SHORT) {
sum = *jstUtilTimerInterruptHandlerByte(task); // LSB first
jstUtilTimerInterruptHandlerNextByte(task);
jstUtilTimerInterruptHandlerNextByte(task, timerId);
} else {
sum = 0;
}
sum |= (unsigned short)(*jstUtilTimerInterruptHandlerByte(task) << 8);
jstUtilTimerInterruptHandlerNextByte(task);
jstUtilTimerInterruptHandlerNextByte(task, timerId);
task->data.buffer.currentValue = (unsigned short)sum;
// now search for other tasks writing to this pin... (polyphony)
int t = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
while (t!=utilTimerTasksHead) {
if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTasks[t].type) &&
utilTimerTasks[t].data.buffer.pin == task->data.buffer.pin)
sum += ((int)(unsigned int)utilTimerTasks[t].data.buffer.currentValue) - 32768;
if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTaskInfo[utilTimerTasks[t]].type) &&
utilTimerTaskInfo[utilTimerTasks[t]].data.buffer.pin == task->data.buffer.pin)
sum += ((int)(unsigned int)utilTimerTaskInfo[utilTimerTasks[t]].data.buffer.currentValue) - 32768;
t = (t+1) & (UTILTIMERTASK_TASKS-1);
}
// saturate
@ -194,31 +210,30 @@ void jstUtilTimerInterruptHandler() {
if (task->repeatInterval) {
// update time (we know time > task->time)
task->time += task->repeatInterval;
// do an in-place bubble sort to ensure that times are still in the right order
unsigned char ta = utilTimerTasksTail;
unsigned char tb = (ta+1) & (UTILTIMERTASK_TASKS-1);
while (tb != utilTimerTasksHead) {
if (utilTimerTasks[ta].time > utilTimerTasks[tb].time) {
UtilTimerTask task = utilTimerTasks[ta];
if (utilTimerTaskInfo[utilTimerTasks[ta]].time > utilTimerTaskInfo[utilTimerTasks[tb]].time) {
uint8_t idx = utilTimerTasks[ta];
utilTimerTasks[ta] = utilTimerTasks[tb];
utilTimerTasks[tb] = task;
utilTimerTasks[tb] = idx;
}
ta = tb;
tb = (tb+1) & (UTILTIMERTASK_TASKS-1);
}
} 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);
}
// 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);
}
// re-schedule the timer if there is something left to do
if (utilTimerTasksTail != utilTimerTasksHead) {
utilTimerPeriod = utilTimerTasks[utilTimerTasksTail].time;
utilTimerPeriod = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
if (utilTimerPeriod<0) utilTimerPeriod=0;
jshUtilTimerReschedule(utilTimerPeriod);
} else {
@ -253,32 +268,41 @@ static bool utilTimerIsFull() {
/* Restart the utility timer with the right period. This should not normally
need to be called by anything outside jstimer.c */
void jstRestartUtilTimer() {
utilTimerPeriod = utilTimerTasks[utilTimerTasksTail].time;
void jstRestartUtilTimer() {
utilTimerPeriod = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
utilTimerSetTime = jshGetSystemTime();
if (utilTimerPeriod<0) utilTimerPeriod=0;
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.
* 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().
* This allows pulse trains/etc to be scheduled in sync.
*/
bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset) {
// check if queue is full or not
if (utilTimerIsFull()) return false;
jshInterruptOff();
void utilTimerInsertTask(uint8_t taskIdx, uint32_t *timerOffset) {
assert(!utilTimerIsFull()); // queue should not be full since we had to allocate a task to get the index
UtilTimerTask *task = &utilTimerTaskInfo[taskIdx];
jshInterruptOff();
// See above - keep times in sync
if (timerOffset)
task->time += (int)*timerOffset - (int)utilTimerOffset;
// 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
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);
bool haveChangedTimer = 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
i = utilTimerTasksTail;
while (i != utilTimerTasksHead) {
if (utilTimerTasks[i].time > timePassed)
utilTimerTasks[i].time -= timePassed;
if (utilTimerTaskInfo[utilTimerTasks[i]].time > timePassed)
utilTimerTaskInfo[utilTimerTasks[i]].time -= timePassed;
else
utilTimerTasks[i].time = 0;
utilTimerTaskInfo[utilTimerTasks[i]].time = 0;
i = (i+1) & (UTILTIMERTASK_TASKS-1);
}
} else // timer hasn't changed, we have to update our task's time
task->time += timePassed;
// add new item
utilTimerTasks[insertPos] = *task;
utilTimerTasks[insertPos] = taskIdx;
//jsiConsolePrint("Head is %d\n", utilTimerTasksHead);
// now set up timer if not already set up...
if (!utilTimerOn || haveChangedTimer) {
@ -313,19 +336,27 @@ bool utilTimerInsertTask(UtilTimerTask *task, uint32_t *timerOffset) {
jstRestartUtilTimer();
}
jshInterruptOn();
return true;
}
/// Remove the task that that 'checkCallback' returns true for. Returns false if none found
bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData) {
/// Find a task that 'checkCallback' returns true for. Returns -1 if none found
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();
jstUtilTimerTaskIsFinished(id); // queue events for task finished
unsigned char ptr = utilTimerTasksHead;
if (ptr != utilTimerTasksTail) {
unsigned char endPtr = ((utilTimerTasksTail+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
while (ptr != endPtr) {
if (checkCallback(&utilTimerTasks[ptr], checkCallbackData)) {
if (utilTimerTasks[ptr]==id) {
// shift tail back along
unsigned char next = (ptr+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1);
while (next!=endPtr) {
@ -335,6 +366,7 @@ bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data),
}
// move 'end' pointer back
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
utilTimerTaskInfo[id].type = UET_NONE;
jshInterruptOn();
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
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();
unsigned char ptr = utilTimerTasksHead;
if (ptr != utilTimerTasksTail) {
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
while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) {
if (checkCallback(&utilTimerTasks[ptr], checkCallbackData)) {
*task = utilTimerTasks[ptr];
if (checkCallback(&utilTimerTaskInfo[utilTimerTasks[ptr]], checkCallbackData)) {
*task = utilTimerTaskInfo[utilTimerTasks[ptr]];
jshInterruptOn();
return true;
}
@ -399,18 +432,6 @@ static bool jstExecuteTaskChecker(UtilTimerTask *task, void *data) {
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) {
assert(pinCount<=UTILTIMERTASK_PIN_COUNT);
UtilTimerTask task;
task.time = (int)time;
task.repeatInterval = 0;
task.type = UET_SET;
int idx = utilTimerGetUnusedIndex(true/*wait*/);
if (idx<0) return false; // no free tasks even after waiting
UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->type = UET_SET;
task->time = (int)time;
task->repeatInterval = 0;
int i;
for (i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
task.data.set.pins[i] = (Pin)((i<pinCount) ? pins[i] : PIN_UNDEFINED);
task.data.set.value = value;
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer");
return utilTimerInsertTask(&task, timerOffset);
task->data.set.pins[i] = (Pin)((i<pinCount) ? pins[i] : PIN_UNDEFINED);
task->data.set.value = value;
utilTimerInsertTask(idx, timerOffset);
return true;
}
// 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 (dutyCycle<=0 || dutyCycle>=1 || freq<=0) {
// 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
jshPinSetValue(pin, dutyCycle >= 0.5);
return false;
@ -468,11 +492,11 @@ bool jstPinPWM(JsVarFloat freq, JsVarFloat dutyCycle, Pin pin) {
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
while (ptr != ((utilTimerTasksTail+UTILTIMERTASK_TASKS-1) & (UTILTIMERTASK_TASKS-1))) {
if (jstPinTaskChecker(&utilTimerTasks[ptr], (void*)&pin)) {
if (utilTimerTasks[ptr].data.set.value)
ptaskon = &utilTimerTasks[ptr];
if (jstPinTaskChecker(&utilTimerTaskInfo[utilTimerTasks[ptr]], (void*)&pin)) {
if (utilTimerTaskInfo[utilTimerTasks[ptr]].data.set.value)
ptaskon = &utilTimerTaskInfo[utilTimerTasks[ptr]];
else
ptaskoff = &utilTimerTasks[ptr];
ptaskoff = &utilTimerTaskInfo[utilTimerTasks[ptr]];
}
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)
if (ptaskon || ptaskoff) {
while (utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin));
while (jstStopPinTimerTask(pin));
}
UtilTimerTask taskon, taskoff;
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.type = UET_SET;
taskoff.type = UET_SET;
taskon.data.set.pins[0] = pin;
taskoff.data.set.pins[0] = pin;
int onidx = utilTimerGetUnusedIndex(true/*wait*/);
if (onidx<0) return false; // no free tasks
UtilTimerTask *taskon = &utilTimerTaskInfo[onidx];
taskon->type = UET_SET;
int offidx = utilTimerGetUnusedIndex(true/*wait*/);
if (offidx<0) {
taskon->type = UET_NONE; // free last task
return false; // no free tasks
}
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;
for (i=1;i<UTILTIMERTASK_PIN_COUNT;i++) {
taskon.data.set.pins[i] = PIN_UNDEFINED;
taskoff.data.set.pins[i] = PIN_UNDEFINED;
taskon->data.set.pins[i] = PIN_UNDEFINED;
taskoff->data.set.pins[i] = PIN_UNDEFINED;
}
// first task is to turn on
jshPinSetValue(pin, 1);
uint32_t timerOffset = jstGetUtilTimerOffset();
// now start the 2 PWM tasks
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer");
if (!utilTimerInsertTask(&taskon, &timerOffset)) return false;
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer");
return utilTimerInsertTask(&taskoff, &timerOffset);
utilTimerInsertTask(onidx, &timerOffset);
utilTimerInsertTask(offidx, &timerOffset);
return true;
}
/** 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
*/
bool jstExecuteFn(UtilTimerTaskExecFn fn, void *userdata, JsSysTime startTime, uint32_t period, uint32_t *timerOffset) {
UtilTimerTask task;
task.time = (int)startTime;
task.repeatInterval = period;
task.type = UET_EXECUTE;
task.data.execute.fn = fn;
task.data.execute.userdata = userdata;
WAIT_UNTIL(!utilTimerIsFull(), "Utility Timer");
return utilTimerInsertTask(&task, timerOffset);
int idx = utilTimerGetUnusedIndex(true/*wait*/);
if (idx<0) return false; // no free tasks even after waiting
UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->time = (int)startTime;
task->repeatInterval = period;
task->type = UET_EXECUTE;
task->data.execute.fn = fn;
task->data.execute.userdata = userdata;
utilTimerInsertTask(idx, timerOffset);
return true;
}
/// Stop executing the given function
@ -544,37 +577,39 @@ bool jstStopExecuteFn(UtilTimerTaskExecFn fn, void *userdata) {
UtilTimerTaskExec e;
e.fn = fn;
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
bool jstSetWakeUp(JsSysTime period) {
UtilTimerTask task;
task.time = (int)period;
task.repeatInterval = 0;
task.type = UET_WAKEUP;
bool hasTimer = false;
int wakeupTime = (int)period;
int nextTime;
// work out if we're waiting for a timer,
// and if so, when it's going to be
jshInterruptOff();
if (utilTimerTasksTail!=utilTimerTasksHead) {
hasTimer = true;
nextTime = utilTimerTasks[utilTimerTasksTail].time;
nextTime = utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].time;
}
jshInterruptOn();
if (hasTimer && task.time >= nextTime) {
if (hasTimer && wakeupTime >= nextTime) {
// we already had a timer, and it's going to wake us up sooner.
// don't create a WAKEUP timer task
return true;
}
bool ok = utilTimerInsertTask(&task, NULL);
// We wait until the timer is out of the reload event, because the reload event itself would wake us up
return ok;
int idx = utilTimerGetUnusedIndex(false/*don't wait*/);
if (idx<0) return false; // no free tasks!
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
@ -585,7 +620,7 @@ void jstClearWakeUp() {
jshInterruptOff();
// while the first item is a wakeup, remove it
while (utilTimerTasksTail!=utilTimerTasksHead &&
utilTimerTasks[utilTimerTasksTail].type == UET_WAKEUP) {
utilTimerTaskInfo[utilTimerTasks[utilTimerTasksTail]].type == UET_WAKEUP) {
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
removedTimer = true;
}
@ -597,37 +632,39 @@ void jstClearWakeUp() {
#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(jsvIsUndefined(nextData) || jsvIsString(nextData));
if (!jshIsPinValid(pin)) return false;
UtilTimerTask task;
task.repeatInterval = (unsigned int)period;
task.time = (int)(startTime + period);
task.type = type;
if (!jshIsPinValid(pin)) return -1;
int idx = utilTimerGetUnusedIndex(true/*wait*/);
if (idx<0) return -1; // no free tasks!
UtilTimerTask *task = &utilTimerTaskInfo[idx];
task->type = type;
task->repeatInterval = (unsigned int)period;
task->time = (int)(startTime + period);
if (UET_IS_BUFFER_WRITE_EVENT(type)) {
task.data.buffer.pin = pin;
task.data.buffer.npin = npin;
task->data.buffer.pin = pin;
task->data.buffer.npin = npin;
} else if (UET_IS_BUFFER_READ_EVENT(type)) {
#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
task.data.buffer.pin = pin;
task->data.buffer.pin = pin;
} else {
assert(0);
return false;
return -1;
}
task.data.buffer.currentBuffer = jsvGetRef(currentData);
task->data.buffer.currentBuffer = jsvGetRef(currentData);
if (nextData) {
// then we're repeating!
task.data.buffer.nextBuffer = jsvGetRef(nextData);
task->data.buffer.nextBuffer = jsvGetRef(nextData);
} else {
// then we're not repeating
task.data.buffer.nextBuffer = 0;
task->data.buffer.nextBuffer = 0;
}
jstUtilTimerSetupBuffer(&task);
return utilTimerInsertTask(&task, NULL);
jstUtilTimerSetupBuffer(task);
utilTimerInsertTask(idx, 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'
bool jstStopBufferTimerTask(JsVar *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
bool jstStopPinTimerTask(Pin pin) {
return utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin);
return utilTimerRemoveTask(utilTimerFindTask(jstPinTaskChecker, (void*)&pin));
}
#endif
void jstReset() {
jshUtilTimerDisable();
@ -660,46 +697,13 @@ void jstSystemTimeChanged(JsSysTime diff) {
// we don't actually care since timers should hopefully just run based on delays
}
void jstDumpUtilityTimers() {
int i;
UtilTimerTask uTimerTasks[UTILTIMERTASK_TASKS];
jshInterruptOff();
for (i=0;i<UTILTIMERTASK_TASKS;i++)
uTimerTasks[i] = utilTimerTasks[i];
unsigned char uTimerTasksHead = utilTimerTasksHead;
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;
/// 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) {
IOCustomEventFlags customFlags = *(IOCustomEventFlags*)data;
if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
int id = customFlags >> EVC_DATA_SHIFT;
if (utilTimerTaskInfo[id].type & UET_FINISHED) {
utilTimerTaskInfo[id].type = UET_NONE;
}
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
UET_STEP, ///< Write stepper motor
#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;
#define UET_IS_SET_EVENT(T) (\
@ -55,6 +56,23 @@ typedef enum {
#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 {
Pin pins[UTILTIMERTASK_PIN_COUNT]; ///< pins to set (must be in same location as UtilTimerTaskStep.pins)
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
} 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();
/// 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 */
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
bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type);
/// 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
int jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, Pin npin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type);
/// Remove the task that uses the buffer 'var'
bool jstStopBufferTimerTask(JsVar *var);
@ -165,26 +187,31 @@ void jstReset();
This should be done with interrupts off */
void jstSystemTimeChanged(JsSysTime diff);
/// Dump the current list of timers
void jstDumpUtilityTimers();
/* Restart the utility timer with the right period. This should not normally
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.
* 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().
* 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
bool utilTimerRemoveTask(bool (checkCallback)(UtilTimerTask *task, void* data), void *checkCallbackData);
/// Find a task that 'checkCallback' returns true for. Returns -1 if none found
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
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_ */

View File

@ -2684,6 +2684,10 @@ bool jsvIsStringIEqualAndUnLock(JsVar *var, const char *str) {
jsvUnLock(var);
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
@ -3529,6 +3533,12 @@ void jsvSetArrayItem(JsVar *arr, JsVarInt index, JsVar *item) {
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).
// Makes sure all of itemPtr either contains a JsVar or 0
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_ARRAY:
case JSV_FUNCTION:
if (*((JsVar**)configs[i].ptr))
jsvUnLock(*((JsVar**)configs[i].ptr));
*((JsVar**)configs[i].ptr) = jsvLockAgain(val); break;
#ifndef ESPR_EMBED
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 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
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.
@ -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 *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 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
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)

View File

@ -1529,20 +1529,6 @@ int jswrap_espruino_reverseByte(int v) {
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{
"type" : "staticmethod",
"class" : "E",

View File

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

View File

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

View File

@ -131,29 +131,6 @@ void jswrap_pin_write(
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{
"type" : "method",
"class" : "Pin",

View File

@ -86,66 +86,51 @@ static bool jswrap_stepper_getPattern(JsVar *stepper, uint8_t *pattern) {
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{
"type" : "idle",
"generate" : "jswrap_stepper_idle",
"type" : "EV_CUSTOM",
"generate" : "jswrap_stepper_eventHandler",
"ifdef" : "ESPR_USE_STEPPER_TIMER"
}*/
bool jswrap_stepper_idle() {
JsVar *steppers = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_STEPPER_NAME);
if (steppers) {
JsvObjectIterator it;
jsvObjectIteratorNew(&it, steppers);
while (jsvObjectIteratorHasValue(&it)) {
JsVar *stepper = jsvObjectIteratorGetValue(&it);
bool running = jsvObjectGetBoolChild(stepper, "running");
Pin pins[4];
if (running) {
UtilTimerTask task;
// Search for a timer task
if (!jswrap_stepper_getPins(stepper, pins) || !jstGetLastPinTimerTask(pins[0], &task)) {
// if the task is now gone...
jsiQueueObjectCallbacks(stepper, JS_EVENT_PREFIX"finish", NULL, 0);
// Update current position
jsvObjectSetChildAndUnLock(stepper, "pos", jsvNewFromInteger(
jsvObjectGetIntegerChild(stepper, "pos")+
jsvObjectGetIntegerChild(stepper, "_direction")));
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?
}
*/
void jswrap_stepper_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length) {
IOCustomEventFlags customFlags = *(IOCustomEventFlags*)data;
if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
int id = customFlags >> EVC_DATA_SHIFT;
JsVar *stepper = _jswrap_stepper_getById(id);
if (stepper) {
jsiQueueObjectCallbacks(stepper, JS_EVENT_PREFIX"finish", NULL, 0);
// Update current position
jsvObjectSetChildAndUnLock(stepper, "pos", jsvNewFromInteger(
jsvObjectGetIntegerChild(stepper, "pos")+
jsvObjectGetIntegerChild(stepper, "_direction")));
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);
}
}
jsvUnLock(stepper);
// if not running, remove stepper from this list
if (!running)
jsvObjectIteratorRemoveAndGotoNext(&it, steppers);
else
jsvObjectIteratorNext(&it);
jsvObjectRemoveChild(stepper, "_turnOff");
jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(false));
JsVar *promise = jsvObjectGetChildIfExists(stepper, "promise");
if (promise) {
jsvObjectRemoveChild(stepper, "promise");
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{
@ -163,7 +148,7 @@ void jswrap_stepper_kill() { // be sure to remove all stepper instances...
bool running = jsvObjectGetBoolChild(stepper, "running");
if (running) {
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");
}
jsvUnLock(stepper);
@ -318,32 +303,33 @@ JsVar *jswrap_stepper_moveTo(JsVar *stepper, int position, JsVar *options) {
JsVar *promise = jspromise_create();
jsvObjectSetChild(stepper, "promise", promise);
UtilTimerTask task;
task.time = 0;
task.repeatInterval = jshGetTimeFromMilliseconds(1000.0 / freq);
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);
int idx = utilTimerGetUnusedIndex(true/*wait*/);
if (idx<0) {
jsvUnLock(promise); // WAIT_UNTIL already errored
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
jsvObjectSetChildAndUnLock(stepper, "timer", jsvNewFromInteger(idx));
jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(true));
jsvObjectSetChildAndUnLock(stepper, "_direction", jsvNewFromInteger(direction));
jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff));
// Add to our list of active steppers
JsVar *steppers = jsvObjectGetChild(execInfo.hiddenRoot, JSI_STEPPER_NAME, JSV_ARRAY);
if (steppers) {
jsvArrayPush(steppers, stepper);
jsvSetArrayItem(steppers, idx, stepper);
jsvUnLock(steppers);
}
return promise;
@ -373,7 +359,7 @@ void jswrap_stepper_stop(JsVar *stepper, JsVar *options) {
bool turnOff = jsvObjectGetBoolChild(options, "turnOff");
if (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];
@ -386,12 +372,10 @@ void jswrap_stepper_stop(JsVar *stepper, JsVar *options) {
jsvObjectGetIntegerChild(stepper, "_direction") -
task.data.step.steps));
}
ok = jstStopPinTimerTask(pins[0]);
ok = jstStopPinTimerTask(pins[0]); // FIXME: use timer index
}
if (!ok)
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"
bool jswrap_stepper_idle();
void jswrap_stepper_kill();
void jswrap_stepper_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length);
JsVar *jswrap_stepper_constructor(JsVar *options);
JsVar *jswrap_stepper_moveTo(JsVar *stepper, int position, 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{
"type" : "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
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");
else
jsvObjectSetChildAndUnLock(waveform, "freq", jsvNewFromInteger(timerId));
jsvUnLock2(buffer,buffer2);
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
JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY);
if (waveforms) {
jsvArrayPush(waveforms, waveform);
jsvSetArrayItem(waveforms, timerId, waveform);
jsvUnLock(waveforms);
}
}
@ -378,8 +325,57 @@ void jswrap_waveform_stop(JsVar *waveform) {
jsExceptionHere(JSET_ERROR, "Waveform couldn't be stopped");
}
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

View File

@ -16,8 +16,8 @@
#include "jshardware.h"
bool jswrap_waveform_idle();
void jswrap_waveform_kill();
void jswrap_waveform_eventHandler(IOEventFlags eventFlags, uint8_t *data, int length);
JsVar *jswrap_waveform_constructor(JsVar *samples, 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);

View File

@ -1096,7 +1096,7 @@ void jshSetSystemTime(JsSysTime time) {
/// Convert a time in Milliseconds to one in ticks.
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.
@ -2956,13 +2956,11 @@ void jsvGetProcessorPowerUsage(JsVar *devices) {
void COMP_LPCOMP_IRQHandler() {
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);
IOCustomEventFlags customFlags = EVC_LPCOMP | EVC_DATA_LPCOMP_UP;
jshPushEvent(EV_CUSTOM, (uint8_t*)&customFlags, sizeof(customFlags));
jshPushCustomEvent(EVC_LPCOMP | EVC_DATA_LPCOMP_UP);
}
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);
IOCustomEventFlags customFlags = EVC_LPCOMP;
jshPushEvent(EV_CUSTOM, (uint8_t*)&customFlags, sizeof(customFlags));
jshPushCustomEvent(EVC_LPCOMP);
}
}