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
Add Servo #248
Comments
A possible API: >>> servo = Servo(17, angle=180)
>>> servo.angle = 0
>>> servo.value
0.0
>>> servo.angle = 180
>>> servo.value
1.0
>>> servo.value = 0.5
>>> servo.angle
90.0 So that However, this means that a servo's We could provide a generic Any thoughts @waveform80? |
Please keep/expose on() and off() ( synonymous to the Arduino servo attach() and detach() functions) The attach/detach allows saving on power when the servos don't need to move, and also help keep some robots still when not in use. As many servos oscillate slightly at any set angle and this can become more pronounced on something like a robot arm (e.g. MeArm), performing an off (or detach) after a few seconds, if there is no required movement, minimises this. A Having a setPosition(float) that takes a range 0.0 ..1.0 and maps it to min_angle .. max_angle could be handy. Being able to set a Also, continuous rotation servos use the 'angle' as a speed, so perhaps consider this type of Servo in the new class? |
This is great! As Wayne also mentions, I would take a look at the Arduino Servo object for inspiration : https://www.arduino.cc/en/Reference/Servo It feels a bit bulky to have different objects for a 180 and 360, I would default to 180 as this seems by far the most common. I like the max_angle, min_duty_cycle, max_duty_cycle options as long as most of the time the defaults were the most common. Again, see the default set up for the Arduino Servo object. Nice to have : a moveTo or similar that interpolates from the current position to the target position with a specified duration, and ease in and ease out (either a linear ramp up and down or an exponential ease-in/out if you're feeling fancy). Otherwise you're relying on the in-built circuitry in a server to manage that, and it's not often very pleasant. (I have some experience in this area so can likely help if required). |
on/off or equivalent makes sense actually, to get it to disengage. |
Is using PWM necessarily the best idea for this? It's something I've not (yet) experimented with myself, but I recall reading in the past that trying to use PWM (especially from Python - see http://raspi.tv/2013/how-to-use-soft-pwm-in-rpi-gpio-pt-2-led-dimming-and-motor-speed-control) is too jittery on a RaspberryPi for accurate servo control (although I guess this may be less of an issue on the Pi2 and Pi3 than it was on the Pi1), and that's why things like ServoBlaster was developed, to allow much more accurate servo control? And having Servos as an "object in their own right" and not inherited from PWMOutputDevice also allays Ben's class-inheritance (and appropriate-method-overloading) concerns. |
I agree as well that maybe having different classes for 180 or 360 is a bit too much, sounds better to have one with sane defaults that can be edited in the constructor. The only thing I would maybe separate would be servos from "continuous servos", have you guys given any though to that? |
@lurch I understand that by now* it might be possible to load Videocore's VPU with a small chunk of user defined code to drive PWM on the GPIO pins. *at a CamJam about/over a year ago I queried using VideoCore todo PWM in a conversation with Eben and he said that the VPU can access the GPIO. He also mentioned the functions the VPU were currently (at the time) performing were moving off the VPU, so it might become available for such 'user' programs. (I think there's a documented interface and a VPU compiler now too (?) ) This is perhaps a far more intricate system level project todo and better kept separate from the gpiozero repository... :) |
That's pretty much the philosophy I aim for. As long as there are reasonable standards we can adhere to, that makes sense.
That makes sense. Will bear in mind. |
I haven't use the PWM in the Pis that much to be honest, but are we saying that the hardware peripheral is not reliable enough to control a servo? Or is the general conversation about alternative PWMs as a software implementation on other GPIOs? |
@carlosperate Just options... The Pi's PWM for servo control is 'good enough' to get demos and fun things up and running, it's probably 'not good enough' for controlling a quad-copter though, but what people do with the Pi is always enlightening. This is a demo (not mine) of using a Python wrapper around the ServoBlaster (software + DMA driven) PWM, which would seem ok for at least 4 PWM's, ServoBlaster says it supports up to at least 8. http://fortoffee.org.uk/2015/02/me-arm-and-me-raspberry-pi/ Although ServoBlaster introduces another dependancy. If I get a chance, I might plug in my MeArm and try controlling it using RPi.GPIO PWM support. |
Re continuous servos, I think most people these days equate a "servo" to a "hobby servo" or "RC servo" ie, the cheap plastic things with built-in controllers that use the pulse system for positional information. As someone who uses industrial servos with separate controllers it can get confusing but I'm not really sure if we can fix that at this stage! Of course the other type of continuous servo is a hobby servo that has had the positional feedback system pulled out (usually a pot), I'm not really sure how to cater for that but I think the basic servo API would suffice. As for using PWM, it is basically PWM but with short pulse between 1 and 2ms followed by a wide gap. If the pulse is 1ms then the motor goes to 0º, if it's 2ms then it goes to 180º. If the servo doesn't get regular pulses or the timing isn't accurate enough then you get the horrible jitter effect. The pulse frequency should be once every 20ms or 50hz which isn't actually that fast. So it's a slightly different use case for PWM with slightly different requirements. But I'm thinking that the PWM libs I've seen should be able to handle it as long as the timing is accurate enough. (I always thought it was a weird protocol!) cheers! Seb PS A good diagram on the wiki page https://en.wikipedia.org/wiki/Servo_control#/media/File:Sinais_controle_servomotor.JPG |
Or GPIO Zero PWM support? :P |
same difference isn't it? :) |
As far as continuous rotation "hobby" servos go, I've seen them being commercially available for robotic kits and similar things. I've perhaps wrongly inferred from @sebleedelisle comment that most continuous servos are manually modified, but I just wanted to clarify that it seems (at least to me) like a potential use case for this library as well. The signal control basics are very much the same as with a normal servo, but the end result can be considered different enough where the API design would most likely change. So maybe being able to derive a “servo” and “continuous servo” class from the same parent could be a good aim to have in order to clearly separate the signal control from the end device implementation, as it has been done with other components in this package. |
Cripes this thread has grown.. Okay, I'll throw some thoughts into the wind:
Sorry, I've just skimmed all the messages here - head buried in unit-testing at the moment... |
Oh, and @WayneKeenan - not exactly the same difference - RPi.GPIO is indeed the default pin implementation but since 1.1 you can use RPIO or pigpiod as the backend instead for "proper" hardware PWM (although the setup's more involved - haven't written any serious docs about that yet largely because pigpiod isn't properly packaged for Raspbian (yet)). Oh, and you can even use a pure Python implementation but that doesn't support PWM so it's a bit of a moot point for this thread. |
So, if you try to create a Servo object using a non-hardware-DMA-able pin (i.e. a NativePin or RPiGPIOPin), should Servo's |
Ok sure. Updated my example above. I think there needs to be a base PWM class, which this and I've got an idea how the |
I guess a Servo could be the same as an ordinary PWM output as long as the On 5 April 2016 at 17:32, Ben Nuttall notifications@github.com wrote:
|
You can easily do 50Hz PWM with DMA on the Pi (in fact, it's damn near millisecond accurate looking at RPIO's oscilloscope pics) but with software PWM (i.e. straight-forward bit-banging from a userspace process like that implemented by RPi.GPIO) you can't guarantee anything. While it might be fine most of the time, you can't be certain that something isn't going steal all your process' clock cycles for the next second or two (even with a fully preemptable kernel - can't remember exactly what Raspbian's defaults are in this area though). In other words, if you're okay with your PWM occasionally going out to lunch and letting your servo go wild, then software PWM might be acceptable. But if you need to ensure your servo does exactly what you request, then you have to use some form of hardware assistance (whether that's a proper PWM implementation, which the Pi does have but only on a very limited set of pins, or programming the DMA controller to do bit-banging on the GPIO registers for you). |
It works well enough to send a servo to the right position. However, I noticed when setting mine to 90 degrees, it would twitch. This could be resolved by disengaging it once it's moved (I'm not sure how we'd time this if we did it automatically, but that could be down to the user). |
Yeah the twitching is almost definitely to do with poor timing on the On 5 April 2016 at 19:39, Ben Nuttall notifications@github.com wrote:
|
Likely both :) |
Do you have decent PWM on gpiozero? Or is it all done in python? On 5 April 2016 at 19:45, Ben Nuttall notifications@github.com wrote:
|
@bennuttall now try it on a slower Pi (like a model 1) and fire up some simultaneous threads - it'll only get worse :) @sebleedelisle here's a rough overview of the way things currently stand: gpiozero (which is pure python) relies on "pin libraries" to handle controlling GPIO pins (for everything from simple high/low settings up to PWM). Currently four "pin libraries" are supported and gpiozero tries to import them in the following preference order:
See the pins documentation for a rough overview of all the above, along with detail of the pin interface gpiozero expects (and source links to all the above libraries), and instructions on overriding the default pin implementation. So, that's how things stand right now. For the near future, RPi.GPIO is almost certain to remain the default library (especially as it's installed by default on Raspbian). For the future, I'd love to see pigpiod given some attention as I think it's the most promising architecture (the fact it exposes nearly all GPIO functionality over sockets is also a potential boon to other languages) but that's largely dependent on how much free time people have (I really ought to spend some more time on picamera which is long overdue a release, then there's all the picademy worksheets that need some work, etc. etc.) |
@waveform80 thanks for the info! I've used similar things in node, including the node wrapper for pigpio but very little experience with Python on the RPi so this was really helpful. |
Ok here's some code that works but isn't perfect: class Servo(PWMOutputDevice):
def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100, max_angle=180, minimum=0.033, maximum=0.254):
super(Servo, self).__init__(pin, active_high, initial_value, frequency)
self._max_angle = max_angle
self._minimum = minimum
self._maximum = maximum
self._angle = None # don't know what it is on init
@property
def angle(self):
return self._angle
@angle.setter
def angle(self, value):
if 0 > value > self._max_angle:
raise OutputDeviceBadValue("angle must be between 0 and %s" % self._max_angle)
self._angle = value
self.value = ((self._maximum - self._minimum) * value / self._max_angle) + self._minimum I want to create a new base class, rather than letting this extend |
Any particular reason this shouldn't extend PWMOutputDevice? You could simply make on a method equivalent to setting the maximum angle (which would be consistent with the fact value will be 1 which is the result of calling on in other PWM devices). If you really want to get rid of on, Python is unusual in that you can remove methods from sub-classes with a little magic, but it's unusual enough that I'd generally discourage it (simply because people won't expect it). |
Do you think ms would be a better unit? Part of me thinks so, and I'm sure for people experienced with servos it probably would be. But I think for complete newbies (which is the major focus of the library), consistency with everything else might be easier (even if it means writing 2e-3, or 2/1000, or 0.002). Happy to be persuaded otherwise though!
When the servo is actively controlled you can't manually adjust the servo's position. There may be some minor use cases for manual adjustment but a more important point (particularly for robotics) is that the servo's constantly drawing power when actively controlled. When "free" it doesn't draw power but there's usually enough resistance in the servo to reasonably assume that a steering wheel on a robot wouldn't veer off. |
Still researching these, but yes I'm thinking another class similar to Motor for them (this was part of the reason I suggested AngularServo earlier ... as I'm not happy with RotationalServo vs FullyRotationalServo ... far too confusing :) |
Maybe. Can you give examples of the full constructor? i.e.
Fair enough |
Minimal example (assuming a servo on GPIO17 with a 20ms overall pulse width, minimum position 1ms pulse, maximum position 2ms pulse):
Alternatively, with all parameters specified:
Adding in angles, the minimum case (assuming angles 0-180):
Alternatively, with all parameters specified:
Definitely thinking AngularServo might be better - hints at the fact there's an angle property... |
Hmm, maybe ContinuousServo for the fully rotational style servos - implies that they continue rotating like a Motor ... or maybe just ServoMotor (although that's a bit confusing because all servos are ultimately motors...) |
I like that. @MarcScott any thoughts on the above usage? |
Real use - how's it going in Scratch? That's probably its biggest exposure at the moment. We need to improve our test suite for it though - we're doing horrible things to the daemon at the moment (killall because I quickly hacked it together); we should be using systemctl for it instead. Will have to see what I can do. |
Good point. I'll ask around. |
Not really (we're way below the precision provided by the 53bit mantissa) - there's more of an issue with what happens when frequencies not directly supportable by DMA are selected (for semi-HW PWM). I need to do a bit more testing of this but my preliminary results are that even my cheapy servos are quite tolerant of timing variances provided they're regular. |
@waveform80 said:
I am not in favour of using numbers less than 1 for the simple fact that it's even more for kids to get their head around. If you're going to use msec, you may just as well go with usec and use nice big whole numbers from 1000 to 1500 to 2000 for example. I know 10 year old kids will get that. Of course, I agree, in engineering, that we should use consistent units but the whole thing about gpiozero is abstracting all this complexity away. Just my 2c (or should that be pence - lol). I won't take it personally. Just some feedback from working with kids (think very small numbers used in sleep()). |
My comments...
I think there ought to be more comprehensive pin unit-tests before this switch is made, which you've already addressed with:
I guess if pigpiod is running before the unit-tests start, then it should still be left running after the unit-tests finish. But if it isn't running before the unit-tests start, then IMHO it should be automatically terminated when the unit-tests finish. How much effort would it be to get the python-wrapper part of pigpiod pip-installable, to make virtualenv and remote-gpio usage easier?
Please enlighten us... what kind of other 'servos' are there? Are any of them likely to be used by the same kind of people using GpioZero? I agree with Ben that
I think this makes sense for the current API.
Why not just skip And I guess perhaps
I guess setting-value-to-0 and setting-value-to-1 makes
IMHO
Agreed. Perhaps @bennuttall 's
Yeah, I think so. I guess the only reason that http://gpiozero.readthedocs.io/en/stable/api_output.html#motor has separate
I think the advantage to using seconds is that it 'matches' the rest of the GpioZero API, and the advantage to using milliseconds is that they're the units traditionally used to specify servo parameters. I don't think using microseconds gives any advantages at all?
Yeah, I was a little bit surprised when I noticed that http://microbit-micropython.readthedocs.io/en/latest/microbit.html#microbit.sleep takes values in milliseconds, in contrast to https://docs.python.org/2/library/time.html#time.sleep which takes values in seconds.
Slightly OffTopic, but I've been pondering 'inverting' the logic of the |
We do need a |
Ahh, thanks :) Do the digital servos use smaller pulse-widths too? (I'm wondering if there should be a BTW: You can use the greater-than symbol |
Nope.
The same make and model of a servo is unlikely to be but a mix of servos can rotate in opposite directions for a given pulse width due to the wires on the motor and the servo gearbox. Therefore, it would be good to have the |
#314 (comment) seems to reinforce the point that we should probably have an optional |
Apparently no reported issues with Scratch. However the reason the daemon isn't run by default is that it hogs a lot of CPU due to constant GPIO polling. |
The PR merged doesn't contain everything everyone wanted, but it's intended to be a start we can build on in 1.4. I'll be opening that milestone shortly as 1.3 should be released tonight (we've got a Raspbian freeze tomorrow so things are a bit rushed!) |
Great work guys. |
Some more notes on the implementation I didn't have time for last night:
|
Oh, missed some (looking back through @lurch's posts):
|
I think making it configurable might be too confusing - perhaps a better option would be a Servo subclass that treats value as 0..1 Something else that's just occurred to me is that a servo.pulse() function might be useful for quick flag-waving type demos ;-) (although that may be possible already with the sourcetools stuff)
I assume you tested with the different Pin backends too? How do they end up comparing in terms of jitteryness?
In that case perhaps this issue should be re-opened for continued discussion? (so that it doesn't get "lost"). |
New issue please!! |
Heh - one of the first things I tried was: $ GPIOZERO_PIN_FACTORY=PiGPIOPin python
Sit back and watch it wave!
Yeah - no time to test RPIO, but tested RPiGPIOPin and PiGPIOPin. The latter is rock solid - just perfect. The former always jitters but how much depends on system load and other factors (and also on servo choice I think - one servo was really awful with - jumped around the desk!)
Yeah - my e-mail notification thread for this one is just ridiculous ;) |
Created #419 :-) |
We should add a Servo class, or more likely, a series of Servo classes (e.g.
Servo180
,Servo360
...). I'm not sure what the variety of different servos is like, but perhaps there needs to be much configuration ability on init.The way servos work is with PWM, setting a specific duty cycle to turn the servo to a specific angle. The one I have, a Tower Pro SG90, moves proportionately between 0 degrees and about 170 degrees when the value is set between 0.033 and 0.253 at a frequency of 100, but any value outside that does nothing.
I started playing with one using
PWMOutputDevice
and got it turning to certain angles by sending various signals via thevalue
property.When thinking about implementing a class for this, I realised I probably didn't want to extend
PWMOutputDevice
as is, because I didn't needon()
,off()
, etc. so I guess we should introduce aDigitalPWMOutputDevice
class for this, and all existing classes which currently extendPWMOutputDevice
would switch toDigitalPWMOutputDevice
, andServo
would extend the newPWMOutputDevice
. Perhaps "digital" is the wrong word in this case...I'm not certain what the servo API should look like, but I guess ideally, you could set it by angle, and maybe setting
value
between 0 and 1 would move proportionally between the minimum and maximum positions.The text was updated successfully, but these errors were encountered: