rpi-io 2.2.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ # Stepper Motor Extensions
2
+
3
+ Using the basic **rpi-io** methods for output lines (*write*, *pulseStart*, *pulseStop*), it is easy to drive stepper motors, ranging from simple hobbyist motors (e.g., 28BYJ-48) to those designed for robotics and industrial applications, such as the NEMA series (e.g., NEMA 17, 23...) using some hardware driver (e.g. DMA542).
4
+
5
+ To facilitate this type of use, since version 3.1 **rpi-io** has included two stepper motor controller classes:
6
+
7
+ - The *StepperUnipolar* class is intended for stepper motors without a hardware driver.
8
+ - The *StepperBipolar* class is intended for stepper motors with a hardware driver such as the DM542.
9
+
10
+
11
+
12
+ ## class StepperUnipolar
13
+
14
+ ### Electronic wiring example
15
+
16
+ ![](https://rpisquare.com//img/stepper-unipolar.png)
17
+
18
+ PLEASE NOTE:
19
+
20
+ - **GND is shared** — the Pi's GND and the ULN2003's GND must be tied together even if you power the motor from a separate supply, or the signals won't have a common reference.
21
+
22
+ - **Power source for VCC**: for light loads the Pi's 5V pin can drive the ULN2003/motor directly, but the 28BYJ-48 can draw enough current that it's safer to power VCC from a separate 5V supply, keeping only GND common with the Pi.
23
+
24
+ - The four GPIO pins (17, 18, 27, 22) are just a common convention — any four free GPIO pins work, as long as your code (e.g. via `RPi.GPIO` or `gpiozero`) references the pins you actually wired.
25
+
26
+ - The ULN2003 board's output side connects to the motor with its keyed 5-pin connector — it only fits one way, so no wiring decisions needed there.
27
+
28
+
29
+
30
+ ### Example
31
+
32
+ See test [script](https://github.com/gdorbes/rpi-io/blob/master/test/stepper-motor-unipolar.js).
33
+
34
+ ```js
35
+ import {StepperUnipolar, RIO, traceCfg, log, warn, sleep} from "../extensions/stepper-unipolar.mjs"
36
+
37
+ traceCfg(2)
38
+
39
+ // Open step controller in half step mode => Half turn forward and backward => Close
40
+ const motor28BYJH = new StepperUnipolar(new RIO(21, "output"), new RIO(22, "output"), new RIO(24, "output"), new RIO(25, "output"), "half", 0)
41
+ log("motor28BYJH creation status:", motor28BYJH.status)
42
+ await motor28BYJH.rotate(2048, "forward", false)
43
+ await motor28BYJH.rotate(2048, "backward", false)
44
+ motor28BYJH.close()
45
+
46
+ // Open step controller in full step mode => Half turn forward and backward => Close
47
+ const motor28BYJF = new StepperUnipolar(new RIO(21, "output"), new RIO(22, "output"), new RIO(24, "output"), new RIO(25, "output"), "full", 4)
48
+ log("motor28BYJF creation status:", motor28BYJF.status)
49
+ await motor28BYJF.rotate(1024, "forward", false)
50
+ await motor28BYJF.rotate(1024, "backward", false)
51
+ motor28BYJF.close()
52
+ ```
53
+
54
+
55
+
56
+ ### API
57
+
58
+ #### constructor StepperUnipolar(coilA, coilB, coilC, coilD, mode, delay)
59
+
60
+ **Parameter(s)**
61
+
62
+ - **coilA** *{Object}* **rpi-io** instance for Coil A
63
+
64
+ - **coilB** *{Object}* **rpi-io** instance for Coil B
65
+
66
+ - **coilC** *{Object}* **rpi-io** instance for Coil C
67
+
68
+ - **coilD** *{Object}* **rpi-io** instance for Coil D
69
+
70
+ - ##### **mode** *{'half','full'}* sequence mode: half step or full step
71
+
72
+ - **delay** *{Number}* delay in ms between steps
73
+
74
+
75
+
76
+ **Return** *{Object}* StepperUnipolar instance
77
+
78
+
79
+
80
+ #### rotate(steps, direction, showStep)
81
+
82
+ **Parameter(s)**
83
+
84
+ - **steps** *{Number}*
85
+ - **direction** *{'forward'|'backward"'*
86
+ - **showStep** *{Boolean}* ** to display step in console (default = false)
87
+
88
+ **Return** *{Promise}*
89
+
90
+
91
+
92
+ #### close()
93
+
94
+ To disable coils and close rpi-io instances.
95
+
96
+
97
+
98
+ ## class StepperBipolar
99
+
100
+ ### Electronic wiring example
101
+
102
+ ![](https://rpisquare.com//img/stepper-bipolar.png)
103
+
104
+ PLEASE NOTE:
105
+
106
+ - **The junction dot** is where the Pi's 5V pin splits into two wires, tying PUL+ and DIR+ together onto one common rail.
107
+
108
+ - **STEP and DIR switch low to trigger**, since GPIO pulls PUL- or DIR- down relative to the shared +5V.
109
+
110
+ - **Voltage margin is tight**: the Pi's GPIO output high is 3.3V, but the common rail here is 5V, giving only ~1.7V across the optocoupler when "off" — close to some optocouplers' turn-off threshold. Many builds get away with this, but if you see erratic stepping consider using a Darlington array (e.g. ULN2003) between RPi and DM542.
111
+
112
+ - **ENA+/ENA-** would follow the same common-anode pattern if you wire it — tied into the same 5V junction, with a GPIO pin driving ENA- low to enable.
113
+
114
+
115
+
116
+ ### Example
117
+
118
+ See test [script](https://github.com/gdorbes/rpi-io/blob/master/test/stepper-motor-bipolar.js).
119
+
120
+ ```js
121
+ import {StepperBipolar, RIO, traceCfg, log, warn, sleep, ctrlC} from "../extensions/stepper-bipolar.mjs"
122
+
123
+ traceCfg(2)
124
+
125
+ ctrlC(() => {
126
+ RIO.closeAll()
127
+ })
128
+
129
+ // Open output lines GPIO21 and GPIO22
130
+ const pul = new RIO(21, "output", {value: 0})
131
+ const dir = new RIO(22, "output", {value: 0})
132
+
133
+ // Create Bipolar Stepper Motor instance
134
+ const bipolar = new StepperBipolar(pul, dir, "desc", 10)
135
+
136
+ // Start rotating full turn (depending on hardware driver settings)
137
+ bipolar.rotate(25600, "forward").then(stepped => {
138
+ log("stepped:", stepped)
139
+ // Reset and close lines
140
+ pul.write(0)
141
+ dir.write(0)
142
+ pul.close()
143
+ dir.close()
144
+ })
145
+
146
+ // Stop rotation after 200ms
147
+ await sleep(200)
148
+ log("stop rotation")
149
+ bipolar.stop()
150
+ ```
151
+
152
+
153
+
154
+ ### API
155
+
156
+ #### constructor StepperBipolar(pul, dir, edge, width)
157
+
158
+ **Parameter(s)**
159
+
160
+ - **pul** *{Object}* **rpi-io** instance for pulse pin
161
+
162
+ - **dir** *{Object}* **rpi-io** instance for direction pin
163
+
164
+ - ##### **edge** *{'desc','asc'}* pulse active edge: descending or ascending
165
+
166
+ - **width** *{Number}* pulse width and space between two edges in µs
167
+
168
+ **Return** *{Object}* StepperBipolar instance
169
+
170
+
171
+
172
+ #### rotate(steps, direction)
173
+
174
+ **Parameter(s)**
175
+
176
+ - **steps** *{Number}*
177
+ - **direction** *{'forward'|'backward"'*
178
+ - **showStep** *{Boolean}* ** to display step in console (default = false)
179
+
180
+ **Return** *{Promise<{elapsedMs: number, pulsesCompleted: number, stopped: boolean}>}*
181
+
182
+
183
+
184
+ #### stop()
185
+
186
+ To interrupt an ongoing pulse sequence if one is in progress.
187
+
188
+
189
+
190
+ #### close()
191
+
192
+ To disable *pul* and *dir* rpi-io instances.
package/esm/log.mjs CHANGED
@@ -1,58 +1,48 @@
1
1
  // -------------------------------------------------------------------
2
2
  // RPI-IO: Custom console.log
3
3
  // -------------------------------------------------------------------
4
- let debug = {
5
- trace: false,
6
- warn: true
4
+ const noop = function () {
7
5
  }
8
-
6
+ export let log = noop
7
+ export let warn = noop
9
8
  /** ------------------------------------------------------------------
10
9
  * @function traceCfg
11
10
  * @description Set log configuration
12
11
  * @param {Number} level 0: no log, 1: warn only, 2: warn and log
13
12
  */
14
13
  export const traceCfg = level => {
15
- debug = {
16
- trace: false,
17
- warn: false
18
- }
19
- level > 1 ? debug.trace = true : false
20
- level > 0 ? debug.warn = true : false
21
- }
22
14
 
23
- /** ------------------------------------------------------------------
24
- * @function nowStr
25
- * @description Return timestamp as formatted string
26
- * @return {String}
27
- */
28
- const nowStr = () => {
29
- const date = new Date()
30
- return date.toLocaleTimeString() + "." + date.getMilliseconds().toLocaleString('en', {
31
- minimumIntegerDigits: 3,
32
- minimumFractionDigits: 0,
33
- useGrouping: false
34
- })
35
- }
36
-
37
- /** ------------------------------------------------------------------
38
- * @function log
39
- * @description Global customized timestamped console.log
40
- * Requires global variable 'debug'
41
- */
42
- export const log = function () {
43
- if (debug.trace)
15
+ const nowStr = () => {
16
+ const date = new Date()
17
+ return date.toLocaleTimeString() + "." + date.getMilliseconds().toLocaleString('en', {
18
+ minimumIntegerDigits: 3,
19
+ minimumFractionDigits: 0,
20
+ useGrouping: false
21
+ })
22
+ }
23
+ const timeStamped = function () {
44
24
  console.log.apply(console, Array.prototype.concat.apply([nowStr(), "🔎 "], arguments))
45
- }
25
+ }
26
+ const timeColored = function () {
27
+ console.log.apply(console, Array.prototype.concat.apply([nowStr(), "⚠️ ", "\x1b[38;2;255;80;0m", ...arguments, "\x1b[0m"]))
28
+ }
46
29
 
47
- /** ------------------------------------------------------------------
48
- * @function warn
49
- * @description Colored log for error messages ⚠️ nodejs only ~ CSS color #F50
50
- */
51
- export const warn = function () {
52
- if (debug.warn)
53
- console.log.apply(console, Array.prototype.concat.apply([nowStr(), "⚠️", "\x1b[38;2;255;80;0m", ...arguments, "\x1b[0m"]))
30
+ switch (level) {
31
+ case 2:
32
+ warn = timeColored
33
+ log = timeStamped
34
+ break
35
+ case 1:
36
+ warn = timeColored
37
+ log = noop
38
+ break
39
+ default:
40
+ warn = noop
41
+ log = noop
42
+ }
54
43
  }
55
44
 
45
+
56
46
  // -------------------------------------------------------------------
57
47
  // EoF
58
48
  // -------------------------------------------------------------------
package/esm/main.mjs CHANGED
@@ -104,7 +104,7 @@ export class RIO {
104
104
  throw new Error("This line is not setup as PWM (see README)")
105
105
 
106
106
  if (opt.period < 0.1 || opt.period > 1000000)
107
- throw new Error("PWM period is out of range (100ns - 1s)")
107
+ throw new RangeError("PWM period is out of range (100ns - 1s)")
108
108
 
109
109
  // Normalize PWM values in ns
110
110
  this.period = opt.period * 1000
@@ -190,7 +190,7 @@ export class RIO {
190
190
  throw new Error("Cannot write to this GPIO mode:", this.mode)
191
191
 
192
192
  if ([0, 1].indexOf(value) === -1)
193
- throw new Error("Value must be either 0 or 1")
193
+ throw new RangeError("Value must be either 0 or 1")
194
194
 
195
195
  ADDON.write(this.handle, value)
196
196
  }
@@ -306,12 +306,65 @@ export class RIO {
306
306
  throw new Error("Duty of PWM line " + this.line + " cannot be updated")
307
307
 
308
308
  if (percent < 0 || percent > 100 || typeof percent !== "number") {
309
- throw new Error("Duty value (%) of PWM line" + this.line + " is not valid")
309
+ throw new RangeError("Duty value (%) of PWM line" + this.line + " is not valid")
310
310
  }
311
311
 
312
312
  writeFileSync(this.pwmPathChannel + "duty_cycle", (this.dutyMin + ((percent / 100) * (this.dutyMax - this.dutyMin))).toString())
313
313
  }
314
314
 
315
+ /** --------------------------------------------------------------
316
+ * @method pulseStart
317
+ * @description Start a train of N pulses on an output GPIO line
318
+ * @param {Number} count - Number of pulses
319
+ * @param {"asc"|"desc"} edge - ascending/descending edge
320
+ * @param {Object} opt - See pulse properties belows
321
+ * @return {Promise<{elapsedMs: number, pulsesCompleted: number, stopped: boolean}>}
322
+ */
323
+ pulseStart(count, edge, opt) {
324
+
325
+ const edges = ["desc", "asc"]
326
+ const defopt = {
327
+ pulseWidth: 10, // Pulse width in µs
328
+ spaceWidth: 10, // Space width between pulses in µs
329
+ }
330
+ opt = {...defopt, ...opt}
331
+
332
+
333
+ if (this.closed)
334
+ throw new Error("GPIO handle has been closed")
335
+
336
+ if (this.mode !== "output")
337
+ throw new Error("Cannot write to this GPIO mode:", this.mode)
338
+
339
+ if (!Number.isInteger(count) || count <= 0) {
340
+ throw new RangeError("Count must be a positive integer")
341
+ }
342
+
343
+ if (edges.indexOf(edge) === -1) {
344
+ throw new RangeError("Invalid edge value")
345
+ }
346
+
347
+ if (!Number.isFinite(opt.pulseWidth) || opt.pulseWidth < 0) {
348
+ throw new RangeError("pulseWidth must be a non-negative number")
349
+ }
350
+ if (!Number.isFinite(opt.spaceWidth) || opt.spaceWidth < 0) {
351
+ throw new RangeError("spaceWidth must be a non-negative number")
352
+ }
353
+
354
+ return ADDON.pulse(this.handle, count, Math.round(opt.spaceWidth), Math.round(opt.spaceWidth), edge === edges[0] ? 0 : 1)
355
+ }
356
+
357
+ /** --------------------------------------------------------------
358
+ * @method pulseStop
359
+ * @description Immediately interrupts an ongoing pulse sequence if one is in progress.
360
+ * Synchronous and blocking (a few ms max in practice), the handle remains open and usable afterwards.
361
+ * Upon return, the line is guaranteed to be idle—no pulses remain in flight.
362
+ * @return {Boolean} true if a sequence was in progress and was stopped.
363
+ */
364
+ pulseStop() {
365
+ return ADDON.pulseStop(this.handle)
366
+ }
367
+
315
368
  // -------------------------------------------------------------------
316
369
  // STATIC FUNCTIONS
317
370
  /** ------------------------------------------------------------------
@@ -0,0 +1,131 @@
1
+ // -------------------------------------------------------------------
2
+ // RPI-IO: BIPOLAR STEPPER MOTOR EXTENSION
3
+ // -------------------------------------------------------------------
4
+ import {RIO, traceCfg, log, warn, sleep, ctrlC} from "../esm/main.mjs"
5
+
6
+ export {RIO, traceCfg, log, warn, sleep, ctrlC}
7
+
8
+ /** ------------------------------------------------------------------
9
+ * @class StepperBipolar
10
+ * @description Class definition for Bipolar Stepper motors.
11
+ * Bipolar stepper motors (e.g. NEMA 17 or 23) require hardware driver
12
+ * such as DM542 or equilavent depending on motor power
13
+ */
14
+ export class StepperBipolar {
15
+
16
+ static STATUS_MSG = {
17
+ 0: "none",
18
+ 11: "pul or dir rpi-io instance definition is missing",
19
+ 12: "At least one rpi-io instance is not valid",
20
+ 13: "Wrong edge {'asc','desc'}",
21
+ 14: "Wrong pulse width (5 ≤ p ≤ 10,000,000)",
22
+ 200: "ok"
23
+ }
24
+
25
+ static EDGES = ["desc", "asc"]
26
+
27
+ // Private method: Update instance status
28
+ #setStatus(code, data) {
29
+ this.status.code = code
30
+ this.status.msg = StepperBipolar.STATUS_MSG[code]
31
+ this.status.data = data
32
+ }
33
+
34
+ /** ------------------------------------------------------------------
35
+ * @method constructor
36
+ * @param {Object} pul // rpi-io output instance for pulse pin
37
+ * @param {Object} dir // rpi-io output instance for direction pin
38
+ * @param {"desc"|"asc"} edge // pulse active edge: descending or ascending
39
+ * @param {Number} width // pulse width and space between two edges in µs
40
+ */
41
+ constructor(pul, dir, edge, width) {
42
+
43
+ // Init instance construction status
44
+ this.status = {
45
+ code: 0,
46
+ msg: StepperBipolar.STATUS_MSG[0],
47
+ data: ""
48
+ }
49
+
50
+ // ERR11: pul or pir rpi-io instance definition is missing
51
+ if (typeof pul === "undefined" || typeof dir === "undefined") {
52
+ this.#setStatus(11, "")
53
+ RIO.closeAll()
54
+ return
55
+ }
56
+
57
+ // ERR12: At least one coil port rpi-io instance is not valid
58
+ try {
59
+ pul.write(0)
60
+ dir.write(0)
61
+ } catch (err) {
62
+ this.#setStatus(12, "")
63
+ RIO.closeAll()
64
+ return
65
+ }
66
+
67
+ // ERR13: Wrong edge {'asc','desc'}
68
+ if (StepperBipolar.EDGES.indexOf(edge) === -1) {
69
+ this.#setStatus(13, edge)
70
+ RIO.closeAll()
71
+ return
72
+ }
73
+
74
+ // ERR14: Wrong pulse width (5 ≤ p ≤ 10,000,000)
75
+ if (typeof width !== "number" || width < 5 || width > 10000000) {
76
+ this.#setStatus(14, width)
77
+ RIO.closeAll()
78
+ return
79
+ }
80
+
81
+ // Test successfully completed => Init properties
82
+ this.ioPulse = pul
83
+ this.ioDir = dir
84
+ this.edge = edge
85
+ this.width = width
86
+ this.#setStatus(200, "")
87
+ }
88
+
89
+ /** ------------------------------------------------------------------
90
+ * @method rotate
91
+ * @description Rotate the motor a certain number of steps forward or backward.
92
+ * Full rotation depends on hardware driver settings.
93
+ * @param {Number} steps
94
+ * @param {"forward"|"backward"} direction
95
+ * @return {Promise<{elapsedMs: number, pulsesCompleted: number, stopped: boolean}>}
96
+ */
97
+ async rotate(steps, direction) {
98
+
99
+ // Set direction
100
+ this.ioDir.write("forward" ? 1 : 0)
101
+
102
+ // Start rotation
103
+ log("starting rotation:", steps, "steps", direction)
104
+ return await this.ioPulse.pulseStart(steps, this.edge, {
105
+ pulseWidth: this.width,
106
+ spaceWidth: this.width,
107
+ })
108
+ }
109
+
110
+ /** ------------------------------------------------------------------
111
+ * @method stop
112
+ * @description Immediately interrupts an ongoing pulse sequence if one is in progress.
113
+ */
114
+ stop() {
115
+ this.ioPulse.pulseStop()
116
+ }
117
+
118
+ /** ------------------------------------------------------------------
119
+ * @method close
120
+ * @description Disable pul and dir rpi-io instances used for this step motor controller
121
+ */
122
+ close() {
123
+ this.ioPulse.close
124
+ this.ioDir.close
125
+ log("Bipolar controller closed")
126
+ }
127
+ }
128
+
129
+ // -------------------------------------------------------------------
130
+ // EoF
131
+ // -------------------------------------------------------------------
@@ -0,0 +1,172 @@
1
+ // -------------------------------------------------------------------
2
+ // RPI-IO: UNIPOLAR STEPPER MOTOR EXTENSION
3
+ // -------------------------------------------------------------------
4
+ import {RIO, traceCfg, log, warn, sleep} from "../esm/main.mjs"
5
+
6
+ export {RIO, traceCfg, log, warn, sleep}
7
+
8
+ /** ------------------------------------------------------------------
9
+ * @class StepperUnipolar
10
+ * @description Class definition for Unipolar Stepper motors.
11
+ * Unipolar stepper motors (e.g. 28BYJ-48) are directly controlled by RPi using
12
+ * a Darlington Transistor Array (e.g. ULN2003)
13
+ */
14
+ export class StepperUnipolar {
15
+
16
+ static STATUS_MSG = {
17
+ 0: "none",
18
+ 11: "At least one coil port rpi-io instance definition is missing",
19
+ 12: "At least one coil port rpi-io instance is not valid",
20
+ 13: "Wrong delay parameter value (0 ≤ d ≤ 1000)",
21
+ 14: "Wrong Unipolar sequence {'full','half'}",
22
+ 200: "ok"
23
+ }
24
+ static MODE = ["half", "full"]
25
+ static STEPS = {
26
+ half: [
27
+ [1, 0, 0, 0],
28
+ [1, 1, 0, 0],
29
+ [0, 1, 0, 0],
30
+ [0, 1, 1, 0],
31
+ [0, 0, 1, 0],
32
+ [0, 0, 1, 1],
33
+ [0, 0, 0, 1],
34
+ [1, 0, 0, 1]
35
+ ],
36
+ full: [
37
+ [1, 0, 0, 1],
38
+ [1, 1, 0, 0],
39
+ [0, 1, 1, 0],
40
+ [0, 0, 1, 1]
41
+ ]
42
+ }
43
+
44
+ // Private method: Update instance status
45
+ #setStatus(code, data) {
46
+ this.status.code = code
47
+ this.status.msg = StepperUnipolar.STATUS_MSG[code]
48
+ this.status.data = data
49
+ }
50
+
51
+ /** ------------------------------------------------------------------
52
+ * @method constructor
53
+ * @param {Object} coilA // rpi-io output instance for CoilA
54
+ * @param {Object} coilB // rpi-io output instance for CoilB
55
+ * @param {Object} coilC // rpi-io output instance for CoilC
56
+ * @param {Object} coilD // rpi-io output instance for CoilD
57
+ * @param {"half"|"ful"} mode // Full or half step sequence
58
+ * @param {Number} delay // Delay between steps in ms
59
+ */
60
+ constructor(coilA, coilB, coilC, coilD, mode = "half", delay = 10) {
61
+
62
+ // Init instance construction status
63
+ this.status = {
64
+ code: 0,
65
+ msg: StepperUnipolar.STATUS_MSG[0],
66
+ data: ""
67
+ }
68
+
69
+ // ERR11: At least one coil port rpi-io instance definition is missing
70
+ if (typeof coilA === "undefined" || typeof coilB === "undefined" || typeof coilC === "undefined" || typeof coilD === "undefined") {
71
+ this.#setStatus(11, "")
72
+ RIO.closeAll()
73
+ return
74
+ }
75
+
76
+ // ERR12: At least one coil port rpi-io instance is not valid
77
+ try {
78
+ coilA.write(0)
79
+ coilB.write(0)
80
+ coilC.write(0)
81
+ coilD.write(0)
82
+ } catch (err) {
83
+ this.#setStatus(12, "")
84
+ RIO.closeAll()
85
+ return
86
+ }
87
+
88
+ // ERR13: Wrong delay parameter value (0 ≤ d ≤ 1000)
89
+ if (typeof delay !== "number" || delay < 0 || delay > 1000) {
90
+ this.#setStatus(13, delay)
91
+ RIO.closeAll()
92
+ return
93
+ }
94
+
95
+ // ERR14: Wrong mode {'full','half'}
96
+ if (StepperUnipolar.MODE.indexOf(mode) === -1) {
97
+ this.#setStatus(14, mode)
98
+ RIO.closeAll()
99
+ return
100
+ }
101
+
102
+ // Test successfully completed => Init properties
103
+ this.coils = {
104
+ a: coilA,
105
+ b: coilB,
106
+ c: coilC,
107
+ d: coilD,
108
+ }
109
+ this.sequence = StepperUnipolar.STEPS[mode]
110
+ this.len = this.sequence.length
111
+ this.delay = delay
112
+ this.index = 0
113
+ this.increment = 0
114
+ this.#setStatus(200, "")
115
+ }
116
+
117
+ /** ------------------------------------------------------------------
118
+ * @method rotate
119
+ * @description Rotate the motor a certain number of steps forward or backward.
120
+ * Full rotation depends on selected mode.
121
+ * @param {Number} steps
122
+ * @param {"forward"|"backward"} direction
123
+ * @param {Boolean} showStep
124
+ */
125
+ async rotate(steps, direction, showStep = false) {
126
+
127
+ this.increment = direction === "forward" ? 1 : -1
128
+
129
+ log("starting rotation:", steps, "steps", direction)
130
+
131
+ // Rotate
132
+ for (let i = 0; i < steps; i++) {
133
+ const [a, b, c, d] = this.sequence[this.index]
134
+ if (showStep) {
135
+ log("rotation step:", i, [a, b, c, d])
136
+ }
137
+ this.coils.a.write(a)
138
+ this.coils.b.write(b)
139
+ this.coils.c.write(c)
140
+ this.coils.d.write(d)
141
+
142
+ this.index = ((this.index + this.increment % this.len + this.len) % this.len)
143
+ await sleep(this.delay, false)
144
+ }
145
+
146
+ // Disable coils
147
+ this.coils.a.write(0)
148
+ this.coils.b.write(0)
149
+ this.coils.c.write(0)
150
+ this.coils.d.write(0)
151
+ }
152
+
153
+ /** ------------------------------------------------------------------
154
+ * @method close
155
+ * @description Disable coils and close rpi-io instances used for this step motor controller
156
+ */
157
+ close() {
158
+ this.coils.a.write(0)
159
+ this.coils.b.write(0)
160
+ this.coils.c.write(0)
161
+ this.coils.d.write(0)
162
+ this.coils.a.close()
163
+ this.coils.b.close()
164
+ this.coils.c.close()
165
+ this.coils.d.close()
166
+ log("Unipolar controller closed")
167
+ }
168
+ }
169
+
170
+ // -------------------------------------------------------------------
171
+ // EoF
172
+ // -------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpi-io",
3
- "version": "2.2.4",
3
+ "version": "3.1.0",
4
4
  "description": "Nodejs module to control Raspberry Pi GPIO",
5
5
  "keywords": [
6
6
  "raspberry",
@@ -18,7 +18,11 @@
18
18
  "bookworm",
19
19
  "trixie",
20
20
  "iot",
21
- "embedded"
21
+ "embedded",
22
+ "motor",
23
+ "stepper",
24
+ "motor control",
25
+ "stepper motor"
22
26
  ],
23
27
  "homepage": "https://github.com/gdorbes/rpi-io#readme",
24
28
  "bugs": {