From 1a3f7c10a0456681687d46205c822ae770bb67cc Mon Sep 17 00:00:00 2001 From: Richard Hirst Date: Sat, 15 Dec 2012 13:29:30 +0000 Subject: [PATCH] Add idle_timeout module parameter which automatically turns a servo control line off some period after the last command. --- ServoBlaster/README.txt | 61 +++++++++++++++++++---------------- ServoBlaster/servoblaster.c | 31 +++++++++++++++++- ServoBlaster/servoblaster.ko | Bin 10863 -> 12179 bytes 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/ServoBlaster/README.txt b/ServoBlaster/README.txt index bb8d241..6cdbe6f 100644 --- a/ServoBlaster/README.txt +++ b/ServoBlaster/README.txt @@ -27,14 +27,14 @@ is currently set. For example, after starting the driver and running the previous command, you would see: pi@raspberrypi ~ $ cat /dev/servoblaster -0=0 -1=0 -2=0 -3=120 -4=0 -5=0 -6=0 -7=0 +0 0 +1 0 +2 0 +3 120 +4 0 +5 0 +6 0 +7 0 pi@raspberrypi ~ $ When the driver is first loaded the GPIO pins are configure to be outputs, and @@ -83,31 +83,11 @@ directory will also create the necessary files. Further to this, running "make install_autostart" will create those files, plus perform the necessary changes to make servoblaster be automatically loaded at boot. -Note that there are three different ways of referring to a specific servo -control pin: by servo number, by GPIO pin on the processor, or by pin number -on the P1 header on the Pi itself. The following table shows the mapping -between these number schemes: - - Servo GPIO number P1 Pin - 0 4 7 - 1 17 11 - 2 18 12 - 3 21 13 - 4 22 15 - 5 23 16 - 6 24 18 - 7 25 22 - 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. -ServoBlaster currently uses the PWM hardware for timing purposes, so cannot be -used at the same time as PWM audio on the 3.5mm jack, and if you play PWM audio -after loading servoblaster.ko, you'll need to unload and reload servoblaster.ko -in order to recover. - I would of course recommend some buffering between the GPIO outputs and the servo controls, 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 @@ -121,6 +101,21 @@ on the wiki (http://elinux.org/RPi_Kernel_Compilation) to compile the kernel, then edit the servoblaster Makefile to point at your kernel tree, then build servoblaster. +Some people have requested that a servo output turns off automatically if no +new pulse width has been requested recently, and I've had two reports of +servos overheating when driven for long periods of time. To support this +request, ServoBlaster implements an idle timeout which can be specified at +module load time. The value is specified in milliseconds, so if you want +to drive your servos for 2 seconds following each new width request you would +do this: + +sudo insmod ./servoblaster.ko idle_timeout=2000 + +Typical small servos take a few 100 milliseconds to rotate from one extreme +to the other, so for small values of idle_timeout you might find the control +pulse is turned off before your servo has reached the required position. +idle_timeout defaults to 0, which disables the feature. + NOTE: There is some doubt over how to configure the PWM clock at present. For me the clock is 600KHz, which leads to a tick lenght of 10us. However at least one person has reported that the pulses are out by about a factor of about 8, @@ -152,5 +147,15 @@ As of August 30th 2012 the servoblaster.ko module is built against a 2.6.27+ kernel source from github. +Related projects: + +Ville has written a simple Qt wrapper for servoblaster, which you can find +here: https://github.com/vranki/kittinger/blob/master/servocontrol.cpp & .h + +Todd wrote a nice script to provide a simple user interface to control your +servos, see his sbcontrol.sh script here: +http://www.raspberrypi.org/phpBB3/viewtopic.php?f=37&t=15011&start=25#p187675 + + Richard Hirst August 2012 diff --git a/ServoBlaster/servoblaster.c b/ServoBlaster/servoblaster.c index 03af80a..d98ad0b 100644 --- a/ServoBlaster/servoblaster.c +++ b/ServoBlaster/servoblaster.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,11 @@ static uint8_t servo2gpio[] = { }; #define NUM_SERVOS (sizeof(servo2gpio)/sizeof(servo2gpio[0])) +// Per-servo timeouts, so we can shut down a servo output after some period +// without a new command - some people want this because the report servos +// overheating after a time. +static struct timer_list idle_timer[NUM_SERVOS]; + // This struct is used to store all temporary data associated with a given // open() of /dev/servoblaster struct private_data @@ -161,12 +167,19 @@ static struct cdev my_cdev; static int my_major; static int cycle_ticks = 2000; static int tick_scale = 6; +static int idle_timeout = 0; // This records the written count values so we can display them on a read() // call. Cannot derive data directly from DMA control blocks as current // algorithm has a special case for a count of zero. static int servo_pos[NUM_SERVOS] = { 0 }; +static void servo_timeout(unsigned long servo) +{ + // Clear GPIO output next time round + ctl->cb[servo*4+0].dst = ((GPIO_BASE + GPCLR0*4) & 0x00ffffff) | 0x7e000000; +} + // Wait until we're not processing the given servo (actually wait until // we are not processing the low period of the previous servo, or the // high period of this one). @@ -213,8 +226,17 @@ int init_module(void) return res; } + for (i = 0; i < NUM_SERVOS; i++) + setup_timer(idle_timer + i, servo_timeout, i); + + if (idle_timeout && idle_timeout < 20) { + printk(KERN_WARNING "ServoBlaster: Increased idle timeout to minimum of 20ms\n"); + idle_timeout = 20; + } + ctldatabase = __get_free_pages(GFP_KERNEL, 0); - printk(KERN_INFO "ServoBlaster: Control page is at 0x%lx, cycle_ticks %d, tick_scale %d\n", ctldatabase, cycle_ticks, tick_scale); + printk(KERN_INFO "ServoBlaster: Control page is at 0x%lx, cycle_ticks %d, tick_scale %d, idle_timeout %d\n", + ctldatabase, cycle_ticks, tick_scale, idle_timeout); if (ctldatabase == 0) { printk(KERN_WARNING "ServoBlaster: alloc_pages failed\n"); cdev_del(&my_cdev); @@ -330,6 +352,7 @@ void cleanup_module(void) // Take care to stop servos with outputs low, so we don't get // spurious movement on module unload for (servo = 0; servo < NUM_SERVOS; servo++) { + del_timer(idle_timer + servo); // Wait until we're not driving this servo if (wait_for_servo(servo)) break; @@ -411,6 +434,9 @@ static int set_servo(int servo, int cnt) if (wait_for_servo(servo)) return -EINTR; + if (idle_timeout) + mod_timer(idle_timer + servo, jiffies + msecs_to_jiffies(idle_timeout)); + // Normally, the first GPIO transfer sets the output, while the second // clears it after a delay. For the special case of a delay of 0, we // ensure that the first GPIO transfer also clears the output. @@ -511,3 +537,6 @@ MODULE_PARM_DESC(cycle_ticks, "number of ticks per cycle, max pulse is cycle_tic module_param(tick_scale, int, 0); MODULE_PARM_DESC(tick_scale, "scale the tick length, 6 should be 10us"); +module_param(idle_timeout, int, 0); +MODULE_PARM_DESC(idle_timeout, "Idle timeout, after which we turn off a servo output (ms)"); + diff --git a/ServoBlaster/servoblaster.ko b/ServoBlaster/servoblaster.ko index 25e6706e4676771a8dd50dcf907f743470b90325..b2752f6a9381481098f56aff3174bc62921741f9 100644 GIT binary patch literal 12179 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 Y*Vov-beS;WPDNZ2m+t!X8r|Cd4{`*|e*gdg literal 10863 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 a&%9p4V&KE#S!dSb5^MS{#H{hH)c*psU!?W`