Skip to content

Commit

Permalink
Waveform: Add 'npin' option to allow +/- output on two pins
Browse files Browse the repository at this point in the history
  • Loading branch information
gfwilliams committed Oct 8, 2024
1 parent 6d9495d commit 471847f
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 24 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ESP32: Timers are now turned off when requested - digitalPulse/soft pwm/writeAtTime more accurate
Bangle.js: Remove debug log messages from E.showScroller
STM32F4: Update stm32f4xx_ll_usb to fix over-buffered USB CDC tx after being woken from deep sleep
Waveform: Add 'npin' option to allow +/- output on two pins

2v24 : Bangle.js2: Add 'Bangle.touchRd()', 'Bangle.touchWr()'
Bangle.js2: After Bangle.showTestScreen, put Bangle.js into a hard off state (not soft off)
Expand Down
20 changes: 13 additions & 7 deletions src/jstimer.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,24 @@ void jstUtilTimerInterruptHandler() {
int t = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
while (t!=utilTimerTasksHead) {
if (UET_IS_BUFFER_WRITE_EVENT(utilTimerTasks[t].type) &&
utilTimerTasks[t].data.buffer.pinFunction == task->data.buffer.pinFunction)
utilTimerTasks[t].data.buffer.pin == task->data.buffer.pin)
sum += ((int)(unsigned int)utilTimerTasks[t].data.buffer.currentValue) - 32768;
t = (t+1) & (UTILTIMERTASK_TASKS-1);
}
// saturate
if (sum<0) sum = 0;
if (sum>65535) sum = 65535;
// and output...
jshSetOutputValue(task->data.buffer.pinFunction, sum);
if (task->data.buffer.npin == PIN_UNDEFINED) {
jshSetOutputValue(jshGetCurrentPinFunction(task->data.buffer.pin), sum);
} else {
sum -= 32768;
jshSetOutputValue(jshGetCurrentPinFunction(task->data.buffer.pin), (sum>0) ? sum*2 : 0);
jshSetOutputValue(jshGetCurrentPinFunction(task->data.buffer.npin), (sum<0) ? -sum*2 : 0);
}
break;
}
#endif
#endif // SAVE_ON_FLASH
#ifdef ESPR_USE_STEPPER_TIMER
case UET_STEP: {
if (task->data.step.steps > 0) {
Expand All @@ -181,7 +187,7 @@ void jstUtilTimerInterruptHandler() {
}
break;
}
#endif
#endif // ESPR_USE_STEPPER_TIMER
case UET_WAKEUP: // we've already done our job by waking the device up
default: break;
}
Expand Down Expand Up @@ -577,7 +583,7 @@ void jstClearWakeUp() {

#ifndef SAVE_ON_FLASH

bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, JsVar *currentData, JsVar *nextData, UtilTimerEventType type) {
bool 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;
Expand All @@ -586,8 +592,8 @@ bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, JsVar *curre
task.time = (int)(startTime + period);
task.type = type;
if (UET_IS_BUFFER_WRITE_EVENT(type)) {
task.data.buffer.pinFunction = jshGetCurrentPinFunction(pin);
if (!task.data.buffer.pinFunction) return false; // no pin function found...
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...
Expand Down
24 changes: 11 additions & 13 deletions src/jstimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ typedef enum {
((T)==UET_WRITE_SHORT))
#endif

#define UTILTIMERTASK_PIN_COUNT (4)
#define UTILTIMERTASK_PIN_COUNT (8)

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
} PACKED_FLAGS UtilTimerTaskSet;
} PACKED_FLAGS UtilTimerTaskSet; // 9 bytes

