diff --git a/ChangeLog b/ChangeLog index 164b4e391..accac5d17 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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) diff --git a/Makefile b/Makefile index 497cf13ff..328caea2e 100755 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/boards/BANGLEJS.blocklist b/boards/BANGLEJS.blocklist index 2af18e54b..ae4a0f241 100644 --- a/boards/BANGLEJS.blocklist +++ b/boards/BANGLEJS.blocklist @@ -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":"*"} ] diff --git a/src/jsdevices.c b/src/jsdevices.c index e69698ed1..223854c01 100644 --- a/src/jsdevices.c +++ b/src/jsdevices.c @@ -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 diff --git a/src/jsdevices.h b/src/jsdevices.h index 9c0cf378c..17e804391 100644 --- a/src/jsdevices.h +++ b/src/jsdevices.h @@ -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(); diff --git a/src/jsinteractive.c b/src/jsinteractive.c index 918f8517d..7d46887e4 100644 --- a/src/jsinteractive.c +++ b/src/jsinteractive.c @@ -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;itk==LEX_ID || lex->tk==LEX_INT || lex->tk==LEX_FLOAT || diff --git a/src/jsparse.h b/src/jsparse.h index 66c848259..87191cd7b 100644 --- a/src/jsparse.h +++ b/src/jsparse.h @@ -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 diff --git a/src/jstimer.c b/src/jstimer.c index 3ef3f75a3..018dc03c5 100644 --- a/src/jstimer.c +++ b/src/jstimer.c @@ -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<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<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;itime += (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;idata.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;idata.set.pins[i] = (Pin)((idata.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;idata.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>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"); - } diff --git a/src/jstimer.h b/src/jstimer.h index 43895ef57..4de307a9f 100644 --- a/src/jstimer.h +++ b/src/jstimer.h @@ -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_ */ diff --git a/src/jsvar.c b/src/jsvar.c index f0bd6e2a4..2d900fd0a 100644 --- a/src/jsvar.c +++ b/src/jsvar.c @@ -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; diff --git a/src/jsvar.h b/src/jsvar.h index 824ea0ad0..d403118bc 100644 --- a/src/jsvar.h +++ b/src/jsvar.h @@ -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) diff --git a/src/jswrap_espruino.c b/src/jswrap_espruino.c index 731c1c5aa..2344c991e 100644 --- a/src/jswrap_espruino.c +++ b/src/jswrap_espruino.c @@ -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", diff --git a/src/jswrap_espruino.h b/src/jswrap_espruino.h index 3ea3ef087..f08e2f5ef 100644 --- a/src/jswrap_espruino.h +++ b/src/jswrap_espruino.h @@ -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(); diff --git a/src/jswrap_io.c b/src/jswrap_io.c index 2d437a793..57baf1918 100644 --- a/src/jswrap_io.c +++ b/src/jswrap_io.c @@ -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)) { diff --git a/src/jswrap_pin.c b/src/jswrap_pin.c index d40b47a03..cd82127f2 100644 --- a/src/jswrap_pin.c +++ b/src/jswrap_pin.c @@ -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", diff --git a/src/jswrap_stepper.c b/src/jswrap_stepper.c index ee8107323..99bb2a68f 100644 --- a/src/jswrap_stepper.c +++ b/src/jswrap_stepper.c @@ -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(); } diff --git a/src/jswrap_stepper.h b/src/jswrap_stepper.h index 9af0af624..8bad65ea1 100644 --- a/src/jswrap_stepper.h +++ b/src/jswrap_stepper.h @@ -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); diff --git a/src/jswrap_timer.c b/src/jswrap_timer.c new file mode 100644 index 000000000..22b8b3bbb --- /dev/null +++ b/src/jswrap_timer.c @@ -0,0 +1,299 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * 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) { + 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;itime = (int)jshGetTimeFromMilliseconds(time); + task->repeatInterval = (unsigned int)jshGetTimeFromMilliseconds(interval); + if (evtType == UET_SET) { + for (int i=0;idata.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); +} diff --git a/src/jswrap_timer.h b/src/jswrap_timer.h new file mode 100644 index 000000000..18ad34d55 --- /dev/null +++ b/src/jswrap_timer.h @@ -0,0 +1,22 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * 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); diff --git a/src/jswrap_waveform.c b/src/jswrap_waveform.c index 6fb353797..98776a2e0 100644 --- a/src/jswrap_waveform.c +++ b/src/jswrap_waveform.c @@ -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 diff --git a/src/jswrap_waveform.h b/src/jswrap_waveform.h index 5aadd3e8a..d6496ce58 100644 --- a/src/jswrap_waveform.h +++ b/src/jswrap_waveform.h @@ -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); diff --git a/targets/nrf5x/jshardware.c b/targets/nrf5x/jshardware.c index d4218a7db..9c291f3dd 100644 --- a/targets/nrf5x/jshardware.c +++ b/targets/nrf5x/jshardware.c @@ -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); } }