-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stepper motor support #70
Comments
Not directly, no, because the Raspberry Pi can't directly control a stepper motor. Stepper motors require very specially timed pulses, something the Raspberry Pi can't do unfortunately. To get the timing right, you either have to be running on bare-metal without an OS (such as the Arduino), or you have to use hardware PWM. The Raspberry Pi doesn't have enough PWM ports on it to drive a stepper motor though, and since it runs an OS, we can't fake it with GPIO and have it be stable because the OS can interrupt timers. That said, pretty much any time you want to use a stepper motor with any microcontroller, not just the Raspberry Pi, the preferred way is to use an extension board, such as this one here. These boards are connected to the Raspberry Pi using I2C, which is already supported by raspi-io. Let me know if you have any other questions! |
Thanks for the quick reply @nebrius . I'm currently using an Arduino and a DRV8825 with johnny-five. Using the DRV8825, I'm able to step a motor "manually" by turning a pin board.on('ready', function() {
board.pinMode(7, board.MODES.OUTPUT);
let spinPulse = true;
setInterval(() => {
board.digitalWrite(7, spinPulse ? 1 : 0);
spinPulse = !spinPulse;
}, 0);
board.pinMode(8, board.MODES.OUTPUT);
let direction = true;
setInterval(() => {
board.digitalWrite(8, direction ? 1 : 0);
direction = !direction;
}, 1000);
}); What this does is step the stepper as fast as possible using pin 7, and also change the direction every second using pin 8. I've recently started using johnny-five's var stepper = new five.Stepper({
type: five.Stepper.TYPE.DRIVER,
stepsPerRev: 200,
pins: {
step: 7,
dir: 8
}
});
stepper.rpm(20).ccw().step(100, function {
// ...
}); My question is, could Also, I've seen https://github.com/nkolban/node-stepper-wiringpi which looks to provide stepper motor control for Raspberry PI. Would this be helpful in adding support? |
@techniq With the DRV8825 this is all just digital writes. Why wouldn't it work with rasp-io? |
@dtex I'll be honest, I haven't tried just yet. I'm currently driving my Arduino with my Raspberry PI, but I saw this, this, and stepper support - no and assumed it wouldn't work (yet). |
Hmmm, I'll need to try it out myself as I haven't actually done it yet either. |
Hi, |
Looking at the product, it should already work with the Raspberry Pi. The Adafruit Motor HAT uses I2C to communicate, which is already supported in raspi-io. Digging deeper, the HAT uses the PCA9685, which is already a supported expander in Johnny-Five. So, there's nothing to port, no assembly needed! :) |
@nebrius I think we still need a Stepper class controller (which would use the expander internally), but that's blocked by the Stepper class rewrite, which is necessary because it brings in controller definition support. However! Ignore all of that, one could definitely use an Expander object to implement a Stepper plugin that's specific to this shield. |
@nebrius and @rwaldron thank you for your response. I realized that the pca9685 is already supported regarding the i2c communication, but how that ic has to write to the two motordriver ic's TB6612 needs to be addressed I think. In the Motor.js class of Johnny5 the ADAFRUIT shield is available, for the Stepper class it is not. I've come this far: `var raspi = require("raspi-io"); board.on("ready", function () {
});` but as the rasp-io board has stepper support turned off in general it will still report 'Stepper not supprted.' Can either off you point me in the right direction on where and how to write a stepper plugin specifically for this board? //==== the other path i've walked... ==// Another attempt I've made was to rewrite the python library from Adafruit to a node module by using the pca9685 node driver and translate the python ADAFRUIT Stepper Class. Unfortunately I'm too new in this kind of thing to get it working and I also beleive that this effort shouldn't be necessary. Just for information purposes I've included my poor attempt here: ` // initialize Stepper function Stepper(options) {
} module.exports = Stepper;` |
This is what I was referring to w/r to
That's correct, because the
A similar thing could be made using an
var five = require("johnny-five");
var Board = five.Board;
var Fn = five.Fn;
var priv = new Map();
var pins = [
{
PWMA: 8,
AIN2: 9,
AIN1: 10,
PWMB: 13,
BIN2: 12,
BIN1: 11,
},
{
PWMA: 2,
AIN2: 3,
AIN1: 4,
PWMB: 7,
BIN2: 6,
BIN1: 5,
}
];
var coilstates = [
[1, 0, 0, 0],
[1, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 1],
[0, 0, 0, 1],
[1, 0, 0, 1],
];
var motors = [
1, 2
];
function Stepper(opts) {
if (!(this instanceof Stepper)) {
return new Stepper(opts);
}
Board.Component.call(
this, opts = Board.Options(opts)
);
var address = opts.address || 0x60;
var frequency = opts.frequency || 1600;
var motor = opts.motor || 1;
var pins = Object.assign({}, pins[motor - 1]);
var rpm = opts.rpm || 30;
var steps = opts.steps || 200;
var state = {
address: address,
currentstep: 0,
frequency: frequency,
interval: null,
motor: motor,
pins: pins,
secondsPerStep: 0,
steps: steps,
};
state.expander = Expander.get({
address: state.address,
bus: 1,
controller: "PCA9685",
frequency: state.frequency,
});
priv.set(this, state);
Object.defineProperties(this, {
pins: {
get: function() {
return state.pins;
}
}
});
this.speed(rpm);
}
Stepper.FORWARD = 1;
Stepper.BACKWARD = 2;
Stepper.SINGLE = 1;
Stepper.DOUBLE = 2;
Stepper.INTERLEAVE = 3;
Stepper.MICROSTEP = 4;
Stepper.MICROSTEPS_4 = 4;
Stepper.MICROSTEPS_8 = 8;
Stepper.MICROSTEPS_16 = 16;
Stepper.MICROSTEPS_24 = 24;
Stepper.MICROSTEPS_32 = 32;
Stepper.MICROSTEP_CURVE = [
0, 50, 98, 142, 180, 212, 236, 250, 255
];
Stepper.prototype.speed = function(rpm) {
var state = priv.get(this);
state.secondsPerStep = 60 / (state.steps * rpm);
return this;
};
Stepper.prototype.step = function(direction, style) {
var pwm = { a: 0xFF, b: 0xFF };
if (style === Stepper.SINGLE) {
if ((state.currentstep / Stepper.MICROSTEPS_4) % 2) {
if (direction === Stepper.FORWARD) {
state.currentstep += Stepper.MICROSTEPS_4;
} else {
state.currentstep -= Stepper.MICROSTEPS_4;
}
} else {
// go to next even step
if (direction === Stepper.FORWARD) {
state.currentstep += Stepper.MICROSTEPS_8;
} else {
state.currentstep -= Stepper.MICROSTEPS_8;
}
}
}
if (style === Stepper.DOUBLE) {
if (!(state.currentstep / Stepper.MICROSTEPS_4 % 2)) {
// we're at an even step, weird
if (direction === Stepper.FORWARD) {
state.currentstep += Stepper.MICROSTEPS_4;
} else {
state.currentstep -= Stepper.MICROSTEPS_4;
}
} else {
// go to next odd step
if (direction === Stepper.FORWARD) {
state.currentstep += Stepper.MICROSTEPS_8;
} else {
state.currentstep -= Stepper.MICROSTEPS_8;
}
}
}
if (style === Stepper.INTERLEAVE) {
if (direction === Stepper.FORWARD) {
state.currentstep += Stepper.MICROSTEPS_4;
} else {
state.currentstep -= Stepper.MICROSTEPS_4;
}
}
if (style === Stepper.MICROSTEP) {
if (direction === Stepper.FORWARD) {
state.currentstep += 1;
} else {
state.currentstep -= 1;
// go to next 'step' and wrap around
state.currentstep += Stepper.MICROSTEPS_32;
state.currentstep %= Stepper.MICROSTEPS_32;
}
pwm.a = 0;
pwm.b = 0;
if (state.currentstep >= 0 &&
state.currentstep < Stepper.MICROSTEPS_8) {
pwm.a = Stepper.MICROSTEP_CURVE[(Stepper.MICROSTEPS_8 - state.currentstep) | 0];
pwm.b = Stepper.MICROSTEP_CURVE[state.currentstep];
} else if (state.currentstep >= Stepper.MICROSTEPS_8 &&
state.currentstep < Stepper.MICROSTEPS_16) {
pwm.a = Stepper.MICROSTEP_CURVE[(state.currentstep - Stepper.MICROSTEPS_8) | 0];
pwm.b = Stepper.MICROSTEP_CURVE[(Stepper.MICROSTEPS_16 - state.currentstep) | 0];
} else if (state.currentstep >= Stepper.MICROSTEPS_16 &&
state.currentstep < Stepper.MICROSTEPS_24) {
pwm.a = Stepper.MICROSTEP_CURVE[(Stepper.MICROSTEPS_24 - state.currentstep) | 0];
pwm.b = Stepper.MICROSTEP_CURVE[(state.currentstep - Stepper.MICROSTEPS_16) | 0];
} else if (state.currentstep >= Stepper.MICROSTEPS_24 &&
state.currentstep < Stepper.MICROSTEPS_32) {
pwm.a = Stepper.MICROSTEP_CURVE[(state.currentstep - Stepper.MICROSTEPS_24) | 0];
pwm.b = Stepper.MICROSTEP_CURVE[(Stepper.MICROSTEPS_32 - state.currentstep) | 0];
}
}
state.currentstep += Stepper.MICROSTEPS_32;
state.currentstep %= Stepper.MICROSTEPS_32;
state.expander.pwmWrite(this.pins.PWMA, pwm.a * 16);
state.expander.pwmWrite(this.pins.PWMB, pwm.b * 16);
// set up coil energizing!
var coils = [0, 0, 0, 0];
if (style === Stepper.MICROSTEP) {
if (state.currentstep >= 0 &&
state.currentstep < Stepper.MICROSTEPS_8) {
coils[0] = 1;
coils[1] = 1;
} else if (state.currentstep >= Stepper.MICROSTEPS_8 &&
state.currentstep < Stepper.MICROSTEPS_16) {
coils[1] = 1;
coils[2] = 1;
} else if (state.currentstep >= Stepper.MICROSTEPS_16 &&
state.currentstep < Stepper.MICROSTEPS_24) {
coils[2] = 1;
coils[3] = 1;
} else if (state.currentstep >= Stepper.MICROSTEPS_24 &&
state.currentstep < Stepper.MICROSTEPS_32) {
coils[0] = 1;
coils[1] = 3;
}
} else {
coils = coilstates[(state.currentstep / (Stepper.MICROSTEPS_4)) | 0];
}
state.expander.digitalWrite(this.pins.AIN2, coils[0]);
state.expander.digitalWrite(this.pins.BIN1, coils[1]);
state.expander.digitalWrite(this.pins.AIN1, coils[2]);
state.expander.digitalWrite(this.pins.BIN2, coils[3]);
return state.currentstep;
};
Stepper.prototype.steps = function(numberOfSteps, direction, style, callback) {
var state = priv.get(this);
var sec = state.secondsPerStep;
var lateststep = 0;
if (state.interval) {
throw new Error("Stepper is active!");
}
if (style === Stepper.INTERLEAVE) {
sec /= 2;
}
if (style === Stepper.MICROSTEP) {
sec /= Stepper.MICROSTEPS_8;
numberOfSteps *= Stepper.MICROSTEPS_8;
}
var ms = sec * 1000;
state.interval = setInterval(function() {
if (numberOfSteps > 0) {
lateststep = this.step(direction, style);
}
// User code may have _really_ slow steps and
// we always want to call the callback AFTER the
// stepper has completed a movement
if (numberOfSteps === 0) {
clearInterval(state.interval);
if (style === Stepper.MICROSTEP) {
// This is the edge case discussed in
// Adafruit_MotorHAT.py...
// If the last latest step was BETWEEN a
// full step, then we'll take another step.
//
// But we're not done with this movement,
// so we don't call the callback yet.
if (lateststep !== 0 &&
lateststep !== Stepper.MICROSTEPS_8) {
this.steps(1, direction, style, callback);
}
} else {
callback();
}
}
numberOfSteps--;
}.bind(this), ms);
return this;
};
module.exports = Stepper;
var five = require("johnny-five");
var raspi = require("raspi-io");
var Stepper = require("./stepper-hat.js");
var board = new five.Board({ io: new raspi() });
board.on("ready", function() {
var stepper = new Stepper({ bus: 1, address: 0x60 });
// Defaults to: 200 steps, 30 rpm
stepper.steps(200, Stepper.FORWARD, Stepper.SINGLE, function() {
console.log("Hopefully this fires when the complete move is done");
});
}); (I only used the address 0x60 in that code because you did, otherwise I would've used the default PCA9685 address of 0x40. Change if necessary.) |
@rwaldron Any plans to include this in the standard Johnny5 lib? I'm using node-red so I want to include it in node-red-contrib-gpio :-) ` var five = require("johnny-five");
|
Once the stepper rewrite lands in Johnny-Five, my understanding is that this shouldn't be necessary. Until that lands, there's not really a place for this in raspi-io or J5, based on how the division between IO plugins and J5 works. That said, it may be possible to get full stepper support into raspi-io itself, now that we have support for software PWM. I'd need to do some testing to see if software PWM is stable enough for stepper motors, but I have the feeling it may be from what I've seen of it so far. So let's game this out. If we decide to get this into Raspi IO, the first thing we need to do is add base stepper support into Raspi IO. This is a unique case where we don't need to create a new Raspi.js plugin, because stepper motors aren't actually a unique peripheral (they're just PWM, not an all-together new peripheral). If you haven't seen the architecture discussion in CONTRIBUTING, I recommend taking a peak. This means that we would need to modify these lines over in raspi-io-core that first checks if software PWM is enabled (we'll probably need to store a flag on the Raspi IO Core instance, as it's currently just a parameter in the constructor), and then if software PWM is enabled, it does the right PWM things, otherwise it throws an error saying software PWM needs to be enabled. Once that's done, my understanding is that the expander should just work, right @rwaldron? |
Probably safest to just make the thing we hashed out here into a plugin that can be |
I like that idea. We can release it quickly, and there's no risk to the existing plugin or worries about handling some controllers but not others or the confusion of not support stepper motors directly since it's a separate module. I can then add a blurb to Raspi IO's README about it. @RonB if you can get a repo setup on your GitHub account with this code and drop the link to it here, we can help you flesh out the non-code bits necessary to get this wrapped into an npm module, if you'd like. |
Thanks both of you for the constructive support. |
There shouldn't be any such locking with address, since |
I don't have a naming convention except for the core libraries for raspi-io. Pick whatever you prefer! I would include something in the name indicating it's specifically for the raspberry pi, maybe |
Hello, I'm sorry to bother, but I wanted to control stepper drivers (A4988, DRV8825 and TB6600) with johnny-five. Why is the stepper class disabled for the raspberry pi? My understanding is that it should work without any (major) issues, since all the RasPi has to do is sending a few pulses to the pins. Could somebody explain whats going on there? Am I missing something? I highly appreciate your help! Kind regards, Triadager |
Update on stepper. There is a PR open for the new stepper API in firmata.js. I think it's nearly ready to land, we're just seeing some failures on Travis that need to be ironed out. I don't think the API itself will change at all. I'm working on the new Stepper class for Johnny-Five. It will include support for both the accelStepper library that can be bundled with configurableFirmata and a slower J5 only solution that will just uses digital pin writes. For the former to work in rasp-io we would need to port the accelStepper library so that we can replicate that functionality. Hopefully someone will step up and want to take that on. I hope to have the J5 class done in the next couple of months. For the latter to work in rasp-io there will need to be one small change made to reflect a change I'm planning to submit for firmata.js. I need to test a hypothesis first. |
So I think I may be missing something, but I cannot for the life of me figure out how to download accelStepper so I can peak at the files. Anyways. My plate is really full with Raspi IO work for the rest of the year, so I won't be able to take on this work, unfortunately. Is this something you would be interested in working on @Triadager? I can help walk you through how to integrate it into Raspi IO |
@nebrius I would love to, but since I only got into touch with both the Raspberry Pi and Node.js a couple of weeks ago and don't really have any other programming experience, I might not be the right guy for that. I still need to learn loads of things. However, if you still think I could be of any help let me know, I would love to contribute! At this point I just want to thank all of you big time for the effort you put into this. It's awesome. |
Ah, thanks for the link @Triadager! Sorry we couldn't be of more help to you right now. And I sadly do think you're right, this particular project will probably require more experience than most changes require :-/ |
I'm going to go ahead and close this issue due to inactivity. Feel free to reopen if needed! |
I still don't get why it's more complicated to talk to a stepper driver with an RPi than with an Arduino? Can somebody explain? |
Super short tl;dr:
Comparing the Raspberry Pi and the Arduino is a lot like comparing ANSI C (not even C++) to JavaScript. They both have their strengths and weaknesses, but they are radically different languages. |
I have successfully controlled a stepper connected to an Arduino from Johnny-Five running on my Macbook Pro without using stepper specific c code on the Arduino (i.e. firmataStepper or firmataAccelSteper). Here's a video https://twitter.com/dtex/status/922597004500504576 I'm using just standardFirmata and JS to toggle the pins. This code will run on a Pi w/o any changes to rasp-io but I have no clue how well it will perform. I've seen decent performance on my Macbook (about 500 steps per second) but I know and accept that my step timing is not gauranteed to be smooth. For instance, when the V8 garbage collector does its thing the event loop grinds to a halt. How long that takes depends on so many things we may as well say it's nondeterministic. It might be a couple milliseconds, it might be less. When it does kick in there'll be a hitch in the stepper's giddyup (sorry, the rodeo is in town this week). So far those events are undetectable by my eye, but my program is doing nothing but spinning a single stepper. Generally performance will depend on how quickly the Pi can run through the event loop (really needs to be more than once every millisecond on average to achieve 500 steps per second smoothly). It will also depend on the interface between the CPU and GPIO because we are toggling lots of pins really quickly. All this code lives in a complete refactor of the Johnny-Five stepper class. It's not even ready for first commit in my mind but if you are adventurous, have time to kill, and want to try it out let me know and I will share the code in a gist. So far you can only spin the motor, but it's a start. BTW, I'm working on getting my first Pi up and running with Johnny-Five so I'll start testing it myself soon enough. I have another J5 refactor that's turned into a real hairball and has led me to back burner this stepper stuff for a while. |
@dtex any updates? I ran steppers with the RPi via a simple python script without any problems. But it feels stupid calling a python script that cares for the steppers while working with johnny five. |
That other project is done. Now I’m working on some changes to rwaldron/temporal to allow for a wider variety of step speeds, but I do have the basic step code working. Haven’t tried with Pi yet but will soon. Little confused by your comment though... Stepper and servo are different things. |
I'm sorry. Of course I meant steppers and not servos. I followed this tutorial for the python script. But for my project I've decided I will use a continuous servo so I can keep the code in plain JavaScript and saving tons of GPIOs. Thank you so much for your work on jonny-five. If you need testers for your code I'm happy to fry a pi for you. |
Hey @RonB @rwaldron @nebrius what's the state of play with this? I recently received the Adafruit Motor Controller HAT https://www.adafruit.com/product/2348 and was hoping to use the stepper motor functionality with J5. Is this natively supported now or does it still require the custom Stepper class as outlined above? Thanks |
Hi @oligriffiths. There hasn't been any progress on this issue since the last posting date. I'm afraid that raspi-io isn't as active as it used to be, and isn't likely to be implemented. |
Any plans to support stepper motors, especially driver mode?
I have an Arduino johnny-five project I'm looking to port to a Raspberry PI.
The text was updated successfully, but these errors were encountered: