Skip to content

Commit

Permalink
Add built-in Stepper motor handling with the Stepper class (if enab…
Browse files Browse the repository at this point in the history
…led in build)
gfwilliams committed Jan 24, 2024
1 parent 6e08a64 commit 173dc29
Showing 12 changed files with 471 additions and 6 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
Throw Exception when a Promise tries to resolve with another Promise (#2450)
Add 'lcdBufferPtr' to Bangle.getOptions
Allow E.nativeCall to take 32 bit floats (eg for Inline C)
Add built-in Stepper motor handling with the `Stepper` class (if enabled in build)

2v20 : Ensure String.charCodeAt returns NaN for out of bounds chars
Bangle.js2: When rendering overlays, *do not* use the current FG/BG color for 1 bit overlays
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -284,6 +284,7 @@ src/jswrap_promise.c \
src/jswrap_serial.c \
src/jswrap_storage.c \
src/jswrap_spi_i2c.c \
src/jswrap_stepper.c \
src/jswrap_stream.c \
src/jswrap_waveform.c
endif
1 change: 1 addition & 0 deletions README_BuildProcess.md
Original file line number Diff line number Diff line change
@@ -146,6 +146,7 @@ This is a partial list of definitions that can be added in a `BOARD.py` file's `
* `ESPR_NO_SOFTWARE_SERIAL` - don't build in software serial support
* `ESPR_NO_SOFTWARE_I2C` - don't build in software I2C support
* `ESPR_NO_BLUETOOTH_MESSAGES` - don't include text versions of Bluetooth error messages (just the error number)
* `ESPR_USE_STEPPER_TIMER` - add builtin `Stepper` class to handle higher speed stepper handling

These are set automatically when `SAVE_ON_FLASH` is set (see `jsutils.h`)

1 change: 1 addition & 0 deletions boards/ESPRUINOBOARD.py
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
'makefile' : [
'DEFINES+=-DESPRUINO_1V3',
'DEFINES+=-DSAVE_ON_FLASH_MATH',
'DEFINES += -DESPR_USE_STEPPER_TIMER=1', # Build in the code for stepping using the timer
'STLIB=STM32F10X_XL',
'PRECOMPILED_OBJS+=$(ROOT)/targetlibs/stm32f1/lib/startup_stm32f10x_hd.o'
]
1 change: 1 addition & 0 deletions boards/JOLTJS.py
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@
# 'DEFINES += -DCONFIG_GPIO_AS_PINRESET', # Allow the reset pin to work
'DEFINES += -DNRF_USB=1 -DUSB',
'DEFINES += -DNEOPIXEL_SCK_PIN=1 -DNEOPIXEL_LRCK_PIN=26', # nRF52840 needs LRCK pin defined for neopixel
'DEFINES += -DESPR_USE_STEPPER_TIMER=1', # Build in the code for stepping using the timer
'DEFINES += -DNRF_SDH_BLE_GATT_MAX_MTU_SIZE=131', # 23+x*27 rule as per https://devzone.nordicsemi.com/f/nordic-q-a/44825/ios-mtu-size-why-only-185-bytes
'DEFINES += -DCENTRAL_LINK_COUNT=2 -DNRF_SDH_BLE_CENTRAL_LINK_COUNT=2', # allow two outgoing connections at once
'LDFLAGS += -Xlinker --defsym=LD_APP_RAM_BASE=0x3660', # set RAM base to match MTU=131 + CENTRAL_LINK_COUNT=2
1 change: 1 addition & 0 deletions boards/MDBT42Q.py
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@
'LDFLAGS += -Xlinker --defsym=LD_APP_RAM_BASE=0x3290', # set RAM base to match MTU=53 + CENTRAL_LINK_COUNT=2
'DEFINES+=-DBLUETOOTH_NAME_PREFIX=\'"MDBT42Q"\'',
'DEFINES+=-DNEOPIXEL_SCK_PIN=23 -DNEOPIXEL_LRCK_PIN=13', # see https://github.com/espruino/Espruino/issues/2071
'DEFINES += -DESPR_USE_STEPPER_TIMER=1', # Build in the code for stepping using the timer
'DFU_PRIVATE_KEY=targets/nrf5x_dfu/dfu_private_key.pem',
'DFU_SETTINGS=--application-version 0xff --hw-version 52 --sd-req 0x8C,0x91'
]
1 change: 1 addition & 0 deletions boards/PICO_R1_3.py
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
'makefile' : [
'DEFINES+=-DUSE_USB_OTG_FS=1 -DPICO -DPICO_1V3',
'DEFINES+=-DPIN_NAMES_DIRECT=1', # Package skips out some pins, so we can't assume each port starts from 0
'DEFINES += -DESPR_USE_STEPPER_TIMER=1', # Build in the code for stepping using the timer
'STLIB=STM32F401xE',
'PRECOMPILED_OBJS+=$(ROOT)/targetlibs/stm32f4/lib/startup_stm32f401xx.o'
]
1 change: 1 addition & 0 deletions boards/PIXLJS.py
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@
'DEFINES+=-DNFC_DEFAULT_URL=\'"https://www.espruino.com/ide"\'',
'DEFINES+=-DDUMP_IGNORE_VARIABLES=\'"g\\0"\'',
'DEFINES+=-DNEOPIXEL_SCK_PIN=14 -DNEOPIXEL_LRCK_PIN=15', # see https://github.com/espruino/Espruino/issues/2071
'DEFINES += -DESPR_USE_STEPPER_TIMER=1', # Build in the code for stepping using the timer
'DEFINES+=-DSAVE_ON_FLASH_MATH',
'DFU_PRIVATE_KEY=targets/nrf5x_dfu/dfu_private_key.pem',
'DFU_SETTINGS=--application-version 0xff --hw-version 52 --sd-req 0x8C,0x91',
49 changes: 45 additions & 4 deletions src/jstimer.c
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ void jstUtilTimerInterruptHandler() {
// 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) &&
if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTasks[t].type) &&
utilTimerTasks[t].data.buffer.pinFunction == task->data.buffer.pinFunction)
sum += ((int)(unsigned int)utilTimerTasks[t].data.buffer.currentValue) - 32768;
t = (t+1) & (UTILTIMERTASK_TASKS-1);
@@ -160,6 +160,27 @@ void jstUtilTimerInterruptHandler() {
jshSetOutputValue(task->data.buffer.pinFunction, sum);
break;
}
#endif
#ifdef ESPR_USE_STEPPER_TIMER
case UET_STEP: {
if (task->data.step.steps > 0) {
task->data.step.steps--;
task->data.step.pIndex = (task->data.step.pIndex+1) & 7;
} else if (task->data.step.steps < 0) {
task->data.step.steps++;
task->data.step.pIndex = (task->data.step.pIndex+7) & 7;
}
uint8_t step = task->data.step.pattern[task->data.step.pIndex>>1] >> (4*(task->data.step.pIndex&1));
jshPinSetValue(task->data.step.pins[0], (step&1)!=0);
jshPinSetValue(task->data.step.pins[1], (step&2)!=0);
jshPinSetValue(task->data.step.pins[2], (step&4)!=0);
jshPinSetValue(task->data.step.pins[3], (step&8)!=0);
if (task->data.step.steps == 0) {
task->repeatInterval = 0; // remove task
jshHadEvent(); // ensure idle loop runs so Stepper class can see if anything needs doing
}
break;
}
#endif
case UET_WAKEUP: // we've already done our job by waking the device up
default: break;
@@ -338,10 +359,13 @@ static bool jstBufferTaskChecker(UtilTimerTask *task, void *data) {

// data = *Pin
static bool jstPinTaskChecker(UtilTimerTask *task, void *data) {
if (task->type != UET_SET) return false;
if (task->type != UET_SET
#ifdef ESPR_USE_STEPPER_TIMER
&& task->type != UET_STEP
#endif
) return false;
Pin pin = *(Pin*)data;
int i;
for (i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
for (int i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
if (task->data.set.pins[i] == pin) {
return true;
} else if (task->data.set.pins[i]==PIN_UNDEFINED)
@@ -355,6 +379,18 @@ 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

// --------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------

@@ -582,6 +618,11 @@ bool jstStopBufferTimerTask(JsVar *var) {
return utilTimerRemoveTask(jstBufferTaskChecker, (void*)&ref);
}

/// Remove the task that uses the given pin
bool jstStopPinTimerTask(Pin pin) {
return utilTimerRemoveTask(jstPinTaskChecker, (void*)&pin);
}

#endif

void jstReset() {
22 changes: 20 additions & 2 deletions src/jstimer.h
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
#include "jspin.h"

typedef enum {
UET_NONE, ///< Nothing
UET_WAKEUP, ///< Does nothing except wake the device up!
UET_SET, ///< Set a pin to a value
UET_EXECUTE, ///< Execute something
@@ -28,6 +29,9 @@ typedef enum {
UET_WRITE_SHORT, ///< Write a short to a DAC/Timer
UET_READ_SHORT, ///< Read a short from an analog input
#endif
#ifdef ESPR_USE_STEPPER_TIMER
UET_STEP, ///< Write stepper motor
#endif
} PACKED_FLAGS UtilTimerEventType;

#define UET_IS_SET_EVENT(T) (\
@@ -52,7 +56,7 @@ typedef enum {
#define UTILTIMERTASK_PIN_COUNT (4)

typedef struct UtilTimerTaskSet {
Pin pins[UTILTIMERTASK_PIN_COUNT]; ///< pins to set
Pin pins[UTILTIMERTASK_PIN_COUNT]; ///< pins to set (must be in same location as UtilTimerTaskStep.pins)
uint8_t value; ///< value to set pins to
} PACKED_FLAGS UtilTimerTaskSet;

@@ -81,11 +85,22 @@ typedef struct UtilTimerTaskExec {
void *userdata;
} PACKED_FLAGS UtilTimerTaskExec;

#ifdef ESPR_USE_STEPPER_TIMER
typedef struct UtilTimerTaskStep {
Pin pins[UTILTIMERTASK_PIN_COUNT]; //< the 4 pins for the stepper motor (must be in same location as UtilTimerTaskSet.pins)
int16_t steps; //< How many steps? When this reaches 0 the timer task is removed
uint8_t pIndex; //< Index in 8 entry pattern array
uint8_t pattern[4]; //< step pattern (2 patterns per array element)
} PACKED_FLAGS UtilTimerTaskStep;
#endif

typedef union UtilTimerTaskData {
UtilTimerTaskSet set;
UtilTimerTaskBuffer buffer;
UtilTimerTaskExec execute;
#ifdef ESPR_USE_STEPPER_TIMER
UtilTimerTaskStep step;
#endif
} UtilTimerTaskData;

typedef struct UtilTimerTask {
@@ -139,9 +154,12 @@ void jstClearWakeUp();
/// Start writing a string out at the given period between samples. 'time' is the time relative to the current time (0 = now)
bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type);

/// Stop a timer task
/// Remove the task that uses the buffer 'var'
bool jstStopBufferTimerTask(JsVar *var);

/// Remove the task that uses the given pin
bool jstStopPinTimerTask(Pin pin);

/// Stop ALL timer tasks (including digitalPulse - use this when resetting the VM)
void jstReset();

373 changes: 373 additions & 0 deletions src/jswrap_stepper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
/*
* 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 Stepper Motor control using builtin timer
* ----------------------------------------------------------------------------
*/
#include "jswrap_stepper.h"
#include "jswrap_arraybuffer.h"
#include "jswrap_promise.h"
#include "jsvar.h"
#include "jsparse.h"
#include "jsinteractive.h"
#include "jstimer.h"

#define JSI_STEPPER_NAME "step"

#ifdef ESPR_USE_STEPPER_TIMER

/*JSON{
"type" : "class",
"ifdef" : "ESPR_USE_STEPPER_TIMER",
"class" : "Stepper"
}
(2v21+ only) This class allows Espruino to control stepper motors.
On Espruino before 2v20 you can still use the Stepper Motor module at https://www.espruino.com/StepperMotor - it just isn't quite as fast.
*/

/// Get pins (4 element array) from stepper object
static bool jswrap_stepper_getPins(JsVar *stepper, Pin *pins) {
JsVar *pinsVar = jsvObjectGetChildIfExists(stepper, "pins");
bool ok = false;
if (jsvIsArray(pinsVar) && jsvGetArrayLength(pinsVar)==4) {
ok = true;
for (int i=0;i<4;i++) {
pins[i] = jsvGetIntegerAndUnLock(jsvGetArrayItem(pinsVar, i));
if (!jshIsPinValid(pins[i]))
ok = false;
}
}
jsvUnLock(pinsVar);
return ok;
}

/// Get pattern (a 4 element uint8 array) from stepper object
static bool jswrap_stepper_getPattern(JsVar *stepper, uint8_t *pattern) {
JsVar *patternVar = jsvObjectGetChildIfExists(stepper, "pattern");
bool ok = false;
if (jsvIsArray(patternVar) && (jsvGetArrayLength(patternVar)==4 || jsvGetArrayLength(patternVar)==8)) {
ok = true;
int l = jsvGetArrayLength(patternVar);
memset(pattern,0,4);
for (int i=0;i<l;i++) {
int v = jsvGetIntegerAndUnLock(jsvGetArrayItem(patternVar, i));
if (v<0 || v>15) ok = false;
if (i&1) v<<=4;
pattern[i>>1] |= v;
}
if (l==4) { // for length of 4 just repeat
pattern[2] = pattern[0];
pattern[3] = pattern[1];
}
}
jsvUnLock(patternVar);
return ok;
}


/*JSON{
"type" : "idle",
"generate" : "jswrap_stepper_idle",
"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 = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(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(
jsvGetIntegerAndUnLock(jsvObjectGetChildIfExists(stepper, "pos"))+
jsvGetIntegerAndUnLock(jsvObjectGetChildIfExists(stepper, "_direction"))));
jsvObjectRemoveChild(stepper, "_direction");
if (jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(stepper, "_turnOff"))) {
Pin pins[4];
int offpattern = jsvGetIntegerAndUnLock(jsvObjectGetChildIfExists(stepper, "offpattern"));
if (jswrap_stepper_getPins(stepper, pins)) {
for (int i=0;i<4;i++)
jshPinSetValue(pins[i], (offpattern>>i)&1);
}
}
jsvObjectRemoveChild(stepper, "_turnOff");
// set running flag
running = false;
jsvObjectSetChildAndUnLock(stepper, "running", jsvNewFromBool(running));
JsVar *promise = jsvObjectGetChildIfExists(stepper, "promise");
if (promise) {
jsvObjectRemoveChild(stepper, "promise");
jspromise_resolve(promise, 0);
jsvUnLock(promise);
}
} else {
// If the timer task is still there, don't do anything
// note... we could fire off a 'stepping' event?
}
}
jsvUnLock(stepper);
// if not running, remove stepper from this list
if (!running)
jsvObjectIteratorRemoveAndGotoNext(&it, steppers);
else
jsvObjectIteratorNext(&it);
}
jsvObjectIteratorFree(&it);
jsvUnLock(steppers);
}
return false; // no need to stay awake - an IRQ will wake us
}

/*JSON{
"type" : "kill",
"generate" : "jswrap_stepper_kill",
"ifdef" : "ESPR_USE_STEPPER_TIMER"
}*/
void jswrap_stepper_kill() { // be sure to remove all stepper instances...
JsVar *steppers = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_STEPPER_NAME);
if (steppers) {
JsvObjectIterator it;
jsvObjectIteratorNew(&it, steppers);
while (jsvObjectIteratorHasValue(&it)) {
JsVar *stepper = jsvObjectIteratorGetValue(&it);
bool running = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(stepper, "running"));
if (running) {
Pin pins[4];
if (!jswrap_stepper_getPins(stepper, pins) || !jstStopPinTimerTask(pins[0]))
jsExceptionHere(JSET_ERROR, "Stepper couldn't be stopped");
}
jsvUnLock(stepper);
// if not running, remove stepper from this list
jsvObjectIteratorRemoveAndGotoNext(&it, steppers);
}
jsvObjectIteratorFree(&it);
jsvUnLock(steppers);
}
}


/*JSON{
"type" : "constructor",
"class" : "Stepper",
"name" : "Stepper",
"ifdef" : "ESPR_USE_STEPPER_TIMER",
"generate" : "jswrap_stepper_constructor",
"params" : [
["options","JsVar","options struct `{pins:[1,2,3,4]}`"]
],
"return" : ["JsVar","An Stepper object"]
}
Create a `Stepper` class. `options` can contain:
```
{
pins : [...], // required - 4 element array of pins
pattern : [...], // optional - a 4/8 element array of step patterns
offpattern : 0, // optional (default 0) - the pattern to output to stop driving the stepper motor
freq : 500, // optional (default 500) steps per second
}
```
`pins` must be supplied as a 4 element array of pins. When created,
if pin state has not been set manually on each pin, the pins will
be set to outputs.
If `pattern` isn't specified, a default pattern of `[0b0001,0b0010,0b0100,0b1000]` will be used. You
can specify different patterns, for example `[0b1100,0b1000,0b1001,0b0001,0b0011,0b0010,0b0110,0b0100]`.
*/
JsVar *jswrap_stepper_constructor(JsVar *options) {
if (!jsvIsObject(options)) {
jsExceptionHere(JSET_ERROR, "Expecting options to be an Object, not %t", options);
return 0;
}

JsVar *pinsVar = 0;
JsVar *patternVar = 0;
JsVarFloat freq = 500;
int offpattern = -1;
jsvConfigObject configs[] = {
{"pins", JSV_ARRAY, &pinsVar},
{"pattern", JSV_ARRAY, &patternVar},
{"freq", JSV_FLOAT, &freq},
{"offpattern", JSV_INTEGER, &offpattern}
};
if (!jsvReadConfigObject(options, configs, sizeof(configs) / sizeof(jsvConfigObject))) {
return 0;
}
if (!jsvIsArray(pinsVar) || jsvGetArrayLength(pinsVar)!=4) {
jsvUnLock2(pinsVar, patternVar);
jsExceptionHere(JSET_ERROR, "'pins' must be a 4 element array");
return 0;
}

JsVar *stepper = jspNewObject(0, "Stepper");
if (!stepper) // out of memory
return 0;
jsvObjectSetChildAndUnLock(stepper, "pins", pinsVar);
jsvObjectSetChildAndUnLock(stepper, "pos", jsvNewFromInteger(0));
jsvObjectSetChildAndUnLock(stepper, "freq", jsvNewFromFloat(freq));
if (offpattern) jsvObjectSetChildAndUnLock(stepper, "offpattern", jsvNewFromInteger(offpattern));
if (patternVar) {
jsvObjectSetChildAndUnLock(stepper, "pattern", patternVar);
uint8_t pattern[4];
if (!jswrap_stepper_getPattern(stepper, pattern)) {
jsExceptionHere(JSET_ERROR, "'pattern' isn't valid 4/8 element array");
}
}

Pin pins[4];
if (jswrap_stepper_getPins(stepper, pins)) {
for (int i=0;i<4;i++)
if (!jshGetPinStateIsManual(pins[i]))
jshPinSetState(pins[i], JSHPINSTATE_GPIO_OUT);
} else {
jsExceptionHere(JSET_ERROR, "Not all pins are valid");
}
return stepper;
}

/*JSON{
"type" : "method",
"class" : "Stepper",
"name" : "moveTo",
"ifdef" : "ESPR_USE_STEPPER_TIMER",
"generate" : "jswrap_stepper_moveTo",
"params" : [
["direction","int","The amount of steps to move in either direction"],
["options","JsVar","Optional options struct"]
],
"return" : ["JsVar","A Promise that resolves when the stepper has finished moving"],
"return_object": "Promise"
}
Move a certain number of steps in either direction, `options` can be:
```
{
freq : 100, // optional (frequency in Hz) step frequency
turnOff : true, // optional (default false) turn off stepper after this movement?
}
```
*/
JsVar *jswrap_stepper_moveTo(JsVar *stepper, int direction, JsVar *options) {
bool running = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(stepper, "running"));
if (running) {
jsExceptionHere(JSET_ERROR, "Stepper is already running");
return 0;
}
if (direction==0) // resolve immediately if not moving
return jswrap_promise_resolve(NULL);

JsVarFloat freq = jsvGetFloatAndUnLock(jsvObjectGetChildIfExists(stepper, "freq"));;
if (jsvIsObject(options)) {
JsVarFloat t = jsvGetFloatAndUnLock(jsvObjectGetChildIfExists(options, "freq"));
if (isfinite(t)) freq = t;
} else if (!jsvIsUndefined(options)) {
jsExceptionHere(JSET_ERROR, "Expecting options to be undefined or an Object, not %t", options);
}
if (freq<0.001) {
jsExceptionHere(JSET_ERROR, "Frequency must be above 0.001Hz");
return 0;
}
bool turnOff = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(options, "turnOff"));


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 = jsvGetIntegerAndUnLock(jsvObjectGetChildIfExists(stepper, "pos"))&7;
task.data.step.pattern[0] = 0b00010010;
task.data.step.pattern[1] = 0b01001000;
task.data.step.pattern[2] = 0b00010010;
task.data.step.pattern[3] = 0b01001000;
jswrap_stepper_getPattern(stepper, task.data.step.pattern);

if (!utilTimerInsertTask(&task, NULL)) {
jsExceptionHere(JSET_ERROR, "Failed to add timer task");
jsvUnLock(promise);
return 0;
}
// And finally set it up
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);
jsvUnLock(steppers);
}
return promise;
}

/*JSON{
"type" : "method",
"class" : "Stepper",
"name" : "stop",
"ifdef" : "ESPR_USE_STEPPER_TIMER",
"generate" : "jswrap_stepper_stop",
"params" : [
["options","JsVar","Optional options struct"]
]
}
Stop a stepper motor that is currently running.
You can specify `.stop({turnOff:true})` to force the stepper motor to turn off.
*/
void jswrap_stepper_stop(JsVar *stepper, JsVar *options) {
bool running = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(stepper, "running"));
if (!running) {
jsExceptionHere(JSET_ERROR, "Stepper is not running");
return;
}
if (jsvIsObject(options)) {
bool turnOff = jsvGetBoolAndUnLock(jsvObjectGetChildIfExists(options, "turnOff"));
if (turnOff)
jsvObjectSetChildAndUnLock(stepper, "_turnOff", jsvNewFromBool(turnOff));
// the _idle handler will see _turnOff and will turn off the stepper
}

Pin pins[4];
bool ok = false;
if (jswrap_stepper_getPins(stepper, pins)) {
UtilTimerTask task;
if (jstGetLastPinTimerTask(pins[0], &task)) {
// update step count to where the timer was when we stopped
jsvObjectSetChildAndUnLock(stepper, "_direction", jsvNewFromInteger(
jsvGetIntegerAndUnLock(jsvObjectGetChildIfExists(stepper, "_direction")) -
task.data.step.steps));
}
ok = jstStopPinTimerTask(pins[0]);
}
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();
}
#endif

25 changes: 25 additions & 0 deletions src/jswrap_stepper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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/.
*
* ----------------------------------------------------------------------------
* JavaScript methods for Stepper Motor control using builtin timer
* ----------------------------------------------------------------------------
*/
#ifndef JSWRAP_STEPPER_H_
#define JSWRAP_STEPPER_H_

#include "jshardware.h"

bool jswrap_stepper_idle();
void jswrap_stepper_kill();
JsVar *jswrap_stepper_constructor(JsVar *options);
JsVar *jswrap_stepper_moveTo(JsVar *stepper, int direction, JsVar *options);
void jswrap_stepper_stop(JsVar *stepper, JsVar *options);

#endif // JSWRAP_WAVEFORM_H_

0 comments on commit 173dc29

Please sign in to comment.