Driving multiple servos from the RaspberryPi


197 posts   Page 1 of 8   1, 2, 3, 4, 5 ... 8
by rgh » Sun Aug 19, 2012 4:51 pm
Hi,
I've written a kernel driver for the Pi which lets you drive multiple servos, without having to resort to additional hardware such as micro-controllers. It seems to work very nicely, giving nice stable outputs even when the Pi is busy with other tasks; here's a screenshot of Panalyzer showing four servo outputs, of varying widths:

Image

You can control the pulse widths by writing commands to /dev/servoblaster. For example, to set servo 2 pulse width to 1200us, you'd do:

# echo 2=120 > /dev/servoblaster

You can find the code, and a ready built kernel module, here: https://github.com/richardghirst/PiBits/tree/master/ServoBlaster.

Here's the README, for more details:


ServoBlaster

This is a Linux kernel driver for the RaspberryPi, which provides an interface
to drive multiple servos via the GPIO pins. You control the servo postions by
sending commands to the driver saying what pulse width a particular servo
output should use. The driver maintains that pulse width until you send a new
command requesting some other width.

Currently is it configured to drive 8 servos. Servos typically need an active
high pulse of somewhere between 0.5ms and 2.5ms, where the pulse width controls
the position of the servo. The pulse should be repeated approximately every
20ms, although pulse frequency is not critical. The pulse width is critical,
as that translates directly to the servo position.

The driver creates a device file, /dev/servoblaster, in to which you can send
commands. The command format is "<servo-number>=<servo-position>", where servo
number is a number from 0 to 7 inclusive, and servo position is the pulse width
you want in units of 10us. So, if you want to set servo 3 to a pulse width of
1.2ms you could do this at the shell prompt:

echo 3=120 > /dev/servoblaster

120 is in units of 10us, so that is 1200us, or 1.2ms.

When the driver is first loaded the GPIO pins are configure to be outputs, and
their pulse widths are set to 0. This is so that servos don't jump to some
arbitrary postion when you load the driver. Once you know where you want your
servos positioned, write a value to /dev/servoblaster to enable the respective
output. When the driver is unloaded it attempts to shut down the outputs
cleanly, rather than cutting some pulse short and causing a servo position to
jump.

The driver allocates a timeslot of 2.5ms to each output (8 servos resulting in
a cycle time of 20ms). A servo output is set high at the start of its 2.5ms
timeslot, and set low after the appropriate delay. There is then a further
delay to take us to the end of that timeslot before the next servo output is
set high. This way there is only ever one servo output active at a time, which
helps keep the code simple.

The driver works by setting up a linked list of DMA control blocks with the
last one linked back to the first, so once initialized the DMA controller
cycles round continuously and the driver does not need to get involved except
when a pulse width needs to be changed. For a given servo there are four DMA
control blocks; the first transfers a single word to the GPIO 'set output'
register, the second transfers some number of words to the PWM FIFO to generate
the required pulse width time, the third transfers a single word to the GPIO
'clear output' register, and the fourth transfers a number of words to the PWM
FIFO to generate a delay up to the end of the 2.5ms timeslot.

While the driver does use the PWM peripheral, it only uses it to pace the DMA
transfers, so as to generate accurate delays. The PWM is set up such that it
consumes one word from the FIFO every 10us, so to generate a delay of 1.2ms the
driver sets the DMA transfer count to 480 (1200/10*4, as the FIFO is 32 bits
wide). The PWM is set to request data as soon as there is a single word free
in the FIFO, so there should be no burst transfers to upset the timing.

I used Panalyzer running on one Pi to mointor the servo outputs from a second
Pi. The pulse widths and frequencies seem very stable, even under heavy SD
card use. This is expected, because the pulse generation is effectively
handled in hardware and not influenced by interrupt latency or scheduling
effects.

Please read the driver source for more details, such as which GPIO pin maps to
which servo number. The comments at the top of servoblaster.c also explain how
to make your system create the /dev/servoblaster device node automatically when
the driver is loaded.

The driver uses DMA channel 0, and PWM channel 1. It makes no attempt to
protect against other code using those peripherals. It sets the relevant GPIO
pins to be outputs when the driver is loaded, so please ensure that you are not
driving those pins externally.