/** Task to write to a specific pin function - eg. a DAC or Timer or to read from an Analog
* To send once, set var=buffer1, currentBuffer==nextBuffer==0
Expand All @@ -72,26 +72,24 @@ typedef struct UtilTimerTaskBuffer {
unsigned short currentValue; ///< current value being written (for writes)
unsigned short charIdx; ///< Index of character in variable
unsigned short endIdx; ///< Final index before we skip to the next var
union {
JshPinFunction pinFunction; ///< Pin function to write to
Pin pin; ///< Pin to read from
};
} PACKED_FLAGS UtilTimerTaskBuffer;
Pin pin; ///< Pin to read from/write to
Pin npin; ///< If we're doing 2 pin output, the pin to write the negated value to
} PACKED_FLAGS UtilTimerTaskBuffer; // ~16 bytes

typedef void (*UtilTimerTaskExecFn)(JsSysTime time, void* userdata);

typedef struct UtilTimerTaskExec {
UtilTimerTaskExecFn fn;
void *userdata;
} PACKED_FLAGS UtilTimerTaskExec;
} PACKED_FLAGS UtilTimerTaskExec; // ~8 bytes

#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)
Pin pins[4]; //< 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;
} PACKED_FLAGS UtilTimerTaskStep; // 11 bytes
#endif

typedef union UtilTimerTaskData {
Expand All @@ -101,7 +99,7 @@ typedef union UtilTimerTaskData {
#ifdef ESPR_USE_STEPPER_TIMER
UtilTimerTaskStep step;
#endif
} UtilTimerTaskData;
} UtilTimerTaskData; // max of the others = ~16 bytes

typedef struct UtilTimerTask {
int time; // time in future (not system time) at which to set pins (JshSysTime scaling, cropped to 32 bits)
Expand Down Expand Up @@ -151,8 +149,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)
bool jstStartSignal(JsSysTime startTime, JsSysTime period, Pin pin, 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
bool 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);
Expand Down
32 changes: 28 additions & 4 deletions src/jswrap_waveform.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,17 +160,26 @@ void jswrap_waveform_kill() { // be sure to remove all waveforms...
"generate" : "jswrap_waveform_constructor",
"params" : [
["samples","int32","The number of samples"],
["options","JsVar","Optional options struct `{doubleBuffer:bool, bits : 8/16}` where: `doubleBuffer` is whether to allocate two buffers or not (default false), and bits is the amount of bits to use (default 8)."]
["options","JsVar","Optional options struct `{ doubleBuffer:bool, bits : 8/16 }` (see below)"]
],
"return" : ["JsVar","An Waveform object"]
}
Create a waveform class. This allows high speed input and output of waveforms.
It has an internal variable called `buffer` (as well as `buffer2` when
double-buffered - see `options` below) which contains the data to input/output.
Options can contain:
```JS
{
doubleBuffer : bool // whether to allocate two buffers or not (default false)
bits : 8/16 // the amount of bits to use (default 8).
```
When double-buffered, a 'buffer' event will be emitted each time a buffer is
finished with (the argument is that buffer). When the recording stops, a
'finish' event will be emitted (with the first argument as the buffer).
*/
JsVar *jswrap_waveform_constructor(int samples, JsVar *options) {
if (samples<=0) {
Expand Down Expand Up @@ -227,11 +236,13 @@ static void jswrap_waveform_start(JsVar *waveform, Pin pin, JsVarFloat freq, JsV

JsSysTime startTime = 0;
bool repeat = false;
Pin npin = PIN_UNDEFINED;
if (jsvIsObject(options)) {
JsVarFloat t = jsvObjectGetFloatChild(options, "time");
if (isfinite(t) && t>0)
startTime = jshGetTimeFromMilliseconds(t*1000) - jshGetSystemTime();
repeat = jsvObjectGetBoolChild(options, "repeat");
npin = jshGetPinFromVarAndUnLock(jsvObjectGetChildIfExists(options, "npin"));
} else if (!jsvIsUndefined(options)) {
jsExceptionHere(JSET_ERROR, "Expecting options to be undefined or an Object, not %t", options);
}
Expand All @@ -250,7 +261,7 @@ 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, buffer, repeat?(buffer2?buffer2:buffer):0, eventType))
if (!jstStartSignal(startTime, jshGetTimeFromMilliseconds(1000.0 / freq), pin, npin, buffer, repeat?(buffer2?buffer2:buffer):0, eventType))
jsWarn("Unable to schedule a timer");
jsvUnLock2(buffer,buffer2);

Expand All @@ -273,13 +284,26 @@ static void jswrap_waveform_start(JsVar *waveform, Pin pin, JsVarFloat freq, JsV
"params" : [
["output","pin","The pin to output on"],
["freq","float","The frequency to output each sample at"],
["options","JsVar","Optional options struct `{time:float,repeat:bool}` where: `time` is the that the waveform with start output at, e.g. `getTime()+1` (otherwise it is immediate), `repeat` is a boolean specifying whether to repeat the give sample"]
["options","JsVar","[optional] options struct `{time:float, repeat:bool, npin:Pin}` (see below)"]
]
}
Will start outputting the waveform on the given pin - the pin must have
previously been initialised with analogWrite. If not repeating, it'll emit a
`finish` event when it is done.
*/
```
{
time : float, // the that the waveform with start output at, e.g. `getTime()+1` (otherwise it is immediate)
repeat : bool, // whether to repeat the given sample
npin : Pin, // If specified, the waveform is output across two pins (see below)
}
```
Using `npin` allows you to split the Waveform output between two pins and hence avoid
any DC bias (or need to capacitor), for instance you could attach a speaker to `H0` and
`H1` on Jolt.js. When the value in the waveform was at 50% both outputs would be 0,
below 50% the signal would be on `npin` with `pin` as 0, and above 50% it would be on `pin` with `npin` as 0.
*/
void jswrap_waveform_startOutput(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options) {
jswrap_waveform_start(waveform, pin, freq, options, true/*write*/);
}
Expand Down

0 comments on commit 471847f

Please sign in to comment.