mirror of
https://github.com/richardghirst/PiBits.git
synced 2025-02-26 19:54:16 +01:00
Major update to servod.c to handle up to 21 servos and to allow pulse widths between 0 and 100%
This commit is contained in:
parent
071fc182bc
commit
29ce55de75
@ -6,17 +6,22 @@ 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
|
use. The driver maintains that pulse width until you send a new command
|
||||||
requesting some other width.
|
requesting some other width.
|
||||||
|
|
||||||
Currently is it configured to drive 8 servos. Servos typically need an active
|
By default is it configured to drive 8 servos, although you can configure it to
|
||||||
high pulse of somewhere between 0.5ms and 2.5ms, where the pulse width controls
|
drive up to 21. Servos typically need an active high pulse of somewhere
|
||||||
the position of the servo. The pulse should be repeated approximately every
|
between 0.5ms and 2.5ms, where the pulse width controls the position of the
|
||||||
20ms, although pulse frequency is not critical. The pulse width is critical,
|
servo. The pulse should be repeated approximately every 20ms, although pulse
|
||||||
as that translates directly to the servo position.
|
frequency is not critical. The pulse width is critical, as that translates
|
||||||
|
directly to the servo position.
|
||||||
|
|
||||||
|
In addition to driving servos, ServoBlaster can be configured to generate pulse
|
||||||
|
widths between 0 and 100% of the 20ms cycle time, making it suitable for
|
||||||
|
controling the brightness of up to 21 LEDs, for example.
|
||||||
|
|
||||||
The driver creates a device file, /dev/servoblaster, in to which you can send
|
The driver creates a device file, /dev/servoblaster, in to which you can send
|
||||||
commands. The command format is "<servo-number>=<sero-position>", where servo
|
commands. The command format is "<servo-number>=<sero-position>", where servo
|
||||||
number is a number from 0 to 7 inclusive, and servo position is the pulse width
|
number is by default a number from 0 to 7 inclusive, and servo position is the
|
||||||
you want in units of 10us. So, if you want to set servo 3 to a pulse width of
|
pulse width you want in units of 10us. So, if you want to set servo 3 to a
|
||||||
1.2ms you could do this at the shell prompt:
|
pulse width of 1.2ms you could do this at the shell prompt:
|
||||||
|
|
||||||
echo 3=120 > /dev/servoblaster
|
echo 3=120 > /dev/servoblaster
|
||||||
|
|
||||||
@ -25,8 +30,8 @@ echo 3=120 > /dev/servoblaster
|
|||||||
If you set a servo width to 0 it turns off the servo output, without changing
|
If you set a servo width to 0 it turns off the servo output, without changing
|
||||||
the current servo position.
|
the current servo position.
|
||||||
|
|
||||||
The code supports 8 servos, the control signals of which should be connected
|
The code defaults to driving 8 servos, the control signals of which should be
|
||||||
to P1 header pins as follows:
|
connected to P1 header pins as follows:
|
||||||
|
|
||||||
Servo number GPIO number Pin in P1 header
|
Servo number GPIO number Pin in P1 header
|
||||||
0 4 P1-7
|
0 4 P1-7
|
||||||
@ -40,20 +45,22 @@ to P1 header pins as follows:
|
|||||||
|
|
||||||
P1-13 is connected to either GPIO-21 or GPIO-27, depending on board revision.
|
P1-13 is connected to either GPIO-21 or GPIO-27, depending on board revision.
|
||||||
|
|
||||||
|
Command line options described later in this document allow you to configure
|
||||||
|
which header pins map to which servo numbers.
|
||||||
|
|
||||||
When the driver is first loaded the GPIO pins are configure to be outputs, and
|
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
|
their pulse widths are set to 0. This is so that servos don't jump to some
|
||||||
arbitrary position when you load the driver. Once you know where you want your
|
arbitrary position when you load the driver. Once you know where you want your
|
||||||
servos positioned, write a value to /dev/servoblaster to enable the respective
|
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
|
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
|
cleanly and revert the GPIO pins to their original configuration, rather than
|
||||||
jump.
|
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
|
The driver takes note of how many servos you have configured and distributes
|
||||||
a cycle time of 20ms). A servo output is set high at the start of its 2.5ms
|
the start time for the servo pulses evenly across the 20ms cycle time. This
|
||||||
timeslot, and set low after the appropriate delay. There is then a further
|
way, provided you are not driving more than 8 servos, the driver ensures that
|
||||||
delay to take us to the end of that timeslot before the next servo output is
|
only one servo pulse will be active at a time, which should help minimise total
|
||||||
set high. This way there is only ever one servo output active at a time, which
|
drive current needed.
|
||||||
helps keep the code simple.
|
|
||||||
|
|
||||||
In the following description it refers to using the PWM peripheral. For the
|
In the following description it refers to using the PWM peripheral. For the
|
||||||
user space implementation it can instead use the PCM peripheral, see below
|
user space implementation it can instead use the PCM peripheral, see below
|
||||||
@ -63,19 +70,14 @@ uses the PWM peripheral, so ServoBlaster can interfere with sound output.
|
|||||||
The driver works by setting up a linked list of DMA control blocks with the
|
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
|
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
|
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
|
when a pulse width needs to be changed. For a given 10us period there are two DMA
|
||||||
control blocks; the first transfers a single word to the GPIO 'set output'
|
control blocks; the first transfers a single word to the GPIO 'clear output'
|
||||||
register, the second transfers some number of words to the PWM FIFO to generate
|
register, while the second transfers some number of words to the PWM FIFO to
|
||||||
the required pulse width time, the third transfers a single word to the GPIO
|
generate the required pulse width time. In addition, interspersed with these
|
||||||
'clear output' register, and the fourth transfers a number of words to the PWM
|
control blocks is one for each configured servo which is used to set an output.
|
||||||
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
|
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
|
transfers, so as to generate accurate delays.
|
||||||
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
|
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
|
Pi. The pulse widths and frequencies seem very stable, even under heavy SD
|
||||||
@ -93,15 +95,15 @@ 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
|
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
|
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
|
anything non-trivial with four servos connected pulls the 5 volts down far
|
||||||
enough to crash the Pi!
|
enough to crash the Pi.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
There are two implementions of ServoBlaster; a kernel module based one, and a
|
There are two implementions of ServoBlaster; a kernel module based one, and a
|
||||||
user space daemon. The kernel module based one is the original, but is more of
|
user space daemon. The kernel module based one is the original, but is more of
|
||||||
a pain to build because you need a matching kernel build. The user space
|
a pain to build because you need a matching kernel build. The user space
|
||||||
daemon implementation is much more convenient to use but does not have all the
|
daemon implementation is much more convenient to use and now has rather more
|
||||||
features of the kernel based one. I would recommend you try the user space
|
features than the kernel based one. I would recommend you try the user space
|
||||||
implementation first, as it is likely to be easier to get going.
|
implementation first, as it is likely to be easier to get going.
|
||||||
|
|
||||||
The kernel module implementation is in the subdirectory 'kernel', while the
|
The kernel module implementation is in the subdirectory 'kernel', while the
|
||||||
@ -117,43 +119,118 @@ The user space daemon
|
|||||||
To use this daemon grab the servod.c source and Makefile and:
|
To use this daemon grab the servod.c source and Makefile and:
|
||||||
|
|
||||||
$ make servod
|
$ make servod
|
||||||
|
$ ./servod --help
|
||||||
|
|
||||||
|
Usage: ./servod <options>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--pcm tells servod to use PCM rather than PWM hardware
|
||||||
|
to implement delays
|
||||||
|
--idle-timeout=N tells servod to stop sending servo pulses for a
|
||||||
|
given output N milliseconds after the last update
|
||||||
|
--min=N specifies the minimum allowed pulse width, default
|
||||||
|
50 or 500us
|
||||||
|
--max=N specifies the maximum allowed pulse width, default
|
||||||
|
250 or 2500us
|
||||||
|
--invert Inverts outputs
|
||||||
|
--p1pins=<list> tells servod which pins on the P1 header to use
|
||||||
|
--p5pins=<list> tells servod which pins on the P5 header to use
|
||||||
|
|
||||||
|
where <list> defaults to "7,11,12,13,15,16,18,22" for p1pins and
|
||||||
|
"" for p5pins. p5pins is only valid on rev 2 boards.
|
||||||
|
|
||||||
$ sudo ./servod
|
$ sudo ./servod
|
||||||
Using hardware: PWM
|
|
||||||
Number of servos: 8
|
Board revision: 1
|
||||||
Servo cycle time: 20000us
|
Using hardware: PWM
|
||||||
Pulse width units: 10us
|
Idle timeout: Disabled
|
||||||
Maximum width value: 249 (2490us)
|
Number of servos: 8
|
||||||
|
Servo cycle time: 20000us
|
||||||
|
Pulse width units: 10us
|
||||||
|
Minimum width value: 50 (500us)
|
||||||
|
Maximum width value: 250 (2500us)
|
||||||
|
Output levels: Normal
|
||||||
|
|
||||||
|
Using P1 pins: 7,11,12,13,15,16,18,22
|
||||||
|
|
||||||
|
Servo mapping:
|
||||||
|
0 on P1-7 GPIO-4
|
||||||
|
1 on P1-11 GPIO-17
|
||||||
|
2 on P1-12 GPIO-18
|
||||||
|
3 on P1-13 GPIO-21
|
||||||
|
4 on P1-15 GPIO-22
|
||||||
|
5 on P1-16 GPIO-23
|
||||||
|
6 on P1-18 GPIO-24
|
||||||
|
7 on P1-22 GPIO-25
|
||||||
|
|
||||||
$
|
$
|
||||||
|
|
||||||
The prompt will return immediately, and servod is left running in the
|
The prompt will return immediately, and servod is left running in the
|
||||||
background. You can check it is running via the "ps ax" command.
|
background. You can check it is running via the "ps ax" command. If you want
|
||||||
If you want to stop servod, the easiest way is to run:
|
to stop servod, the easiest way is to run:
|
||||||
|
|
||||||
$ sudo killall servod
|
$ sudo killall servod
|
||||||
|
|
||||||
Note that use of PWM will interfere with 3.5mm jack audio output. Instead
|
Note that use of PWM will interfere with 3.5mm jack audio output. Instead
|
||||||
of using the PWM hardware, you can use the PCM hardware, which is less likely
|
of using the PWM hardware, you can use the PCM hardware, which is less likely
|
||||||
to cause a conflict. Please be aware that the PCM mode is very lightly tested
|
to cause a conflict. Please be aware that the PCM mode is very lightly tested
|
||||||
at present. To use PCM mode, invoke servod as follows:
|
at present.
|
||||||
|
|
||||||
$ sudo ./servod --pcm
|
|
||||||
Using hardware: PCM
|
|
||||||
...
|
|
||||||
|
|
||||||
Some people have requested that a servo output turns off automatically if no
|
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
|
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
|
servos overheating when driven for long periods of time. To support this
|
||||||
request, servod implements an idle timeout which can be specified at
|
request, servod implements an idle timeout which can be specified at
|
||||||
module load time. The value is specified in milliseconds, so if you want
|
module load time. The value is specified in milliseconds.
|
||||||
to drive your servos for 2 seconds following each new width request you would
|
|
||||||
do this:
|
|
||||||
|
|
||||||
$ sudo ./servod --idle-timeout=2000
|
|
||||||
|
|
||||||
Typical small servos take a few 100 milliseconds to rotate from one extreme
|
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
|
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.
|
pulse is turned off before your servo has reached the required position.
|
||||||
idle_timeout defaults to 0, which disables the feature.
|
idle-timeout defaults to 0, which disables the feature.
|
||||||
|
|
||||||
|
By default ServoBlaster attempts to protect your servos by setting minimum and
|
||||||
|
maximum values on the pulse width of 50 and 250 (500us and 2.5ms). If you want
|
||||||
|
to generate pulse widths up to 100% for some other purpose, you need to specify
|
||||||
|
the minimum and maximum values you want (probably as 0 and 2000, for 0ms and
|
||||||
|
20ms).
|
||||||
|
|
||||||
|
If you are connecting some external drive circuitry you may want active low
|
||||||
|
rather than active high outputs. In that case you can specify an option to
|
||||||
|
invert the outputs.
|
||||||
|
|
||||||
|
The final options relate to which header pins you want to use to drive your
|
||||||
|
servos. On a Rev 1 board you can use up to 17 pins on the P1 header, and on a
|
||||||
|
Rev 2 board there are an additional 4 pins available on the P5 header. The
|
||||||
|
default option is the equivalent of specifying
|
||||||
|
|
||||||
|
--p1pins=7,11,12,13,15,16,18,22
|
||||||
|
|
||||||
|
As anotehr exmple, if for some reason you want only two servos but you want
|
||||||
|
them to be referenced as servos 4 and 5 (perhaps you have existing software
|
||||||
|
that uses those servo numbers), you can use '0' as a placeholder for unused
|
||||||
|
servo IDs, as follows:
|
||||||
|
|
||||||
|
$ sudo ./servod --p1pins=0,0,0,0,15,16
|
||||||
|
...
|
||||||
|
Using P1 pins: 0,0,0,0,15,16
|
||||||
|
|
||||||
|
Servo mapping:
|
||||||
|
4 on P1-15 GPIO-22
|
||||||
|
5 on P1-16 GPIO-23
|
||||||
|
|
||||||
|
If you specify both --p1pins and --p5pins, then the order in which you specify
|
||||||
|
them is relevant because servo numbers are allocated in the order the
|
||||||
|
parameters are specified on the command line.
|
||||||
|
|
||||||
|
For the full set of P1 and P5 header pins you can use, please refer to the
|
||||||
|
GPIO section of the following web page:
|
||||||
|
|
||||||
|
http://elinux.org/Rpi_Low-level_peripherals
|
||||||
|
|
||||||
|
It should also be clear from the servod.c source which header pins ServoBlaster
|
||||||
|
will allow you to select. Clearly if you tell ServoBlaster to use pins that
|
||||||
|
are normally used for some other purpose, then that other functionality will
|
||||||
|
not be available while servod is running.
|
||||||
|
|
||||||
|
|
||||||
If you want servod to start automatically when the system boots, then you
|
If you want servod to start automatically when the system boots, then you
|
||||||
can install it along with a startup script as follows:
|
can install it along with a startup script as follows:
|
||||||
@ -161,7 +238,8 @@ can install it along with a startup script as follows:
|
|||||||
$ sudo make install
|
$ sudo make install
|
||||||
|
|
||||||
You may wish to edit /etc/init.d/servoblaster to change the parameters that are
|
You may wish to edit /etc/init.d/servoblaster to change the parameters that are
|
||||||
specified in that script (e.g. the idle-timeout, which is set to 2 seconds).
|
specified in that script (e.g. the idle-timeout, which is set to 2 seconds in
|
||||||
|
the shipped version of that script).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -171,6 +249,10 @@ The kernel space implementation
|
|||||||
Please note that the user space implementation is the preferred one to use and
|
Please note that the user space implementation is the preferred one to use and
|
||||||
the kernel implementation (servoblaster.ko) has been depreciated.
|
the kernel implementation (servoblaster.ko) has been depreciated.
|
||||||
|
|
||||||
|
The kernel implementation is missing most of the command line options available
|
||||||
|
in the user space implementation, and always uses the default set of 8 pins
|
||||||
|
described above.
|
||||||
|
|
||||||
Upon reading /dev/servoblaster, the device file provides feedback as to what
|
Upon reading /dev/servoblaster, the device file provides feedback as to what
|
||||||
position each servo is currently set. For example, after starting the driver
|
position each servo is currently set. For example, after starting the driver
|
||||||
and sending the command "3=120", you would see:
|
and sending the command "3=120", you would see:
|
||||||
|
@ -36,36 +36,110 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
|
||||||
/* Define which GPIOs/P1-pins to use slightly differently for Rev 1 and
|
/* Define which P1 header pins to use by default. These are the eight standard
|
||||||
* Rev 2 boards, as P1-13 is different. On Rev 1 GPIO 27 was used for
|
* GPIO pins (those coloured green in the diagram on this page:
|
||||||
* camera control and GPIO-21 was routed to P1-13, but on Rev 2 it is the
|
* http://elinux.org/Rpi_Low-level_peripherals
|
||||||
* other way round.
|
*
|
||||||
|
* Which P1 header pins are actually used can be overridden via command line
|
||||||
|
* parameter '--p1pins=...'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static uint8_t servo2gpio_rev1[] = {
|
static char *default_p1_pins = "7,11,12,13,15,16,18,22";
|
||||||
4, // P1-7
|
static char *default_p5_pins = "";
|
||||||
17, // P1-11
|
|
||||||
18, // P1-12
|
#define DMY 255 // Used to represent an invalid P1 pin, or unmapped servo
|
||||||
21, // P1-13
|
|
||||||
22, // P1-15
|
#define NUM_P1PINS 26
|
||||||
23, // P1-16
|
#define NUM_P5PINS 8
|
||||||
24, // P1-18
|
|
||||||
25, // P1-22
|
#define MAX_SERVOS 21 // Count of GPIO pins on P1 and P5
|
||||||
|
|
||||||
|
static uint8_t rev1_p1pin2gpio_map[] = {
|
||||||
|
DMY, // P1-1 3v3
|
||||||
|
DMY, // P1-2 5v
|
||||||
|
0, // P1-3 GPIO 0 (SDA)
|
||||||
|
DMY, // P1-4 5v
|
||||||
|
1, // P1-5 GPIO 1 (SCL)
|
||||||
|
DMY, // P1-6 Ground
|
||||||
|
4, // P1-7 GPIO 4 (GPCLK0)
|
||||||
|
14, // P1-8 GPIO 14 (TXD)
|
||||||
|
DMY, // P1-9 Ground
|
||||||
|
15, // P1-10 GPIO 15 (RXD)
|
||||||
|
17, // P1-11 GPIO 17
|
||||||
|
18, // P1-12 GPIO 18 (PCM_CLK)
|
||||||
|
21, // P1-13 GPIO 21
|
||||||
|
DMY, // P1-14 Ground
|
||||||
|
22, // P1-15 GPIO 22
|
||||||
|
23, // P1-16 GPIO 23
|
||||||
|
DMY, // P1-17 3v3
|
||||||
|
24, // P1-18 GPIO 24
|
||||||
|
10, // P1-19 GPIO 10 (MOSI)
|
||||||
|
DMY, // P1-20 Ground
|
||||||
|
9, // P1-21 GPIO 9 (MISO)
|
||||||
|
25, // P1-22 GPIO 25
|
||||||
|
11, // P1-23 GPIO 11 (SCLK)
|
||||||
|
8, // P1-24 GPIO 8 (CE0)
|
||||||
|
DMY, // P1-25 Ground
|
||||||
|
7, // P1-26 GPIO 7 (CE1)
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint8_t servo2gpio_rev2[] = {
|
static uint8_t rev1_p5pin2gpio_map[] = {
|
||||||
4, // P1-7
|
DMY, // (P5-1 on rev 2 boards)
|
||||||
17, // P1-11
|
DMY, // (P5-2 on rev 2 boards)
|
||||||
18, // P1-12
|
DMY, // (P5-3 on rev 2 boards)
|
||||||
27, // P1-13
|
DMY, // (P5-4 on rev 2 boards)
|
||||||
22, // P1-15
|
DMY, // (P5-5 on rev 2 boards)
|
||||||
23, // P1-16
|
DMY, // (P5-6 on rev 2 boards)
|
||||||
24, // P1-18
|
DMY, // (P5-7 on rev 2 boards)
|
||||||
25, // P1-22
|
DMY, // (P5-8 on rev 2 boards)
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint8_t *servo2gpio;
|
static uint8_t rev2_p1pin2gpio_map[] = {
|
||||||
|
DMY, // P1-1 3v3
|
||||||
|
DMY, // P1-2 5v
|
||||||
|
2, // P1-3 GPIO 2 (SDA)
|
||||||
|
DMY, // P1-4 5v
|
||||||
|
3, // P1-5 GPIO 3 (SCL)
|
||||||
|
DMY, // P1-6 Ground
|
||||||
|
4, // P1-7 GPIO 4 (GPCLK0)
|
||||||
|
14, // P1-8 GPIO 14 (TXD)
|
||||||
|
DMY, // P1-9 Ground
|
||||||
|
15, // P1-10 GPIO 15 (RXD)
|
||||||
|
17, // P1-11 GPIO 17
|
||||||
|
18, // P1-12 GPIO 18 (PCM_CLK)
|
||||||
|
27, // P1-13 GPIO 27
|
||||||
|
DMY, // P1-14 Ground
|
||||||
|
22, // P1-15 GPIO 22
|
||||||
|
23, // P1-16 GPIO 23
|
||||||
|
DMY, // P1-17 3v3
|
||||||
|
24, // P1-18 GPIO 24
|
||||||
|
10, // P1-19 GPIO 10 (MOSI)
|
||||||
|
DMY, // P1-20 Ground
|
||||||
|
9, // P1-21 GPIO 9 (MISO)
|
||||||
|
25, // P1-22 GPIO 25
|
||||||
|
11, // P1-23 GPIO 11 (SCLK)
|
||||||
|
8, // P1-24 GPIO 8 (CE0)
|
||||||
|
DMY, // P1-25 Ground
|
||||||
|
7, // P1-26 GPIO 7 (CE1)
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t rev2_p5pin2gpio_map[] = {
|
||||||
|
DMY, // P5-1 5v0
|
||||||
|
DMY, // P5-2 3v3
|
||||||
|
28, // P5-3 GPIO 28 (I2C0_SDA)
|
||||||
|
29, // P5-4 GPIO 29 (I2C0_SCL)
|
||||||
|
30, // P5-5 GPIO 30
|
||||||
|
31, // P5-6 GPIO 31
|
||||||
|
DMY, // P5-7 Ground
|
||||||
|
DMY, // P5-8 Ground
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t servo2gpio[MAX_SERVOS];
|
||||||
|
static int servostart[MAX_SERVOS];
|
||||||
|
static int servowidth[MAX_SERVOS];
|
||||||
static int num_servos;
|
static int num_servos;
|
||||||
|
static uint32_t gpiomode[MAX_SERVOS];
|
||||||
|
static int restore_gpio_modes;
|
||||||
|
|
||||||
#define DEVFILE "/dev/servoblaster"
|
#define DEVFILE "/dev/servoblaster"
|
||||||
|
|
||||||
@ -82,14 +156,13 @@ static int num_servos;
|
|||||||
|
|
||||||
#define CYCLE_TIME_US 20000
|
#define CYCLE_TIME_US 20000
|
||||||
#define SAMPLE_US 10
|
#define SAMPLE_US 10
|
||||||
#define SERVO_TIME_US (CYCLE_TIME_US/num_servos)
|
#define DEFAULT_MIN (500/SAMPLE_US)
|
||||||
#define SERVO_SAMPLES (SERVO_TIME_US/SAMPLE_US)
|
#define DEFAULT_MAX (2500/SAMPLE_US)
|
||||||
#define SERVO_MIN 0
|
|
||||||
#define SERVO_MAX (SERVO_SAMPLES - 1)
|
|
||||||
#define NUM_SAMPLES (CYCLE_TIME_US/SAMPLE_US)
|
#define NUM_SAMPLES (CYCLE_TIME_US/SAMPLE_US)
|
||||||
#define NUM_CBS (NUM_SAMPLES*2)
|
#define NUM_CBS (NUM_SAMPLES*2+MAX_SERVOS)
|
||||||
|
|
||||||
#define NUM_PAGES ((NUM_CBS * 32 + NUM_SAMPLES * 4 + \
|
#define NUM_PAGES ((NUM_CBS * 32 + NUM_SAMPLES * 4 + \
|
||||||
|
MAX_SERVOS * 4 + \
|
||||||
PAGE_SIZE - 1) >> PAGE_SHIFT)
|
PAGE_SIZE - 1) >> PAGE_SHIFT)
|
||||||
|
|
||||||
#define DMA_BASE 0x20007000
|
#define DMA_BASE 0x20007000
|
||||||
@ -157,24 +230,27 @@ static int num_servos;
|
|||||||
#define DELAY_VIA_PWM 0
|
#define DELAY_VIA_PWM 0
|
||||||
#define DELAY_VIA_PCM 1
|
#define DELAY_VIA_PCM 1
|
||||||
|
|
||||||
|
#define ROUNDUP(val, blksz) (((val)+((blksz)-1)) & ~(blksz-1))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t info, src, dst, length,
|
uint32_t info, src, dst, length,
|
||||||
stride, next, pad[2];
|
stride, next, pad[2];
|
||||||
} dma_cb_t;
|
} dma_cb_t;
|
||||||
|
|
||||||
struct ctl {
|
struct ctl {
|
||||||
uint32_t sample[NUM_SAMPLES];
|
uint32_t turnoff[NUM_SAMPLES];
|
||||||
|
uint32_t turnon[ROUNDUP(MAX_SERVOS,8)];
|
||||||
dma_cb_t cb[NUM_CBS];
|
dma_cb_t cb[NUM_CBS];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t *virtaddr;
|
|
||||||
uint32_t physaddr;
|
uint32_t physaddr;
|
||||||
} page_map_t;
|
} page_map_t;
|
||||||
|
|
||||||
page_map_t *page_map;
|
page_map_t *page_map;
|
||||||
|
|
||||||
static uint8_t *virtbase;
|
static uint8_t *virtbase;
|
||||||
|
static uint8_t *virtcached;
|
||||||
|
|
||||||
static volatile uint32_t *pwm_reg;
|
static volatile uint32_t *pwm_reg;
|
||||||
static volatile uint32_t *pcm_reg;
|
static volatile uint32_t *pcm_reg;
|
||||||
@ -187,8 +263,12 @@ static int delay_hw = DELAY_VIA_PWM;
|
|||||||
static struct timeval *servo_kill_time;
|
static struct timeval *servo_kill_time;
|
||||||
|
|
||||||
static int idle_timeout = 0;
|
static int idle_timeout = 0;
|
||||||
|
static int invert = 0;
|
||||||
|
static int servo_min = DEFAULT_MIN;
|
||||||
|
static int servo_max = DEFAULT_MAX;
|
||||||
|
|
||||||
static void set_servo(int servo, int width);
|
static void set_servo(int servo, int width);
|
||||||
|
static void gpio_set_mode(uint32_t gpio, uint32_t mode);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
udelay(int us)
|
udelay(int us)
|
||||||
@ -204,12 +284,20 @@ terminate(int dummy)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (dma_reg && virtbase) {
|
if (dma_reg && virtbase) {
|
||||||
for (i = 0; i < num_servos; i++)
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
set_servo(i, 0);
|
if (servo2gpio[i] != DMY)
|
||||||
|
set_servo(i, 0);
|
||||||
|
}
|
||||||
udelay(CYCLE_TIME_US);
|
udelay(CYCLE_TIME_US);
|
||||||
dma_reg[DMA_CS] = DMA_RESET;
|
dma_reg[DMA_CS] = DMA_RESET;
|
||||||
udelay(10);
|
udelay(10);
|
||||||
}
|
}
|
||||||
|
if (restore_gpio_modes) {
|
||||||
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
|
if (servo2gpio[i] != DMY)
|
||||||
|
gpio_set_mode(servo2gpio[i], gpiomode[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
unlink(DEVFILE);
|
unlink(DEVFILE);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -228,7 +316,7 @@ fatal(char *fmt, ...)
|
|||||||
static void
|
static void
|
||||||
init_idle_timers(void)
|
init_idle_timers(void)
|
||||||
{
|
{
|
||||||
servo_kill_time = calloc(num_servos, sizeof(struct timeval));
|
servo_kill_time = calloc(MAX_SERVOS, sizeof(struct timeval));
|
||||||
if (!servo_kill_time)
|
if (!servo_kill_time)
|
||||||
fatal("servod: calloc() failed\n");
|
fatal("servod: calloc() failed\n");
|
||||||
}
|
}
|
||||||
@ -257,8 +345,8 @@ get_next_idle_timeout(struct timeval *tv)
|
|||||||
long this_diff, min_diff;
|
long this_diff, min_diff;
|
||||||
|
|
||||||
gettimeofday(&now, NULL);
|
gettimeofday(&now, NULL);
|
||||||
for (i = 0; i < num_servos; i++) {
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
if (servo_kill_time[i].tv_sec == 0)
|
if (servo2gpio[i] == DMY || servo_kill_time[i].tv_sec == 0)
|
||||||
continue;
|
continue;
|
||||||
else if (servo_kill_time[i].tv_sec < now.tv_sec ||
|
else if (servo_kill_time[i].tv_sec < now.tv_sec ||
|
||||||
(servo_kill_time[i].tv_sec == now.tv_sec &&
|
(servo_kill_time[i].tv_sec == now.tv_sec &&
|
||||||
@ -278,23 +366,30 @@ get_next_idle_timeout(struct timeval *tv)
|
|||||||
*tv = min;
|
*tv = min;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static uint32_t gpio_get_mode(uint32_t gpio)
|
||||||
gpio_set_mode(uint32_t pin, uint32_t mode)
|
|
||||||
{
|
{
|
||||||
uint32_t fsel = gpio_reg[GPIO_FSEL0 + pin/10];
|
uint32_t fsel = gpio_reg[GPIO_FSEL0 + gpio/10];
|
||||||
|
|
||||||
fsel &= ~(7 << ((pin % 10) * 3));
|
return (fsel >> ((gpio % 10) * 3)) & 7;
|
||||||
fsel |= mode << ((pin % 10) * 3);
|
|
||||||
gpio_reg[GPIO_FSEL0 + pin/10] = fsel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gpio_set(int pin, int level)
|
gpio_set_mode(uint32_t gpio, uint32_t mode)
|
||||||
|
{
|
||||||
|
uint32_t fsel = gpio_reg[GPIO_FSEL0 + gpio/10];
|
||||||
|
|
||||||
|
fsel &= ~(7 << ((gpio % 10) * 3));
|
||||||
|
fsel |= mode << ((gpio % 10) * 3);
|
||||||
|
gpio_reg[GPIO_FSEL0 + gpio/10] = fsel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gpio_set(int gpio, int level)
|
||||||
{
|
{
|
||||||
if (level)
|
if (level)
|
||||||
gpio_reg[GPIO_SET0] = 1 << pin;
|
gpio_reg[GPIO_SET0] = 1 << gpio;
|
||||||
else
|
else
|
||||||
gpio_reg[GPIO_CLR0] = 1 << pin;
|
gpio_reg[GPIO_CLR0] = 1 << gpio;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
static uint32_t
|
||||||
@ -321,28 +416,51 @@ map_peripheral(uint32_t base, uint32_t len)
|
|||||||
return vaddr;
|
return vaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 'servo' has already been validated as to refer to a mapped servo */
|
||||||
static void
|
static void
|
||||||
set_servo(int servo, int width)
|
set_servo(int servo, int width)
|
||||||
{
|
{
|
||||||
struct ctl *ctl = (struct ctl *)virtbase;
|
struct ctl *ctl = (struct ctl *)virtbase;
|
||||||
dma_cb_t *cbp = ctl->cb + servo * SERVO_SAMPLES * 2;
|
volatile uint32_t *dp;
|
||||||
uint32_t phys_gpclr0 = 0x7e200000 + 0x28;
|
|
||||||
uint32_t phys_gpset0 = 0x7e200000 + 0x1c;
|
|
||||||
uint32_t *dp = ctl->sample + servo * SERVO_SAMPLES;
|
|
||||||
int i;
|
int i;
|
||||||
uint32_t mask = 1 << servo2gpio[servo];
|
uint32_t mask = 1 << servo2gpio[servo];
|
||||||
|
|
||||||
dp[width] = mask;
|
if (width == servowidth[servo])
|
||||||
|
return;
|
||||||
|
|
||||||
if (width == 0) {
|
if (width == 0)
|
||||||
cbp->dst = phys_gpclr0;
|
ctl->turnon[servo] = 0;
|
||||||
|
|
||||||
|
if (width > servowidth[servo]) {
|
||||||
|
dp = ctl->turnoff + servostart[servo] + width;
|
||||||
|
if (dp >= ctl->turnoff + NUM_SAMPLES)
|
||||||
|
dp -= NUM_SAMPLES;
|
||||||
|
|
||||||
|
for (i = width; i > servowidth[servo]; i--) {
|
||||||
|
dp--;
|
||||||
|
if (dp < ctl->turnoff)
|
||||||
|
dp = ctl->turnoff + NUM_SAMPLES - 1;
|
||||||
|
//printf("%5d, clearing at %p\n", dp - ctl->turnoff, dp);
|
||||||
|
*dp &= ~mask;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i = width - 1; i > 0; i--)
|
dp = ctl->turnoff + servostart[servo] + width;
|
||||||
dp[i] = 0;
|
if (dp >= ctl->turnoff + NUM_SAMPLES)
|
||||||
dp[0] = mask;
|
dp -= NUM_SAMPLES;
|
||||||
cbp->dst = phys_gpset0;
|
|
||||||
update_idle_time(servo);
|
for (i = width; i < servowidth[servo]; i++) {
|
||||||
|
//printf("%5d, setting at %p\n", dp - ctl->turnoff, dp);
|
||||||
|
*dp++ |= mask;
|
||||||
|
if (dp >= ctl->turnoff + NUM_SAMPLES)
|
||||||
|
dp = ctl->turnoff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
servowidth[servo] = width;
|
||||||
|
|
||||||
|
if (width)
|
||||||
|
ctl->turnon[servo] = mask;
|
||||||
|
|
||||||
|
update_idle_time(servo);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -362,23 +480,28 @@ make_pagemap(void)
|
|||||||
fd = open(pagemap_fn, O_RDONLY);
|
fd = open(pagemap_fn, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
fatal("servod: Failed to open %s: %m\n", pagemap_fn);
|
fatal("servod: Failed to open %s: %m\n", pagemap_fn);
|
||||||
if (lseek(fd, (uint32_t)virtbase >> 9, SEEK_SET) !=
|
if (lseek(fd, (uint32_t)virtcached >> 9, SEEK_SET) !=
|
||||||
(uint32_t)virtbase >> 9) {
|
(uint32_t)virtcached >> 9) {
|
||||||
fatal("servod: Failed to seek on %s: %m\n", pagemap_fn);
|
fatal("servod: Failed to seek on %s: %m\n", pagemap_fn);
|
||||||
}
|
}
|
||||||
for (i = 0; i < NUM_PAGES; i++) {
|
for (i = 0; i < NUM_PAGES; i++) {
|
||||||
uint64_t pfn;
|
uint64_t pfn;
|
||||||
page_map[i].virtaddr = virtbase + i * PAGE_SIZE;
|
|
||||||
// Following line forces page to be allocated
|
|
||||||
page_map[i].virtaddr[0] = 0;
|
|
||||||
if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn))
|
if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn))
|
||||||
fatal("servod: Failed to read %s: %m\n", pagemap_fn);
|
fatal("servod: Failed to read %s: %m\n", pagemap_fn);
|
||||||
if (((pfn >> 55) & 0x1bf) != 0x10c)
|
if (((pfn >> 55) & 0x1bf) != 0x10c)
|
||||||
fatal("servod: Page %d not present (pfn 0x%016llx)\n", i, pfn);
|
fatal("servod: Page %d not present (pfn 0x%016llx)\n", i, pfn);
|
||||||
page_map[i].physaddr = (uint32_t)pfn << PAGE_SHIFT | 0x40000000;
|
page_map[i].physaddr = (uint32_t)pfn << PAGE_SHIFT | 0x40000000;
|
||||||
|
if (mmap(virtbase + i * PAGE_SIZE, PAGE_SIZE, PROT_READ|PROT_WRITE,
|
||||||
|
MAP_SHARED|MAP_FIXED|MAP_LOCKED|MAP_NORESERVE,
|
||||||
|
memfd, (uint32_t)pfn << PAGE_SHIFT | 0x40000000) !=
|
||||||
|
virtbase + i * PAGE_SIZE) {
|
||||||
|
fatal("Failed to create uncached map of page %d at %p\n",
|
||||||
|
i, virtbase + i * PAGE_SIZE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
close(memfd);
|
close(memfd);
|
||||||
|
memset(virtbase, 0, NUM_PAGES * PAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -402,34 +525,74 @@ init_ctrl_data(void)
|
|||||||
{
|
{
|
||||||
struct ctl *ctl = (struct ctl *)virtbase;
|
struct ctl *ctl = (struct ctl *)virtbase;
|
||||||
dma_cb_t *cbp = ctl->cb;
|
dma_cb_t *cbp = ctl->cb;
|
||||||
uint32_t phys_fifo_addr;
|
uint32_t phys_fifo_addr, cbinfo;
|
||||||
uint32_t phys_gpclr0 = 0x7e200000 + 0x28;
|
uint32_t phys_gpclr0;
|
||||||
int servo, i;
|
uint32_t phys_gpset0;
|
||||||
|
int servo, i, numservos = 0, curstart = 0;
|
||||||
|
uint32_t maskall = 0;
|
||||||
|
|
||||||
if (delay_hw == DELAY_VIA_PWM)
|
if (invert) {
|
||||||
phys_fifo_addr = (PWM_BASE | 0x7e000000) + 0x18;
|
phys_gpclr0 = 0x7e200000 + 0x1c;
|
||||||
else
|
phys_gpset0 = 0x7e200000 + 0x28;
|
||||||
phys_fifo_addr = (PCM_BASE | 0x7e000000) + 0x04;
|
} else {
|
||||||
|
phys_gpclr0 = 0x7e200000 + 0x28;
|
||||||
memset(ctl->sample, 0, sizeof(ctl->sample));
|
phys_gpset0 = 0x7e200000 + 0x1c;
|
||||||
for (servo = 0 ; servo < num_servos; servo++) {
|
|
||||||
for (i = 0; i < SERVO_SAMPLES; i++)
|
|
||||||
ctl->sample[servo * SERVO_SAMPLES + i] = 1 << servo2gpio[servo];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (delay_hw == DELAY_VIA_PWM) {
|
||||||
|
phys_fifo_addr = (PWM_BASE | 0x7e000000) + 0x18;
|
||||||
|
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5);
|
||||||
|
} else {
|
||||||
|
phys_fifo_addr = (PCM_BASE | 0x7e000000) + 0x04;
|
||||||
|
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(ctl->turnon, 0, sizeof(ctl->turnon));
|
||||||
|
|
||||||
|
for (servo = 0 ; servo < MAX_SERVOS; servo++) {
|
||||||
|
servowidth[servo] = 0;
|
||||||
|
if (servo2gpio[servo] != DMY) {
|
||||||
|
numservos++;
|
||||||
|
maskall |= 1 << servo2gpio[servo];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < NUM_SAMPLES; i++)
|
||||||
|
ctl->turnoff[i] = maskall;
|
||||||
|
|
||||||
|
for (servo = 0; servo < MAX_SERVOS; servo++) {
|
||||||
|
if (servo2gpio[servo] != DMY) {
|
||||||
|
servostart[servo] = curstart;
|
||||||
|
curstart += NUM_SAMPLES / num_servos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servo = 0;
|
||||||
|
while (servo < MAX_SERVOS && servo2gpio[servo] == DMY)
|
||||||
|
servo++;
|
||||||
|
|
||||||
for (i = 0; i < NUM_SAMPLES; i++) {
|
for (i = 0; i < NUM_SAMPLES; i++) {
|
||||||
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
|
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
|
||||||
cbp->src = mem_virt_to_phys(ctl->sample + i);
|
cbp->src = mem_virt_to_phys(ctl->turnoff + i);
|
||||||
cbp->dst = phys_gpclr0;
|
cbp->dst = phys_gpclr0;
|
||||||
cbp->length = 4;
|
cbp->length = 4;
|
||||||
cbp->stride = 0;
|
cbp->stride = 0;
|
||||||
cbp->next = mem_virt_to_phys(cbp + 1);
|
cbp->next = mem_virt_to_phys(cbp + 1);
|
||||||
cbp++;
|
cbp++;
|
||||||
|
if (i == servostart[servo]) {
|
||||||
|
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
|
||||||
|
cbp->src = mem_virt_to_phys(ctl->turnon + servo);
|
||||||
|
cbp->dst = phys_gpset0;
|
||||||
|
cbp->length = 4;
|
||||||
|
cbp->stride = 0;
|
||||||
|
cbp->next = mem_virt_to_phys(cbp + 1);
|
||||||
|
cbp++;
|
||||||
|
servo++;
|
||||||
|
while (servo < MAX_SERVOS && servo2gpio[servo] == DMY)
|
||||||
|
servo++;
|
||||||
|
}
|
||||||
// Delay
|
// Delay
|
||||||
if (delay_hw == DELAY_VIA_PWM)
|
cbp->info = cbinfo;
|
||||||
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5);
|
|
||||||
else
|
|
||||||
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2);
|
|
||||||
cbp->src = mem_virt_to_phys(ctl); // Any data will do
|
cbp->src = mem_virt_to_phys(ctl); // Any data will do
|
||||||
cbp->dst = phys_fifo_addr;
|
cbp->dst = phys_fifo_addr;
|
||||||
cbp->length = 4;
|
cbp->length = 4;
|
||||||
@ -499,6 +662,30 @@ init_hardware(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_debug(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
uint32_t mask = 0;
|
||||||
|
uint32_t last = 0xffffffff;
|
||||||
|
struct ctl *ctl = (struct ctl *)virtbase;
|
||||||
|
|
||||||
|
printf("Servo Start Width\n");
|
||||||
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
|
if (servo2gpio[i] != DMY) {
|
||||||
|
printf("%3d: %6d %6d\n", i, servostart[i], servowidth[i]);
|
||||||
|
mask |= 1 << servo2gpio[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\nData:\n");
|
||||||
|
for (i = 0; i < NUM_SAMPLES; i++) {
|
||||||
|
uint32_t curr = ctl->turnoff[i] & mask;
|
||||||
|
if (curr != last)
|
||||||
|
printf("%5d: %08x\n", i, curr);
|
||||||
|
last = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
go_go_go(void)
|
go_go_go(void)
|
||||||
{
|
{
|
||||||
@ -525,11 +712,15 @@ go_go_go(void)
|
|||||||
line[++nchars] = '\0';
|
line[++nchars] = '\0';
|
||||||
nchars = 0;
|
nchars = 0;
|
||||||
n = sscanf(line, "%d=%d%c", &servo, &width, &nl);
|
n = sscanf(line, "%d=%d%c", &servo, &width, &nl);
|
||||||
if (n !=3 || nl != '\n') {
|
if (!strcmp(line, "debug\n")) {
|
||||||
|
do_debug();
|
||||||
|
} else if (n !=3 || nl != '\n') {
|
||||||
fprintf(stderr, "Bad input: %s", line);
|
fprintf(stderr, "Bad input: %s", line);
|
||||||
} else if (servo < 0 || servo >= num_servos) {
|
} else if (servo < 0 || servo >= MAX_SERVOS) {
|
||||||
fprintf(stderr, "Invalid servo number %d\n", servo);
|
fprintf(stderr, "Invalid servo number %d\n", servo);
|
||||||
} else if (width < SERVO_MIN || width > SERVO_MAX) {
|
} else if (servo2gpio[servo] == DMY) {
|
||||||
|
fprintf(stderr, "Servo %d is not mapped to a GPIO pin\n", servo);
|
||||||
|
} else if (width && (width < servo_min || width > servo_max)) {
|
||||||
fprintf(stderr, "Invalid width %d\n", width);
|
fprintf(stderr, "Invalid width %d\n", width);
|
||||||
} else {
|
} else {
|
||||||
set_servo(servo, width);
|
set_servo(servo, width);
|
||||||
@ -588,10 +779,103 @@ board_rev(void)
|
|||||||
return rev;
|
return rev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
parse_pin_lists(int p1first, char *p1pins, char*p5pins)
|
||||||
|
{
|
||||||
|
char *name, *pins;
|
||||||
|
int mapcnt;
|
||||||
|
uint8_t *map;
|
||||||
|
int lst, servo = 0;
|
||||||
|
|
||||||
|
memset(servo2gpio, DMY, sizeof(servo2gpio));
|
||||||
|
for (lst = 0; lst < 2; lst++) {
|
||||||
|
if (lst == 0 && p1first) {
|
||||||
|
name = "P1";
|
||||||
|
pins = p1pins;
|
||||||
|
if (board_rev() == 1) {
|
||||||
|
map = rev1_p1pin2gpio_map;
|
||||||
|
mapcnt = sizeof(rev1_p1pin2gpio_map);
|
||||||
|
} else {
|
||||||
|
map = rev2_p1pin2gpio_map;
|
||||||
|
mapcnt = sizeof(rev2_p1pin2gpio_map);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = "P5";
|
||||||
|
pins = p5pins;
|
||||||
|
if (board_rev() == 1) {
|
||||||
|
map = rev1_p5pin2gpio_map;
|
||||||
|
mapcnt = sizeof(rev1_p5pin2gpio_map);
|
||||||
|
} else {
|
||||||
|
map = rev2_p5pin2gpio_map;
|
||||||
|
mapcnt = sizeof(rev2_p5pin2gpio_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (*pins) {
|
||||||
|
char *end;
|
||||||
|
long pin = strtol(pins, &end, 0);
|
||||||
|
|
||||||
|
if (*end && (end == pins || *end != ','))
|
||||||
|
fatal("Invalid character '%c' in %s pin list\n", *end, name);
|
||||||
|
if (pin < 0 || pin > mapcnt)
|
||||||
|
fatal("Invalid pin number %d in %s pin list\n", pin, name);
|
||||||
|
if (servo == MAX_SERVOS)
|
||||||
|
fatal("Too many servos specified\n");
|
||||||
|
if (pin == 0) {
|
||||||
|
servo++;
|
||||||
|
} else {
|
||||||
|
if (map[pin-1] == DMY)
|
||||||
|
fatal("Pin %d on header %s cannot be used for a servo output\n", pin, name);
|
||||||
|
servo2gpio[servo++] = map[pin-1];
|
||||||
|
num_servos++;
|
||||||
|
}
|
||||||
|
pins = end;
|
||||||
|
if (*pins == ',')
|
||||||
|
pins++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t gpiosearch(uint8_t gpio, uint8_t *map, int len)
|
||||||
|
{
|
||||||
|
while (--len) {
|
||||||
|
if (map[len] == gpio)
|
||||||
|
return len+1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
gpio2pinname(uint8_t gpio)
|
||||||
|
{
|
||||||
|
static char res[16];
|
||||||
|
uint8_t pin;
|
||||||
|
|
||||||
|
if (board_rev() == 1) {
|
||||||
|
if ((pin = gpiosearch(gpio, rev1_p1pin2gpio_map, sizeof(rev1_p1pin2gpio_map))))
|
||||||
|
sprintf(res, "P1-%d", pin);
|
||||||
|
else if ((pin = gpiosearch(gpio, rev1_p5pin2gpio_map, sizeof(rev1_p5pin2gpio_map))))
|
||||||
|
sprintf(res, "P5-%d", pin);
|
||||||
|
else
|
||||||
|
fatal("Cannot map GPIO %d to a header pin\n", gpio);
|
||||||
|
} else {
|
||||||
|
if ((pin = gpiosearch(gpio, rev2_p1pin2gpio_map, sizeof(rev2_p1pin2gpio_map))))
|
||||||
|
sprintf(res, "P1-%d", pin);
|
||||||
|
else if ((pin = gpiosearch(gpio, rev2_p5pin2gpio_map, sizeof(rev2_p5pin2gpio_map))))
|
||||||
|
sprintf(res, "P5-%d", pin);
|
||||||
|
else
|
||||||
|
fatal("Cannot map GPIO %d to a header pin\n", gpio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
char *p1pins = default_p1_pins;
|
||||||
|
char *p5pins = default_p5_pins;
|
||||||
|
int p1first = 1, hadp1 = 0, hadp5 = 0;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
int c;
|
int c;
|
||||||
@ -600,12 +884,16 @@ main(int argc, char **argv)
|
|||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{ "pcm", no_argument, 0, 'p' },
|
{ "pcm", no_argument, 0, 'p' },
|
||||||
{ "idle-timeout", required_argument, 0, 't' },
|
{ "idle-timeout", required_argument, 0, 't' },
|
||||||
{ "idle_timeout", required_argument, 0, 't' },
|
|
||||||
{ "help", no_argument, 0, 'h' },
|
{ "help", no_argument, 0, 'h' },
|
||||||
|
{ "p1pins", required_argument, 0, '1' },
|
||||||
|
{ "p5pins", required_argument, 0, '5' },
|
||||||
|
{ "min", required_argument, 0, 'm' },
|
||||||
|
{ "max", required_argument, 0, 'x' },
|
||||||
|
{ "invert", no_argument, 0, 'i' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
c = getopt_long(argc, argv, "hpt:", long_options, &option_index);
|
c = getopt_long(argc, argv, "mxhnt:15i", long_options, &option_index);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
} else if (c == 'p') {
|
} else if (c == 'p') {
|
||||||
@ -615,36 +903,77 @@ main(int argc, char **argv)
|
|||||||
if (idle_timeout < 10 || idle_timeout > 3600000)
|
if (idle_timeout < 10 || idle_timeout > 3600000)
|
||||||
fatal("Invalid idle_timeout specified\n");
|
fatal("Invalid idle_timeout specified\n");
|
||||||
} else if (c == 'h') {
|
} else if (c == 'h') {
|
||||||
printf("\nUsage: %s [--pcm] [--idle-timeout=N]\n\n"
|
printf("\nUsage: %s <options>\n\n"
|
||||||
"Where:\n"
|
"Options:\n"
|
||||||
" --pcm tells servod to use PCM rather than PWM hardware\n"
|
" --pcm tells servod to use PCM rather than PWM hardware\n"
|
||||||
" to implement delays\n"
|
" to implement delays\n"
|
||||||
" --idle-timeout=N tells servod to stop sending servo pulses for a\n"
|
" --idle-timeout=N tells servod to stop sending servo pulses for a\n"
|
||||||
" given output N milliseconds after the last update\n\n",
|
" given output N milliseconds after the last update\n"
|
||||||
argv[0]);
|
" --min=N specifies the minimum allowed pulse width, default\n"
|
||||||
|
" %d or %dus\n"
|
||||||
|
" --max=N specifies the maximum allowed pulse width, default\n"
|
||||||
|
" %d or %dus\n"
|
||||||
|
" --invert Inverts outputs\n"
|
||||||
|
" --p1pins=<list> tells servod which pins on the P1 header to use\n"
|
||||||
|
" --p5pins=<list> tells servod which pins on the P5 header to use\n"
|
||||||
|
"\nwhere <list> defaults to \"%s\" for p1pins and\n"
|
||||||
|
"\"%s\" for p5pins. p5pins is only valid on rev 2 boards.\n\n",
|
||||||
|
argv[0],
|
||||||
|
DEFAULT_MIN, DEFAULT_MIN * SAMPLE_US,
|
||||||
|
DEFAULT_MAX, DEFAULT_MAX * SAMPLE_US,
|
||||||
|
default_p1_pins, default_p5_pins);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
} else if (c == '1') {
|
||||||
|
p1pins = optarg;
|
||||||
|
hadp1 = 1;
|
||||||
|
if (!hadp5)
|
||||||
|
p1first = 1;
|
||||||
|
} else if (c == '5') {
|
||||||
|
p5pins = optarg;
|
||||||
|
hadp5 = 1;
|
||||||
|
if (!hadp1)
|
||||||
|
p1first = 0;
|
||||||
|
} else if (c == 'm') {
|
||||||
|
servo_min = atoi(optarg);
|
||||||
|
} else if (c == 'x') {
|
||||||
|
servo_max = atoi(optarg);
|
||||||
|
} else if (c == 'i') {
|
||||||
|
invert = 1;
|
||||||
} else {
|
} else {
|
||||||
fatal("Invalid parameter\n");
|
fatal("Invalid parameter\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (servo_min < 0 || servo_max > NUM_SAMPLES || servo_min >= servo_max)
|
||||||
|
fatal("Invalid min and/or max values, min=%d, max=%d\n", servo_min, servo_max);
|
||||||
|
if (board_rev() == 1 && p5pins[0])
|
||||||
|
fatal("Board rev 1 does not have a P5 header\n");
|
||||||
|
|
||||||
/* Select the correct pin mapping based on board rev */
|
parse_pin_lists(p1first, p1pins, p5pins);
|
||||||
if (board_rev() == 1) {
|
|
||||||
servo2gpio = servo2gpio_rev1;
|
printf("\nBoard revision: %5d\n", board_rev());
|
||||||
num_servos = sizeof(servo2gpio_rev1);
|
printf("Using hardware: %s\n", delay_hw == DELAY_VIA_PWM ? "PWM" : "PCM");
|
||||||
} else {
|
if (idle_timeout)
|
||||||
servo2gpio = servo2gpio_rev2;
|
printf("Idle timeout: %5dms\n", idle_timeout);
|
||||||
num_servos = sizeof(servo2gpio_rev2);
|
else
|
||||||
|
printf("Idle timeout: Disabled\n");
|
||||||
|
printf("Number of servos: %5d\n", num_servos);
|
||||||
|
printf("Servo cycle time: %5dus\n", CYCLE_TIME_US);
|
||||||
|
printf("Pulse width units: %5dus\n", SAMPLE_US);
|
||||||
|
printf("Minimum width value: %5d (%dus)\n", servo_min,
|
||||||
|
servo_min * SAMPLE_US);
|
||||||
|
printf("Maximum width value: %5d (%dus)\n", servo_max,
|
||||||
|
servo_max * SAMPLE_US);
|
||||||
|
printf("Output levels: %s\n", invert ? "Inverted" : " Normal");
|
||||||
|
printf("\nUsing P1 pins: %s\n", p1pins);
|
||||||
|
if (board_rev() > 1)
|
||||||
|
printf("Using P5 pins: %s\n", p5pins);
|
||||||
|
printf("\nServo mapping:\n");
|
||||||
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
|
if (servo2gpio[i] == DMY)
|
||||||
|
continue;
|
||||||
|
printf(" %2d on %-5s GPIO-%d\n", i, gpio2pinname(servo2gpio[i]), servo2gpio[i]);
|
||||||
}
|
}
|
||||||
|
printf("\n");
|
||||||
printf("Board revision: %5d\n", board_rev());
|
|
||||||
printf("Using hardware: %5s\n", delay_hw == DELAY_VIA_PWM ? "PWM" : "PCM");
|
|
||||||
printf("Idle timeout (ms): %5d\n", idle_timeout);
|
|
||||||
printf("Number of servos: %5d\n", num_servos);
|
|
||||||
printf("Servo cycle time: %5dus\n", CYCLE_TIME_US);
|
|
||||||
printf("Pulse width units: %5dus\n", SAMPLE_US);
|
|
||||||
printf("Maximum width value: %5d (%dus)\n", SERVO_MAX,
|
|
||||||
SERVO_MAX * SAMPLE_US);
|
|
||||||
|
|
||||||
init_idle_timers();
|
init_idle_timers();
|
||||||
setup_sighandlers();
|
setup_sighandlers();
|
||||||
@ -655,20 +984,47 @@ main(int argc, char **argv)
|
|||||||
clk_reg = map_peripheral(CLK_BASE, CLK_LEN);
|
clk_reg = map_peripheral(CLK_BASE, CLK_LEN);
|
||||||
gpio_reg = map_peripheral(GPIO_BASE, GPIO_LEN);
|
gpio_reg = map_peripheral(GPIO_BASE, GPIO_LEN);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map the pages to our vitual address space; this reserves them and
|
||||||
|
* locks them in memory. However, these are L1 & L2 non-coherent
|
||||||
|
* cached pages and we want coherent access to them so the DMA
|
||||||
|
* controller sees our changes immediately. To get that, we create a
|
||||||
|
* second mapping of the same size and immediately free it. This gives
|
||||||
|
* us an address in our virtual address space where we can map in a
|
||||||
|
* coherent view of the physical pages that were allocated by the first
|
||||||
|
* mmap(). This coherent mapping happens in make_pagemap(). All
|
||||||
|
* accesses to our memory that is shared with the DMA controller are
|
||||||
|
* via this second coherent mapping. The memset() below forces the
|
||||||
|
* pages to be allocated.
|
||||||
|
*/
|
||||||
|
virtcached = mmap(NULL, NUM_PAGES * PAGE_SIZE, PROT_READ|PROT_WRITE,
|
||||||
|
MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,
|
||||||
|
-1, 0);
|
||||||
|
if (virtcached == MAP_FAILED)
|
||||||
|
fatal("servod: Failed to mmap for cached pages: %m\n");
|
||||||
|
if ((unsigned long)virtcached & (PAGE_SIZE-1))
|
||||||
|
fatal("servod: Virtual address is not page aligned\n");
|
||||||
|
memset(virtcached, 0, NUM_PAGES * PAGE_SIZE);
|
||||||
|
|
||||||
virtbase = mmap(NULL, NUM_PAGES * PAGE_SIZE, PROT_READ|PROT_WRITE,
|
virtbase = mmap(NULL, NUM_PAGES * PAGE_SIZE, PROT_READ|PROT_WRITE,
|
||||||
MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,
|
MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,
|
||||||
-1, 0);
|
-1, 0);
|
||||||
if (virtbase == MAP_FAILED)
|
if (virtbase == MAP_FAILED)
|
||||||
fatal("servod: Failed to mmap physical pages: %m\n");
|
fatal("servod: Failed to mmap uncached pages: %m\n");
|
||||||
if ((unsigned long)virtbase & (PAGE_SIZE-1))
|
if ((unsigned long)virtbase & (PAGE_SIZE-1))
|
||||||
fatal("servod: Virtual address is not page aligned\n");
|
fatal("servod: Virtual address is not page aligned\n");
|
||||||
|
munmap(virtbase, NUM_PAGES * PAGE_SIZE);
|
||||||
|
|
||||||
make_pagemap();
|
make_pagemap();
|
||||||
|
|
||||||
for (i = 0; i < num_servos; i++) {
|
for (i = 0; i < MAX_SERVOS; i++) {
|
||||||
gpio_set(servo2gpio[i], 0);
|
if (servo2gpio[i] == DMY)
|
||||||
|
continue;
|
||||||
|
gpiomode[i] = gpio_get_mode(servo2gpio[i]);
|
||||||
|
gpio_set(servo2gpio[i], invert ? 1 : 0);
|
||||||
gpio_set_mode(servo2gpio[i], GPIO_MODE_OUT);
|
gpio_set_mode(servo2gpio[i], GPIO_MODE_OUT);
|
||||||
}
|
}
|
||||||
|
restore_gpio_modes = 1;
|
||||||
|
|
||||||
init_ctrl_data();
|
init_ctrl_data();
|
||||||
init_hardware();
|
init_hardware();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user