I would of course recommend some buffering between the GPIO outputs and the
servo control inputs, to protect the Pi. That said, I'm living dangerously and doing
without.. If you just want to experiment with a small servo you can probably
take the 5 volts for it from the header pins on the Pi, but I find that doing
anything non-trivial with four servos connected pulls the 5 volts down far
enough to crash the Pi (no surprise there).


Richard
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by joan » Sun Aug 19, 2012 5:43 pm
Excellent work!

Of course sod's law dictates that rpi-update has just installed a new incompatible kernel but a recompile will deal with that.
User avatar
Posts: 5389
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by joan » Sun Aug 19, 2012 7:23 pm
I recompiled for the new kernel and it seems to operate properly. My make system complained about the null servoblaster.h so I had to add a dummy define.
User avatar
Posts: 5389
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by rgh » Sun Aug 19, 2012 7:59 pm
joan wrote:I recompiled for the new kernel and it seems to operate properly. My make system complained about the null servoblaster.h so I had to add a dummy define.


Thanks for testing!
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by Gert van Loo » Mon Aug 20, 2012 5:24 pm
"..here's a screenshot of Panalyzer ...".
Is that the logic analyzer running on the raspberry-Pi measuring the output waveform generated by the Raspberry-Pi?
Somehow I have a feeling there is a flaw in there somewhere... :-)

What I really wanted to say is:
Well done but beware that you might have artifacts in there which are not visible due to my remark above.
User avatar
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 2029
Joined: Tue Aug 02, 2011 7:27 am
by rgh » Mon Aug 20, 2012 6:26 pm
Hi Gert, thanks :) Actually that is Panalyzer running on one Raspberry-Pi monitoring the servo control outputs from a second Raspberry-Pi.

I should have credited you with the idea of using dummy DMA cycles to generate a delay. In this case I DMA to the PWM FIFO, which is configured to consume one word every 10us. So, there isn't really any reason the servo pulse timing should have jitter of more than whatever contention the DMA controller might see on the bus (unless you know differently).

I did have a question for you... are you sure the register accesses in the gertboard example code for setting the PWM clock rate are correct? I used your values which gives a frequency of 600KHz, then set the range to 6, so one FIFO word is consumed every 10us (600/6 = 100KHz = 10us cycle time). However, if I try changing the (32<<12) it seems to have no effect on how quickly the FIFO empties. The registers are clearly valid registers that only take a new value when you set the top byte to 5A, but I'm wondering if they are controlling some other clock. I tried values such as (16<<12) or (8<<12), expecting the FIFO to then consume a word every 5us or 2.5us. Maybe there is something else I need to do to make a new clock rate active. It's not really important; I'm just curious.
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by Gert van Loo » Mon Aug 20, 2012 7:56 pm
I received a question about the PWM clock frequency yesterday.
You are not the first to have an 'issue' where the clock/frequency/output signal are not what is expected.
I have not had time to look into the details. For my code I was relying in the data sheet and my general knowledge
how most circuits on the chip work.
I think the original designer of the PWM module no longer works with use which
means I have to delve deep into the Verilog code or run a simulation to see what is going on.
I am afraid it will be while before I have time for that.
Don't hesitate to poke me again in two weeks time if you have not received an answer.
User avatar
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 2029
Joined: Tue Aug 02, 2011 7:27 am
by bobc » Wed Aug 22, 2012 11:09 pm
rgh wrote:Hi,
I've written a kernel driver for the Pi which lets you drive multiple servos, without having to resort to additional hardware such as micro-controllers. It seems to work very nicely, giving nice stable outputs even when the Pi is busy with other tasks; here's a screenshot of Panalyzer showing four servo outputs, of varying widths:


Neat stuff ;)

With a small modifications, it should be possible to generate pulses for stepper motors as well. A 10us resolution is plenty enough for most applications. The user code would request "send N pulses at rate f with direction d on output x", and the device driver does the rest. I assuming here there is an external stepper driver with a Step and Dir input, like the Allegro A4988, which is used on Pololu boards for about £10.

If someone can hook this up with LinuxCnc, it might be a way to get that running on the Pi. It would also be an interesting project to start from scratch, LinuxCnc is a bit of a monster.
Posts: 86
Joined: Fri Apr 06, 2012 8:01 am
by bulldog5046 » Fri Aug 24, 2012 8:18 am
Great work!

I don't supose you could give some visual reference for the layman on how to connect the servo's to the board?

Also, how could the servo's be buffered to protect the Pi?
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by fbutler » Sat Aug 25, 2012 12:13 am
Not being a hardware orientated person I've got a question:

What would be required to drive multiple RGB LED strips using the same PWM techniques?

Would this be possible?
User avatar
Posts: 299
Joined: Thu Mar 15, 2012 4:09 pm
Location: Surrey, England
by danpeirce » Sat Aug 25, 2012 6:01 pm
Intriguing.
User avatar
Posts: 88
Joined: Thu May 10, 2012 8:32 am
Location: Richmond & Surrey BC Canada
by bobc » Sun Aug 26, 2012 12:31 pm
fbutler wrote:Not being a hardware orientated person I've got a question:

What would be required to drive multiple RGB LED strips using the same PWM techniques?

Would this be possible?


You would need MOSFET Drivers which can take a 3.3V logic input, perhaps something like this http://www.ebay.co.uk/itm/Arduino-FET-s ... 56491d2abd or if you use suitable buffer any of the shields designed for 5V could be used.
Posts: 86
Joined: Fri Apr 06, 2012 8:01 am
by rgh » Wed Aug 29, 2012 9:10 pm
bulldog5046 wrote:Great work!

I don't supose you could give some visual reference for the layman on how to connect the servo's to the board?

Also, how could the servo's be buffered to protect the Pi?


Nothing visual at the moment, but I used these servos: http://www.hobbytronics.co.uk/robotics/sparkfun-small-servo. It has three wires, black goes to GND, red goes to 5V, white goes to the relevant GPIO pin. You can find all of those signals on the P1 header on the Pi. There seems to be enough current available to drive at least one servo from there. If you carefully remove the connectors from the black plastic bit on the end of the wires, they'll even plug direct on to the P1 header pins. To protect the Pi I'd probably use something like a 74LS241 as I have them kicking around and they'll buffer all 8 lines in one chip, but I'm sure you could find examples that use a transistor instead via google. If your circuit ends up inverting the signal you can always change the driver to invert also to compensate.
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by joan » Wed Aug 29, 2012 9:27 pm
In my case ignorance is bliss. I've just been connecting the control signal directly from the gpio to the servo control line.

http://www.youtube.com/watch?v=Jse3cziVyeg
User avatar
Posts: 5389
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by rgh » Thu Aug 30, 2012 9:19 pm
Thanks for the video Joan, I only got four on mine so far :)

I just updated github; the servoblaster.ko is now built against a 3.2.27+ kernel so will probably work for anyone who ran rpi-update recently. I also had one report of the pulse timing being out by about a factor of 8, presumably because I'm not programming the PWM clock correctly, and for some reason that system happens to have it set to some different frequency. Anyway, I added a couple of module parameters you can use to tweak the timing, see the README.txt for details. https://github.com/richardghirst/PiBits/tree/master/ServoBlaster.
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by bulldog5046 » Fri Aug 31, 2012 1:03 pm
Thank you for the details. This could prove very useful for a project i had in mind.

Just wish i had enough free time to play!
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by bulldog5046 » Fri Aug 31, 2012 6:01 pm
As it's friday night and my plan's have been cancelled and the XBMC image on my SD card has gone up it's self i thought i'd take a look at this.

Unfortunately it seems the Makefile doesnt work with the latest 18-08-2012 raspbian image?

make: Nothing to be done for `all'.


Also, i'm slightly confused by the readme. It sugests i shouldnt need to compile a kernel but to get the kernel_tree location and Module.symvers google suggests i need to compile one??

I must admit i know little about this side of linux, i just wanted to get on with writing a bit of python to get some servo's moving....

Any help appreciated.

Thanks,
Ry
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by rgh » Sat Sep 01, 2012 1:54 pm
Hi bulldog5046. 'make' decides what to do based on the rules in the Makefile and the timestamps on the files. In this case the target for 'all' is servoblaster.ko, and if you already have a file by that name with a timestamp newer than the things it is created from, then make will conclude that it doesn't have to do anything. You could try 'make clean' and then 'make', which works for me. However, the easiest way to get going is probably to update to the latest firmware and kernel by running rpi-update, and then simply use the servoblaster.ko module I put on github; no need to compile anything. You still have to follow the instructions in the top of servoblaster.c to get the system to create the /dev/servoblaster device file automatically for you when you load the module. I didn't see what confused you with the current README, but I'm happy to update it if you point me at the problem.
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by bulldog5046 » Sat Sep 01, 2012 2:41 pm
When you say 'use serverblaster.ko' how do you use it?

Just "sudo insmod servoblaster.ko"?
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by bulldog5046 » Sat Sep 01, 2012 3:23 pm
Wehey! got it working :)

I created the 2 files but it dodnt creat /dev/servoblaster so i had to do it manually, then load the driver with 'sudo insmod servoblaster.ko' and i'm now able to successfully issue commands to the servo :)

Thanks,
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by bulldog5046 » Sun Sep 02, 2012 6:33 pm
For the benefit of anyone like me who may struggle with this, here is a brief laymans how-to from a fresh build:

Install Git
Code: Select all
sudo apt-get install git


Clone the servoblaster github
Code: Select all
git clone git://github.com/richardghirst/PiBits.git


CD to the servoblaster dir
Code: Select all
cd PiBits/ServoBlaster


Load the servoblaster module
Code: Select all
sudo insmod servoblaster.ko


Not sure what this does but it returns a number, i think it create a process for servoblaster and returns the PID
Code: Select all
sudo sed -n 's/ servoblaster//p' /proc/devices


Use the number returned in the below to create /dev/servoblaster. Without square brackets.
Code: Select all
sudo mknod -m 0666 /dev/servoblaster c [NUMBER HERE] 0

Eg.
Code: Select all
sudo mknod -m 0666 /dev/servoblaster c 251 0


You should be good to go :)
Posts: 36
Joined: Thu Jun 21, 2012 1:50 pm
by seregus » Wed Sep 12, 2012 12:20 pm
Hi,

I was trying to follow the instructions, but got error message:
Error: could not insert module servoblaster.ko: Invalid module format

Here is information about my kernel:
Linux raspberrypi 3.2.27+ #143 PREEMPT Tue Sep 11 02:02:37 BST 2012 armv6l GNU/Linux
User avatar
Posts: 8
Joined: Wed Jul 18, 2012 1:29 pm
by rgh » Sat Sep 15, 2012 3:39 pm
seregus: That error is because you are running an older kernel than the one I built the module for. Try running rpi-update to update your kernel and firmware, then try again.
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm
by seregus » Sat Sep 15, 2012 7:42 pm
Hm, sounds strange,, I've run an upgrade again to make sure I've a latest one, like I mentioned befor it was 3.2.27+ and after it's the same 3.2.27+
Here is output:
---
git clone git://github.com/richardghirst/PiBits.git
Cloning into 'PiBits'...
remote: Counting objects: 34, done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 34 (delta 10), reused 27 (delta 6)
Receiving objects: 100% (34/34), 28.28 KiB, done.
Resolving deltas: 100% (10/10), done.

cd PiBits/ServoBlaster/
root@raspberrypi:/home/pi/PiBits/ServoBlaster#

root@raspberrypi:/home/pi/PiBits/ServoBlaster# uname -a
Linux raspberrypi 3.2.27+ #151 PREEMPT Fri Sep 14 17:00:51 BST 2012 armv6l GNU/Linux

root@raspberrypi:/home/pi/PiBits/ServoBlaster# insmod servoblaster.ko
Error: could not insert module servoblaster.ko: Invalid module format
---
User avatar
Posts: 8
Joined: Wed Jul 18, 2012 1:29 pm
by rgh » Sat Sep 15, 2012 8:45 pm
seregus wrote:Hm, sounds strange
---

I'm sorry, I jumped to conclusions.. I've just rebuilt against the latest kernel and pushed the new servoblaster.ko to github. Can you grab that one and try again?

Richard
Posts: 220
Joined: Fri Nov 25, 2011 3:53 pm