Bahnplaner in C
Trajectory generator in C
The simple trajectory generator described here was implemented for positioning
purposes in educational context, and consists of only one platform independent
C header/implementation file pair. It does not contain volatile
structures nor
system specific synchronisation. Instead, it works with one struct
type intermediate
variable (type trajgen_t
) that can be allocated externally and is unchanged during
the update process. This variable stores configuration, internal states, input
data and position output data. Added/adapted features, such as position/velocity
synchronisation shall make it suitable to be integrated in LinuxCNC (EMC) context.
The brief feature list:
Axes: Main axes
X
,Y
,Z
, (translation)A
,B
,C
(rotation), Auxiliary axesU
,V
,W
(normally collinear toX
/Y
/Z
).Coordinated motion planer: Line motion, arc/circle/helix motion, (spline/nurbs in test), blending, synchronised I/O channels (required for EMC compatibility), velocity synchronisation (required for EMC), position synchronisation (required for EMC).
Manual operation: Planer for command velocities (e.g. from joystick), which includes kinematics.
Joint jogging: Planer for command positions for each joint without kinematics.
Main trajectory generator interface: Finite state machine based coordinator for the "sub planers".
Interpolation: Cubic joint position interpolators affecting the final output (command positions for the closed loop controllers) for all planers.
"Callback kinematics": The internal kinematic model is the "identity" model. Th.m. the pose axes directly correspond to the joint indices. Other kinematics can be defined by configuring function pointers to the forward/ inverse/reset functionalities.
Extensive compiling options: For optimisation, all planers and the interpolators can be
static __inline__
'd or exported. Integer and floating point types can be specified, features, such as interpolation, synchronisation, debugging, error stringification and the like can be switched off entirely to optimise memory space and the performance.
The implementation files shown below include not only the generator files (trajgen.c/.h
),
but also a test program with a C++ wrapper (trajgen.hh
), build scripts, Makefile
and GNU Octave analysis (stats.m
).
Example x/y motion of the coordinated motion generator:
(Die gewünschten allgemeinen Erläuterungen zu Bahnplanern sind hier).
Der hier beschriebene Bahngenerator wurde für Positionierungszwecke im Lehr/Forschungsbereich
implementiert und für die Integration in LinuxCNC erweitert. Er besteht aus nur einem
plattformunabhängigen C Header/Implementation-Dateipaar und enthält keine systemspezifischen
Echtzeitsynchronisationsmechanismen. Stattdessen arbeitet er mit einer Strukturvariablen
(Typ trajgen_t
), die extern alloziiert wird und als Zwischenspeicher dient, der
während dem Rechenvorgang nicht geändert werden darf. Sie enthält die Konfiguration,
interne Zustände, Eingabewerte und Ausgabewerte.
Die Features in aller Kürze:
Achsen: Hauptachsen
X
,Y
,Z
, (Translation)A
,B
,C
(Rotation), HilfsachsenU
,V
,W
(normalerweise kollinear zu den kartesischen Hauptachsen).Koordinierte Bewegungen: Linie, Kreis/Bogen/Helix, (Spline/Nurbs im Test), Überschleifen, Geschwindigkeits-/Positionssynchronisation, synchronisierte digitale Ausgänge.
Manueller Betrieb: Aus Geschwindigkeitseingaben (z.B. von Joystick) werden mit Kinematik neue Sollpositionen erzeugt.
Motor-Jogging: Aus Geschwindigkeitseingaben werden (ohne Kinematik) unabhängig neue Sollpositionen für die einzelnen Motorachen erzeugt.
Interpolation: Kubische Spline-Interpolation kann wahlweise zwischen die Ausgabepositionen der Planer und die Motor-Sollpositionen geschaltet werden.
"Callback"-Kinematik: Das interne Default-Modell der Gerätekinematik ist das "Identity"-Modell, d.h. die Werkzeug-Achspositionen entsprechen direkt den Motorachspositionen (joints). Über konfigurierbare Funktionspointer kann das Kinematische Modell extern implementiert und eingebunden werden.
Extensive Kompilierungseinsellungen: Um dem Compiler Raum für Optimierung zu geben können alle Planer, sowie die Interpolatoren entweder exportiert oder als
static __inline__
kompiliert werden. Weiterhin können Integer- und Fließkommatypen angepasst und Funktionalitäten selektiv abgeschaltet werden (Interpolation, Synchronisation, Überschleifen, Fehlertexte usw.).
Neben den Hautdateien (trajgen.c/.h
) ist weiter unten auch ein Testprogramm
mit C++ Wrapper (trajgen.hh
) GNU-Octave Auswertung (stats.m
) angegeben.
Beispiel: X/Y Bahnbewegung des Planers für koordinierte Bewegungen:
Usage
(Ab hier geht es in die Programmierdetails, und da ist die "Amtssprache" Englisch).
In the very end I cannot deny that a close look into the source code is required to use all the features properly. The inline function/type documentaion helps you there. To get "booted" read the gist of it here.
Info
The function const char* trajgen_info(char* s, unsigned length);
writes a
string representations of its compilation settings into a string (s
). This
can be handy to validate parameters of the compiled program. That looks like e.g.:
"trajgen.c/h, version 1.0b, without-0pointer-checks, no-debug, with-interpolation, with-blending, with-syncing, with-abc, with-uvw, float-type=double, max-joints=9, queue-size=32, machine-resolution=1e-9, dimensional-resolution=1e-9, angle-resolution=1e-6, blending-max-angle=85deg, straight-blending-max-angle=1deg, ..."
Initialisation and configuration
To use the trajectory generator, copy the files trajgen.h
and trajgen.c
in
your source tree and compile latter. Include trajgen.h
and allocate a variable
of the type trajgen_t
(e.g. tg
). Then configure the planers, either directly
using tg.config.(settings)
or by allocating an auxiliary configuration variable
of type trajgen_config_t
:
#include <trajgen.h>
trajgen_t tg; /* The main trajectory generator data */
int init_trajgen()
{
unsigned i;
/* This is where we do our configuration. You can either use a separate
* variable like this one or use tg.config directly. It makes no difference.*/
trajgen_config_t cfg;
/* That makes you safe! The trajgen checks null pointers.
* Any configuration trouble will cause appropriate errors. */
memset(&cfg, 0, sizeof(trajgen_config_t));
/* 1st: How many axes do we use? Less axes -> better performance.
* Here, let's say only x,y,z */
cfg.number_of_used_joints = 3;
/* Next: The sample rate. That is the sample rate which the axes closed loop
* controllers want new command positions. Here we choose 4kHz ... */
cfg.sample_interval = 1.0/4e3; /* in seconds */
/* ... but the planers only run with 1kHz, the interpolators do the positions
* in between for each joint. */
cfg.interpolation_rate = 4;
/* Here you can specify your own kinematics, but we use NULL pointers here so
* that the internal identity kinematics are used. Actually you don't need to
* set these values at all if you used `memset(0)` before.
*/
cfg.kinematics_functions.forward = 0; /* function(*joints, *pose) */
cfg.kinematics_functions.inverse = 0; /* function(*pose, *joints) */
cfg.kinematics_functions.reset = 0; /* function(*joints, *pose) */
/* Now about the restrictions for coordinated motions. We use 100mm/s and
* 1000mm/ss here: */
cfg.max_coord_velocity = 100e-3;
cfg.max_coord_acceleration = 1000e-3;
/* The settings for each independent joint planer. As those are often used for
* initialisation purposes, we restrict them all to 10mm/s and 100mm/ss: */
for(i=0; i<cfg.number_of_used_joints; i++) {
cfg.joints[i].max_acceleration = 100e-3;
cfg.joints[i].max_velocity = 10e-3;
}
/* And for the manual planer we have individual settings: 100mm/ss and
* 50mm/s: */
cfg.max_manual_accelerations.x = 100e-3;
cfg.max_manual_accelerations.y = 100e-3;
cfg.max_manual_accelerations.z = 100e-3;
cfg.max_manual_velocities.x = 50e-3;
cfg.max_manual_velocities.y = 50e-3;
cfg.max_manual_velocities.z = 50e-3;
/* Finally we call init */
if(trajgen_initialize(&tg, cfg)) {
char error_text[64];
trajgen_errstr(tg.last_error, error_text, sizeof(error_text));
rt_printf("Initialisation error: %s\n", error_text);
return 1;
} else {
rt_printf("Initialisation successful\n");
return 0;
}
}
Cyclic call
Supposingly you use a realtime threading system with fifos or shared memory
synchronisation. No matter how this framework looks like, you have to ensure
that the function trajgen_tick()
is called with the sample interval that you
configured. The process for each cycle is:
Check for hardware errors, such as amplifier fault, limit switches ...
Dispatch data from the realtime fifo.
Transfer input data into the trajgen_t structure tg, e.g. new command velocities from the joystick, or updated positions/velocities from synchronisation references.
CALL trajgen_tick()
Check its error
Transfer output data, such as command positions for the axis controllers and digital outputs.
Handle dispatched commands, e.g. to add line/arc/spline motion to the trajgen queue, or switch the state of the trajgen.
/* This function has to be called with the configured 4kHz. */
void cycle()
{
/* Here some generalised dummy code to see roughly where it points to ... */
dispatch_fifo(&cmd);
if(check_hardware() != OK) {
trajgen_abort();
rt_error_message(...);
} else {
switch(cmd.what) {
case ABORT:
trajgen_abort();
break;
case PAUSE:
if(trajgen_pause() != TRAJ_ERROR_OK) {
rt_error_message(...);
}
break;
default:
/* Something to get back to later */
}
}
/* Call trajgen_tick() */
if(trajgen_tick() != TRAJ_ERROR_OK) {
/* Send the error via the used realtime messaging */
rt_error_message(tg.last_error);
/* Abort the generator (switch to disabled state) */
trajgen_abort();
/* Cutoff intermediate circuit */
set_estop(1);
/* We even don't update the axis command positions because it could be
* a numerical error as well */
return;
}
/* Transfer output data ... */
for(i=0; i<tg.config.number_of_used_joints; i++) {
axis_controller_set_command_position(tg.joints[i].position);
}
if(tg.state == TRAJ_STATE_COORDINATED) {
/* Transfer sync I/O */
digital_output_register_set(tg.coord_planer.sync_dio_current);
/* Add commands */
switch(cmd.what) {
case MOVE_LINE:
if(!trajgen_queue_full()) {
if(trajgen_add_line(cmd.to_pose, cmd.speed, cmd.accel)) {
trajgen_abort();
rt_error_message(...);
}
} else {
rt_notify(QUEUE_FULL);
}
break;
case MOVE_ARC:
...
case MOVE_SPLINE:
...
case SWITCH:
...
}
} else {
switch(cmd.what) {
...
}
}
}
States
If you use more than one of the integrated generators, you need handle state dependent date in the thread function. Handling and switching states is quite easy: The important functions and variable are:
trajgen_error_t trajgen_switch_state(trajgen_state_t state)
The states you can select are
TRAJ_STATE_DISABLED
,TRAJ_STATE_JOINT
,TRAJ_STATE_COORDINATED
andTRAJ_STATE_MAN
.The state you selected is stored in
trajgen_t.requested_state
, and the currently active state is stored intrajgen_t.state
. Latter can be as well switching state, namedTRAJ_STATE_OK_TO_SWITCH
,TRAJ_STATE_DISABLING
,TRAJ_STATE_*_ENTER
,TRAJ_STATE_*_LEAVE
.The interesting indicator you should refer to is the boolean flag
trajgen_t.is_done
, Before switching and after initiating a state switch you should wait until the generator has finished. Generally, when a planer is switched to, it will be implicitly reset in theENTER
state, and in theLEAVE
state it will be implicitly aborted. When you switch from planer to another, the state changes from the<CURRENT_PLANER>
state over<CURRENT_PLANER>_LEAVE
,OK_TO_SWITCH
,<NEWPLANER>_ENTER
to<NEWPLANER>
, where the machine velocity is zero whenOK_TO_SWITCH
is active. Only when the target state is active theis_done
flag will be set. Callingtrajgen_abort()
will implicitly cause all generators, no matter if active or not, to disable or finish up, and it always switches to the stateTRAJ_STATE_DISABLED
.
The example shows the use of that without threading:
extern trajgen_t tg;
extern trajgen_config_t cfg;
extern void handle_error(trajgen_t* tg);
void sequential_example()
{
unsigned i;
trajgen_initialize(&_tg, cfg);
if(trajgen_switch_state(TRAJ_STATE_COORDINATED)) handle_error(&tg);
while(!tg.is_done) {
if(trajgen_tick()) handle_error(&tg);
}
/* State is now TRAJ_STATE_COORDINATED */
for(i=0; i<10; i++) {
/* Simply nothing will happen, even if it is selected the coord planer
* is idle, and is_done === 1. */
if(trajgen_tick()) handle_error(&tg);
}
if(trajgen_switch_state(TRAJ_STATE_MAN)) handle_error(&tg);
while(!tg.is_done) {
if(trajgen_tick()) handle_error(&tg);
}
/* State is now TRAJ_STATE_MAN */
...
}
About errors
Simply said: All errors that the trajectory generator returns are critical and
a reason to cut the intermediate circuit. The error type trajgen_error_t
is
generally used as function return, where all nonzero values indicate errors.
The size is normed to uint16_t
for compatibility. Additionally, the type
traigen_error_details_t
is a union
-struct
bit set with the same size,
but it splits the error into a code part (12 bits) and a joint part (4 bits).
Whenever an error is raised from interpolators or joint planers, the joint will
be specified (value 0 to max joints). If the subsystem is not related to a
joint, the value will be 0. You can get a string representation of the error
with the function trajgen_errstr()
. Take a look at the following example to
get the gist of it:
#include "trajgen.h"
#include <stdio.h>
#include <string.h>
trajgen_t tg;
trajgen_config_t cfg;
trajgen_error_t erno;
char errmsg[64];
void try_initialize()
{
if(!!(erno=trajgen_initialize(&tg, cfg))) {
trajgen_errstr(tg.last_error.errno, errmsg, sizeof(errmsg));
printf("Error: %s (%u)\n", errmsg, erno);
} else {
printf("Alright, init ok\n");
}
}
int main()
{
memset(&cfg, 0, sizeof(trajgen_config_t));
/* Prints: "Error: [main trajgen] Null pointer" */
if((erno=trajgen_initialize(NULL, cfg))) {
trajgen_errstr(erno, errmsg, sizeof(errmsg));
printf("Error: %s\n", errmsg);
}
try_initialize(); /* "Error: Invalid last used joint value" */
cfg.number_of_used_joints = 1;
try_initialize(); /* Error: [main trajgen] Invalid interpolation rate setting */
cfg.interpolation_rate = 1;
try_initialize(); /* "Error: [coord tg] Invalid acceleration" */
cfg.max_coord_acceleration = 1000;
try_initialize(); /* Error: [coord tg] Invalid velocity */
cfg.max_coord_velocity = 100;
try_initialize(); /* Error: [coord tg] Invalid sample interval */
cfg.sample_interval = 1e-3;
cfg.joints[0].max_acceleration = -5;
try_initialize(); /* "Error: [joint tg] Invalid maximum acceleration[0]" */
/* etc etc etc */
return 0;
}
About reset
The function trajgen_reset()
resets all planers to a state where they are disabled
and flushed. Internal positions are set to current device positions if required.
Note that reset really means reset. If you call trajgen_reset()
or the reset
function of any planer while the machine is moving you will cause an immediate
freezing of the positions, which will lead to very high accelerations. Hence,
resetting is only recommended in standstill state. When switching between the
planers they will be implicitly reset in a safe situation.
Joint planers
The joint planers work with a very simple principle. They calcaulate a required
velocity from the triangle ramp of the remaining distance to move and the configured
axis acceleration, and truncate this velocity to the maximum configured or commanded
velocity. Then they increase the current positions with s = v * t
. You can change
command velocity and command position any time. Complete usage:
Each joint has its own joint planer. The configuration is located in
tg.config.tg[i]
, the data are located in the joints (tg.joints[i].tg
);Switch to state
TRAJ_STATE_JOINT
and wait fortg.is_done
. The main coordinator will enable and reset all joint planers that have a configuredmax_acceleration > 0
andmax_velocity > 0
. During the reset, the command position will be set to the current axis position to prevent that the axes starts moving to an unexpected position. The lastcommand_velocity
is preserved.Set the
command_velocity
of the planer, e.g.tg.joints[0].tg.command_velocity=10e-3
.Set the
command_position
of the planer, e.g.tg.joints[0].tg.command_position=10e-3
.The planer will start moving immediately. Wait until it is done, either by checking individual planers (
tg.joints[0].tg.is_done
) or all (tg.is_done
).The
is_done
flag of a joint planer is set when its current velocity is zero and the current position is the commanded position.If you disable the planer during the motion, it will decelerate to 0 before its
is_done
flag is set. Note that the command position will always be set to the current position during the abort deceleration phase. Hence, aborting a joint planer is equivalent to disabling it.The main generator functions
trajgen_abort()
,trajgen_pause()
,trajgen_resume()
,trajgen_reset()
apply to all joint planers, too.You can also use the exported joint planer functions (see
trajgen.h
) it you compile with the switch-DEXPORT_JOINT_TG
. However, as the planer is that simple there is no need.
Simple example:
#include <trajgen.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
trajgen_t tg;
trajgen_config_t cfg;
double t = 0;
static void tick()
{
t += tg.config.sample_interval;
if(trajgen_tick()) exit(3);
printf("%8.3f, %12.6f, %12.6f, %12.6f\n",
t, tg.joints[0].position, tg.joints[0].velocity, tg.joints[0].acceleration
);
}
int main()
{
memset(&cfg, 0, sizeof(trajgen_config_t));
cfg.number_of_used_joints = 3;
cfg.sample_interval = 1e-3;
cfg.interpolation_rate = 1;
cfg.max_coord_acceleration = 1000;
cfg.max_coord_velocity = 100;
cfg.joints[0].max_acceleration = 100;
cfg.joints[0].max_velocity = 10;
if(trajgen_initialize(&tg, cfg)) return 1;
/* Output list header */
printf(" t , j0s , j0v , j0a\n");
if(trajgen_switch_state(TRAJ_STATE_JOINT)) return 2;
while(!tg.is_done) tick();
/* Check command_velocity trimmed tp 10 */
tg.joints[0].tg.command_velocity = 20;
tg.joints[0].tg.command_position = 2.5;
do { tick(); } while((!tg.is_done));
/* Check command_velocity === 5 */
tg.joints[0].tg.command_velocity = 5;
tg.joints[0].tg.command_position = 5;
do { tick(); } while((!tg.is_done));
/* Abort behaviour */
tg.joints[0].tg.command_velocity = 5;
tg.joints[0].tg.command_position = 10;
do {
if(tg.joints[0].tg.is_enabled && tg.joints[0].position >= 7.5) {
tg.joints[0].tg.is_enabled = 0;
}
tick();
} while((!tg.is_done && t < 10));
/* Jogging: Set command position to Inf/-Inf */
tg.joints[0].tg.is_enabled = 1;
tg.joints[0].tg.command_velocity = 10;
tg.joints[0].tg.command_position = -INFINITY;
do {
if(tg.joints[0].tg.is_enabled && tg.joints[0].position <= 0) {
tg.joints[0].tg.is_enabled = 0;
}
tick();
} while((!tg.is_done && t < 10));
return 0;
}
Manual planer
The manual planer is based on command velocities of the axes X
, Y
, Z
, A
,
B
, C
, U
, V
and W
. As manual operation is quite different from automatic
coordinated motion, this planer has individual acceleration and speed settings for
each axis. These settings should be set to appropriate values for human interaction -
th.m. much slower. The input velocities can come from handwheels joysticks and
the like.
What it does is integrating the velocities for all axes according to their accelerations
while scaling down all the current velocity increment so that all axes meet their
acceleration restrictions, and finally integrating the positions from the truncated
velocities. The resulting pose is pass through the kinematics to yield the joint
positions.
All you need to do is to switch to the state
TRAJ_STATE_MAN
.You disabled it by switching back to another state. The planer decelerates to speed zero before setting the
is_done
status.Whenever the velocities of all axes is zero,
tg.is_done
equals1
.You set new command velocities using the
pose_t
variabletg.man_planer.velocity
, e.g.tg.man_planer.velocity.x = 10e-3;
.The current velocity can be fetched from the variable
tg.man_planer.current_velocity
, but do not change this value.trajgen_abort()
applies to the manual planer, but ther is no pause nor resume.
Coordinated planer
The planer for queued automated coordinated motion (with kinematics). It basically
determins the linear lengths of the segments when they are added to the queue, and
generates each tick cycle a linear acceleration/velocity profile for the remaining
length (to move) along the total segment length to obtain the current path progress.
It then calculates the current pose (position of all axes) from the latter progress
and feeds the result into the kinematics to calculate the joint command positions.
See the functions trajgen_sg_tick()
and trajgen_coord_tick()
.
All remaining functions are related to queue handling, precalculations of the
segments, obtaining the pose from the progress for different motion types,
modification of parameters to meet the configured or external restrictions, and
error checks.
Usage:
Switch to the state
TRAJ_STATE_COORDINATED
and wait fortg.is_done
. The planer is now reset, and the queue empty.Use the functions
trajgen_add_line()
,trajgen_add_arc()
ortrajgen_add_spline()
to push new segments into the queue. If you did not pause the planer, the motion will start immediately after adding the first segment. Wait until the planer is done. You specify speed and acceleration of the segment directly in these functions, there is no global velocity or acceleration setting. The starting point of the segment is always the end pose of the last queued segment (or the current pose if the queue is empty). Invalid or problematic arguments will raise an error.Any error, be it a fatal numeric error or just a "queue full" will cause an immediate abort of the motion, so check
trajgen_queue_full()
before pushing new segments.Depending on whatever input you have for this, you can set
tg.coord_planer.override
to a value between0
and1
to scale down the velocity.When you set
tg.coord_planer.override_enabled=0
(default after reset == 1), the described override will NOT apply to all segments that are added when this setting is0
(all subsequent segments).The planer has the capability to set synchronised I/O channels. The current state of these digital outputs is located in
tg.coord_planer.sync_dio_current
, which is a 16 bit unsigned (uint16_t
). You can modify this value to whatever and whenever you like (the planer does not evaluate it). When the planer switches from a segment to the next, it can set or clear bits insync_dio_current
. To do this, set the variabletg.coord_planer.sync_dio_command
(which is astruct
with the fieldsset
andclear
, both areuint16_t
as well) before you add a segment to the queue. The command variable will be cleared after adding the segment. Additionally, theclear
field is dominant, so if there are the same bits set inset
andclear
, they will be cleared. When resetting the planer, the command resisters and the current state will be reset to0x0000
.To enable blending, set a maximum path deviation with
tg.coord_planer.blending_tolerance
, which is in the same unit as you configured the space dimensions (e.g. millimeters). All subsequent motion will be blending if the angle between end direction of a segment and start direction of the next segment is not greater thanMAX_ALLOWED_BLENDING_ANGLE_DEG
(default 90º). If blending is off (tolerance0
), the planer decelerates to velocity0
for each segment to accelerate again for the next segment. Otherwise it moves with the highest possible velocity that still meets the tolerance requirement (basically the next segment is started before the current one is finished, and the poses are superpositioned).Velocity sychronisation is done by setting values in the structure
tg.coord_planer.sync
. The basic working principle is that an input reference velocity can modify the current speed of the planer between0
and the current segments command velocity (the normal velocity that the machine moves without synchronisation). This internal override is calculated (each tick) from a the current reference velocity (reference
), and a scaler from the reference velocity to the trajectory velocity (scaler
):v_traj = reference * scaler
. Ifv_traj < 0
the machine will stop, and wait to start new segments as well. Andv_traj
is truncated to the segment speed, of cause. Usage:Before adding synchronised segments, set
tg.coord_planer.sync.type = TP_SYNC_VELOCITY
, settg.coord_planer.sync.scaler
etc. Note that you need to set.type
back toTP_SYNC_NONE
when segments shall not be synchronised, it is not auto resetting like the I/Os.Before calling
trajgen_tick().
update thereference
to the current value.Note you can change the scaler any time, but it will affect all segments. The queue does not store individual thresholds and scalings for each segment.
Note that the machine never moves back. Negative reference velocities (or if the product with the scaler is negative) will be interpreted as
0
and only cause the machine to stop.Any speed thresholding, curve lookup, minimum speed etc has to be done by modifying
reference
before passing to the trajgen.Setting the
reference
orscaler
toNAN
or a non-finite value, or the scaler to0
will be interpreted as command speed0
.
Position synchronisation is similar to velocity syncing, except that an absolute position is the
reference
, and thescaler
the scaler from the reference position to the trajectory (linear progress) position (not the pose). The start of synchronised motion will be postponed until the reference and the scaler are valid (meansisfinite
andscaler!=0
). Usage:Set
tg.coord_planer.sync.type = TP_SYNC_POSITION
before adding a segment. (affects all subsequent motion untilTP_SYNC_NONE
orTP_SYNC_VELOCITY
). Set thescaler
appropriately to calculate from the reference position to the trajectory position (e.g. with the spindle slope).Update the reference position each cycle before calling
trajgen_tick()
.If the reference has to be initialised before starting the synchronised motion, set it to
NAN
. When the synchronised segment is activated, the planer will wait then and set an internal offset position the first time a valid reference value is set, and work relative to this offset then. Th.m. the reference position does not need to be initialised to0
, it can be any finite number.Once the position motion has started, the reference and the scaler must remain valid, otherwise the planer will abort and raise an error.
In the same manner as during velocity syncing, the machine does not move backwards.
Interpolators
The (cubic) spline interpolation is done every trajgen_tick()
cycle, and work
exclusively with joint positions. Each joint interpolator has a buffer of four
positions from the planer, and calculate a new output position (and also velocity
and acceleration) from their current progresses and the sample interval. When new
position is almost exceeding the last buffer value, they request a new value from
the planer, which will be invoked implicitly and push new values into the interpolator
buffers. Usage notes:
Setting
interpolation_rate = N
in the configuration will cause the interpolators to request a new position form the planer everyN
th tick, in between they interpolate (and cause less CPU load).If you do not switch the interpolation feature completely off using the compiler switch
-DNO_INTERPOLATION
they will be enabled.A positive side effect of the interpolation is a smoothing of the trajectory velocities and accelerations. A negative effect of that smoothing is a little delay compared to the "raw" planer output joint positions.
Note that the interpolators reduce the total CPU load, but they do not help at all according to meeting the calculation dead lines. If your processor does not calculate the trajectory fast enough without interpolation it will not be quick enough with interpolation neither.
Compiler switches
To be usable with different architectures and platforms, as well for debugging and performance/size optimisation, there are various compiling switches, which are all listed and documented in
trajgen.h
. You can#define
...... the used floating point type by defining
real_t
todouble
orfloat
(default isdouble
).... the type used for booleans (
bool_t
, defaultunsigned
).... the type used for bytes (
byte_t
, defaultuint8_t
).... the type used for bytes (
byte_t
, defaultuint8_t
).... the maximum configurable number of joints:
JOINT_MAX_JOINTS
(default 9).... the size of the coordinated planer queue (
TG_QUEUE_SIZE
), which is fixed size, so that no dynamic allocation in the realtime/kernel space is required.... the resolution of the planers (
TG_RESOLUTION
). The value is used in various situations if a length is zero (absolute smaller thanTG_RESOLUTION
).... a custom
sincos
function. Depending on the platform they are builtin functions for that, which are faster than separately callingsin
andcos
. Lookup table based calculation could be used as well for fixed calculation times.... a custom memory-zero function (
ZERO_MEMORY(ptr, size)
). Similar tomemset(ptr,0,size)
.... if the functions shall do argument
NULL
pointer checks. (ARG_POINTER_CHECKS
). It's more a debugging/development feature.... if you like to define your own joint data type, which has to be compliant with the type in
trajgen.h
:JOINT_DECL_NONE
: Use your own,JOINT_DECL_FULL
: use full internal joint declaration. Otherwise a minimal default joint declaration is used.... to switch off the interpolation feature:
NO_INTERPOLATION
(default undefined).... to switch off speed/position syncing:
NO_TRAJECTORY_SYNC
(default undefined).... to switch off blending:
NO_MOTION_BLENDING
(default undefined).... to export the functions of the internal functions:
EXPORT_INTERPOLATORS
,EXPORT_JOINT_TG
,EXPORT_MANUAL_TG
,EXPORT_COORD_TG
(default undefined).... to define a debug level:
TG_DEBUG_LEVEL
(0 to 2, default undefined).... define own values for calculation floating point resolutions:
TG_D_RES
for space dimensional values (defaultTG_RESOLUTION
) andTG_A_RES
for angular/polar resolutions (defaultTG_RESOLUTION
).
ToDo's, glitches, etc
During blending, the speed is sometimes reduced a little bit event if this is not necessary.
Spline motion is currently tested and not in the published planer yet. The function depends on a blending special "straight blending" case feature that is tested.
Maximum blending angle would be better configurable and not a compiler seting.
About performance
Compiling and running the example
of the output binary of test/test-coord.cc
on a Raspberry PI (ARMV7 with 700MHz and floating point unit - the slowest I
have that is not a micro controller/DSP) yields an average calculation time for
trajgen_tick()
of 16.3us
. To reduce noise from the timing measurement, the
statistics of 200 test program runs (with identical commands) was evaluated.
All axes, blending, syncing, etc are switched on except interpolation, so that
the planer has to recalculate completely every tick.
The stats show that the most calculation times are below 25us, however, there
are peaks that can mess up the realtime requirements. With a RPi and e.g. a
Xenomai RT patch, the maximum recommended sample rate for the planer would be
about 1kHz
(without interpolation, with interpolation a factor 5 is unproblematic),
which is still suitable for most of the applications.
Motion profile of the example
line {x:40, y:0, z:0, v:10, a:1000, tol:0 }
arc {x:30, y:10, z:0, cx:40, cy:10, cz:0, nx:0, ny:0,nz:1, v:10, a:1000, tol:0 }
arc {x:5, y:15, z:0, cx:25, cy:15, cz:0, nx:0, ny:0,nz:1, v:10, a:1000, tol:1 }
line {x:5, y:10, z:0, v:10, a:1000, tol:1 }
line {x:-25, y:10, z:0, v:100, a:1000, tol:1 }
line {x:-50, y:0, z:0, v:100, a:250, tol:1 }
line {x:-60, y:50, z:0, v:100, a:250, tol:1 }
line {x:-70, y:60, z:0, v:100, a:250, tol:1 }
line {x:-70, y:70, z:0, v:100, a:250, tol:1 }
line {x:-60, y:80, z:0, v:100, a:250, tol:1 }
line {x:0, y:70, z:0, v:100, a:250, tol:1 }
line {x:60, y:60, z:0, v:100, a:250, tol:1 }
line {x:70, y:-10, z:0, v:100, a:250, tol:1 }
line {x:50, y:-20, z:0, v:100, a:250, tol:5 }
line {x:-25, y:-10, z:0, v:100, a:250, tol:5 }
line {x:0, y:-5, z:0, v:100, a:250, tol:5 }
line {x:0, y:0, z:0, v:100, a:250, tol:5 }
Performance
Maximum calculation time: 156.0us
Average calculation time: 16.3us
StdDev calculation time: 3.0us
Ticks < 10% of max time : 63.9%
Ticks < 15% of max time : 99.8%
Ticks < 20% of max time : 99.9%
The figure shows the calculation times over the motion progress.
Header
/*******************************************************************************
* @file trajgen.h
*
* Allows to move the machine tool tip to specified positions, velocities and
* accelerations by modifying a virtual machine position every generator cycle.
* The output is always an updated position for every joint. The closed loop
* controllers of the joints follow this position or, if the cannot, raise a
* trailing error fault.
*
* @author: Stefan Wilhelm (c erberos@atwillys.de)
* @license: BSD (see below)
* @version: 1.0b
* @standard C99
*
* -----------------------------------------------------------------------------
*
* Coordinated motion (coord planer, manual) are related the planer pose (tool
* tip position and direction), t.m. a new pose is calculated first, then the
* joint position are determined using inverse kinematics. The joint planers
* update the scalar position of every joint individually, the planer pose is
* updated (for status or equivalence) using forward kinematics. After the new
* joint positions are computed, they are used to feed the interpolators. The
* output of the interpolatoes is the final output of the trajgen and can be
* used as command position for the closed loop controllers.
*
* As wrapper for the different trajectory planers, the main trajgen organises
* the initialisation, reset and synchronisation of the submodules. It is based
* on a finite state machine. This means the generator can be only in one defined
* state, and switching between states causes leaving the current state (wait
* for the current motion to be finished and cleanup after the sub modules), and
* entering a new state (initialise and switch to module run state). Hence,
* switching takes at least one tick (cycle period).
*
* Usage:
*
* Variables:
*
* All data of the trajgen are stored in one single data structure of the
* type trajgen_t. This structure contains the configuration, the current
* state, the (requested) state to switch to, the last error occurred,
* current pose, kinematic data, coordinated planer data, manual op data,
* as well as data for individual joints. Latter are joint data structures
* containing data of the interpolators, joint planers, and the
* joint command position outputs.
*
* You can decide how and where this structure is located, either as part
* of a bigger data structure, global, local, stack, heap, etc. Only few
* aspects are important:
*
* (1) The location must not change, as the pointer is saved in a
* static variable.
*
* (2) There can be only one main trajgen (but multiple sub-planers
* e.g. for simulation purposes).
*
* Synchronisation and threading
*
* Thread safety is not implemented due to platform dependencies. You must
* take care yourself that no other thread or shared memory write process
* interferes with any function of the trajgen. It absolutely assumes that
* the data are not changed during a function call.
*
* Configuration and initialisation:
*
* The trajgen is configured once in the startup phase of the machine.
* Create a variable of type trajgen_config_t and pass it together with
* the pointer to the main trajgen structure to trajgen_initialize();
* The config will not be modified by any internal function.
*
* Reset:
*
* Calling trajgen_reset() will reset the internal state and internal
* variables of the trajgen and all submodules to a "clean" disabled state.
* The config will not be changed. The state will be 0 (TRAJ_STATE_DISABLED)
* and is_done will be 1 (true).
*
* Cyclic function:
*
* Call trajgen_tick() with the sample rate that the servo position
* need to be updated. The configuration variable "interpolation_rate"
* decides how frequently the trajectory needs to be recalculated. For a
* closed loop sample rate of 2KHz and an interpolation rate of 4, the
* trajectory will be recalculated with 500Hz, the missing cycles will be
* filled by the cubic interpolator of each joint. This means also that
* synchronous outputs and condition handling will be done with 500Hz.
*
* State switching:
*
* Use trajgen_switch_state() to switch between the submodules. After calling
* the function, is_done will be 0 and remain zero until the current planer
* stopped moving, is cleaned up and the new state is in running state.
* However, the state might remain zero if the newly selected generator
* started moving already.
*
* States:
*
* TRAJ_STATE_DISABLED:
*
* The trajgen is idle and does not perform any calculations or
* position updates.
*
* TRAJ_STATE_COORDINATED:
*
* The coordinated motion planer is running and processes its
* queue.
*
* TRAJ_STATE_MAN:
*
* The (coordinated) teleoperation planer is running and processes
* according its given input velocities.
*
* TRAJ_STATE_JOINT:
*
* The joint individual planers are active.
*
* Submodule access:
*
* You have full access to the individual planners and get in touch with
* each you want to use if you define the export-enable macros described
* below.
*
* Errors:
*
* Each of the trajgen and submodule functions that can cause errors return
* an error code. The convention is: code 0 (zero) means OK, nonzero means
* error. You should always treat trajgen errors that are called cyclic in
* trajgen_coord_tick(), as well as initialisation errors, as fatal and cause
* an e-stop by opening the machine's intermediate circuit immediately.
*
* Every generator has a variable last_error, where you can query the last
* error occurred. This values are only valid if a function returned nonzero
* before (the variable is not explicitly reset to 0 if a function returns
* OK). The main trajgen's last_error is a structure containing one single
* encoded 16bit error value (type trajgen_error_details_t). This error
* code contains the
*
* (1) the code
* (2) the joint, which is only valid if an interpolator or joint planer
* failed.
*
* This error code allows to identify quite accurately what went wrong using
* a single number that can be saved or passed to the user space easily.
*
* -----------------------------------------------------------------------------
* License header: Copyright (c) 2008-2014, Stefan Wilhelm. All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
******************************************************************************/
#ifndef SW_TRAJGEN_H
#define SW_TRAJGEN_H
#ifdef __cplusplus
extern "C" {
#endif
/* <editor-fold defaultstate="collapsed" desc="Build config: types and libraries"> */
#if defined __linux__
#include <stdint.h>
#include <sys/types.h>
#elif defined __MACH__
#include <stdint.h>
#include <sys/types.h>
#else
#error "Can you please define the rttypes for this platform and send a patch? :)"
/*
#define int8_t char
#define uint8_t unsigned char
#define int16_t short
#define uint16_t unsigned short
#define int32_t int
#define uint32_t unsigned int
#define int64_t long
#define uint64_t unsigned long
#define float32_t float
#define float64_t double
#define size_t unsigned int
*/
#endif
/**
* Allows to specify 32 bit or 64 bot float, unfortunately "float_t" is already
* reserved on many frames, so "real_t" is used instead. Can be also redefined
* as typedef. The macro is used to be able to switch it easily using the pre
* compiler.
*/
#ifndef real_t
#define real_t double
#endif
#ifndef float32_t
#define float32_t float
#endif
/**
* Explicit declaration of a boolean type. Can be switched for memory optimizsation
* (e.g. char) or best data type for the processor (e.g. int)
*/
#ifndef bool_t
#define bool_t unsigned
#endif
/**
* Explicit declaration of a byte, no matter if signed or unsigned. Normally
* used for bit operations or 8bit port i/o.
*/
#ifndef byte_t
#define byte_t uint8_t
#endif
/**
* "Auto include" float.h
* Note: This allows you to include float.h or a RT optimised one yourself.
*/
#ifndef FLT_MAX
#include <float.h>
#endif
/**
* "Auto include" math.h, characteristic macro: M_PI
* Note: This allows you to include math.h or a RT optimised one yourself.
*/
#ifndef M_PI
#include <math.h>
#endif
/*
* Defines the maximum number of joints the program can handle.
*/
#ifndef JOINT_MAX_JOINTS
#define JOINT_MAX_JOINTS 9
#endif
/*
* The maximum number of joints that the trajectory generator supports
*/
#ifndef TG_MAX_JOINTS
#define TG_MAX_JOINTS JOINT_MAX_JOINTS
#endif
/*
* The maximum size if the coordinated motion generator queue (fixed allocated)
* Note: This is an intermediate fifo with big data structures and should be small.
* You are supposed to dispatch and push new values from a realtime fifo whenever
* possible.
*/
#ifndef TG_QUEUE_SIZE
#define TG_QUEUE_SIZE 32
#endif
/*
* Defines then threshold when the coordinated motion planer sees numbers as
* zero or not.
*/
#ifndef TG_RESOLUTION
#define TG_RESOLUTION 1e-6
#endif
/*
* Smallest floating point number that is not zero. (Epsilon)
*/
#ifndef FLOAT_EPS
#define FLOAT_EPS (sizeof(real_t) == sizeof(double) ? FLT_EPSILON : DBL_EPSILON)
#endif
/*
* Spline/bezier iterations to find the length
*/
#ifndef TG_CURVE_ACC
#define TG_CURVE_ACC (16)
#endif
/*
* Sin/cos, can be replaced platform dependent with the corresponding builtin.
* #define SINCOS(x, sx, cx) do { *(sx)=sin((x)); *(cx)=cos((x)); } while(0);
*/
/*
* You can set your own memory clear (set to 0) function. By default a static
* inline function is used that is independent of any external library.
* #define ZERO_MEMORY(ptr, size)
*/
/*
* Define this to force the trajgen to check argument pointers
* #define ARG_POINTER_CHECKS
*/
/*
* Normally you like to define your own joint type and config for the machine,
* but if you to it has to be compliant with the joint_t here.
* #define JOINT_DECL_NONE
*/
/*
* You can switch off the interpolator feature here. Any settings related to
* it will be ignored. The interpolation rate must be set to 1.
* #define NO_INTERPOLATION
*/
/*
* You can switch off the trajectory position/velocity synchronisation using:
* #define NO_TRAJECTORY_SYNC
*/
/*
* You can optionally export the interpolators or leave them
* inline static in the implementation file to improve code optimisation.
* Interpolator types and error definitions are not affected by this setting,
* they are always exported.
* #define EXPORT_INTERPOLATORS
*/
/*
* You can optionally not export the joint planers or leave them
* inline static in the implementation file to improve code optimisation.
* Types and error definitions are not affected by this setting, only functions.
* #define EXPORT_JOINT_TG
*/
/*
* You can optionally not export the manual/joystick generator or leave it
* inline static in the implementation file to improve code optimisation.
* Types and error definitions are not affected by this setting, only functions.
* #define EXPORT_MANUAL_TG
*/
/*
* You can optionally not export the coordinated motion generator or leave it
* inline static in the implementation file to improve code optimisation.
* Types and error definitions are not affected by this setting, only functions.
* #define EXPORT_COORD_TG
*/
/*
* You can switch off the interpolator feature here. Any settings related to
* it will be ignored. The interpolation rate must be set to 1.
* #define NO_MOTION_BLENDING
*/
/*
* Defining this will enable stderr debug messages. Note: this can be quite
* detailed. (command line option -DTG_DEBUG_LEVEL=<1,2,3>)
* #define TG_DEBUG_LEVEL <0,1,2...> (default undefined)
*/
/*
* Math "resolution", values define when a dimension or angle is treated
* as zero.
* #define TG_D_RES (default TG_RESOLUTION)
* #define TG_A_RES (default TG_RESOLUTION)
*/
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="pose_*: Robot pose types and operations"> */
/********************************************************************
*
* Data structure defining a position of logical axes:
*
* Translation : x,z,y
* Rotation : a,b,c
* Auxiliary translation: u,v,w
*
********************************************************************/
/* Axis enumerators for double vector access */
typedef enum
{
POSE_X_AXIS = 0, POSE_Y_AXIS, POSE_Z_AXIS, POSE_A_AXIS, POSE_B_AXIS,
POSE_C_AXIS, POSE_U_AXIS, POSE_V_AXIS, POSE_W_AXIS
} pose_axis_t;
/* Pose by axes */
typedef struct { real_t x, y, z, a, b, c, u, v, w; } pose_t;
typedef struct { real_t x, y, z; } pose_vector_t;
#define pose_position_t pose_vector_t
/* Line definition */
typedef struct
{
pose_position_t start, end; /* Start/end point */
pose_vector_t u; /* Unit vector of translation */
real_t tm; /* Magnitude /length */
} pose_line_t;
/* Circle definition */
typedef struct
{
pose_position_t cp; /* Circle centre point */
pose_vector_t nv; /* Circle normal vector */
pose_vector_t rt,rp,rh; /* radius vectors: tangent, perpendicular, helix */
real_t l, r, a, s; /* Length, start radius, angle, parallel component coeff */
} pose_circle_t;
/* Curve (bezier/spline) definition */
typedef struct
{
#ifdef WITH_CURVES
pose_position_t start,end; /* End point */
pose_vector_t suv, euv; /* Start/end unit vectors */
real_t len; /* Length of the segment */
real_t c[3][4]; /* Path component coefficients */
real_t d[TG_CURVE_ACC]; /* Correction */
#endif
} pose_curve_t;
typedef real_t pose_array_t[9];
/**
* Inliners (These macros are not upper case)
* Using byval for sources and by pointer for dst, so if somthing is switched
* the compiler will raise an error.
*/
/* Assignment p=0 */
#define pose_set_zero(p) { \
(p)->x=(p)->y=(p)->z=(p)->a=(p)->b=(p)->c=(p)->u=(p)->v=(p)->w= 0.0; \
}
#define pose_set_all(p, val) { \
(p)->x=(p)->y=(p)->z=(p)->a=(p)->b=(p)->c=(p)->u=(p)->v=(p)->w=val; \
}
/**
* Checks if all elements are valid numbers (not nan) and finite.
*/
#define pose_isfinite(p) ( \
isfinite((p).x) && isfinite((p).y) && isfinite((p).z) && \
isfinite((p).a) && isfinite((p).b) && isfinite((p).c) && \
isfinite((p).u) && isfinite((p).v) && isfinite((p).w) \
)
/* Assignment dst = src */
/* stfwi: no memcpy, let the compiler handle optimisation */
#define pose_set(dst, src) { \
(dst)->x = (src).x; (dst)->y = (src).y; (dst)->z = (src).z; \
(dst)->a = (src).a; (dst)->b = (src).b; (dst)->c = (src).c; \
(dst)->u = (src).u; (dst)->v = (src).v; (dst)->w = (src).w; \
}
/* Difference po = p1-p2 */
#define pose_diff(p1, p2, po) { \
(po)->x = (p1).x-(p2).x; (po)->y = (p1).y-(p2).y; (po)->z=(p1).z - (p2).z; \
(po)->a = (p1).a-(p2).a; (po)->b = (p1).b-(p2).b; (po)->c=(p1).c - (p2).c; \
(po)->u = (p1).u-(p2).u; (po)->v = (p1).v-(p2).v; (po)->w=(p1).w - (p2).w; \
}
/* Sum po = p1+p2 */
#define pose_sum(p1, p2, po){ \
(po)->x = (p1).x+(p2).x; (po)->y = (p1).y+(p2).y; (po)->z = (p1).z+(p2).z; \
(po)->a = (p1).a+(p2).a; (po)->b = (p1).b+(p2).b; (po)->c = (p1).c+(p2).c; \
(po)->u = (p1).u+(p2).u; (po)->v = (p1).v+(p2).v; (po)->w = (p1).w+(p2).w; \
}
/* Accumulate po += p2 */
#define pose_acc(po, p2){ \
(po)->x += (p2).x; (po)->y += (p2).y; (po)->z += (p2).z; \
(po)->a += (p2).a; (po)->b += (p2).b; (po)->c += (p2).c; \
(po)->u += (p2).u; (po)->v += (p2).v; (po)->w += (p2).w; \
}
/* Substract po -= p2 */
#define pose_sub(po, p2){ \
(po)->x -= (p2).x; (po)->y -= (p2).y; (po)->z -= (p2).z; \
(po)->a -= (p2).a; (po)->b -= (p2).b; (po)->c -= (p2).c; \
(po)->u -= (p2).u; (po)->v -= (p2).v; (po)->w -= (p2).w; \
}
/* Negation po = -po */
#define pose_neg(po){ \
(po)->x = -(po)->x; (po)->y = -(po)->y; (po)->z = -(po)->z; \
(po)->a = -(po)->a; (po)->b = -(po)->b; (po)->c = -(po)->c; \
(po)->u = -(po)->u; (po)->v = -(po)->v; (po)->w = -(po)->w; \
}
/* Scalar multiply po *= (double) d */
#define pose_scale(po, d){ \
(po)->x *= (d); (po)->y *= (d); (po)->z *= (d); \
(po)->a *= (d); (po)->b *= (d); (po)->c *= (d); \
(po)->u *= (d); (po)->v *= (d); (po)->w *= (d); \
}
/* Trim every axis to a maximum value (defined for every axis) */
#define pose_trim_all_upper(po, max) { \
if ((po)->x > (max).x) (po)->x = (max).x; \
if ((po)->y > (max).y) (po)->y = (max).y; \
if ((po)->z > (max).z) (po)->z = (max).z; \
if ((po)->a > (max).a) (po)->a = (max).a; \
if ((po)->b > (max).b) (po)->b = (max).b; \
if ((po)->c > (max).c) (po)->c = (max).c; \
if ((po)->u > (max).u) (po)->u = (max).u; \
if ((po)->v > (max).v) (po)->v = (max).v; \
if ((po)->w > (max).w) (po)->w = (max).w; \
}
/* Trim every axis to a minimum value (defined for every axis) */
#define pose_trim_all_lower(po, min) { \
if ((po)->x < (min).x) (po)->x = (min).x; \
if ((po)->y < (min).y) (po)->y = (min).y; \
if ((po)->z < (min).z) (po)->z = (min).z; \
if ((po)->a < (min).a) (po)->a = (min).a; \
if ((po)->b < (min).b) (po)->b = (min).b; \
if ((po)->c < (min).c) (po)->c = (min).c; \
if ((po)->u < (min).u) (po)->u = (min).u; \
if ((po)->v < (min).v) (po)->v = (min).v; \
if ((po)->w < (min).w) (po)->w = (min).w; \
}
/* Scalar multiply po *= (double) d */
/* StfWi: memcmp() with reference 0[9]? */
#define pose_is_zero(p) ( \
((p).x == 0.0 && (p).y == 0.0 && (p).z == 0.0 && \
(p).a == 0.0 && (p).b == 0.0 && (p).c == 0.0 && (p).u == 0.0 && \
(p).v == 0.0 && (p).w == 0.0) \
)
/* Scalar multiply po *= (double) d */
#define pose_is_all_greater_equal_zero(p) ( \
((p).x >= 0.0 && (p).y >= 0.0 && \
(p).z >= 0.0 && (p).a >= 0.0 && (p).b >= 0.0 && (p).c >= 0.0 && \
(p).u >= 0.0 && (p).v >= 0.0 && (p).w >= 0.0) \
)
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="joint_*: Robot joint decls"> */
/*******************************************************************************
*
* Represents one joint of the machine, including configuration, status and
* command transfer.
*
******************************************************************************/
#ifdef JOINT_DECL_NONE
/*
* NOTE: You have to define the joints compatible to the minimum settings
*/
#elif defined JOINT_DECL_FULL
/**
* Type of joint
*/
typedef enum
{
JOINT_TYPE_NONE = 0, /* Not defined */
JOINT_AXIS_TYPE_LINEAR, /* Linear axis, unit will be meters */
JOINT_AXIS_TYPE_ROTARY /* Polar axis, unit will be degrees */
} joint_axis_type_t;
/**
* Feedback type of a joint
*/
typedef enum
{
/* E.g. stepper without encoder */
JOINT_POSITION_FEEDBACK_NONE = 0,
/* Incremental encoder, requires homing for all except axis free motion */
JOINT_POSITION_FEEDBACK_INCREMENTAL,
/* Incremental encoder, requires no homing */
JOINT_POSITION_FEEDBACK_ABSOLUTE
} joint_position_feedback_t;
/**
* I/O logic of various joint inputs/outputs, e.g. limit switches, index ...
*/
typedef enum
{
JOINT_LOGIC_ACTIVE_HIGH = 0x00,
JOINT_LOGIC_ACTIVE_LOW = 0x01, /* used with xor */
JOINT_LOGIC_DISABLED
} joint_logic_t;
/**
* Bit field of joint-related
*/
typedef union
{
uint16_t all;
struct
{
/* The joint/axis is enabled */
unsigned enabled : 1;
/* The amplifier is enabled */
unsigned amplifier_enabled : 1;
/* The joint is homed */
unsigned homed : 1;
/* The trajectory planer is at the goal position */
unsigned trajectory_done : 1;
/* The joint is in the position tolerance of the goal position */
unsigned in_position : 1;
/* The joint does not move (in standstill velocity tolerance) */
unsigned standstill : 1;
/* The joint amplifier has an error */
unsigned amplifier_fault : 1;
/* The joint encoder has an error */
unsigned encoder_fault : 1;
/* The home switch is activated (also depends on config) */
unsigned home_index : 1;
/* The positive limit switch is activated (depends also on config) */
unsigned positive_limit : 1;
/* The negative limit switch is activated (depends also on config) */
unsigned negative_limit : 1;
/* Fill up remaining bits */
unsigned reserved : 5;
} bits;
} joint_status_t;
typedef struct
{
/* Joint type, linear or polar/rotary */
joint_axis_type_t type;
/* The type of feedback */
joint_position_feedback_t feedback_type;
/* The positive software limit in meters/degrees */
real_t positive_software_limit;
/* The negative software limit in meters/degrees */
real_t negative_software_limit;
/* Scales the axis position (normally increments) to meters/degrees */
real_t position_to_meters_scaler;
/* The maximum velocity the joint can do in m/s / degrees/s */
real_t max_velocity;
/* The maximum acceleration the joint can do in m/s / degrees/s */
real_t max_acceleration;
/* Max trailing error at speed=0 in meters/degrees */
real_t max_trailing_error_min;
/* Max trailing error at speed=max_velocity in meters/degrees */
real_t max_trailing_error_max;
/* The deceleration used for ESTOP full break. It can exceed the max_acceleration. */
real_t estop_deceleration;
/* Max position deviation at standstill to say the measured joint position is */
/* accurate enough, in m/s / degrees/s */
real_t in_position_tolerance;
/* Max speed deviation at standstill to say the joint does not move anymore, */
/* m/s / degrees/s */
real_t standstill_tolerance;
/* 1 = a limit switch exists and has to be respected */
joint_logic_t positive_hardware_limit;
/* 1 = a limit switch exists and has to be respected */
joint_logic_t negative_hardware_limit;
/* If not, a limit switch will be used OR, if not limit switch, the mechanical */
/* joint endpoint (measured using trailing error) */
joint_logic_t home_index;
} joint_config_t;
typedef struct
{
/* Axis configuration */
joint_config_t config;
/* State of the joint, see joint_state_t */
joint_status_t status;
/* The interpolated position of the trajectory generator (for all kinds of trajgen) */
real_t command_position;
/* The interpolated velocity of the trajectory generator (for all kinds of trajgen) */
real_t command_velocity;
/* The interpolated acceleration of the trajectory generator (for all kinds of trajgen) */
real_t command_acceleration;
/* The scaled position measured with the encoders */
real_t position;
/* Position captured by a latch operation, e.g. homing index */
real_t latched_position;
} joint_t;
#else
/**
* Minimum joint definition required to use the trajgen
*/
typedef struct
{
struct { real_t max_velocity, max_acceleration; } config;
real_t command_position, command_velocity, command_acceleration, position;
} joint_t;
#endif
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="trajgen_error_*: Trajgen error main decls"> */
/*******************************************************************************
*
*
* Main trajectory generator error types and definitions.
*
*
******************************************************************************/
/**
* Error declarations for the trajgen itself. However, because this is the "master"
* File of the generator, it will return error codes defined in trajgen_error.h,
* which includes joint, code and error source.
*/
enum
{
/* Main controller */
TRAJ_ERROR_OK = 0,
TRAJ_ERROR_ERROR,
TRAJ_ERROR_NULL_POINTER,
TRAJ_ERROR_NUMERIC,
TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID,
TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID,
TRAJ_ERROR_CONFIG_OVERRIDE_INVALID,
TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID,
TRAJ_ERROR_INVALID_STATE, /* The motion state is not valid (Internal problem) */
TRAJ_ERROR_INVALID_SWITCHING_STATE, /* The operating mode to set is invalid */
TRAJ_ERROR_INVALID_JOINT_NO,
/* Kinematics */
KINEMATICS_ERR_ERROR,
KINEMATICS_ERR_NULL_POINTER,
KINEMATICS_ERR_INIT_FUNCTION_NULL,
KINEMATICS_ERR_FORWARD_FAILED,
KINEMATICS_ERR_INVERSE_FAILED,
KINEMATICS_ERR_RESET_FAILED,
/* Interpolators */
INTERPOLATOR_ERROR,
INTERPOLATOR_ERROR_INIT_ARG_INVALID,
INTERPOLATOR_ERROR_QUEUE_FULL,
INTERPOLATOR_ERROR_OFFSET_IP_NULLPOINTER,
INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER,
INTERPOLATOR_ERROR_NOT_RESET,
INTERPOLATOR_ERROR_NULLPOINTER,
/* Coordinated planer */
TP_ERR_ERROR,
TP_ERR_TP_NULL_POINTER,
TP_ERR_ABORTING,
TP_ERR_QUEUE_PUT_FAILED,
TP_ERR_INVALID_PARAM,
TP_ERR_QUEUE_FULL,
TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE,
TP_ERR_INVALID_MOTION_TYPE,
TP_ERR_INVALID_SPEED,
TP_ERR_INVALID_ACCEL,
TP_ERR_INVALID_POSE,
TP_ERR_SEGMENT_LENGTH_ZERO,
TP_ERR_INVALID_SAMPLE_INTERVAL,
TP_ERR_ALREADY_MOVING,
TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE,
TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION,
/* Joint planers */
TRAJGEN_FREE_ERROR_ERROR,
TRAJGEN_FREE_ERROR_INIT_NULLPOINTER,
TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL,
TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY,
TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL,
TRAJGEN_FREE_ERROR_INVALID_ACCELERATION,
/* Manual operation planer */
TG_MAN_ERROR_ERROR,
TG_MAN_ERROR_INIT_NULLPOINTER,
TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL,
TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY,
TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL,
TG_MAN_ERROR_INVALID_ACCELERATION
};
enum { KINEMATICS_ERR_OK=0 };
enum { INTERPOLATOR_OK=0 };
enum { TP_ERR_OK=0 };
enum { TRAJGEN_FREE_ERROR_OK=0 };
enum { TG_MAN_ERROR_OK=0 };
typedef uint16_t trajgen_error_t;
#define kinematics_error_t trajgen_error_t
#define trajgen_coord_error_t trajgen_error_t
#define interpolator_error_t trajgen_error_t
#define interpolator_error_t trajgen_error_t
#define trajgen_jointtg_error_t trajgen_error_t
#define trajgen_man_error_t trajgen_error_t
/**
* Main error type
*/
typedef union
{
int16_t errno;
struct
{
unsigned code : 12;
unsigned joint : 4;
} sel;
} trajgen_error_details_t;
/**
* Writes a text version of a trajgen error into *errstr
* @param uint16_t errnum
* @param char* errstr
* @param unsigned length
*/
extern const char* trajgen_errstr(uint16_t errnum, char* errstr, unsigned length);
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="interpolator_*: Interpolation"> */
/********************************************************************
* Cyclic Cubic interpolation, used to interpolate joint command position for
* the closed loop controllers between the way points produced by the trajectory
* generators. This allows to enhance the system performance, especially when
* high filter sample rates are required.
*
********************************************************************/
/**
* Cubic interpolator
*/
typedef struct
{
uint16_t n; /* Specifies number of queued points */
real_t T; /* The trajectory planer sample time */
real_t Ti; /* The time between two interpolated points */
real_t t; /* Current time, reset on push */
real_t x0, x1, x2, x3;/* Buffer for the interpolation, used like a FIFO */
real_t w0, w1; /* Way points */
real_t v0, v1; /* Velocities at the way points */
real_t a, b, c, d; /* Coefficients polynomial, a*x^3 + b*x^2 + c*x + d */
} interpolator_t;
#ifdef EXPORT_INTERPOLATORS
/**
* Initialise the interpolator.
*/
extern interpolator_error_t interpolator_init(interpolator_t*, real_t sample_interval,
unsigned interpolation_rate);
/**
* Reset the interpolator, clear the queue to the specified position value.
*/
extern interpolator_error_t interpolator_reset(interpolator_t*, real_t initial_position);
/**
* Performs one interpolation tick. All pointer argument must refer to existing
* variables (not NULL).
*/
extern interpolator_error_t interpolator_interpolate(interpolator_t*, real_t *x,
real_t *v, real_t *a, real_t *j);
/**
* Push a new position into the fifo. That is only allowed when the fifo is not full.
*/
extern interpolator_error_t interpolator_push(interpolator_t*, real_t point);
/**
* Returns nonzero if the fifo is not full. You must push a value before the
* next interpolation step, or the last value in the fifo will be pushed
* automatically.
*/
extern bool_t interpolator_need_update(interpolator_t*);
#endif
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="kinematics_*: Kinematics"> */
/*******************************************************************************
*
* Calculates the position and direction of the end effector (where the tool is
* mounted) in three dimensional space based in the position of all relevant
* machine joints and vice versa. Several kinematic machine models are available
* and implementing own models is possible.
*
******************************************************************************/
typedef struct
{
/**
* The reset kinematics function sets all its arguments to their proper
* values at the known initial position. When called, these should be set,
* when known, to initial values, e.g., from an INI file. If the home
* kinematics can accept arbitrary starting points, these initial values
* should be used.
*/
kinematics_error_t(*reset)(pose_t *pose, real_t *joint_positions[]);
/**
* The forward kinematics take joint values and determine world coordinates,
* given forward kinematics flags to resolve any ambiguities. The inverse
* flags are set to indicate their value appropriate to the joint values
* passed in.
*/
kinematics_error_t(*forward)(real_t *joint_positions[], pose_t *pose);
/**
* The inverse kinematics take world coordinates and determine joint values,
* given the inverse kinematics flags to resolve any ambiguities. The forward
* are set to indicate their value appropriate to the world coordinates
* passed in.
*/
kinematics_error_t(*inverse)(pose_t *pose, real_t *joint_positions[]);
} kinematics_t;
extern kinematics_error_t kinematics_initialize(kinematics_t *kin, kinematics_t
device_kinematics);
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="trajgen_coord_*, trajgen_sg_*: Coordinated Motion"> */
/********************************************************************
*
* Planer for coordinated queued motion with blending, i/o synchronisation and
* motion synchronisation.
*
********************************************************************/
/**
* Naming of motion task types (typedef is int'ed for bit packing)
*/
#define trajgen_sg_motion_type_t int
enum
{
SG_INVALID = 0x00,
SG_LINEAR = 0x01,
SG_CIRCULAR = 0x02,
SG_CURVE = 0x03,
SG_END_OF_MOTION
};
/**
* Type naming of motion synchronisation (typedef is int'ed for bit packing)
*/
#define trajgen_coord_motion_sync_type_t int
enum
{
TP_SYNC_NONE = 0x00,
TP_SYNC_VELOCITY = 0x01,
TP_SYNC_POSITION = 0x02
};
/**
* Type for specification of synchronised motion (motion dependent on external
* positions/velocities).
*/
typedef struct
{
/**
* Scales from the reference position/velocity (e.g. spindle position/speed)
* to the scalar position/velocity of the trajectory when multiplied.
*/
real_t scaler;
/*
* Position sync: The current (input) reference position
* Velocity sync: The current (input) reference velocity
*/
real_t reference;
/* INTERNAL USE: Accumulates the position-synchronized segment lengths of
* all surpassed segments for position sync.
*/
real_t i_offset;
/**
* The type of synchronisation, for subsequent motion, default is TP_SYNC_NONE
*/
trajgen_coord_motion_sync_type_t type: 4;
/**
* INTERNAL USE: Saves if the position offset does not require to be updated
* before starting a position synced motion.
*/
bool_t i_isset: 1;
} trajgen_coord_motion_sync_t;
/**
* Coordinated planer configuration
*/
typedef struct
{
/* Sample interval (cycle interval time) */
real_t sample_interval;
/* Max velocity allowed by machine constraints (normally set in a config file) */
real_t max_velocity;
/* Max acceleration allowed by machine constraints (normally set in a config file) */
real_t max_acceleration;
} trajgen_coord_config_t;
/**
* Mask type for synchronised digital I/O states (bit field)
*/
typedef uint16_t trajgen_coord_syncdio_t;
/**
* Synchronised digital I/Os to set/reset when the next queued task is shifted.
*/
typedef struct
{
/* the bits to be set to HIGH */
trajgen_coord_syncdio_t set;
/* the bits to be set to LOW */
trajgen_coord_syncdio_t clear;
} trajgen_coord_syncdio_cmd_t;
/**
* Task data specialisation for line motion
*/
typedef struct
{
pose_line_t xyz, abc, uvw;
} trajgen_sg_line_t;
/**
* Task data specialisation for circle motion
*/
typedef struct
{
pose_circle_t xyz;
pose_line_t abc, uvw;
} trajgen_sg_arc_t;
/**
* Task data specialisation for curve motion
*/
typedef struct
{
#ifdef WITH_CURVES
pose_curve_t xyz;
#else
pose_line_t xyz;
#endif
pose_line_t abc, uvw;
} trajgen_sg_curve_t;
/**
* Coordinated planer task task type, used in the queue.
*/
typedef struct
{
union {
trajgen_sg_line_t line; /* Specific line data */
trajgen_sg_arc_t arc; /* Specific arc/circle/helix data */
trajgen_sg_curve_t curve; /* Specific curve/nurbs data */
} coords;
/**
* segment's serial number
*/
uint32_t id;
/**
* The scalar path length that the pose moved already in this segment, values
* are from 0..target
*/
real_t progress;
/**
* Scalar length of the path segment
*/
real_t length;
/**
* Speed set by the user speed (requested speed)
*/
real_t v;
/**
* Acceleration set by the user (requested acceleration)
*/
real_t a;
/**
* Internally calculated (keep track of current step (vel * cycle_time) )
*/
real_t current_velocity;
/**
* during the blend at the end of this move, stay within this distance from
* the path
*/
real_t blending_tolerance;
/**
* velocity below which we should start blending
*/
real_t blending_velocity;
/**
* synched DIO's for this move. what to turn on/off
*/
trajgen_coord_syncdio_cmd_t sync_dio;
/**
* SG_LINEAR (coords.line) or
*/
trajgen_sg_motion_type_t motion_type: 4;
/**
* The type of synchronisation, default is TP_SYNC_NONE
*/
trajgen_coord_motion_sync_type_t sync_type: 4;
/**
* this motion is being executed
*/
bool_t is_active: 1;
/**
* segment is being blended into following segment
*/
bool_t is_blending: 1;
/**
* Feed scale, etc, enable bits for this move
*/
bool_t override_enabled: 1;
} trajgen_sg_t;
typedef struct
{
/* ptr to the tcs */
trajgen_sg_t queue[TG_QUEUE_SIZE];
/* size of queue */
uint16_t size;
/* number of tcs now in queue */
uint16_t num_elements;
/* indices to next to get, next to put */
uint16_t start, end;
} trajgen_queue_t;
typedef struct
{
real_t override;
pose_t last_position;
} trajgen_coord_internals_t;
typedef struct
{
/* Static configuration, set during initialisation */
trajgen_coord_config_t config;
/* Last error that occurred, only valid if a function does not return 0 */
trajgen_coord_error_t last_error;
/* The buffer containing queued segments */
trajgen_queue_t queue;
/* Defines for subsequent motion where spindle synchronisation is required */
trajgen_coord_motion_sync_t sync;
/* Will be added to (only) the next pushed segments and transferred to
* sync_dio_current when the elements are processed */
trajgen_coord_syncdio_cmd_t sync_dio_command;
/* Contains the actual digital i/o word that has to be transferred to the HAL
* or hardware port register */
trajgen_coord_syncdio_t sync_dio_current;
/* Internally used current values, DO NOT MODIFY */
trajgen_coord_internals_t _i;
/* number of motions blending */
uint16_t num_queued_active;
/* Id of the actual queued segment, can be used to identify the program line
* in gnc programs */
uint32_t sg_id;
/* Id of the next segment that is pushed into the queue */
uint32_t next_sg_id;
/* Subsequent motions: stay within this distance of the programmed path during
* blends */
real_t blending_tolerance;
/* Actual position/pose of the trajectory */
pose_t pose;
/* End position of the last queued segment */
pose_t end_pose;
/* Velocity scaling, normal value is 1.0 (means 100% = specified speed) */
real_t override;
/* Abort in progress */
bool_t is_aborting;
/* Motion paused */
bool_t is_pausing;
/* Queue empty and goal position reached */
bool_t is_done;
/* Subsequent motion: Flags defining which speed overrides are allowed */
uint8_t override_enabled;
} trajgen_coord_t;
#ifdef EXPORT_COORD_TG
/**
* Initialise the planer
*/
extern trajgen_coord_error_t trajgen_coord_init(trajgen_coord_t*, trajgen_coord_config_t config);
/**
* Reset all mutable states, clear queue, but not the config.
*/
extern trajgen_coord_error_t trajgen_coord_reset(trajgen_coord_t*);
/**
* Set the current pose. Use this for initialising or if axes moved manually.
*/
extern trajgen_coord_error_t trajgen_coord_set_position(trajgen_coord_t*, pose_t pos);
/**
* Move a line
*/
extern trajgen_coord_error_t trajgen_coord_add_line(trajgen_coord_t*, pose_t end,
real_t velocity, real_t acceleration);
/**
* Move a circle
*/
extern trajgen_coord_error_t trajgen_coord_add_arc(trajgen_coord_t*, pose_t end,
pose_position_t center, pose_vector_t normal, unsigned n_turns, real_t velocity,
real_t acceleration);
/**
* Move a curve/nurbs segment
*/
extern trajgen_coord_error_t trajgen_coord_add_curve(trajgen_coord_t* tp,
pose_t end, pose_vector_t start_ctrl_point, pose_vector_t end_ctrl_point,
real_t velocity, real_t acceleration);
/**
* Pause motion
*/
extern trajgen_coord_error_t trajgen_coord_pause(trajgen_coord_t*);
/**
* Resume from pause
*/
extern trajgen_coord_error_t trajgen_coord_resume(trajgen_coord_t*);
/**
* Stop motion, clear the queue
*/
extern trajgen_coord_error_t trajgen_coord_abort(trajgen_coord_t*);
/**
* Tick: has to be called with the configured sample frequency.
*/
extern trajgen_coord_error_t trajgen_coord_tick(trajgen_coord_t*);
/**
* Returns nonzero if the queue is full, so you have to wait until pushing
* next motion tasks.
*/
extern bool_t trajgen_coord_is_queue_full(trajgen_coord_t*);
/**
* Returns nonzero if all motion has finished and the planer pose reached the
* destination position.
*/
extern bool_t trajgen_coord_is_done(trajgen_coord_t*);
/**
* Returns the current pose
*/
extern pose_t trajgen_coord_get_position(trajgen_coord_t*);
/**
* Returns the ID of the actually processed task
*/
extern uint32_t trajgen_coord_get_current_id(trajgen_coord_t*);
/**
* Returns the number of currently queued tasks.
*/
extern uint16_t trajgen_coord_get_num_queued(trajgen_coord_t*);
/**
* Returns maximum size of the queue
*/
extern uint16_t trajgen_coord_get_queue_size(trajgen_coord_t*);
/**
* Returns returns the number of currently processed tasks. That can be greater
* one e.g. when blending.
*/
extern uint16_t trajgen_coord_get_num_queued_active(trajgen_coord_t*);
#endif
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="trajgen_jointtg_*: Separate joint motion"> */
/********************************************************************
*
* Simple trajectory planner for one joint. When enabled, corrects the joint
* position based on a given command position and command velocity (maximum
* velocity when override == 1.0).
*
* Can be used for absolute motion, relative motion, position correction (
* (e.g. mouse or jogwheel).
*
********************************************************************/
typedef struct
{
/* Maximum acceleration of this joint for the joint trajgen */
real_t max_acceleration;
/* Maximim positive and negative velocity */
real_t max_velocity;
/* Sample interval, seconds */
real_t sample_interval;
} trajgen_jointtg_config_t;
typedef struct
{
/* The last error that occurred, only valid if a function returns nonzero */
trajgen_jointtg_error_t last_error;
/* Configuration */
trajgen_jointtg_config_t config;
/* Internal actual speed */
real_t velocity;
/* Actual position (also returned by trajgen_jointtg_get_position()) */
real_t position;
/* The position where the tp shall move to */
real_t command_position;
/* The maximum velocity of this move */
real_t command_velocity;
/* Enabled flag */
bool_t is_enabled;
/* Pause flag */
bool_t is_pause;
/* Done flag */
bool_t is_done;
} trajgen_jointtg_t;
#ifdef EXPORT_JOINT_TG
/**
* Initializes the joint generator
* @param trajgen_jointtg_t *tp
* @param trajgen_jointtg_config_t config
* @return trajgen_jointtg_error_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_initialize(trajgen_jointtg_t *tp,
trajgen_jointtg_config_t config);
/**
* Resets the manual operation generator except the configuration.
* @return trajgen_jointtg_error_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_reset(trajgen_jointtg_t *tp);
/**
* Aborts the motion (force velocity to 0.0)
* @return
*/
extern trajgen_jointtg_error_t trajgen_jointtg_set_enabled(trajgen_jointtg_t *tp,
bool_t enabled);
/**
* Input a new command position for the joint
* @return trajgen_jointtg_error_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_set_command_position(trajgen_jointtg_t *tp,
real_t position);
/**
* Input a new velocity for the joint
* @return trajgen_jointtg_error_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_set_command_velocity(trajgen_jointtg_t *tp,
real_t velocity);
/**
* Returns the last generated pose (output)
* @return pose_t
*/
extern real_t trajgen_jointtg_get_current_position(trajgen_jointtg_t *tp);
/**
* Sets the internal position. Used to update when the amplifiers/closed loop
* controls are off
* @param pose_t position
* @return pose_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_set_current_position(trajgen_jointtg_t *tp,
real_t position);
/**
* Calculates a new position from the command velocity
* @return pose_t
*/
extern trajgen_jointtg_error_t trajgen_jointtg_tick(trajgen_jointtg_t *tp);
/**
* Returns true (nonzero) if the command velocity and the actual total velocity
* is zero.
* @return bool_t
*/
extern bool_t trajgen_jointtg_is_done(trajgen_jointtg_t *tp);
#endif
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="trajgen_man_*: Remote/manually operated motion"> */
/********************************************************************
*
* Trajectory generator coordinated motion based on input velocities
* (e.g. Joystick). Output is a pose, which has to be converted to the individual
* joint positions using inverse kinematics.
*
********************************************************************/
typedef struct
{
/* Maximum positive and negative velocity for all axes */
pose_t max_velocity;
/* Maximum acceleration for each axis */
pose_t max_acceleration;
/* Sample interval, seconds */
real_t sample_interval;
} trajgen_man_config_t;
typedef struct
{
/* The last error that occurred, only valid if a function returns nonzero */
trajgen_man_error_t last_error;
/* Configuration, used to init */
trajgen_man_config_t config;
/* The (input) velocities that the endeffector/axes shall move with */
pose_t velocity;
/* Internal actual speed */
pose_t current_velocity;
/* Actual position (also returned by trajgen_man_get_position()) */
pose_t pose;
/* Enabled flag */
bool_t is_enabled;
} trajgen_man_t;
#ifdef EXPORT_MANUAL_TG
/**
* Initializes the manual operation generator
* @param trajgen_man_t *tp
* @param trajgen_man_config_t config
* @return trajgen_man_error_t
*/
extern trajgen_man_error_t trajgen_man_initialize(trajgen_man_t *mtp, trajgen_man_t *_tp,
trajgen_man_config_t config);
/**
* Resets the manual operation generator except the configuration.
* @return trajgen_man_error_t
*/
extern trajgen_man_error_t trajgen_man_reset(trajgen_man_t *mtp);
/**
* Aborts the motion (force velocity to 0.0)
* @return
*/
extern trajgen_man_error_t trajgen_man_abort(trajgen_man_t *mtp);
/**
* Input a new velocity for every axis
* @return trajgen_man_error_t
*/
extern trajgen_man_error_t trajgen_man_set_velocity(trajgen_man_t *mtp, pose_t velocity);
/**
* Returns the last generated pose (output)
* @return pose_t
*/
extern pose_t trajgen_man_get_position(trajgen_man_t *mtp);
/**
* Sets the internal position. Used to update when the amplifiers/closed loop
* controls are off
* @param pose_t position
* @return pose_t
*/
extern trajgen_man_error_t trajgen_man_set_position(trajgen_man_t *mtp, pose_t position);
/**
* Calculates a new position from the command velocity
* @return pose_t
*/
extern trajgen_man_error_t trajgen_man_tick(trajgen_man_t *mtp);
/**
* Returns true (nonzero) if the command velocity and the actual total velocity
* is zero.
* @return bool_t
*/
extern bool_t trajgen_man_is_done(trajgen_man_t *mtp);
#endif
/* </editor-fold> */
/* <editor-fold defaultstate="collapsed" desc="trajgen_*: Main trajectory generator"> */
/**
* Operating mode state, including switching between modes
*/
typedef enum
{
TRAJ_STATE_DISABLED = 0, /* Controller disabled, implicitly stops all motion */
TRAJ_STATE_JOINT, /* Free joint motion */
TRAJ_STATE_COORDINATED, /* Coordinated motion, requires homing */
TRAJ_STATE_MAN, /* Manual operation, requires homing */
TRAJ_______INTERNAL_STATES,
TRAJ_STATE_OK_TO_SWITCH,
TRAJ_STATE_DISABLING,
TRAJ_STATE_JOINT_ENTER,
TRAJ_STATE_JOINT_LEAVE,
TRAJ_STATE_COORDINATED_ENTER,
TRAJ_STATE_COORDINATED_LEAVE,
TRAJ_STATE_TG_MAN_ENTER,
TRAJ_STATE_TG_MAN_LEAVE
} trajgen_state_t;
/**
* Configuration
*/
typedef struct
{
/* Own machine kinematics or NULL for "identity" (x=x,y=y, ...) */
kinematics_t kinematics_functions;
/* Max (axes, not joints) accelerations for manual operation, but corresponds */
/* to joints if the machine type is cartesian */
pose_t max_manual_accelerations;
/* Max (axes, not joints) velocities for manual operation, but corresponds to */
/* joints if the machine type is cartesian */
pose_t max_manual_velocities;
/* Maximum trajectory velocity of the end effector ("tool tip"/"pose") */
real_t max_coord_velocity;
/* Maximum trajectory acceleration of the end effector ("tool tip"/"pose") */
real_t max_coord_acceleration;
/* The interval time of the positioning, equals 1/sample rate */
real_t sample_interval;
/* The number of used joints, NOTE: it corresponds to the LAST USED JOINT + 1) */
/* if there are inactive joints between, they still need to be calculated. */
uint8_t number_of_used_joints;
/* The trajectory generators will run interpolation_rate times slower than the */
/* positioning. Position between are interpolated. */
uint8_t interpolation_rate;
/* All configurations for the joint planers */
trajgen_jointtg_config_t joints[TG_MAX_JOINTS];
} trajgen_config_t;
/**
* Joint definition
*/
typedef struct
{
/* Interpolator data */
interpolator_t interpolator;
/* The trajgen for this axis */
trajgen_jointtg_t tg;
/* Uninterpolated positions */
real_t planer_position;
/* The interpolated position */
real_t position;
/* The interpolated velocity */
real_t velocity;
/* The interpolated acceleration */
real_t acceleration;
/* The interpolated jerk */
real_t jerk;
} trajgen_joint_t;
/**
* Internal state variables
*/
typedef struct
{
uint32_t tick;
real_t* joint_positions[TG_MAX_JOINTS];
real_t* planer_positions[TG_MAX_JOINTS];
} trajgen_internals_t;
/**
* Main trajgen type
*/
typedef struct
{
/* Configuration of the trajectory generator */
trajgen_config_t config;
/* The last error occurred, only valid if a function returns nonzero */
trajgen_error_details_t last_error;
/* The actual operating state of the trajectory generator */
trajgen_state_t state;
/* The externally requested */
trajgen_state_t requested_state;
/* The joint data that the planer needs */
trajgen_joint_t joints[TG_MAX_JOINTS];
/* Instance of the coordinated motion planer */
trajgen_coord_t coord_planer;
/* Instance of the manual operation planer */
trajgen_man_t man_planer;
/* Internal state variables, NO NOT MODIFY */
trajgen_internals_t _internals;
/* The kinematics data structure */
kinematics_t kinematics;
/* The pose of the last planer calculation, not the interpolated one */
pose_t planer_pose;
/* Specifies if the planed motion is done */
bool_t is_done;
} trajgen_t;
/**
* Initialises the trajectory generator and all sub systems. This function
* MUST BE CALLED BEFORE ANY OTHER OPERATIONS with the trajgen, as internal
* pointer require initialisation.
* @param trajgen_t *trajectory_generator_data_location
* @param trajgen_config_t config
* @return trajgen_error_t
*/
extern trajgen_error_t trajgen_initialize(trajgen_t *trajectory_generator_data_location,
trajgen_config_t config);
/**
* Resets the internal state, except the configuration
* @return trajgen_error_t
*/
extern trajgen_error_t trajgen_reset();
/**
* Sets the new requested trajgen state.
* @param trajgen_state_t state
* @return trajgen_error_t
*/
extern trajgen_error_t trajgen_switch_state(trajgen_state_t state);
/**
* Abort the current generator, decelerate to standstill. After aborting you
* should wait for done state.
* @return trajgen_error_t
*/
extern trajgen_error_t trajgen_abort();
/**
* Called frequently with the sample period tg.config.sample_interval.
* @return trajgen_error_t
*/
extern trajgen_error_t trajgen_tick();
/**
* Returns the current tick counter of the TG, which is increased every time
* trajgen_tick() is called.
* @return uint32_t
*/
extern uint32_t trajgen_get_tick();
/**
* Writes build information into the *s.
* @param char* s
* @param unsigned length
* @return const char*
*/
extern const char* trajgen_info(char* s, unsigned length);
/**
* Coordinated planer accessors
* APPLY COORDINATED MOTION PLANER ONLY. See trajgen_coord functions for
* documentation.
*/
extern bool_t trajgen_queue_full();
extern uint32_t trajgen_current_id();
extern uint16_t trajgen_num_queued();
extern uint16_t trajgen_queue_size();
extern trajgen_error_t trajgen_pause();
extern trajgen_error_t trajgen_resume();
extern trajgen_error_t trajgen_add_line(pose_t end, real_t velocity, real_t acceleration);
extern trajgen_error_t trajgen_add_arc(pose_t end, pose_position_t center, pose_vector_t
normal, unsigned n_turns, real_t velocity, real_t acceleration);
extern trajgen_coord_error_t trajgen_add_curve(pose_t end, pose_vector_t start_ctrl_point,
pose_vector_t end_ctrl_point, real_t velocity, real_t acceleration);
/* </editor-fold> */
#ifdef __cplusplus
}
#endif
#endif
Implementation
/*******************************************************************************
* @file trajgen.c
* @author: Stefan Wilhelm (c erberos@atwillys.de)
* @license: BSD (see below)
* @version: 1.0b
* @standard C99
*
* Allows to move the machine tool tip to specified positions, velocities and
* accelerations by modifying a virtual machine position every generator cycle.
* The output is always an updated position for every joint. The closed loop
* controllers of the joints follow this position or, if the cannot, raise a
* trailing error fault.
*
* More details in the header file trajgen.h
*
* Annotations:
*
* - This file contains static variables pointing to your trajgen structure.
* That means it is assumed that you have only one trajectory generator
* in one process. This means as well that you instance of trajgen_t
* shall NOT be moved in memory. This disadvantage is the cost of much
* easier handling of the this piece of software. All static variables
* are assigned when you call trajgen_init(). The normal usage is to
* have a statically or dynamically allocated trajgen_t in the process
* heap or shared memory and then work with this memory block until the
* process quits.
* Interpolators, coordinated, manual and joint planer functions have
* the pointer to "this" as first function argument.
*
* - For performance reasons this file does not use volatile declarations,
* interfacing it to the realtime system is your part. The normal usage
* procedure is:
*
* - (Realtime (timed) thread function start (for the current sample interval)
* - Do pre-checks (motor states/estop/intermediate circuit/sync flags ...)
* - Conditionally trajgen_abort() on error conditions.
* - trajgen_tick();
* - Do post checks with your trajgen_t structure variable (errors etc)
* - Transfer new joint command positions into the motor control registers.
* - Transfer new digital I/O bits into the corresponding registers.
* - Process the real time FIFO/shared memory commands, e.g. to switch
* states, adapt override, add new motion commands, etc.
* - Leave thread function / wait for next sample in thread loop.
*
* -----------------------------------------------------------------------------
* License header: Copyright (c) 2008-2014, Stefan Wilhelm. All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
******************************************************************************/
#include <stdio.h>
// <editor-fold defaultstate="collapsed" desc="Auxiliary functions/decls">
#include "trajgen.h"
/*******************************************************************************
*
* Auxiliary functions and declarations
*
******************************************************************************/
// Version for info
#define TG_VERSION "1.0b"
// Definable memset(0)
#ifndef ZERO_MEMORY
#define ZERO_MEMORY zero_memory
static void zero_memory(void *dst, size_t length)
{
char *p = (char*) dst;
char *end = ((char*) p) + length - sizeof (long);
while ((long*) p < (long*) end) {
*((long*) p) = (long) 0;
p += (unsigned long) sizeof (long);
}
end += sizeof (long);
while (p < end) { *(char*) p = (char) 0; p++; }
}
#endif
// Definable sincos
#ifndef SINCOS
#define SINCOS(x, sx, cx) do { *(sx)=sin((x)); *(cx)=cos((x)); } while(0);
#endif
// Definable square root function
#ifndef TG_SQRT_FN
#define sqrtf sqrt
#else
#define sqrtf TG_SQRT_FN
#endif
// Pointer checks
#ifdef ARG_POINTER_CHECKS
#define CHK_PTR(X) if((X))
#else
#define CHK_PTR(X) if(0)
#endif
#ifndef NO_MOTION_BLENDING
#define NO_MOTION_BLENDING (0)
#else
#undef NO_MOTION_BLENDING
#define NO_MOTION_BLENDING (1)
#endif
/* Max angle between end->start vectors where blending is still allowed */
#ifndef MAX_ALLOWED_BLENDING_ANGLE_DEG
#define MAX_ALLOWED_BLENDING_ANGLE_DEG 85
#endif
#ifndef STRAIGHT_BLENDING_ANGLE
#define STRAIGHT_BLENDING_ANGLE 1
#endif
/* Define it if you don't like inlining */
#ifndef TG_INLINE
#define TG_INLINE __inline__
#endif
// stfwi: redo this a bit nicer
#ifdef TG_DEBUG_LEVEL
#if TG_DEBUG_LEVEL > 0
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
static void tg_debug(const char* fmt, ...)
{ va_list args; va_start (args, fmt); fflush(stdout);
vfprintf(stderr, fmt, args); va_end(args); fflush(stderr); }
#define debug tg_debug
#else
#define debug tg_nodebug
#endif
#if TG_DEBUG_LEVEL < 3
static void tg_nodebug(const char* pattern, ...) { ; }
#endif
#if TG_DEBUG_LEVEL > 1
#define debug2 tg_debug
#else
#define debug2 tg_nodebug
#endif
#if TG_DEBUG_LEVEL > 2
#define debug3 tg_debug
#else
#define debug3 tg_nodebug
#endif
#else
TG_INLINE static void tg_nodebug(const char* pattern, ...) { ; }
#define debug tg_nodebug
#define debug2 tg_nodebug
#define debug3 tg_nodebug
#endif
/*
* Math "resolution", values define when a dimension or angle is treated
* as zero.
*/
#ifndef TG_D_RES
#define TG_D_RES TG_RESOLUTION
#endif
#ifndef TG_A_RES
#define TG_A_RES TG_RESOLUTION
#endif
/*
* Static switches, helps optimising the machine code
*/
#ifdef EXPORT_INTERPOLATORS
#define INTERPOLATION_STATIC
#else
#ifndef INTERPOLATION_STATIC
#define INTERPOLATION_STATIC static TG_INLINE
#endif
#endif
#ifdef EXPORT_JOINT_TG
#define JOINT_TG_STATIC
#else
#ifndef JOINT_TG_STATIC
#define JOINT_TG_STATIC static TG_INLINE
#endif
#endif
#ifdef EXPORT_MANUAL_TG
#define MANUAL_TG_STATIC
#else
#ifndef MANUAL_TG_STATIC
#define MANUAL_TG_STATIC static TG_INLINE
#endif
#endif
#ifdef EXPORT_COORD_TG
#define COORD_TG_STATIC
#define COORD_TG_STATICI
#else
#ifndef COORD_TG_STATIC
#define COORD_TG_STATIC static
#endif
#define COORD_TG_STATICI static TG_INLINE
#endif
static trajgen_error_t tg_err(unsigned code, unsigned joint);
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Math/vector auxiliaries">
/********************************************************************
*
* Vector / robotic auxiliary functions
*
********************************************************************/
#ifndef M_PI
#define M_PI (3.1415926535897932384626433832795029L) /* pi */
#endif
#ifndef M_SQRT2
#define M_SQRT2 (sqrt(2.)) /* sqrt(2) */
#endif
#define sqr(x) ((x)*(x))
#define sqrtz(X) ((X)>0?sqrtf(X):((X)>-TG_RESOLUTION?0:NAN))
#define v_cpy(vs,vd) (*(vd)=(vs))
#define v_magnitude_sq(v) (sqr(v.x)+sqr(v.y)+sqr(v.z))
#define v_magnitude(v) (sqrtz(sqr(v.x)+sqr(v.y)+sqr(v.z)))
#define v_distance(v1,v2) sqrtz(sqr((v2).x-(v1).x)+sqr((v2).y-(v1).y)+sqr((v2).z-(v1).z))
#define v_dot(v1,v2) ((v1).x*(v2).x+(v1).y*(v2).y+(v1).z*(v2).z) /* "dot" scalar product */
#define v_err_t int
#define v_isfinite(p) (isfinite((p).x) && isfinite((p).y) && isfinite((p).z))
#define v_invalidate(p) { (p).x = (p).y = (p).z = NAN; }
#define v_setzero(p) { (p).x=(p).y=(p).z=0; }
#define v_pose_split(pose, xyz, abc, uvw) { \
(xyz)->x=(pose).x; (xyz)->y=(pose).y; (xyz)->z=(pose).z; \
(uvw)->x=(pose).u; (uvw)->y=(pose).v; (uvw)->z=(pose).w; \
(abc)->x=(pose).a; (abc)->y=(pose).b; (abc)->z=(pose).c; \
}
#define v_pose_merge(xyz, abc, uvw, pose) { \
(pose)->x=(xyz).x; (pose)->y=(xyz).y; (pose)->z=(xyz).z; \
(pose)->u=(uvw).x; (pose)->v=(uvw).y; (pose)->w=(uvw).z; \
(pose)->a=(abc).x; (pose)->b=(abc).y; (pose)->c=(abc).z; \
}
TG_INLINE static v_err_t v_scale(pose_vector_t v1, real_t d, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x*d; r->y=v1.y*d; r->z=v1.z*d; return 0; }
TG_INLINE static v_err_t v_sum(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x+v2.x; r->y=v1.y+v2.y; r->z=v1.z+v2.z; return 0; }
TG_INLINE static v_err_t v_sub(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x-v2.x; r->y=v1.y-v2.y; r->z=v1.z-v2.z; return 0; }
TG_INLINE static v_err_t v_cross(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.y*v2.z-v1.z*v2.y; r->y=v1.z*v2.x-v1.x*v2.z;
r->z=v1.x*v2.y-v1.y*v2.x; return 0; }
TG_INLINE static v_err_t v_unit(pose_vector_t v, pose_vector_t * r)
{ CHK_PTR(!r) return 1; real_t l; if ((l=v_magnitude(v)) == 0.0) {
r->x=r->y=r->z=0; return 1; } r->x = v.x/l; r->y = v.y/l; r->z = v.z/l; return 0; }
TG_INLINE static v_err_t v_project(pose_vector_t v1, pose_vector_t v2, pose_vector_t *r)
{ CHK_PTR(!r) return 1; if(v_unit(v2, &v2)) return 1; v_scale(v2, v_dot(v1,v2), r);
return 0; }
TG_INLINE static v_err_t v_plane_project(pose_vector_t v, pose_vector_t normal, pose_vector_t *r)
{ CHK_PTR(!r) return 1; pose_vector_t p; return v_project(v, normal, &p) || v_sub(v, p, r); }
/**
* Defines a line, precalculates characteristic values.
* @param pose_line_t *line
* @param pose_position_t start
* @param pose_position_t end
* @return int
*/
static v_err_t v_lin_define(pose_line_t *line, pose_position_t start, pose_position_t end)
{
CHK_PTR(!line) return 1;
line->start = start;
line->end = end;
v_sub(end, start, &line->u);
line->tm = v_magnitude(line->u);
if (line->tm < TG_D_RES) {
line->u.x = 1;
line->tm = line->u.y = line->u.z = 0;
} else if(v_unit(line->u, &line->u)) {
line->tm = line->u.x = line->u.y = line->u.z = 0;
return 1;
}
return 0;
}
/**
* Calculates a point for a line motion from a progress (current one-dim position)
* @param pose_line_t *line
* @param real_t progress
* @param pose_position_t *point
* @return int
*/
static v_err_t v_lin_get(pose_line_t *line, real_t progress, pose_position_t *point)
{
CHK_PTR(!line || !point) return 1;
if (line->tm <= 0 || progress >= line->tm) {
*point = line->end;
} else {
v_scale(line->u, progress, point);
v_sum(line->start, *point, point);
}
return 0;
}
/**
* Create a circle/arc/helix definition, precalculate characteristics.
* @param pose_circle_t *circle
* @param pose_position_t start
* @param pose_position_t end
* @param pose_vector_t center
* @param pose_vector_t normal
* @param int n_turns
* @return int
*/
static v_err_t v_arc_define(pose_circle_t * circle, pose_position_t start,
pose_position_t end, pose_vector_t center, pose_vector_t normal, unsigned n_turns)
{
CHK_PTR(!circle) return 1;
pose_vector_t v, e; // v:aux, e: rev. end vector
real_t d;
v_sub(start, center, &v); // determine projected centre point
if(v_project(v, normal, &v)) return 1;
v_sum(v, center, &circle->cp); // real center: point on normal in plane of the start point
v_unit(normal, &circle->nv); // unit normal vector (below: negative turns -> other direction)
circle->r = v_distance(start, circle->cp); // radius from start point
if(circle->r < TG_D_RES) return 1; // start radius 0
v_sub(end, center, &v); // determine projected centre point
if(v_project(v, normal, &v)) return 1;
v_sub(start, circle->cp, &circle->rt); // tangent normal vector
v_cross(circle->nv, circle->rt, &circle->rp); // vector in helix direction
v_sub(end, circle->cp, &circle->rh); // helix
v_plane_project(circle->rh, circle->nv, &e);
circle->s = v_magnitude(e) - circle->r;
v_sub(circle->rh, e, &circle->rh);
v_unit(e, &e);
v_scale(e, circle->r, &e);
if(v_magnitude(e) == 0) { v_scale(circle->nv, FLOAT_EPS, &v); v_sum(e, v, &e); }
d = v_dot(circle->rt, e)/sqr(circle->r); // determine angle
circle->a = d>1.0 ? 0.0 : (d<-1?M_PI : acos(d));
v_cross(circle->rt, e, &v); // Angle correction (0->PI --> PI->2PI)
if(v_dot(v, circle->nv) < 0) circle->a = (2.0*M_PI) - circle->a;
if(circle->a > -(TG_A_RES) && circle->a < (TG_A_RES)) circle->a = 0;
if(n_turns > 0) circle->a += n_turns * 2.0*M_PI; // add turns
circle->l = sqrtz(sqr(circle->a * circle->r) + sqr(v_magnitude(circle->rh))); // Segment length
return (circle->a == 0); // angle 0 -> error
}
/**
* Returns a point on a circle at a given angle (progress).
* @param pose_circle_t *circle
* @param real_t angle
* @param pose_position_t *point
* @return int
*/
static v_err_t v_arc_get(pose_circle_t * circle, real_t progress, pose_position_t *point)
{
CHK_PTR(!circle || !point) return 1;
pose_vector_t p, s;
real_t sx, cx;
progress /= circle->l; // length --> 0..1
SINCOS(progress * circle->a, &sx, &cx); // length --> angle
v_scale(circle->rp, sx, &s);
v_scale(circle->rt, cx, &p);
v_sum(p, s, point); // radius vector to center
v_unit(*point, &p); // add scaled radius vector
v_scale(p, progress * circle->s, &p);// parallel component
v_sum(*point, p, point);
v_scale(circle->rh, progress, &s); // perpendicular component
v_sum(*point, s, point);
v_sum(circle->cp, *point, point); // translate relative to center point
return 0;
}
#ifdef WITH_CURVES
/* Experimental, not places this code yet. */
#include "curve.inc.c"
#endif
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="interpolator_*: Interpolator">
/*******************************************************************************
*
* Interpolator (cubic spline)
*
******************************************************************************/
#ifndef NO_INTERPOLATION
INTERPOLATION_STATIC bool_t interpolator_need_update(interpolator_t *ci)
{
CHK_PTR(!ci) return INTERPOLATOR_ERROR_NULLPOINTER;
return ci->n<4;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_push(interpolator_t * ip,
real_t point)
{
debug3("# ip: push %g\n", point);
// TG_CC_FN_CALL(TG_CC_interpolator_push);
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
if (ip->n >= 4) {
return INTERPOLATOR_ERROR_QUEUE_FULL;
} else if (ip->n == 0) {
return INTERPOLATOR_ERROR_NOT_RESET;
} else {
ip->x0 = ip->x1;
ip->x1 = ip->x2;
ip->x2 = ip->x3;
ip->x3 = point;
ip->n++;
}
real_t T = ip->T;
ip->w0 = (ip->x0 + 4.0*ip->x1 + ip->x2) / 6.0;
ip->w1 = (ip->x1 + 4.0*ip->x2 + ip->x3) / 6.0;
ip->v0 = ((ip->x2) - (ip->x0)) / (2.0 * (T));
ip->v1 = ((ip->x3) - (ip->x1)) / (2.0 * (T));
ip->d = ip->w0;
ip->c = ip->v0;
ip->b = 3 * (ip->w1 - ip->w0)/(T*T) - (2 * ip->v0 + ip->v1)/T;
ip->a = (ip->v1 - ip->v0)/(3.0*(T*T)) - (2.0 * ip->b)/(3.0*T);
ip->t = 0.0;
return INTERPOLATOR_OK;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_reset(interpolator_t * ip,
real_t initial_position)
{
// TG_CC_FN_CALL(TG_CC_interpolator_reset);
debug3("# ip: reset\n");
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
ip->n = 1;
ip->t = 0.0;
ip->x0 = ip->x1 = ip->x2 = ip->x3 = ip->w0 = ip->w1 = ip->d = initial_position;
ip->v0 = ip->v1 = ip->a = ip->b = ip->c = 0.0;
return interpolator_push(ip, initial_position);
}
INTERPOLATION_STATIC interpolator_error_t interpolator_init(interpolator_t * ip,
real_t sample_interval, unsigned interpolation_rate)
{
debug3("# ip: init\n");
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
if (sample_interval <= 0.0 || interpolation_rate < 1) {
return INTERPOLATOR_ERROR_INIT_ARG_INVALID;
}
ip->T = sample_interval;
ip->Ti = ip->T / interpolation_rate;
interpolator_reset(ip, 0);
return INTERPOLATOR_OK;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_interpolate(interpolator_t *ip,
real_t *x, real_t *v, real_t *a, real_t *j)
{
// TG_CC_FN_CALL(TG_CC_interpolator_interpolate);
CHK_PTR(!ip||!x||!v||!a||!j) return INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER;
if (ip->n<4) interpolator_push(ip, ip->x3); // auto refill
if(ip->x3 == ip->x2 && ip->x1 == ip->x3) {
*x = ip->x3; *v = *a = *j = 0;
debug3("# ip: interpolate: positions equal: %g (v-0, a=0)\n", ip->x3);
} else {
real_t t = ip->t; // let the compiler optimise all this
*x = (((ip->a*t + ip->b)*t) + ip->c) * t + ip->d;
*v = ((3.0 * ip->a*t) + 2.0 * ip->b) * t + ip->c;
*a = 6.0 * ip->a*t + 2.0 * ip->b;
*j = 6.0 * ip->a;
debug3("# ip: interpolate: s%g v%g a%g\n", *x, *v, *a);
}
ip->t += ip->Ti;
if (fabs(ip->T - ip->t) < 0.5 * ip->Ti) ip->n--;
return INTERPOLATOR_OK;
}
#else
INTERPOLATION_STATIC bool_t interpolator_need_update(interpolator_t *ip)
{
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
return ip->n<4;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_push(interpolator_t * ip,
real_t point)
{
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
if (ip->n >= 4) {
return INTERPOLATOR_ERROR_QUEUE_FULL;
} else if (ip->n == 0) {
return INTERPOLATOR_ERROR_NOT_RESET;
} else {
ip->x0 = ip->x1;
ip->x1 = ip->x2;
ip->x2 = ip->x3;
ip->x3 = point;
ip->n++;
}
return INTERPOLATOR_OK;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_reset(interpolator_t * ip,
real_t initial_position)
{
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
ip->n = 2;
ip->t = 0.0;
ip->x0 = ip->x1 = ip->x2 = ip->x3 = ip->w0 = ip->w1 = ip->d = initial_position;
ip->v0 = ip->v1 = ip->a = ip->b = ip->c = 0.0;
return interpolator_push(ip, initial_position);
}
INTERPOLATION_STATIC interpolator_error_t interpolator_init(interpolator_t * ip,
real_t sample_interval, unsigned interpolation_rate)
{
CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
if (sample_interval <= 0.0 || interpolation_rate != 1) {
return INTERPOLATOR_ERROR_INIT_ARG_INVALID;
}
ip->T = sample_interval;
ip->Ti = 1.0/ip->T;
interpolator_reset(ip, 0);
return INTERPOLATOR_OK;
}
INTERPOLATION_STATIC interpolator_error_t interpolator_interpolate(interpolator_t *ip,
real_t *x, real_t *v, real_t *a, real_t *j)
{
CHK_PTR(!ip || !x || !v || !a || !j) return INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER;
if (ip->n<4) interpolator_push(ip, ip->x3);
if(ip->x3 == ip->x2 && ip->x1 == ip->x3) {
*x = ip->x3; *v = *a = *j = 0;
} else {
*x = ip->x3;
*v = (ip->x3 - ip->x2) * ip->Ti;
*a = (*v-ip->v0) * ip->Ti;
*j = (*a-ip->v1) * ip->Ti;
ip->v0 = *v;
ip->v1 = *a;
}
ip->n--;
return INTERPOLATOR_OK;
}
#endif
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="kinematics_*: Default identity kinematic model">
/*******************************************************************************
*
* Kinematics manager. Built-in default is an identity system.
* See header file for documentation about how to set your own kinematics.
*
******************************************************************************/
/**
* Default passthrough forward kinematics
* @param real_t *joint_positions
* @param pose_t *world
* @return kinematics_error_t
*/
static kinematics_error_t kinematics_identity_forward(real_t **joint_positions,
pose_t *world)
{
CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
world->x = *(joint_positions[0]);
world->y = *(joint_positions[1]);
world->z = *(joint_positions[2]);
world->a = *(joint_positions[3]);
world->b = *(joint_positions[4]);
world->c = *(joint_positions[5]);
world->u = *(joint_positions[6]);
world->v = *(joint_positions[7]);
world->w = *(joint_positions[8]);
return KINEMATICS_ERR_OK;
}
/**
* Default passthrough inverse kinematics
* @param pose_t *world
* @param real_t *joint_positions
* @return kinematics_error_t
*/
static kinematics_error_t kinematics_identity_inverse(pose_t *world, real_t **joint_positions)
{
CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
*(joint_positions[0]) = world->x;
*(joint_positions[1]) = world->y;
*(joint_positions[2]) = world->z;
*(joint_positions[3]) = world->a;
*(joint_positions[4]) = world->b;
*(joint_positions[5]) = world->c;
*(joint_positions[6]) = world->u;
*(joint_positions[7]) = world->v;
*(joint_positions[8]) = world->w;
return KINEMATICS_ERR_OK;
}
/**
* Default passthrough kinematics homeing
* @param pose_t *world
* @param real_t *joint_positions
* @return kinematics_error_t
*/
static kinematics_error_t kinematics_identity_home(pose_t *world, real_t **joint_positions)
{
CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
return kinematics_identity_forward(joint_positions, world);
}
/**
* Selects and initializes a machine type, sets the appropriate pointers to the
* associated functions.
* @param kinematics_t *kin
* @param kinematics_t *device_kinematics
* @return kinematics_error_t
*/
kinematics_error_t kinematics_initialize(kinematics_t *kin, kinematics_t device_kinematics)
{
if (!kin) return KINEMATICS_ERR_NULL_POINTER;
kin->forward = kinematics_identity_forward;
kin->inverse = kinematics_identity_inverse;
kin->reset = kinematics_identity_home;
if(device_kinematics.forward) kin->forward = device_kinematics.forward;
if(device_kinematics.inverse) kin->inverse = device_kinematics.inverse;
if(device_kinematics.reset) kin->reset = device_kinematics.reset;
if((!device_kinematics.forward) ^ (!device_kinematics.inverse)) {
return KINEMATICS_ERR_INIT_FUNCTION_NULL;
}
return KINEMATICS_ERR_OK;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="trajgen_coord_*: Coordinated motion planer">
/*******************************************************************************
*
*
* Coordinated motion planner.
*
*
******************************************************************************/
/**
* Error callback wrapper, passes through the error code without changing.
* @param trajgen_coord_error_t e
* @return trajgen_coord_error_t
*/
static TG_INLINE trajgen_coord_error_t trajgen_coord_raise_error(trajgen_coord_t* tp,
trajgen_coord_error_t e)
{
if (!tp) return e;
tg_err(e, 0);
return tp->last_error = e;
}
/**
* Calculates a unit vector for starting a segment.
* @param trajgen_sg_t *sg
* @param pose_vector_t* result
* @return trajgen_coord_error_t
*/
static trajgen_coord_error_t trajgen_sg_start_direction(trajgen_coord_t* tp,
trajgen_sg_t *sg, pose_vector_t *result)
{
CHK_PTR(!sg || !result) return TP_ERR_TP_NULL_POINTER;
switch (sg->motion_type) {
case SG_LINEAR:
{
v_sub(sg->coords.line.xyz.end, sg->coords.line.xyz.start, result);
v_unit(*result, result);
return TP_ERR_OK;
}
case SG_CIRCULAR:
{
pose_position_t startpoint;
pose_vector_t radius, tan, perp;
v_arc_get(&sg->coords.arc.xyz, 0.0, &startpoint);
v_sub(startpoint, sg->coords.arc.xyz.cp, &radius);
v_cross(sg->coords.arc.xyz.nv, radius, &tan);
v_unit(tan, &tan);
v_sub(sg->coords.arc.xyz.cp, startpoint, &perp);
v_unit(perp, &perp);
v_scale(tan, sg->a, &tan);
v_scale(perp, sqr(0.5 * sg->v) / sg->coords.arc.xyz.r, &perp);
v_sum(tan, perp, result);
v_unit(*result, result);
return TP_ERR_OK;
}
#ifdef WITH_CURVES
case SG_CURVE:
{
v_cpy(sg->coords.curve.xyz.suv, result);
return TP_ERR_OK;
}
#endif
}
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
}
/**
* Calculates a unit vector at the end of a segment.
* @param trajgen_sg_t *sg
* @param pose_vector_t* result
* @return trajgen_coord_error_t
*/
static trajgen_coord_error_t trajgen_sg_end_direction(trajgen_coord_t* tp,
trajgen_sg_t *sg, pose_vector_t *result)
{
CHK_PTR(!sg || !result) return TP_ERR_TP_NULL_POINTER;
switch (sg->motion_type) {
case SG_LINEAR:
{
v_sub(sg->coords.line.xyz.end, sg->coords.line.xyz.start, result);
v_unit(*result, result);
return TP_ERR_OK;
}
case SG_CIRCULAR:
{
pose_position_t endpoint;
pose_vector_t radius;
v_arc_get(&sg->coords.arc.xyz, sg->coords.arc.xyz.a, &endpoint);
v_sub(endpoint, sg->coords.arc.xyz.cp, &radius);
v_cross(sg->coords.arc.xyz.nv, radius, result);
v_unit(*result, result);
return TP_ERR_OK;
}
#ifdef WITH_CURVES
case SG_CURVE:
{
v_cpy(sg->coords.curve.xyz.euv, result);
return TP_ERR_OK;
}
#endif
}
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
}
/**
* Push a new segment into the queue.
*/
static TG_INLINE trajgen_coord_error_t trajgen_coord_queue_push_back(trajgen_coord_t* tp,
trajgen_sg_t *sg)
{
CHK_PTR(sg) return trajgen_coord_raise_error(tp, TP_ERR_TP_NULL_POINTER);
unsigned next = (tp->queue.end + 1) % tp->queue.size;
if (next == tp->queue.start) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
tp->queue.queue[tp->queue.end] = *sg;
tp->queue.num_elements++;
tp->queue.end = next;
debug2("# tq: pushed segment %lu\n", (unsigned long)sg->id);
return TP_ERR_OK;
}
/**
* Removes n elements from the front of the queue
* @return trajgen_coord_error_t
*/
static TG_INLINE trajgen_coord_error_t trajgen_coord_queue_pop_front(trajgen_coord_t* tp,
int n)
{
if (n <= 0) return TP_ERR_OK;
if (n > tp->queue.num_elements) {
return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE);
}
debug2("# tq: popped segment %lu\n", (unsigned long)(tp->queue.queue[tp->queue.start].id));
tp->queue.start = (tp->queue.start + n) % tp->queue.size;
tp->queue.num_elements -= n;
return TP_ERR_OK;
}
/**
* Returns the head item of the queue
* @return trajgen_sg_t *
*/
static TG_INLINE trajgen_sg_t *trajgen_coord_queue_front(trajgen_coord_t* tp)
{ return (tp->queue.num_elements==0) ? (trajgen_sg_t*) 0 :
(&(tp->queue.queue[tp->queue.start])); }
/**
* Returns a queue item by value without modifying the queue
* @return trajgen_sg_t *
*/
static TG_INLINE trajgen_sg_t *trajgen_coord_queue_get(trajgen_coord_t* tp, unsigned n)
{ return (n>=tp->queue.num_elements) ? (trajgen_sg_t*) 0 :
&(tp->queue.queue[(tp->queue.start + n) % tp->queue.size]); }
/**
* Returns true if the queue is completely full. For margin checks, the function
* trajgen_coord_get_num_queued() can be used in combination with the known size.
* @return bool_t
*/
COORD_TG_STATICI bool_t trajgen_coord_is_queue_full(trajgen_coord_t* tp)
{ return tp->queue.num_elements >= tp->queue.size-1; }
//{ return (bool_t) (((tp->queue.end+1) % tp->queue.size) == tp->queue.start); }
/**
* Returns the size (maximum length) of the queue
* @return uint16_t
*/
COORD_TG_STATICI uint16_t trajgen_coord_get_queue_size(trajgen_coord_t* tp)
{ return tp->queue.size; }
/**
* Resets the planner without overwriting the configuration and actual position
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_reset(trajgen_coord_t* tp)
{
CHK_PTR(!tp) return TP_ERR_TP_NULL_POINTER;
tp->last_error = TP_ERR_OK;
tp->end_pose = tp->_i.last_position = tp->pose;
tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
tp->sg_id = tp->next_sg_id = tp->num_queued_active = 0;
tp->is_aborting = tp->is_pausing = 0;
tp->sync_dio_current = tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
tp->override = tp->_i.override = 1.0;
tp->override_enabled = 1;
tp->sync.type = TP_SYNC_NONE;
tp->sync.reference = 0;
tp->sync.i_offset = tp->sync.i_isset = 0;
tp->is_done = 1;
return TP_ERR_OK;
}
/**
* Initializes the planner
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_init(trajgen_coord_t *tp,
trajgen_coord_config_t config)
{
if (!tp) return TP_ERR_TP_NULL_POINTER;
ZERO_MEMORY(tp, sizeof (trajgen_coord_t)); // Set all values and pointers to 0
trajgen_coord_reset(tp);
tp->queue.size = TG_QUEUE_SIZE;
tp->config = config;
if (config.max_acceleration <= 0.0) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
} else if (config.max_velocity <= 0.0) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
} else if (config.sample_interval <= 0.0) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SAMPLE_INTERVAL);
} else {
return TP_ERR_OK;
}
}
/**
* Set the current position of the planer. DO NOT TO THIS WHEN THE planer is
* running (done!=1).
* @param pose_t pos
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_set_position(trajgen_coord_t* tp,
pose_t p)
{ tp->pose = tp->end_pose = p; return TP_ERR_OK; }
/**
* Pause motion
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_pause(trajgen_coord_t* tp)
{ tp->is_pausing = 1; return TP_ERR_OK; }
/**
* Resume from pause state
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_resume(trajgen_coord_t* tp)
{ tp->is_pausing = 0; return TP_ERR_OK; }
/**
* Abort all coordinated motion
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_abort(trajgen_coord_t* tp)
{
CHK_PTR(!tp) return TP_ERR_TP_NULL_POINTER;
if (!tp->is_aborting) tp->is_pausing = tp->is_aborting = 1;
tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
return TP_ERR_OK;
}
/**
* Returns the id of the last motion that is currently executing.
* @return uint32_t
*/
COORD_TG_STATICI uint32_t trajgen_coord_get_current_id(trajgen_coord_t* tp)
{ return tp->sg_id; }
/**
* Returns the actual output position of the generator
* @return pose_t
*/
COORD_TG_STATICI pose_t trajgen_coord_get_position(trajgen_coord_t* tp)
{ return tp->pose; }
/**
* Returns nonzero if the generator finished processing the queue and
* reached the target position.
* @return bool_t
*/
COORD_TG_STATICI bool_t trajgen_coord_is_done(trajgen_coord_t* tp)
{ return tp->is_done; }
/**
* Returns the number of currently queued items
* @return uint16_t
*/
COORD_TG_STATICI uint16_t trajgen_coord_get_num_queued(trajgen_coord_t* tp)
{ return tp->queue.num_elements; }
/**
* Returns the number of currently processed items in the queue
* @return uint16_t
*/
COORD_TG_STATICI uint16_t trajgen_coord_get_num_queued_active(trajgen_coord_t* tp)
{ return tp->num_queued_active; }
/**
* Add a straight line into the queue. Current speed/accel/io settings apply.
* @param pose_t end_point
* @param real_t velocity
* @param real_t acceleration
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_line(trajgen_coord_t* tp,
pose_t end_point, real_t velocity, real_t acceleration)
{
trajgen_sg_t sg;
//pose_line_t line_xyz, line_uvw, line_abc;
pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
sg.sync_dio = tp->sync_dio_command;
tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
} else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
} else if(!pose_isfinite(end_point)) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
}
v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
v_pose_split(end_point, &end_xyz, &end_abc, &end_uvw);
if(
v_lin_define(&sg.coords.line.xyz, start_xyz, end_xyz)
#ifndef NO_UVW
|| v_lin_define(&sg.coords.line.uvw, start_uvw, end_uvw)
#endif
#ifndef NO_ABC
|| v_lin_define(&sg.coords.line.abc, start_abc, end_abc)
#endif
) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
}
#ifdef NO_ABC
v_setzero(sg.coords.line.abc.start);
v_setzero(sg.coords.line.abc.end);
v_setzero(sg.coords.line.abc.u);
sg.coords.line.abc.tm = 0;
#endif
#ifdef NO_UVW
v_setzero(sg.coords.line.uvw.start);
v_setzero(sg.coords.line.uvw.end);
v_setzero(sg.coords.line.uvw.u);
sg.coords.line.uvw.tm = 0;
#endif
sg.length = (sg.coords.line.xyz.tm>TG_D_RES) ? sg.coords.line.xyz.tm :
(sg.coords.line.uvw.tm>TG_D_RES) ? (sg.coords.line.uvw.tm) : (sg.coords.line.abc.tm);
if(sg.length < TG_RESOLUTION) {
sg.length = 0;
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
}
sg.id = tp->next_sg_id;
sg.is_active = 0;
sg.v = velocity;
sg.a = acceleration;
sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
sg.sync_type = tp->sync.type;
sg.override_enabled = tp->override_enabled;
sg.motion_type = SG_LINEAR;
if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
return tp->last_error;
}
tp->end_pose = end_point; // remember the end of this move, as it's the start of the next one.
tp->next_sg_id++;
tp->is_done = 0;
return TP_ERR_OK;
}
/**
* Push an arc/circle/helix motion into the queue. Current speed/accel/io
* settings apply.
* @param pose_t end
* @param pose_vector_t center
* @param pose_vector_t normal
* @param unsigned n_turns
* @param real_t velocity
* @param real_t acceleration
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_arc(trajgen_coord_t* tp,
pose_t end, pose_position_t center, pose_vector_t normal, unsigned n_turns,
real_t velocity, real_t acceleration)
{
trajgen_sg_t sg;
pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
sg.sync_dio = tp->sync_dio_command;
tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
sg.motion_type = SG_CIRCULAR;
sg.id = tp->next_sg_id;
sg.is_active = 0;
sg.sync_type = tp->sync.type;
sg.override_enabled = tp->override_enabled;
sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
} else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
} else if(!pose_isfinite(end) || !v_isfinite(center) || !v_isfinite(normal)) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
}
sg.v = velocity;
sg.a = acceleration;
v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
v_pose_split(end, &end_xyz, &end_abc, &end_uvw);
#ifdef NO_ABC
v_setzero(sg.coords.arc.abc.start);
v_setzero(sg.coords.arc.abc.end);
v_setzero(sg.coords.arc.abc.u);
sg.coords.arc.abc.tm = 0;
#endif
#ifdef NO_UVW
v_setzero(sg.coords.arc.uvw.start);
v_setzero(sg.coords.arc.uvw.end);
v_setzero(sg.coords.arc.uvw.u);
sg.coords.arc.uvw.tm = 0;
#endif
if(
v_distance(start_xyz, center) < TG_RESOLUTION ||
v_distance(end_xyz, center) < TG_RESOLUTION
) {
debug("# tp: trajgen_coord_add_arc(): distance from center ~== 0\n");
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
} else if(
v_arc_define(&sg.coords.arc.xyz, start_xyz, end_xyz, center, normal, n_turns)
#ifndef NO_ABC
|| v_lin_define(&sg.coords.arc.abc, start_abc, end_abc)
#endif
#ifndef NO_UVW
|| v_lin_define(&sg.coords.arc.uvw, start_uvw, end_uvw)
#endif
) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
} else if(sg.coords.arc.xyz.a == 0) {
debug("# tp: trajgen_coord_add_arc(): arc.a===0\n");
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
}
sg.length = sg.coords.arc.xyz.l;
if(sg.length < TG_RESOLUTION) {
sg.length = 0;
debug("# tp: trajgen_coord_add_arc(): sg.length~==0\n");
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
}
if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
return tp->last_error;
}
tp->end_pose = end;
tp->next_sg_id++;
tp->is_done = 0;
return TP_ERR_OK;
}
/**
* Push a curve/nurbs segment.
* @param pose_t end
* @param pose_vector_t start_handle
* @param pose_vector_t end_handle
* @param real_t velocity
* @param real_t acceleration
* @return trajgen_coord_error_t
*/
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_curve(trajgen_coord_t* tp,
pose_t end, pose_vector_t start_ctrl_point, pose_vector_t end_ctrl_point,
real_t velocity, real_t acceleration)
{
#ifdef WITH_CURVES
trajgen_sg_t sg;
pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
sg.sync_dio = tp->sync_dio_command;
tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
sg.motion_type = SG_CURVE;
sg.id = tp->next_sg_id;
sg.is_active = 0;
sg.sync_type = tp->sync.type;
sg.override_enabled = tp->override_enabled;
sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
} else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
} else if(!pose_isfinite(end) || !v_isfinite(start_ctrl_point) || !v_isfinite(end_ctrl_point)) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
}
sg.v = velocity;
sg.a = acceleration;
v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
v_pose_split(end, &end_xyz, &end_abc, &end_uvw);
#ifdef NO_ABC
v_setzero(sg.coords.curve.abc.start);
v_setzero(sg.coords.curve.abc.end);
v_setzero(sg.coords.curve.abc.u);
sg.coords.curve.abc.tm = 0;
#endif
#ifdef NO_UVW
v_setzero(sg.coords.curve.uvw.start);
v_setzero(sg.coords.curve.uvw.end);
v_setzero(sg.coords.curve.uvw.u);
sg.coords.curve.uvw.tm = 0;
#endif
if(
v_distance(start_xyz, end_xyz) < TG_RESOLUTION
) {
debug("# tp: trajgen_coord_add_curve(): segment length ~== 0\n");
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
} else if(
v_curve_define(&sg.coords.curve.xyz, start_xyz, end_xyz, start_ctrl_point, end_ctrl_point)
#ifndef NO_ABC
|| v_lin_define(&sg.coords.curve.abc, start_abc, end_abc)
#endif
#ifndef NO_UVW
|| v_lin_define(&sg.coords.curve.uvw, start_uvw, end_uvw)
#endif
) {
return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
}
sg.length = sg.coords.curve.xyz.len;
if(sg.length < TG_RESOLUTION) {
sg.length = 0;
debug("# tp: trajgen_coord_add_curve(): sg.length~==0\n");
return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
} else if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
return tp->last_error;
}
tp->end_pose = end;
tp->next_sg_id++;
tp->is_done = 0;
return TP_ERR_OK;
#else
return TP_ERR_INVALID_MOTION_TYPE;
#endif
}
/**
* Initialises auxiliary variables of a segment in the queue.
* @param trajgen_sg_t *sg
* @return trajgen_coord_error_t
*/
static TG_INLINE trajgen_coord_error_t trajgen_sg_activate(trajgen_sg_t *sg)
{
CHK_PTR(!sg) return TP_ERR_TP_NULL_POINTER;
if(sg->is_active) return TP_ERR_OK;
sg->is_active = 1;
sg->is_blending = 0;
sg->blending_velocity = 0.0;
sg->progress = 0.0;
sg->current_velocity = 0.0;
return TP_ERR_OK;
}
/**
* Writes the start/end position of a trajectory segment into pos.
* @param trajgen_sg_t *sg
* @param bool_t of_endpoint
* @param pose_t* pos
* @return trajgen_coord_error_t
*/
typedef enum { sg_of_curr=0, sg_of_end } trajgen_sg_get_pose_sel;
static trajgen_coord_error_t trajgen_sg_get_pose(trajgen_coord_t* tp, trajgen_sg_t *sg,
trajgen_sg_get_pose_sel of_endpoint, pose_t* pos)
{
CHK_PTR(!tp || !sg || !pos) return TP_ERR_TP_NULL_POINTER;
pose_position_t xyz, abc, uvw;
real_t progress = of_endpoint==sg_of_end ? sg->length : sg->progress;
#ifdef NO_ABC
v_setzero(abc);
#endif
#ifdef NO_UVW
v_setzero(uvw);
#endif
switch (sg->motion_type) {
case SG_LINEAR:
if (sg->coords.line.xyz.tm > 0.) {
v_lin_get(&sg->coords.line.xyz, progress, &xyz);
#ifndef NO_ABC
v_lin_get(&sg->coords.line.abc, progress * sg->coords.line.abc.tm/sg->length, &abc);
#endif
#ifndef NO_UVW
v_lin_get(&sg->coords.line.uvw, progress * sg->coords.line.uvw.tm/sg->length, &uvw);
#endif
#ifndef NO_ABC
} else if (sg->coords.line.abc.tm > 0.) {
v_lin_get(&sg->coords.line.abc, progress, &abc);
xyz = sg->coords.line.xyz.start;
#ifndef NO_UVW
uvw = sg->coords.line.uvw.start;
#endif
#endif
#ifndef NO_UVW
} else if (sg->coords.line.uvw.tm > 0.) {
v_lin_get(&sg->coords.line.uvw, progress, &uvw);
xyz = sg->coords.line.xyz.start;
#ifndef NO_ABC
abc = sg->coords.line.abc.start;
#endif
#endif
} else {
xyz = sg->coords.line.xyz.start;
#ifndef NO_ABC
abc = sg->coords.line.abc.start;
#endif
#ifndef NO_UVW
uvw = sg->coords.line.uvw.start;
#endif
}
break;
case SG_CIRCULAR:
// Args checked in add function
//v_arc_get(&sg->coords.arc.xyz, progress * sg->coords.arc.xyz.a/sg->length, &xyz);
v_arc_get(&sg->coords.arc.xyz, progress, &xyz);
#ifndef NO_ABC
v_lin_get(&sg->coords.arc.abc, progress * sg->coords.arc.abc.tm/sg->length, &abc);
#endif
#ifndef NO_UVW
v_lin_get(&sg->coords.arc.uvw, progress * sg->coords.arc.uvw.tm/sg->length, &uvw);
#endif
break;
case SG_CURVE:
#ifdef WITH_CURVES
// Curve length checked in add function
v_curve_get(&sg->coords.curve.xyz, progress, &xyz);
#ifndef NO_ABC
v_lin_get(&sg->coords.curve.abc, progress * sg->coords.curve.abc.tm/sg->length, &abc);
#endif
#ifndef NO_UVW
v_lin_get(&sg->coords.curve.uvw, progress * sg->coords.curve.uvw.tm/sg->length, &uvw);
#endif
break;
#endif
default:
v_invalidate(xyz); v_invalidate(abc); v_invalidate(uvw);
trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
}
v_pose_merge(xyz, abc, uvw, pos);
return TP_ERR_OK;
}
/**
* Calculates the next linearised position for a segment
* v= new velocity, a=new acceleration, v0=velocity without override constrain.
*
* @param trajgen_sg_t *sg
* @param real_t *overrun
* @param real_t *v
* @param real_t *a
* @param real_t *v0
* @return trajgen_coord_error_t
*/
static trajgen_coord_error_t trajgen_sg_tick(trajgen_coord_t* tp, trajgen_sg_t *sg,
real_t *overrun, real_t *v, real_t *a, real_t *v0)
{
CHK_PTR(!tp || !sg) return TP_ERR_TP_NULL_POINTER;
real_t d=0, vn0=0, vn, an, T=tp->config.sample_interval;
if ((sg->progress >= sg->length)
|| ((d = sg->current_velocity*T + 2.*(sg->progress-sg->length)) >= 0)
|| ((vn0=(sqrtz(T*T/4-d/sg->a) - T/2) * sg->a) < TG_RESOLUTION)
) {
an = vn = vn0 = 0.0;
d = sg->progress - sg->length;
sg->progress = sg->length;
} else {
if (vn0 > tp->config.max_velocity) vn0 = tp->config.max_velocity;
if (vn0 > sg->v) vn0 = sg->v;
if ((vn=vn0) > sg->v * tp->_i.override) vn = sg->v * tp->_i.override;
an = (vn - sg->current_velocity) / T;
real_t aa = sg->a ;
an = (an > aa) ? (aa) : ((an < -aa) ? (-aa) : (an));
vn = sg->current_velocity + an * T;
sg->progress += (vn + sg->current_velocity) * T/2;
d = sg->progress - sg->length;
if(d >= 0) { sg->progress = sg->length; vn = an = vn0 = 0; }
}
sg->current_velocity = vn;
// if(next_sg && next_sg->progress == 0) next_sg->progress = d;
if(overrun && d > 0) *overrun = d;
if (v) *v = vn;
if (a) *a = an;
if (v0) *v0 = vn0;
return TP_ERR_OK;
}
/**
* Perform one planer interval tick.
* @return trajgen_coord_error_t
*/
COORD_TG_STATIC trajgen_coord_error_t trajgen_coord_tick(trajgen_coord_t* tp)
{
trajgen_coord_error_t err = TP_ERR_OK;
trajgen_sg_t *sg, *nextsg;
// Initialisation
tp->_i.override = tp->override;
tp->_i.last_position = tp->pose;
// Read the queue front
if (!(sg = trajgen_coord_queue_front(tp))) {
// Queue empty, nothing to do
tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
tp->end_pose = tp->pose;
tp->is_done = 1;
tp->num_queued_active = tp->sg_id = tp->is_pausing = tp->is_aborting = 0;
tp->sync.i_offset = tp->sync.i_isset = 0;
return TP_ERR_OK;
} else if (sg->progress == sg->length) {
trajgen_coord_queue_pop_front(tp, 1);
if (!(sg = trajgen_coord_queue_front(tp))) return TP_ERR_OK;
}
// Fetch next segment for blending
// NO_MOTION_BLENDING: Compiler will subsequently remove blocks below as nextsg
// evaluates to 0.
nextsg = ((!NO_MOTION_BLENDING) && sg->blending_tolerance>0) ?
trajgen_coord_queue_get(tp, 1) : (trajgen_sg_t*) 0;
// Abort handling
if (tp->is_aborting) {
if (sg->current_velocity == 0.0 && (!nextsg ||
(nextsg && nextsg->current_velocity == 0.0))) {
tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
tp->end_pose = tp->pose;
tp->is_done = 1;
tp->is_aborting = tp->is_pausing = tp->num_queued_active = tp->sg_id =
tp->sync_dio_current = 0;
return TP_ERR_OK; // v=0, return possible
} else {
sg->v = 0.0;
sg->a = tp->config.max_acceleration;
if (nextsg) {
nextsg->v = 0.0;
nextsg->a = tp->config.max_acceleration;
}
}
}
// Start of motion (first queue item read or if blending disabled)
if (!sg->is_active) {
trajgen_sg_activate(sg);
tp->sg_id = sg->id;
tp->num_queued_active = 1;
tp->sync_dio_current = (tp->sync_dio_current | sg->sync_dio.set) &
(~sg->sync_dio.clear);
debug2("# tp: activated segment %lu ...\n", (unsigned long) sg->id);
if(sg->motion_type != SG_CIRCULAR) debug2("# tp: segment target={%g, %g, %g}\n",
sg->coords.line.xyz.end.x, sg->coords.line.xyz.end.y, sg->coords.line.xyz.end.z);
}
// Override initialisation, more override constrains may follow
if(!sg->override_enabled) tp->_i.override = 1.0;
// Handle next queued segment (only if blending enabled), on syncing
// blending will be disabled, so this is block executed only once.
if (nextsg && !nextsg->is_active) {
#ifndef NO_TRAJECTORY_SYNC
nextsg->sync_type = TP_SYNC_NONE; // give the compiler a reason to kick all this stuff
#endif
// Next segment processing needs to be postponed in case syncing ?
if((nextsg->sync_type != TP_SYNC_NONE && !isfinite(tp->sync.reference*tp->sync.scaler))
|| (nextsg->sync_type != TP_SYNC_NONE && (tp->sync.scaler == 0))
|| (nextsg->sync_type == TP_SYNC_VELOCITY && tp->sync.reference <= 0)
) {
sg->blending_tolerance = 0;
nextsg = (trajgen_sg_t*) 0;
} else {
// Check if blending is possible, d evaluates to the angle between vectors.
//
// stfwi: Find way to optimise acos and cos later. Performance waste.
//
pose_vector_t v1, v2; real_t cos_phi, phi;
if(trajgen_sg_end_direction(tp, sg, &v1) != TP_ERR_OK
|| trajgen_sg_start_direction(tp, nextsg, &v2) != TP_ERR_OK
) {
return trajgen_coord_raise_error(tp, TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE);
}
if((cos_phi = v_dot(v1,v2)) < 0 // double check for erroneous calcs
|| (phi = acos(cos_phi)) > M_PI/180.*MAX_ALLOWED_BLENDING_ANGLE_DEG) {
debug("# tp: disabled blending for %lu->%lu (angle=%.0fdeg > %.0fdeg)\n",
sg->id, nextsg->id, phi*(180./M_PI), (real_t)MAX_ALLOWED_BLENDING_ANGLE_DEG);
sg->blending_tolerance = 0;
nextsg = (trajgen_sg_t*) 0;
} else {
// Blending allowed, precalculate blending activation velocity.
// Blending rules:
// - Only from second half of the current segment to the first half of
// the next to prevent 3-segment race conditions.
// - Velocity constraints must apply.
// Triangle speed ramp (without trimmed velocities) --> v^2=s/2 * a.
//
// Peak velocity check
real_t v = sqrtz(nextsg->length/2 * (sg->a<nextsg->a ? sg->a : nextsg->a));
if(cos_phi > 1) cos_phi = 1; // If unit vectors are a little bit too long
if(cos_phi > 0 && phi > M_PI/180 * STRAIGHT_BLENDING_ANGLE) {
real_t vv;
// Additional tolerance dependent speed constrain
vv = 2.0 * sqrtz(sg->a * sg->blending_tolerance / cos_phi);
if(v > vv) v = vv;
debug2("# tp: activated segment %lu for blending at speed %g...\n",
(unsigned long) nextsg->id, v);
} else {
// Blending in same direction
// StfWi: Check if more accurate cross product and arcsin is required
// to validate this.
sg->blending_tolerance = TG_RESOLUTION; // hint for straight blending
debug2("# tp: activated segment %lu for 0-angle blending at speed %g...\n",
(unsigned long) nextsg->id, v);
}
if (v > nextsg->v) v = nextsg->v;
if (v > sg->v) v = sg->v;
sg->blending_velocity = v;
// Checkout/activate next segment
trajgen_sg_activate(nextsg);
tp->num_queued_active = 2;
if(sg->motion_type != SG_CIRCULAR) debug2("# tp: segment target={%g, %g, %g}\n",
nextsg->coords.line.xyz.end.x, nextsg->coords.line.xyz.end.y,
nextsg->coords.line.xyz.end.z);
}
}
}
// Syncing (manipulates the override depending on an external position or velocity).
#ifndef NO_TRAJECTORY_SYNC
tp->sync.i_isset &= (sg->sync_type == TP_SYNC_POSITION);
if (sg->sync_type == TP_SYNC_VELOCITY && !tp->is_aborting) {
// Update calculated override depending on the reference speed:
real_t v = tp->sync.scaler * tp->sync.reference;
if (tp->is_pausing || !isfinite(v) || v < 0) v = 0;
if (v < sg->v) tp->_i.override *= (v / sg->v);
} else if (sg->sync_type == TP_SYNC_POSITION) {
// Stfwi: CHECK: How's the abort behaviour supposed to be ?
// - Thread tapping: critical to stay in sync <-- vs. -->
// - Touch probe position tune-in: estop important.
if (sg->progress >= sg->length) {
// Segment overrun/finished, initialise sync flag for next one:
tp->sync.i_isset = isfinite(tp->sync.reference*tp->sync.scaler) && tp->sync.scaler!=0;
tp->sync.i_offset += tp->sync.i_isset ? sg->length : 0.0;
} else if (!tp->sync.i_isset && isfinite(tp->sync.reference*tp->sync.scaler)
&& tp->sync.scaler!=0) {
// Ref position now validated, initialise position offset:
tp->sync.i_offset = (tp->sync.reference * tp->sync.scaler) - sg->progress;
tp->sync.i_isset = 1;
}
// Determine the new override value for this cycle
if (!tp->sync.i_isset) {
tp->_i.override = 0;
} else {
// ds/dt = 1/2 a * dt + d^2s ---(d^2s===dv)---> dv = ds/dt - a * dt/2
real_t ds = (tp->sync.reference * tp->sync.scaler) - tp->sync.i_offset
- sg->progress - (nextsg ? nextsg->progress : 0.0);
if(!isfinite(ds) || tp->sync.scaler==0) {
// Reference position was invalidated during the motion.
err = trajgen_coord_raise_error(tp, TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION);
trajgen_coord_abort(tp);
tp->_i.override = 0;
} else if (ds < 0) {
// Trajectory is ahead of reference
tp->_i.override = 0;
} else {
// Reference is ahead or matches
real_t T = tp->config.sample_interval;
real_t dv = ds / T - sg->a * T / 2.0;
tp->_i.override = (sg->current_velocity + dv) / sg->v;
tp->_i.override = (tp->_i.override < 0) ? 0 : ((tp->_i.override > 1) ? 1 : tp->_i.override);
}
}
}
else // else below macro end : End TP_SYNC_POSITION || (TP_SYNC_VELOCITY && !is_aborting)
#endif
if (tp->is_pausing) {
tp->_i.override = 0;
}
if (!nextsg) {
// No next --> no blending --> quickly calculate next pose and return.
trajgen_sg_tick(tp, sg, 0,0,0,0);
trajgen_sg_get_pose(tp, sg, sg_of_curr, &tp->pose);
return err;
} else if(!sg->is_blending) {
// Check when to start blending
real_t v, vn;
trajgen_sg_tick(tp, sg, 0, &v, 0, &vn);
trajgen_sg_get_pose(tp, sg, sg_of_curr, &tp->pose);
if((sg->progress/sg->length > 0.5) && (vn < sg->blending_velocity)) {
// Blending_velocity changes its meaning if blending=1:
// sg->blending_velocity = sg->current_velocity;
nextsg->current_velocity = sg->blending_velocity - sg->current_velocity;
sg->is_blending = 1;
debug2("# tp: start blending segment %lu -> %lu...\n", (unsigned long) sg->id,
(unsigned long) nextsg->id);
}
} else {
// Blending in progress
real_t v;
pose_t last_pose, curr_pose, nextsg_last_pose, nextsg_curr_pose;
trajgen_sg_get_pose(tp, sg, sg_of_curr, &last_pose);
trajgen_sg_tick(tp, sg, 0, &v, 0, 0);
trajgen_sg_get_pose(tp, sg, sg_of_curr, &curr_pose);
if (tp->sg_id != nextsg->id && sg->current_velocity < nextsg->current_velocity) {
tp->sync_dio_current = (tp->sync_dio_current|nextsg->sync_dio.set)
& (~nextsg->sync_dio.clear);
tp->sg_id = nextsg->id;
debug2("# tp: switched to segment %lu ...\n", (unsigned long) tp->sg_id);
}
trajgen_sg_get_pose(tp, nextsg, sg_of_curr, &nextsg_last_pose);
real_t vv = nextsg->v;
nextsg->v = sg->blending_velocity - v;
nextsg->v = nextsg->v < 0.0 ? 0 : nextsg->v;
trajgen_sg_tick(tp, nextsg, 0,0,0,0);
nextsg->v = vv;
trajgen_sg_get_pose(tp, nextsg, sg_of_curr, &nextsg_curr_pose);
// Add increments of segment and nextsg to the position
pose_acc(&tp->pose, curr_pose);
pose_sub(&tp->pose, last_pose);
pose_acc(&tp->pose, nextsg_curr_pose);
pose_sub(&tp->pose, nextsg_last_pose);
}
return err;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="trajgen_jointtg_*: Free single joint planer">
/********************************************************************
*
* Simple single axis trajectory planer, used for individual joints.
*
********************************************************************/
static trajgen_jointtg_error_t trajgen_jointtg_err(trajgen_jointtg_t *tp, trajgen_jointtg_error_t e)
{ if (!tp) return e; return tp->last_error = e; }
trajgen_jointtg_error_t trajgen_jointtg_initialize(trajgen_jointtg_t *tp, trajgen_jointtg_config_t config)
{
CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
tp->config = config;
tp->velocity = tp->position = 0.0;
tp->is_enabled = 0;
tp->last_error = TRAJGEN_FREE_ERROR_OK;
if (config.max_acceleration < 0.0) {
return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL);
} else if (config.max_velocity < 0.0) {
return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY);
} else if (config.sample_interval <= 0) {
return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL);
}
return TRAJGEN_FREE_ERROR_OK;
}
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_reset(trajgen_jointtg_t *tp)
{
CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
tp->velocity = 0.0;
tp->command_position = tp->position;
tp->last_error = TRAJGEN_FREE_ERROR_OK;
tp->is_pause = 0;
tp->is_done = 1;
return TRAJGEN_FREE_ERROR_OK;
}
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_enabled(trajgen_jointtg_t *tp,
bool_t enabled)
{
CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
if (enabled == tp->is_enabled) {
return TRAJGEN_FREE_ERROR_OK;
} else if (!enabled) {
tp->is_enabled = 0;
} else {
if (tp->is_done) trajgen_jointtg_reset(tp);
tp->is_enabled = 1;
}
return TRAJGEN_FREE_ERROR_OK;
}
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_command_position(trajgen_jointtg_t *tp,
real_t position)
{ tp->command_position = position; return TRAJGEN_FREE_ERROR_OK; }
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_command_velocity(trajgen_jointtg_t *tp,
real_t velocity)
{
CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
if (velocity > tp->config.max_velocity) velocity = tp->config.max_velocity;
else if (velocity < -tp->config.max_velocity) velocity = -tp->config.max_velocity;
tp->command_velocity = velocity;
return TRAJGEN_FREE_ERROR_OK;
}
JOINT_TG_STATIC real_t trajgen_jointtg_get_current_position(trajgen_jointtg_t *tp)
{ return tp->position; }
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_current_position(trajgen_jointtg_t *tp,
real_t position)
{ tp->position = position; return TRAJGEN_FREE_ERROR_OK; }
JOINT_TG_STATIC bool_t trajgen_jointtg_is_done(trajgen_jointtg_t *tp)
{ return tp->is_done && tp->velocity == 0.0; }
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_tick(trajgen_jointtg_t *tp)
{
CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
if(tp->command_velocity < 0) tp->is_enabled = 0; // Force disable
if(!tp->is_enabled) tp->command_position = tp->position; // Force break
if (tp->is_enabled || tp->velocity != 0 || tp->command_position != tp->position) {
real_t T = tp->config.sample_interval;
real_t dp = tp->command_position - tp->position;
real_t vd = tp->config.max_acceleration * T;
real_t dir = dp > 0 ? 1 : -1;
real_t v;
// Velocity required using s = 1/2 a t^2 + v0 * t + s0; v^2 = s*a
dp *= dir; // Work with positive values
if(dp < TG_RESOLUTION) dp = 0;
v = (dp <= 0) ? 0 : (sqrtf(2.0 * tp->config.max_acceleration*dp + vd*vd)-vd);
if(tp->is_pause) v = 0;
if(v > tp->config.max_velocity && tp->config.max_velocity > 0) v = tp->config.max_velocity;
if(v > tp->command_velocity && tp->command_velocity > 0) v = tp->command_velocity;
if((v*=dir) > tp->velocity + vd) {
tp->velocity += vd;
} else if (v < tp->velocity - vd) {
tp->velocity -= vd;
} else {
tp->velocity = v;
}
tp->position += tp->velocity * T;
if (v < TG_RESOLUTION && fabs(tp->position - tp->command_position) < TG_RESOLUTION) {
tp->position = tp->command_position;
tp->velocity = 0.0;
}
}
tp->is_done = (tp->velocity == 0.0 && tp->position == tp->command_position);
return TRAJGEN_FREE_ERROR_OK;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="trajgen_man_*: Manual operation planer (tele operation)">
/********************************************************************
*
*
* Manual operation planer (tele operation)
*
*
********************************************************************/
static trajgen_man_error_t trajgen_man_err(trajgen_man_t *mtp, trajgen_man_error_t e)
{ if (!mtp) return e; return mtp->last_error = e; }
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_initialize(trajgen_man_t *_tp,
trajgen_man_config_t config)
{
CHK_PTR(!_tp) return TG_MAN_ERROR_INIT_NULLPOINTER;
ZERO_MEMORY(_tp, sizeof (trajgen_man_t)); // Set all data here to 0
if (!pose_is_all_greater_equal_zero(config.max_acceleration)) {
return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL);
} else if (!pose_is_all_greater_equal_zero(config.max_velocity)) {
return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY);
} else if (config.sample_interval <= 0) {
return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL);
}
_tp->config = config;
return TG_MAN_ERROR_OK;
}
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_reset(trajgen_man_t *mtp)
{
pose_set_zero(&mtp->velocity);
pose_set_zero(&mtp->current_velocity);
mtp->is_enabled = 0;
mtp->last_error = TG_MAN_ERROR_OK;
return TG_MAN_ERROR_OK;
}
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_abort(trajgen_man_t *mtp)
{ mtp->is_enabled = 0; return TG_MAN_ERROR_OK; }
MANUAL_TG_STATIC bool_t trajgen_man_is_done(trajgen_man_t *mtp)
{ return pose_is_zero(mtp->velocity) && pose_is_zero(mtp->current_velocity); }
MANUAL_TG_STATIC pose_t trajgen_man_get_position(trajgen_man_t *mtp)
{ return mtp->pose; }
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_set_position(trajgen_man_t *mtp,
pose_t position)
{ mtp->pose = position; return TG_MAN_ERROR_OK; }
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_set_velocity(trajgen_man_t *mtp,
pose_t speed)
{
pose_trim_all_upper(&speed, mtp->config.max_velocity);
pose_neg(&speed);
pose_trim_all_upper(&speed, mtp->config.max_velocity);
pose_neg(&speed);
mtp->velocity = speed;
return TG_MAN_ERROR_OK;
}
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_tick(trajgen_man_t *mtp)
{
pose_t dv; real_t T=mtp->config.sample_interval;
if (!mtp->is_enabled) pose_set_zero(&mtp->velocity);
// Determine maximum required acceleration to obtain the speed in one cycle,
// including the polar axes
pose_diff(mtp->velocity, mtp->current_velocity, &dv);
if (!pose_is_zero(dv)) {
real_t d, scl = 1.;
// Something changed
pose_scale(&dv, 1./T);
// Accelerations for.x/y/z can be pre-checked with the trajectory acceleration
if (dv.x != 0 && (d = fabs(mtp->config.max_acceleration.x / dv.x)) < scl) scl = d;
if (dv.y != 0 && (d = fabs(mtp->config.max_acceleration.y / dv.y)) < scl) scl = d;
if (dv.z != 0 && (d = fabs(mtp->config.max_acceleration.z / dv.z)) < scl) scl = d;
#ifndef NO_ABC
if (dv.a != 0 && (d = fabs(mtp->config.max_acceleration.a / dv.a)) < scl) scl = d;
if (dv.b != 0 && (d = fabs(mtp->config.max_acceleration.b / dv.b)) < scl) scl = d;
if (dv.c != 0 && (d = fabs(mtp->config.max_acceleration.c / dv.c)) < scl) scl = d;
#else
dv.a = dv.b = dv.c = 0;
#endif
#ifndef NO_UVW
if (dv.u != 0 && (d = fabs(mtp->config.max_acceleration.u / dv.u)) < scl) scl = d;
if (dv.v != 0 && (d = fabs(mtp->config.max_acceleration.v / dv.v)) < scl) scl = d;
if (dv.w != 0 && (d = fabs(mtp->config.max_acceleration.w / dv.w)) < scl) scl = d;
#else
dv.u = dv.v = dv.w = 0;
#endif
if (scl < 1.0) {
d = scl * T; // Scale the speed down to the maximum acceleration allowed
pose_scale(&dv, d); // Integrate the new velocity
pose_acc(&mtp->current_velocity, dv);
} else {
// We reached the requested velocity in the actual sample period. accel
// is now near zero. So we round to the requested velocity.
mtp->current_velocity = mtp->velocity;
}
}
dv = mtp->current_velocity;
pose_scale(&dv, T); // Integrate position
pose_acc(&mtp->pose, dv);
return TG_MAN_ERROR_OK;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="trajgen_*: Main trajectory generation coordinator">
/*******************************************************************************
*
* Main trajectory generation coordinator. Manages Free planers for all joints,
* manual and coordinated planer, including errors and switching, interpolating,
* and trajgen position updating.
*
* It is in the very end a finite state machine based organisation of the
* available functionalities.
*
******************************************************************************/
static trajgen_t *tg;
#define MTG (&tg->man_planer)
#define CTG (&tg->coord_planer)
/**
* Generates the last_error variable and forwards the code passed as argument.
* @param source
* @param code
* @param joint
* @return
*/
static trajgen_error_t tg_err(unsigned code, unsigned joint)
{
trajgen_error_details_t e;
if (!tg) return (trajgen_error_t) code;
e.errno = 0;
e.sel.joint = joint;
e.sel.code = code;
tg->last_error = e;
#ifdef TG_DEBUG_LEVEL
if(code) {
char s[128]; s[0]=s[127]='\0'; trajgen_errstr(tg->last_error.errno, s, 127);
debug("# tg: trajgen_err(): ! Error: %s\n", s);
}
#endif
return (trajgen_error_t) code;
}
/* not static but not exported in header, linker shall drop it if unused */
const char* trajgen_state_name(trajgen_state_t s)
{
if(s<0 || s>TRAJ_STATE_TG_MAN_LEAVE) return "INVALID";
static const char *names[] = {
"disabled", "jointtg", "coordinated", "manual", "INVALID",
"(ok-to-switch)", "(disabling)", "(jointtg enter)", "(jointtg leave)",
"(coordinated enter)", "(coordinated leave)", "(manual enter)",
"(manual leave)"
};
return names[s];
}
#define CHKERR(X) tg_err((X), 0)
/* Coord wrappers */
trajgen_error_t trajgen_pause()
{
for(int i=0; i<tg->config.number_of_used_joints; i++) {
tg->joints[i].tg.is_pause = 1;
}
return CHKERR(trajgen_coord_pause(CTG));
}
trajgen_error_t trajgen_resume()
{
for(int i=0; i<tg->config.number_of_used_joints; i++) {
tg->joints[i].tg.is_pause = 0;
}
return CHKERR(trajgen_coord_resume(CTG));
}
bool_t trajgen_queue_full()
{ return trajgen_coord_is_queue_full(CTG); }
uint32_t trajgen_current_id()
{ return trajgen_coord_get_current_id(CTG); }
uint16_t trajgen_num_queued()
{ return trajgen_coord_get_num_queued(CTG); }
uint16_t trajgen_queue_size()
{ return trajgen_coord_get_queue_size(CTG); }
trajgen_error_t trajgen_add_line(pose_t end, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_line(CTG, end, velocity, acceleration)); }
trajgen_error_t trajgen_add_arc(pose_t end, pose_position_t center, pose_vector_t normal,
unsigned n_turns, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_arc(CTG, end, center, normal, n_turns, velocity,
acceleration)); }
trajgen_coord_error_t trajgen_add_curve(pose_t end, pose_vector_t start_ctrl_point,
pose_vector_t end_ctrl_point, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_curve(CTG, end, start_ctrl_point, end_ctrl_point,
velocity, acceleration)); }
/* Free wrappers */
trajgen_error_t trajgen_joint_command_velocity(unsigned joint, real_t v)
{
if(joint > tg->config.number_of_used_joints) return CHKERR(TRAJ_ERROR_INVALID_JOINT_NO);
return CHKERR(trajgen_jointtg_set_command_velocity(&tg->joints[joint].tg, v));
}
#undef CHKERR
/**
* Returns the current tick counter of the TG
* @return uint32_t
*/
uint32_t trajgen_get_tick()
{ return tg->_internals.tick; }
/**
* Abort / stop (all planers)
* @return trajgen_error_t
*/
trajgen_error_t trajgen_abort()
{
debug("# tg: trajgen_abort()\n");
unsigned i;
trajgen_coord_abort(CTG);
trajgen_man_abort(MTG);
for (i=0; i < TG_MAX_JOINTS; i++) tg->joints[i].tg.is_enabled = 0;
tg->is_done = 0;
tg->requested_state = TRAJ_STATE_DISABLED;
return TRAJ_ERROR_OK;
}
/**
* Sets the new requested trajgen state.
* @param trajgen_state_t state
* @return trajgen_error_t
*/
trajgen_error_t trajgen_switch_state(trajgen_state_t state)
{
if (state < TRAJ_STATE_DISABLED || state >= TRAJ_______INTERNAL_STATES) {
return tg_err(TRAJ_ERROR_INVALID_SWITCHING_STATE, 0);
}
tg->is_done = 0;
tg->requested_state = state;
debug("# tg: trajgen_switch_state(%s)\n", trajgen_state_name(state));
return TRAJ_ERROR_OK;
}
/**
* Initializes the trajectory generator and all sub systems. This function
* MUST BE CALLED BEFORE ANY OTHER OPERATIONS with the trajgen, as internal
* pointer require initialization.
* @param trajgen_t *trajectory_generator_data_location
* @param trajgen_config_t config
* @return trajgen_error_t
*/
trajgen_error_t trajgen_initialize(trajgen_t *trajgen_data_location, trajgen_config_t config)
{
int i, err = 0;
debug("# tg: trajgen_initialize(...)\n");
// Assign basics
tg = trajgen_data_location;
if (!tg) return tg_err(TRAJ_ERROR_NULL_POINTER, 0);
ZERO_MEMORY(tg, sizeof (trajgen_t));
// Fixed configuration checks (in case the compiler does not raise errors before)
if (FLOAT_EPS <= 0
|| TG_RESOLUTION <= FLOAT_EPS // stfwi: intentionally at least 2*FLOAT_EPS
|| TG_D_RES <= FLOAT_EPS
|| TG_A_RES <= FLOAT_EPS
|| TG_QUEUE_SIZE < 4
|| TG_MAX_JOINTS > JOINT_MAX_JOINTS
|| sizeof(real_t) < sizeof(float)
){
return tg_err(TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID, 0);
}
// Configuration checks
if (config.number_of_used_joints > TG_MAX_JOINTS || !config.number_of_used_joints) {
return tg_err(TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID, 0);
}
if(!config.interpolation_rate) {
return tg_err(TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID, 0);
}
// Coordinated planer initialisation
{
trajgen_coord_config_t trajgen_coord_config;
trajgen_coord_config.sample_interval = config.sample_interval * config.interpolation_rate;
trajgen_coord_config.max_velocity = config.max_coord_velocity;
trajgen_coord_config.max_acceleration = config.max_coord_acceleration;
if ((err = trajgen_coord_init(&(tg->coord_planer), trajgen_coord_config)) != 0) {
return tg_err(err, 0);
}
}
// Manual op planer initialisation
{
trajgen_man_config_t trajgen_man_config;
trajgen_man_config.max_acceleration = config.max_manual_accelerations;
trajgen_man_config.max_velocity = config.max_manual_velocities;
trajgen_man_config.sample_interval = config.sample_interval * config.interpolation_rate;
if ((err = trajgen_man_initialize(MTG, trajgen_man_config)) != 0) {
return tg_err(err, 0);
}
}
// Cubic interpolators and joints
for (i = 0; i < TG_MAX_JOINTS; i++) {
config.joints[i].sample_interval = config.sample_interval * config.interpolation_rate;
tg->_internals.joint_positions[i] = &(tg->joints[i].position);
tg->_internals.planer_positions[i] = &(tg->joints[i].planer_position);
if (i >= config.number_of_used_joints) {
config.joints[i].max_acceleration = config.joints[i].max_velocity = 0;
}
// Interpolators are all standard initialised
if ((err = interpolator_init(&(tg->joints[i].interpolator), config.sample_interval
*config.interpolation_rate, config.interpolation_rate)) != 0) {
return tg_err(err, i);
}
// Free joint trajgens
if ((err = trajgen_jointtg_initialize(&tg->joints[i].tg, config.joints[i])) != 0) {
return tg_err(err, i);
}
}
// Kinematics
if ((err = kinematics_initialize(&(tg->kinematics), config.kinematics_functions)) != 0) {
return tg_err(err, 0);
}
tg->config = config;
// Reset state
trajgen_reset();
return TRAJ_ERROR_OK;
}
/**
* Resets the whole trajgen
* @return trajgen_error_t
*/
trajgen_error_t trajgen_reset()
{
debug("# tg: trajgen_reset()\n");
// Reset axes trajgens and interpolators
unsigned i;
for (i = 0; i < tg->config.number_of_used_joints; i++) {
tg->_internals.planer_positions[i] = tg->_internals.joint_positions[i];
tg->joints[i].tg.command_position = tg->joints[i].position;
trajgen_jointtg_reset(&tg->joints[i].tg);
interpolator_reset(&tg->joints[i].interpolator, tg->joints[i].planer_position);
}
// Update planer pose from planer position
tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose);
tg->_internals.tick = 0;
// Reset coordinated trajgens
trajgen_coord_reset(CTG);
trajgen_man_reset(MTG);
// Reset state and status
tg->is_done = 1;
tg->state = TRAJ_STATE_DISABLED;
return TRAJ_ERROR_OK;
}
/**
* Sets the planer poses and positions corresponding to the current joint
* positions.
* @return trajgen_error_t
*/
static TG_INLINE trajgen_error_t trajgen_reset_positions()
{
int err, i;
for (i=0; i < tg->config.number_of_used_joints; i++) {
tg->joints[i].planer_position = tg->joints[i].tg.command_position =
tg->joints[i].position;
tg->joints[i].acceleration = tg->joints[i].velocity = tg->joints[i].jerk = 0;
interpolator_reset(&tg->joints[i].interpolator, tg->joints[i].position);
}
if ((err = tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose)) != 0) {
return tg_err(err, 0);
}
tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose);
trajgen_coord_set_position(CTG, tg->planer_pose);
trajgen_man_set_position(MTG, tg->planer_pose);
return TRAJ_ERROR_OK;
}
/**
* Called frequently with the sample period tg.config.sample_interval.
* @return trajgen_error_t
*/
trajgen_error_t trajgen_tick()
{
int err, i;
#if TG_DEBUG_LEVEL > 1
static trajgen_state_t _dbg_state = TRAJ_STATE_DISABLED;
if(_dbg_state != tg->state) {
debug2("# tg: trajgen_tick(): state change: %s --> %s\n", trajgen_state_name(_dbg_state),
trajgen_state_name(tg->state));
_dbg_state = tg->state;
}
#endif
tg->_internals.tick++;
// Commanded state switching
if (tg->state != tg->requested_state) {
switch (tg->state) {
case TRAJ_STATE_DISABLED:
case TRAJ_STATE_OK_TO_SWITCH:
switch (tg->requested_state) {
case TRAJ_STATE_COORDINATED:
tg->state = TRAJ_STATE_COORDINATED_ENTER;
tg->is_done = 0;
break;
case TRAJ_STATE_JOINT:
tg->state = TRAJ_STATE_JOINT_ENTER;
tg->is_done = 0;
break;
case TRAJ_STATE_MAN:
tg->state = TRAJ_STATE_TG_MAN_ENTER;
tg->is_done = 0;
break;
case TRAJ_STATE_DISABLED:
// Prevent error below
break;
default:
tg->requested_state = TRAJ_STATE_DISABLED;
return tg_err(TRAJ_ERROR_INVALID_SWITCHING_STATE, 0);
}
break;
case TRAJ_STATE_COORDINATED:
tg->state = TRAJ_STATE_COORDINATED_LEAVE;
break;
case TRAJ_STATE_JOINT:
tg->state = TRAJ_STATE_JOINT_LEAVE;
break;
case TRAJ_STATE_MAN:
tg->state = TRAJ_STATE_TG_MAN_LEAVE;
break;
default:
; // This will be caught below
}
}
// State execution and implicit switching
switch (tg->state) {
case TRAJ_STATE_COORDINATED_ENTER:
{
// Reset the coord planer position to the main generator position.
// (if jogging/manual/axis independent motions were done before).
// Update planer pose from joints
trajgen_reset_positions();
tg->state = TRAJ_STATE_COORDINATED;
tg->is_done = 1;
break;
}
case TRAJ_STATE_COORDINATED_LEAVE:
{
// Stop the motion, trajgen_coord_abort() can be called every cycle until
// the motion is done.
trajgen_coord_abort(CTG);
// After finishing, switch to disabled preparation state
// stfwi: CHECK: TIMEOUT TO PREVENT INTERNAL ERROR CASES ?
if (tg->is_done) {
tg->state = TRAJ_STATE_OK_TO_SWITCH;
}
// NO BREAK, the planer has to run until the motion is finished
}
case TRAJ_STATE_COORDINATED:
{
trajgen_joint_t *joint;
// The trajectory is defined using the functions in trajgen_coordinated.h,
// mainly trajgen_coord_add_line() and trajgen_coord_add_circle().
// Generate new coarse joint positions for the interpolator
if (interpolator_need_update(&(tg->joints[0].interpolator))) {
if ((err = trajgen_coord_tick(CTG)) != 0) {
return tg_err(err, 0);
}
tg->planer_pose = trajgen_coord_get_position(CTG);
if ((err = tg->kinematics.inverse(&(tg->planer_pose), tg->_internals.planer_positions)) != 0) {
return tg_err(err, 0);
}
for (i = 0; i < tg->config.number_of_used_joints; i++) {
if ((err = interpolator_push(&(tg->joints[i].interpolator),
*tg->_internals.planer_positions[i])) != 0) {
return tg_err(err, i);
}
}
}
bool_t v0 = 1;
if (!interpolator_need_update(&(tg->joints[0].interpolator))) {
// Interpolate (every sample interval)
for (i = 0; i < tg->config.number_of_used_joints; i++) {
joint = &tg->joints[i];
if ((err = interpolator_interpolate(&(joint->interpolator), &(joint->position),
&(joint->velocity), &(joint->acceleration), &(joint->jerk))) != 0) {
return tg_err(err, i);
}
v0 &= (joint->velocity < TG_RESOLUTION);
if(!isfinite(joint->position)) {
return tg_err(TRAJ_ERROR_NUMERIC, i);
}
}
}
// Transfer state variables
tg->is_done = trajgen_coord_is_done(CTG) && v0;
break;
}
////////////////////////////////////////////////////////////////////////
case TRAJ_STATE_JOINT_ENTER:
{
int i;
trajgen_reset_positions();
for (i = 0; i < tg->config.number_of_used_joints; i++) {
if((err = trajgen_jointtg_reset(&tg->joints[i].tg))) {
tg_err(err, i);
} else {
if(tg->config.joints[i].max_acceleration > 0
&& tg->config.joints[i].max_velocity > 0) {
tg->joints[i].tg.is_enabled = 1;
tg->joints[i].tg.is_done = 0;
} else {
tg->joints[i].tg.is_enabled = 0;
}
}
}
tg->state = TRAJ_STATE_JOINT;
break;
}
case TRAJ_STATE_JOINT_LEAVE:
{
int i;
for (i = 0; i < tg->config.number_of_used_joints; i++) {
tg->joints[i].tg.is_enabled = 0;
}
if (tg->is_done) {
tg->state = TRAJ_STATE_OK_TO_SWITCH;
}
}
case TRAJ_STATE_JOINT:
{
int i, n_done = 0;
trajgen_joint_t *joint;
for (i = 0; i < tg->config.number_of_used_joints; i++) {
joint = &tg->joints[i];
if (interpolator_need_update(&(joint->interpolator))) {
if ((err = trajgen_jointtg_tick(&joint->tg)) != 0) {
return tg_err(err, i);
}
joint->planer_position = joint->tg.position;
if (joint->tg.is_done) {
n_done++;
}
if ((err = interpolator_push(&(joint->interpolator), joint->planer_position)) != 0) {
return tg_err(err, i);
}
}
if (interpolator_interpolate(&(joint->interpolator), &(joint->position),
&(joint->velocity), &(joint->acceleration), &(joint->jerk)) != 0) {
return tg_err(err, i);
}
if(!isfinite(joint->position)) {
return tg_err(TRAJ_ERROR_NUMERIC, i);
}
}
tg->is_done = n_done >= tg->config.number_of_used_joints;
break;
}
////////////////////////////////////////////////////////////////////////
case TRAJ_STATE_TG_MAN_ENTER:
{
// Reset velocities/accelerations
trajgen_man_reset(MTG);
// Update planer pose
trajgen_reset_positions();
// Enable
MTG->is_enabled = 1;
// Switch state
tg->state = TRAJ_STATE_MAN;
break;
}
case TRAJ_STATE_TG_MAN_LEAVE:
{
// Force manual op command velocity to zero
trajgen_man_abort(MTG);
// If the axes are not moving anymore, switch state
if (tg->is_done) {
tg->state = TRAJ_STATE_OK_TO_SWITCH;
}
// NO BREAK HERE
}
case TRAJ_STATE_MAN:
{
int i;
trajgen_joint_t *joint;
// The pose_t variable tg->trajgen_man_planer,_planer->velocity shall
// contain the new requested velocities. This variable shall be set every
// before calling trajgen_tick(), e.g. transferring scaled joystick
// inputs, velocity command from the computer keyboard etc.
while (interpolator_need_update(&(tg->joints[0].interpolator))) {
if ((err = trajgen_man_tick(MTG)) != 0) {
return tg_err(err, 0);
}
tg->planer_pose = trajgen_man_get_position(MTG);
if ((err = tg->kinematics.inverse(&(tg->planer_pose),
tg->_internals.planer_positions)) != 0) {
return tg_err(err, 0);
}
for (i = 0; i < tg->config.number_of_used_joints; i++) {
if ((err = interpolator_push(&(tg->joints[i].interpolator),
*tg->_internals.planer_positions[i])) != 0) {
return tg_err(err, i);
}
}
}
// Interpolate (every sample interval)
for (i = 0; i < tg->config.number_of_used_joints; i++) {
joint = &tg->joints[i];
interpolator_interpolate(&(joint->interpolator), &(joint->position),
&(joint->velocity), &(joint->acceleration), &(joint->jerk));
if(!isfinite(joint->position)) {
return tg_err(TRAJ_ERROR_NUMERIC, i);
}
}
// Transfer state variables
tg->is_done = trajgen_man_is_done(MTG);
break;
}
////////////////////////////////////////////////////////////////////////
case TRAJ_STATE_DISABLING:
{
// Reset the trajgen, which does not include the config or actual
// joint values.
trajgen_reset();
// Proceed directly in DISABLED state
tg->state = TRAJ_STATE_DISABLED;
// NO BREAK
}
case TRAJ_STATE_DISABLED:
{
tg->is_done = 1;
// State block reserved for updating states
}
case TRAJ_STATE_OK_TO_SWITCH:
{
// Planer position needs to be updated externally
break;
}
////////////////////////////////////////////////////////////////////////
default:
{
tg->state = TRAJ_STATE_DISABLING;
return tg_err(TRAJ_ERROR_INVALID_STATE, 0);
}
}
return TRAJ_ERROR_OK;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="trajgen_errstr/info: Error to string conversion">
/********************************************************************
*
* Trajectory generator error to string conversion (no standard libs required)
* Trajectory generator info string
*
********************************************************************/
#define sprinterr(_T) do {\
if(_T) for(tp=_T; (*tp) && (errstr_p != errstr_e); errstr_p++, tp++) *errstr_p=*tp; \
} while(0);
#define add_joint() do {\
if(errstr_p != errstr_e) { *errstr_p='['; errstr_p++; } \
if(errstr_p != errstr_e) { *errstr_p='0'+(err.sel.joint%10); errstr_p++; } \
if(errstr_p != errstr_e) { *errstr_p=']'; errstr_p++; } \
} while(0);
/**
* Writes build information into the given string and returns latter as
* const pointer.
* @param char* s
* @param unsigned length
* @return const char*
*/
const char* trajgen_info(char* s, unsigned length) {
if(!s || !length) return (const char*) s;
char *errstr_p = s, *errstr_e = s+length;
const char* tp;
do { unsigned i; for(i=0; i<length; i++) s[i] = '\0'; } while(0);
sprinterr("trajgen.c/h, version "); sprinterr(TG_VERSION);
#ifdef ARG_POINTER_CHECKS
sprinterr(", with-0pointer-checks");
#else
sprinterr(", without-0pointer-checks");
#endif
#ifdef TG_DEBUG_LEVEL
#if TG_DEBUG_LEVEL > 1
sprinterr(", debug2");
#else
sprinterr(", debug2");
#endif
#else
sprinterr(", no-debug");
#endif
#ifdef NO_INTERPOLATION
sprinterr(", without-interpolation");
#else
sprinterr(", with-interpolation");
#endif
#if NO_MOTION_BLENDING > 0
sprinterr(", without-blending");
#else
sprinterr(", with-blending");
#endif
#ifdef WITH_CURVES
sprinterr(", with-curves");
#else
sprinterr(", without-curves");
#endif
#ifdef NO_TRAJECTORY_SYNC
sprinterr(", without-syncing");
#else
sprinterr(", with-syncing");
#endif
#ifdef NO_ABC
sprinterr(", without-abc");
#else
sprinterr(", with-abc");
#endif
#ifdef NO_UVW
sprinterr(", without-uvw");
#else
sprinterr(", with-uvw");
#endif
#ifdef EXPORT_INTERPOLATORS
sprinterr(", exported-interpolators");
#endif
#ifdef EXPORT_JOINT_TG
sprinterr(", exported-jointtg");
#endif
#ifdef EXPORT_MANUAL_TG
sprinterr(", exported-manualtg");
#endif
#ifdef EXPORT_COORD_TG
sprinterr(", exported-coordtg");
#endif
#define TG_STR(s) TG_STRINNER(s)
#define TG_STRINNER(s) #s
sprinterr((", float-type=" TG_STR(real_t)));
sprinterr(", max-joints=" TG_STR(TG_MAX_JOINTS));
sprinterr(", queue-size=" TG_STR(TG_QUEUE_SIZE));
sprinterr(", machine-resolution=" TG_STR(TG_RESOLUTION));
sprinterr(", dimensional-resolution=" TG_STR(TG_D_RES));
sprinterr(", angle-resolution=" TG_STR(TG_A_RES));
sprinterr(", blending-max-angle=" TG_STR(MAX_ALLOWED_BLENDING_ANGLE_DEG) "deg");
sprinterr(", straight-blending-max-angle=" TG_STR(STRAIGHT_BLENDING_ANGLE) "deg");
sprinterr(", stfwi. ");
#undef TG_STR
#undef TG_STRINNER
return (const char*) s;
}
/**
* Writes a text version of a trajgen error into *errstr
* @param errnum
* @param errstr
* @param length
*/
const char* trajgen_errstr(uint16_t errnum, char* errstr, unsigned length)
{
if(!errstr || !length) return (const char*) errstr;
char *errstr_p = errstr, *errstr_e = errstr+length;
const char* tp;
trajgen_error_details_t err;
err.errno = errnum;
do { unsigned i; for(i=0; i<length; i++) errstr[i] = '\0'; } while(0);
switch (err.sel.code) {
case TRAJ_ERROR_OK:
sprinterr("(ok)");
break;
case TRAJ_ERROR_ERROR:
sprinterr("[main trajgen] General error");
break;
case TRAJ_ERROR_NULL_POINTER:
sprinterr("[main trajgen] Null pointer");
break;
case TRAJ_ERROR_NUMERIC:
sprinterr("[main trajgen] Null pointer");
break;
case TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID:
sprinterr("[main trajgen] Invalid last used joint value");
break;
case TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID:
sprinterr("[main trajgen] Invalid interpolation rate setting");
break;
case TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID:
sprinterr("[main trajgen] Invalid compilation setting (preprocessor defined)");
break;
case TRAJ_ERROR_CONFIG_OVERRIDE_INVALID:
sprinterr("[main trajgen] Invalid override");
break;
case TRAJ_ERROR_INVALID_STATE:
sprinterr("[main trajgen] Invalid state");
break;
case TRAJ_ERROR_INVALID_SWITCHING_STATE:
sprinterr("[main trajgen] Invalid switching state");
break;
case TRAJ_ERROR_INVALID_JOINT_NO:
sprinterr("[main trajgen] Invalid joint index");
break;
case KINEMATICS_ERR_ERROR:
sprinterr("[kinematics] General error");
break;
case KINEMATICS_ERR_NULL_POINTER:
sprinterr("[kinematics] Null pointer");
break;
case KINEMATICS_ERR_INIT_FUNCTION_NULL:
sprinterr("[kinematics] Invalid kinematics definition (contains function null-pointer)");
break;
case KINEMATICS_ERR_FORWARD_FAILED:
sprinterr("[kinematics] Forward kinematics failed");
break;
case KINEMATICS_ERR_INVERSE_FAILED:
sprinterr("[kinematics] Inverse kinematics failed");
break;
case KINEMATICS_ERR_RESET_FAILED:
sprinterr("[kinematics] Reset setting failed");
break;
case INTERPOLATOR_ERROR:
sprinterr("[interpolator] General error"); add_joint();
break;
case INTERPOLATOR_ERROR_INIT_ARG_INVALID:
sprinterr("[interpolator] Invalid initialisation argument"); add_joint();
break;
case INTERPOLATOR_ERROR_QUEUE_FULL:
sprinterr("[interpolator] Queue full"); add_joint();
break;
case INTERPOLATOR_ERROR_OFFSET_IP_NULLPOINTER:
sprinterr("[interpolator] Offset CI null pointer"); add_joint();
break;
case INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER:
sprinterr("[interpolator] Interpolate: Null pointer"); add_joint();
break;
case INTERPOLATOR_ERROR_NOT_RESET:
sprinterr("[interpolator] Not initialised"); add_joint();
break;
case TRAJGEN_FREE_ERROR_ERROR:
sprinterr("[joint tg] General error"); add_joint();
break;
case TRAJGEN_FREE_ERROR_INIT_NULLPOINTER:
sprinterr("[joint tg] Initialisation null pointer"); add_joint();
break;
case TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL:
sprinterr("[joint tg] Invalid maximum acceleration"); add_joint();
break;
case TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY:
sprinterr("[joint tg] Invalid maximum velocity"); add_joint();
break;
case TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL:
sprinterr("[joint tg] Invalid sample interval"); add_joint();
break;
case TRAJGEN_FREE_ERROR_INVALID_ACCELERATION:
sprinterr("[joint tg] Invalid acceleration"); add_joint();
break;
case TP_ERR_ERROR:
sprinterr("[coord tg] General error");
break;
case TP_ERR_TP_NULL_POINTER:
sprinterr("[coord tg] Null pointer");
break;
case TP_ERR_ABORTING:
sprinterr("[coord tg] Aborting");
break;
case TP_ERR_QUEUE_PUT_FAILED:
sprinterr("[coord tg] Queue push failed");
break;
case TP_ERR_INVALID_PARAM:
sprinterr("[coord tg] Invalid argument");
break;
case TP_ERR_QUEUE_FULL:
sprinterr("[coord tg] Queue full");
break;
case TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE:
sprinterr("[coord tg] Too many items to remove");
break;
case TP_ERR_INVALID_MOTION_TYPE:
sprinterr("[coord tg] Invalid motion type");
break;
case TP_ERR_INVALID_SPEED:
sprinterr("[coord tg] Invalid velocity");
break;
case TP_ERR_INVALID_ACCEL:
sprinterr("[coord tg] Invalid acceleration");
break;
case TP_ERR_INVALID_POSE:
sprinterr("[coord tg] Invalid argument pose");
break;
case TP_ERR_INVALID_SAMPLE_INTERVAL:
sprinterr("[coord tg] Invalid sample interval");
break;
case TP_ERR_ALREADY_MOVING:
sprinterr("[coord tg] Already moving");
break;
case TP_ERR_SEGMENT_LENGTH_ZERO:
sprinterr("[coord tg] Segment to move has a length of zero");
break;
case TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE:
sprinterr("[coord tg] Unit vector calculation failed");
break;
case TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION:
sprinterr("[coord tg] Ref-position invalidated during motion");
break;
case TG_MAN_ERROR_ERROR:
sprinterr("[manual tg] General error");
break;
case TG_MAN_ERROR_INIT_NULLPOINTER:
sprinterr("[manual tg] Initialisation: null pointer");
break;
case TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL:
sprinterr("[manual tg] Invalid maximum acceleration");
break;
case TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY:
sprinterr("[manual tg] Invalid maximum velocity");
break;
case TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL:
sprinterr("[manual tg] Invalid sample interval");
break;
case TG_MAN_ERROR_INVALID_ACCELERATION:
sprinterr("[manual tg] Invalid acceleration");
break;
default:
sprinterr("Unknown error code");
}
return (const char*) errstr;
}
#undef sprinterr
#undef add_joint
// </editor-fold>
Test program C++ wrapper
/**
* Trajectory planer test c++ wrapper
* @author stfwi
* @file test/trajgen.cc
*/
#ifndef TRAJGEN_HH
#define TRAJGEN_HH
#include "../trajgen.h"
#include <string>
#include <sstream>
#include <iostream>
#include <cstring>
#include <cstdio>
template <typename flt_t=real_t>
class basic_trajgen
{
public:
typedef enum {
notg = TRAJ_STATE_DISABLED,
coordtg = TRAJ_STATE_COORDINATED,
freetg = TRAJ_STATE_JOINT,
manualtg = TRAJ_STATE_MAN
} selection_t;
public:
static std::string dump_pose(const pose_t& p)
{
std::stringstream ss;
ss << "{" << p.x << "," << p.y << "," << p.z;
if(p.u!=0 && p.v!=0 && p.w!=0 && p.a!=0 && p.b!=0 && p.c!=0) {
ss << ", " << p.a << "," << p.b << "," << p.c;
}
if(p.u!=0 && p.v!=0 && p.w!=0) {
ss << ", " << p.u << "," << p.v << "," << p.w;
}
ss << "}";
return ss.str();
}
public:
basic_trajgen() : _speed(0.1), _accel(1), _blending(0)
{
memset(_err_str, 0, sizeof(_err_str));
memset(&_last_pose, 0, sizeof(pose_t));
memset(&_tg, 0, sizeof(trajgen_t));
memset(&_cfg, 0, sizeof(trajgen_config_t));
}
virtual ~basic_trajgen()
{ ; }
basic_trajgen& init(flt_t max_speed, flt_t max_accel, flt_t sample_frequency,
unsigned interpolation_rate, unsigned max_joints)
{
_n_joints = max_joints > TG_MAX_JOINTS ? TG_MAX_JOINTS : max_joints;
memset(&_cfg, 0, sizeof(trajgen_config_t));
_cfg.number_of_used_joints = _n_joints;
_cfg.max_coord_velocity = max_speed;
_cfg.max_coord_acceleration = max_accel;
_cfg.sample_interval = 1.0/sample_frequency;
_cfg.interpolation_rate = interpolation_rate;
pose_set_all(&_cfg.max_manual_velocities, max_speed);
pose_set_all(&_cfg.max_manual_accelerations, max_accel);
for(unsigned i = 0; i < _n_joints; i++) {
_cfg.joints[i].max_velocity = max_speed;
_cfg.joints[i].max_acceleration = max_speed;
}
echk(trajgen_initialize(&_tg, _cfg));
return *this;
}
inline const trajgen_t& tg() const
{ return _tg; }
inline const trajgen_config_t& config() const
{ return _tg.config; }
inline bool done() const
{ return _tg.is_done; }
inline trajgen_state_t state() const
{ return _tg.state; }
inline std::string info() {
char s[4096];
trajgen_info(s, 4096);
return std::string(s);
}
inline unsigned current_tick() const
{ return trajgen_get_tick(); }
inline flt_t current_tick_time() const
{ return (flt_t) trajgen_get_tick() * _tg.config.sample_interval; }
inline bool queue_full() const
{ return trajgen_queue_full(); }
inline unsigned num_queued() const
{ return trajgen_num_queued(); }
inline unsigned queue_size() const
{ return trajgen_queue_size(); }
inline basic_trajgen& select(selection_t s)
{
echk(trajgen_switch_state((trajgen_state_t)s));
while(!_tg.is_done) echk(trajgen_tick());
return *this;
}
inline pose_t pose() const
{ return _tg.planer_pose; }
inline basic_trajgen& pose(pose_t p)
{
if(_tg.config.number_of_used_joints > 0) _tg.joints[0].position = p.x;
if(_tg.config.number_of_used_joints > 1) _tg.joints[1].position = p.y;
if(_tg.config.number_of_used_joints > 2) _tg.joints[2].position = p.z;
if(_tg.config.number_of_used_joints > 3) _tg.joints[3].position = p.a;
if(_tg.config.number_of_used_joints > 4) _tg.joints[4].position = p.b;
if(_tg.config.number_of_used_joints > 5) _tg.joints[5].position = p.c;
if(_tg.config.number_of_used_joints > 6) _tg.joints[6].position = p.u;
if(_tg.config.number_of_used_joints > 7) _tg.joints[7].position = p.v;
if(_tg.config.number_of_used_joints > 8) _tg.joints[8].position = p.w;
_last_pose = p;
trajgen_reset();
return *this;
}
inline flt_t blending() const
{ return _tg.coord_planer.blending_tolerance; }
inline basic_trajgen& blending(flt_t tol)
{ _blending = _tg.coord_planer.blending_tolerance = tol > 0 ? tol : 0; return *this; }
inline flt_t speed() const
{ return _speed; }
inline basic_trajgen& speed(flt_t v)
{ if(v <= 0) throw("Invalid speed"); _speed=v; return *this; }
inline flt_t accel() const
{ return _accel; }
inline basic_trajgen& accel(flt_t a)
{ if(a <= 0) throw("Invalid acceleration"); _accel=a; return *this; }
inline flt_t override() const
{ return _tg.coord_planer.override; }
inline basic_trajgen& override(flt_t o)
{ if(o<0||o>1) throw("Invalid override"); _tg.coord_planer.override=o; return *this; }
inline unsigned num_joints() const
{ return _n_joints; }
inline const trajgen_joint_t& joint(unsigned index) const
{ if(index>_n_joints) throw("Invalid joint index"); return _tg.joints[index]; }
inline const trajgen_joint_t& operator [] (unsigned i) const
{ return joint(i); }
inline unsigned long current_segment() const
{ return _tg.coord_planer.sg_id; }
// Digital I/O synced motion of the coord planer
inline const unsigned short dio_state() const
{ return _tg.coord_planer.sync_dio_current & 0xffff; }
inline basic_trajgen& dio_set_next(unsigned short v)
{ _tg.coord_planer.sync_dio_command.set |= (v&0xffff); return *this; }
inline basic_trajgen& dio_reset_next(unsigned short v)
{ _tg.coord_planer.sync_dio_command.clear |= (v&0xffff); return *this; }
inline basic_trajgen& reset()
{ echk(trajgen_reset()); return *this; }
inline basic_trajgen& pause()
{ echk(trajgen_pause()); return *this; }
inline basic_trajgen& resume()
{ echk(trajgen_resume()); return *this; }
inline basic_trajgen& abort()
{ echk(trajgen_abort()); return *this; }
inline basic_trajgen& tick()
{ echk(trajgen_tick()); return *this; }
basic_trajgen& line(double x=NAN, double y=NAN, double z=NAN, double a=NAN,
double b=NAN, double c=NAN, double u=NAN, double v=NAN, double w=NAN)
{
pose_t p;
p.x = isnan(x) ? _last_pose.x : x; p.y = isnan(y) ? _last_pose.y : y;
p.z = isnan(z) ? _last_pose.z : z; p.a = isnan(a) ? _last_pose.a : a;
p.b = isnan(b) ? _last_pose.b : b; p.c = isnan(c) ? _last_pose.c : c;
p.u = isnan(u) ? _last_pose.u : u; p.v = isnan(v) ? _last_pose.v : v;
p.w = isnan(w) ? _last_pose.w : w;
_last_pose = p;
if(isnan(a) && isnan(b) && isnan(c)) {
fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, v:%.6g, "
"a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, _speed, _accel, _blending);
} else if(isnan(u) && isnan(v) && isnan(w)) {
fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, a:%.6g, b:%.6g, c:%.6g, v:%.6g, "
"a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, p.a, p.b, p.c, _speed, _accel, _blending);
} else {
fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, a:%.6g, b:%.6g, c:%.6g, v:%.6g, u:%.6g, "
"v:%.6g, w:%.6g, a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, p.a, p.b, p.c, p.u, p.v, p.w,
_speed, _accel, _blending);
}
echk(trajgen_add_line(p, _speed, _accel));
return *this;
}
basic_trajgen& arc(double xe, double ye, double ze, double xc, double yc, double zc,
double xn, double yn, double zn, int turns=0)
{
pose_t p;
pose_position_t c;
pose_vector_t n;
pose_set_zero(&p);
p.x = xe; p.y = ye; p.z = ze;
c.x = xc; c.y = yc; c.z = zc;
n.x = xn; n.y = yn; n.z = zn;
_last_pose.x = p.x;
_last_pose.y = p.y;
_last_pose.z = p.z;
fprintf(stderr,"# arc {x:%.6g, y:%.6g, z:%.6g, cx:%.6g, cy:%.6g, cz:%.6g, nx:%.6g, ny:%.6g,"
"nz:%.6g, v:%.6g, a:%.6g, tol:%.6g }\n", p.x,p.y,p.z, c.x,c.y,c.z,
n.x,n.y,n.z, _speed, _accel, _blending);
echk(trajgen_add_arc(p,c,n,turns, _speed, _accel));
return *this;
}
basic_trajgen& curve(
double xe, double ye, double ze,
double xscp, double yscp, double zscp,
double xecp, double yecp, double zecp
){
pose_t p;
pose_position_t sh, eh;
pose_set_zero(&p);
p.x = xe; p.y = ye; p.z = ze;
sh.x = xscp; sh.y = yscp; sh.z = zscp;
eh.x = xecp; eh.y = yecp; eh.z = zecp;
_last_pose.x = p.x;
_last_pose.y = p.y;
_last_pose.z = p.z;
fprintf(stderr,"# curve {x:%.6g, y:%.6g, z:%.6g, cp1.x:%.6g, cp1.y:%.6g, cp1.z:%.6g, cp2.x:%.6g, cp2.y:%.6g,"
"cp2.z:%.6g, v:%.6g, a:%.6g, tol:%.6g }\n", p.x,p.y,p.z, sh.x,sh.y,sh.z,
eh.x,eh.y,eh.z, _speed, _accel, _blending);
echk(trajgen_add_curve(p, sh, eh, _speed, _accel));
return *this;
}
private:
void echk(unsigned errno)
{ if(errno) throw trajgen_errstr(_tg.last_error.errno, _err_str, 1024); }
private:
flt_t _speed;
flt_t _accel;
flt_t _blending;
unsigned _n_joints;
trajgen_t _tg;
trajgen_config_t _cfg;
pose_t _last_pose;
char _err_str[1024];
};
typedef basic_trajgen<real_t> trajgen;
#endif
Test program for the coordinated planer (C++)
/**
* Example test file for the coordinated trajgen.
*
* @author stfwi
* @file test/test-coord.cc
*/
#include "trajgen.hh"
#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
#include <time.h>
#include <cstdlib>
#include <cstdio>
#include <stdarg.h>
using namespace std;
// <editor-fold defaultstate="collapsed" desc="Auxiliaries">
////////////////////////////////////////////////////////////////////////////////
// Auxiliaries
////////////////////////////////////////////////////////////////////////////////
#if defined (__linux__)
#include <sys/time.h>
inline double clk() {
timespec ts;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
return (double) ((uint64_t)ts.tv_nsec + ((uint64_t)1e9) * (uint64_t)ts.tv_sec) / 1e3;
}
#elif defined (__MACH__)
#include <mach/mach_time.h>
inline double clk() {
static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;
if (!orwl_timestart) {
mach_timebase_info_data_t tb = {0};
mach_timebase_info(&tb);
orwl_timebase = tb.numer;
orwl_timebase /= tb.denom;
orwl_timestart = mach_absolute_time();
}
return (double)((mach_absolute_time() - orwl_timestart) * orwl_timebase) / 1e3;
}
#else
#define clk() 0
#endif
#if defined (__linux__) || defined (__MACH__)
std::string exec(std::string cmd) {
FILE* pipe = popen((cmd+" 2>/dev/null").c_str(), "r");
if(!pipe) return "(Error: Failed to invoke shell process)";
char s[4096];
s[4095] = '\0';
std::string r = "";
while(!feof(pipe)) if(fgets(s, 4095, pipe)) r += s;
pclose(pipe);
for(unsigned i=0; i<r.length(); i++) if(r[i]=='\n'||r[i]=='\r'||r[i]=='\t') r[i]=' ';
while(r.length() && r[r.length()-1]==' ') r.resize(r.length()-1);
while(r.length() && r[0]==' ') r.erase(0,1);
return r;
}
std::string platform()
{
std::stringstream ss;
ss << "# Platform: "
<< "architecture '" << exec("arch") << "'";
#if defined (__linux__)
std::string s = exec("cat /proc/cpuinfo |grep -m1 -i 'mhz' |grep -woP '([^\\s]+)$'");
if(s.length()) ss << ", cpu=" << s << "MHz";
#elif defined (__APPLE__)
std::string s = exec("sysctl -a machdep.cpu|grep 'machdep.cpu.brand_string'|grep -woP '([^\\s]+)$'");
ss << ", cpu=" << s;
#endif
ss << ", kernel '" << exec("uname -s") << " " << exec("uname -r") << "'";
ss << std::endl;
return ss.str();
}
#else
std::string platform() { return std::string("(Windows maybe)"); }
#endif
// Platform independent float random
inline double rnd() {
return ((double)rand())/RAND_MAX;
}
// Digit rounding (the q&d version)
inline double roundd(double v, unsigned digits=0) {
for(unsigned i=0; i<digits; i++) v*=10.;
v = round(v);
for(unsigned i=0; i<digits; i++) v/=10.;
return v;
}
// Random position, we use millimeter like scaling here
inline double randpos(double max) {
return roundd(max*((rnd()*2)-1), 0);
}
// To ease some annoyances with iosbase formatting flags: the old way.
inline std::string fmt(const char* fmt, ...)
{ char s[512];
va_list args;
va_start(args, fmt);
vsnprintf(s, 512, fmt, args);
va_end(args);
return s;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Test run / protocol generation">
////////////////////////////////////////////////////////////////////////////////
// Test run / protocol generation
////////////////////////////////////////////////////////////////////////////////
// Argument settings
typedef struct {
unsigned p:1, k:1, t:1, j0:1, j1:1, j2:1, j3:1, j4:1, j5:1, j6:1, j7:1, j8:1,
v:1, a:1, s:1, n:1, o:1, C:1, N:1, O:1, P:1, A:1, S:1,
END:1;
} opts_t;
opts_t opts;
void run_tg(trajgen &tg) {
const trajgen_coord_t& tp = tg.tg().coord_planer;
bool joints[9];
do {
std::stringstream ss;
std::string s;
joints[0] = opts.j0 && (tg.num_joints() > 0);
joints[1] = opts.j1 && (tg.num_joints() > 1);
joints[2] = opts.j2 && (tg.num_joints() > 2);
joints[3] = opts.j3 && (tg.num_joints() > 3);
joints[4] = opts.j4 && (tg.num_joints() > 4);
joints[5] = opts.j5 && (tg.num_joints() > 5);
joints[6] = opts.j6 && (tg.num_joints() > 6);
joints[7] = opts.j7 && (tg.num_joints() > 7);
joints[8] = opts.j8 && (tg.num_joints() > 8);
// CSV head
ss.clear();
ss << " ";
if(opts.p) ss << "t_calc_us, ";
if(opts.k) ss << " tick, ";
if(opts.t) ss << " t ,";
if(opts.s) ss << "segment, ";
for(unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << "j" << i << "s, ";
}
if(opts.v) {
for(unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << "j" << i << "v, ";
}
}
if(opts.a) {
for(unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << "j" << i << "a, ";
}
}
if(opts.C) ss << "v_check, a_check, ";
if(opts.n) ss << "n_queued, ";
if(opts.N) ss << "n_active, ";
if(opts.o) ss << "io, ";
if(opts.O) ss << "override, ";
if(opts.P) ss << "pausing, ";
if(opts.A) ss << "aborting, ";
if(opts.S) ss << "s_c_process, s_c_length, s_c_vc, s_c_v, s_c_a, s_c_v_blend, "
<< "s_c_blending, s_n_process, s_n_length, s_n_vc, s_n_v, s_n_a, ";
s = ss.str();
cout << s.substr(0, s.length()-2) << endl;
} while(0);
// CSV body
do {
double c;
std::stringstream ss;
std::string s;
if(opts.p) {
c=clk(); tg.tick(); c=clk()-c;
ss << fmt("%11.3f, ", c);
} else {
tg.tick();
}
if(opts.k) {
ss << fmt("%5d, ", tg.current_tick());
}
if(opts.t) {
ss << fmt("%7.3f, ", tg.current_tick_time());
}
if(opts.s) {
ss << fmt("%2lu, ", tg.current_segment());
}
for (unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << fmt("%+10.6f, ", tg[i].position);
}
if(opts.v) {
for (unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << fmt("%9.3f, ", tg[i].velocity);
}
}
if(opts.a) {
for (unsigned i=0; i<tg.num_joints(); i++) {
if(joints[i]) ss << fmt("%9.3f, ", tg[i].acceleration);
}
}
if(opts.C) {
ss << fmt("%9.3f", sqrt(
tg[0].velocity * tg[0].velocity +
tg[1].velocity * tg[1].velocity +
tg[2].velocity * tg[2].velocity
)) << ", ";
ss << fmt("%9.3f", sqrt(
tg[0].acceleration * tg[0].acceleration +
tg[1].acceleration * tg[1].acceleration +
tg[2].acceleration * tg[2].acceleration
)) << ", ";
}
if(opts.n) {
ss << fmt("%2d, ", tg.num_queued());
}
if(opts.N) {
ss << fmt("%1d, ", tp.num_queued_active);
}
if(opts.o) {
ss << fmt("%2d, ", tg.dio_state());
}
if(opts.O) {
ss << fmt("%4.2f, ", tp.override);
}
if(opts.P) {
ss << fmt("%1d, ", tp.is_pausing);
}
if(opts.A) {
ss << fmt("%1d, ", tp.is_aborting);
}
if(opts.S) {
if(tp.queue.size >0 && tp.num_queued_active > 0) {
const trajgen_sg_t& sg0 = tp.queue.queue[tp.queue.start];
const trajgen_sg_t& sg1 = tp.queue.queue[(tp.queue.start+1)%tp.queue.size];
if(tp.num_queued_active > 0) {
ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, %10.4f, %1d, ", sg0.progress,
sg0.length, sg0.current_velocity, sg0.v, sg0.a, sg0.blending_velocity,
sg0.is_blending);
}
if(tp.num_queued_active >= 2) {
ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", sg1.progress, sg1.length,
sg1.current_velocity, sg1.v, sg1.a);
} else {
ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", 0,0,0,0,0);
}
} else {
ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, %10.4f, %1d, ", 0,0,0,0,0,0,0);
ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", 0,0,0,0,0);
}
}
s = ss.str();
cout << s.substr(0, s.length()-2) << endl;
} while(!tg.done());
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Main">
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
const string default_opts = "-txyz";
int usage()
{
cerr << endl << "Usage: test-coord [-*|-ptksxyz012345678vaCnNoOPAS] task" << endl
<< "Where task one of: " << endl << endl
<< " example, line, arc, curve, straight-blending, rand-straight-blending," << endl
<< " rand-lines, rand-arcs, override, push-line, push-arc, sizes " << endl << endl
<< "Note: options must be all inline as the first argument, default is"
<< "'" << default_opts << "'. Meanings: " << endl
<< " * : Everything" << endl
<< " p : Performance (in csv: 't_calc_us')" << endl
<< " k : Tick index of trajgen_tick() (in csv: 'tick')" << endl
<< " t : Time (in csv: 't')" << endl
<< " s : Id of current segment (in csv: 'segment')" << endl
<< " x,0 : Positions joint 0 (in csv: 'j0s')" << endl
<< " y,1 : Positions joint 1 (in csv: 'j1s')" << endl
<< " z,2 : Positions joint 2 (in csv: 'j2s')" << endl
<< " 3-8 : Positions joint N (in csv: 'jNs' -> a,b,c,u,v,w)" << endl
<< " v : Velocities (in csv: 'jNv')" << endl
<< " a : Accelerations (in csv: 'jNa')" << endl
<< " C : Recalculated trajectory speed/accel (in csv: 'v_check, a_check')" << endl
<< " n : Number of currently queued segments (in csv: 'n_queued')" << endl
<< " N : Number of active segments (in csv: 'n_active')" << endl
<< " o : Sync digital output state (in csv: 'io')" << endl
<< " O : Current override (in csv: 'override')" << endl
<< " P : Current 'pause' status (in csv: 'pausing')" << endl
<< " A : Current 'abort' status (in csv: 'aborting')" << endl
<< " S : Segment information (multiple entries)" << endl
<< endl;
return 1;
}
int main(int argc, char** argv)
{
string argopt, test_task;
test_task = (argc==3) ? argv[2] : (argc==2 ? argv[1] : "");
argopt = (argc==3) ? argv[1] : default_opts;
if(argopt.length() > 0 && argopt.at(0) != '-') return usage();
if(test_task.length() == 0 || test_task.at(0) == '-') return usage();
memset(&opts, 0, sizeof(opts_t));
opts.p = argopt.find('p') != string::npos;
opts.k = argopt.find('k') != string::npos;
opts.t = argopt.find('t') != string::npos;
opts.s = argopt.find('s') != string::npos;
opts.j0= argopt.find('0') != string::npos || argopt.find('x') != string::npos;
opts.j1= argopt.find('2') != string::npos || argopt.find('y') != string::npos;
opts.j2= argopt.find('2') != string::npos || argopt.find('z') != string::npos;
opts.j3= argopt.find('3') != string::npos;
opts.j4= argopt.find('4') != string::npos;
opts.j5= argopt.find('5') != string::npos;
opts.j6= argopt.find('6') != string::npos;
opts.j7= argopt.find('7') != string::npos;
opts.j8= argopt.find('8') != string::npos;
opts.v = argopt.find('v') != string::npos;
opts.a = argopt.find('a') != string::npos;
opts.C = argopt.find('C') != string::npos;
opts.n = argopt.find('n') != string::npos;
opts.N = argopt.find('N') != string::npos;
opts.o = argopt.find('o') != string::npos;
opts.O = argopt.find('O') != string::npos;
opts.P = argopt.find('P') != string::npos;
opts.A = argopt.find('A') != string::npos;
opts.S = argopt.find('S') != string::npos;
if(argopt.find('*') != string::npos) memset(&opts, 0xff, sizeof(opts_t));
srand(time(NULL));
try {
trajgen tg;
if(argc >1) cout << "# Build: " << tg.info() << endl << platform();
#ifndef NO_INTERPOLATION
tg.init(500, 2000, 1000.0, 4, 3);
#else
tg.init(500, 2000, 1000.0, 1, 3);
#endif
if(test_task=="sizes") {
cout << "#" << endl;
cout << "# trajgen_t : " << (sizeof(trajgen_t)) << "b" << endl;
cout << "# trajgen_joint_t : " << sizeof(trajgen_joint_t) << "b" << endl;
cout << "# trajgen_config_t : " << sizeof(trajgen_config_t) << "b" << endl;
cout << "# trajgen_internals_t : " << sizeof(trajgen_internals_t) << "b" << endl;
cout << "# trajgen_state_t : " << sizeof(trajgen_state_t) << "b" << endl;
cout << "#" << endl;
cout << "# trajgen_coord_t : " << sizeof(trajgen_coord_t) << "b" << endl;
cout << "# trajgen_coord_internals_t : " << sizeof(trajgen_coord_internals_t) << "b" << endl;
cout << "# trajgen_coord_config_t : " << sizeof(trajgen_coord_config_t) << "b" << endl;
cout << "# trajgen_coord_syncdio_t : " << sizeof(trajgen_coord_syncdio_t) << "b" << endl;
cout << "# trajgen_coord_syncdio_cmd_t : " << sizeof(trajgen_coord_syncdio_cmd_t) << "b" << endl;
cout << "# trajgen_queue_t : " << (sizeof(trajgen_queue_t)) << "b" << endl;
cout << "# trajgen_sg_line_t : " << sizeof(trajgen_sg_line_t) << "b" << endl;
cout << "# trajgen_sg_arc_t : " << sizeof(trajgen_sg_arc_t) << "b" << endl;
cout << "# trajgen_sg_curve_t : " << sizeof(trajgen_sg_curve_t) << "b" << endl;
cout << "# trajgen_sg_t : " << sizeof(trajgen_sg_t) << "b" << endl;
cout << "# pose_vector_t : " << sizeof(pose_vector_t) << "b" << endl;
cout << "# pose_line_t : " << sizeof(pose_line_t) << "b" << endl;
cout << "# pose_circle_t : " << sizeof(pose_circle_t) << "b" << endl;
cout << "# pose_curve_t : " << sizeof(pose_curve_t) << "b" << endl;
cout << "# pose_t : " << sizeof(pose_t) << "b" << endl;
cout << "# real_t : " << sizeof(real_t) << "b" << endl;
cout << "#" << endl;
cout << "# trajgen_jointtg_t : " << sizeof(trajgen_jointtg_t) << "b" << endl;
cout << "# trajgen_jointtg_config_t : " << sizeof(trajgen_jointtg_config_t) << "b" << endl;
cout << "#" << endl;
cout << "# trajgen_man_t : " << sizeof(trajgen_man_t) << "b" << endl;
cout << "# trajgen_man_config_t : " << sizeof(trajgen_man_config_t) << "b" << endl;
cout << "#" << endl;
cout << "# interpolator_t : " << sizeof(interpolator_t) << "b" << endl;
cout << "# kinematics_t : " << sizeof(kinematics_t) << "b" << endl;
cout << "# joint_t : " << sizeof(joint_t) << "b" << endl;
} else if(test_task=="push-line") {
cout << "t_calc_us" << endl;
double c=clk();
for(unsigned i=0; i<1000; i++) {
if(tg.queue_full()) { tg.reset(); tg.select(trajgen::coordtg); }
c=clk();
tg.line(randpos(100), randpos(100), randpos(100));
cout << (clk()-c) << endl;
}
return 0;
} else if(test_task=="push-arc") {
cout << "t_calc_us" << endl;
double c=clk();
for(unsigned i=0; i<1000; i++) {
if(tg.num_queued() > tg.queue_size()-3) { tg.reset(); tg.select(trajgen::coordtg); }
double x = randpos(100);
double y = randpos(100);
tg.line(x, y);
c=clk();
tg.arc(x, y, 0, 0,0,0, 0,0,1, 1);
cout << (clk()-c) << endl;
}
return 0;
} else if(test_task=="rand-lines") {
tg.select(trajgen::coordtg);
while(!tg.queue_full()) {
double speed = roundd(rnd()*250+10);
double accel = roundd(rnd()*500+500);
double blending = roundd(rnd()*13-3, 1);
double x = randpos(100);
double y = randpos(100);
double z = randpos(100);
blending = blending <0 ? 0:blending;
tg.speed(speed).accel(accel).blending(blending).line(x,y,z);
}
run_tg(tg);
} else if(test_task=="rand-arcs") {
double x = randpos(100);
double y = randpos(100);
run_tg(tg
.select(trajgen::coordtg)
.speed(roundd(rnd()*250+10))
.accel(roundd(rnd()*500+500))
.blending(roundd(rnd()*6-1, 1))
.line(x,y)
.arc(x,y,0, 0,0,0, 0,0,1, 1)
);
} else if(test_task=="straight-blending") {
run_tg(tg
.select(trajgen::coordtg)
.accel(1000.0)
.speed(10.0)
.blending(0.1)
.line(2.5,0)
.speed(100.0)
.line(5,0)
);
} else if(test_task=="rand-straight-blending") {
run_tg(tg
.select(trajgen::coordtg)
.accel(1000.0)
.speed(rnd()*75+25)
.blending(rnd() + TG_RESOLUTION)
.line(2.5,0)
.speed(rnd()*75+25)
.line(5,0)
);
} else if(test_task=="line1") {
run_tg(tg
.select(trajgen::coordtg)
.accel(1000.0)
.speed(53.3144)
.blending(rnd() + TG_RESOLUTION)
.line(2.5,0)
.speed(89.8083)
.line(5,0)
);
} else if(test_task=="line") {
run_tg(tg
.select(trajgen::coordtg)
.accel(100.0)
.speed(10.0)
.blending(0)
.line(10,0)
);
} else if(test_task=="arc") {
pose_t start = {10,0,0, 0,0,0, 0,0,0};
run_tg(tg
.pose(start)
.select(trajgen::coordtg)
.accel(100.0)
.speed(10.0)
.arc(10,0,0, 0,0,0, 0,0,1, 1)
);
} else if(test_task=="curve") {
run_tg(tg
.select(trajgen::coordtg)
.accel(100.0)
.speed(10.0)
.blending(0)
.curve(10,10,0, 10,2,0, 7,5,0)
);
} else if(test_task=="override") {
run_tg(tg
.select(trajgen::coordtg)
.accel(100.0)
.speed(10.0)
.blending(0)
.override(0.5)
.line(5,0)
);
} else if(test_task=="example") {
run_tg(tg
.select(trajgen::coordtg)
.accel(1000.0)
.speed(10.0)
.blending(0)
.line(40,0)
.arc(30,10,0, 40,10,0, 0,0,1, 0)
.blending(1.0)
.arc(20,10,0, 25,5,0, 0,0,1, 0)
.line(5,10)
.speed(100)
.dio_set_next(0x0001)
.line(-25,10)
.dio_set_next(0x0002)
.accel(250)
.dio_set_next(0x0004)
.line(-50,0)
.dio_set_next(0x0008)
.line(-60,50)
.dio_reset_next(0x0008)
.line(-70,60)
.dio_reset_next(0x0004)
.line(-70,70)
.dio_reset_next(0x0002)
.line(-60,80)
.dio_reset_next(0x0001)
.line(0,70)
.line(60,60)
.line(70,-10)
.blending(5.0)
.line(50,-20)
.line(-25,-10)
.line(0,-5)
.line(0,0)
);
} else {
return usage();
}
} catch (const char *s) {
cerr << "Exception: " << s << endl;
return 1;
} catch (int n) {
cerr << "Exception: " << n << endl;
return n;
} catch(...) {
cerr << "Exception: (uncaught)" << endl;
return 1;
}
return 0;
}
// </editor-fold>
Test Makefile
#CC=/usr/local/bin/gcc-4.9
CC=gcc
CCC=g++
AR=ar
AS=as
RANLIB=ranlib
CFLAGS= -Wall -std=c99 -pedantic -O3
CCFLAGS= -Wall -std=c++98 -pedantic -O3
LDFLAGS=
SHELL_INTERP=/bin/bash
OPTS=
ifeq ($(OS),Windows_NT)
OPTS +=
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
LDFLAGS+=-lm -lrt
DISASM=objdump -d -M intel -S
endif
ifeq ($(UNAME_S),Darwin)
DISASM=otool -XVtd
LDFLAGS+=-lm
endif
endif
ifndef WITH_INTERPOLATION
OPTS+=-DNO_INTERPOLATION
endif
ifdef NO_SYNC
OPTS+=-DNO_TRAJECTORY_SYNC
endif
ifdef NO_BLEND
OPTS+=-DNO_MOTION_BLENDING
endif
ifdef NO_ABC
OPTS+=-DNO_ABC
endif
ifdef NO_UVW
OPTS+=-DNO_UVW
endif
ifdef DEBUG
OPTS+=-DTG_DEBUG_LEVEL=$(DEBUG)
endif
ifdef WITH_CURVES
OPTS+=-DWITH_CURVES
endif
.PHONY: default
default: coord
.PHONY: clean
clean:
-@rm -f trajgen.o
-@rm -rf stats
-@rm -rf bin
.PHONY: all
all: clean test analyse show-analysis
.PHONY: test
test:
@./test/build-run.sh run no
.PHONY: all-tests
all-tests:
@./test/build-run.sh run-all no
.PHONY: example
example:
@./test/build-run.sh example no
@echo '--> The result is in directory "stats"'
.PHONY: error
error: test/test-error.c
@mkdir -p bin
@$(CC) $(CFLAGS) -o bin/test-error test/test-error.c trajgen.c $(OPTS) $(LDFLAGS)
@./bin/error
.PHONY: test-error
test-error: error
.PHONY: joint
joint:
@mkdir -p bin
$(CC) $(CFLAGS) -o bin/joint test/test-jointtg.c trajgen.c $(OPTS) $(LDFLAGS)
@./bin/joint
.PHONY: test-joint
test-joint: joint
.PHONY: coord
coord:
@mkdir -p bin
$(CCC) $(CCFLAGS) -o bin/coord test/test-coord.cc trajgen.c $(OPTS) $(LDFLAGS)
.PHONY: test-coord
test-coord: coord
.PHONY: sizes
sizes: coord
@./bin/coord sizes
.PHONY: disasm
disasm:
@mkdir -p bin
$(CC) -c -g -fverbose-asm $(CFLAGS) -o bin/trajgen.o trajgen.c $(OPTS)
$(DISASM) bin/trajgen.o > bin/trajgen.lst
.PHONY: analyse
analyse:
@./test/build-run.sh no analyse
.PHONY: analyze
analyze: analyse
.PHONY: analysis
analysis: analyse
.PHONY: stats
stats: analyse
.PHONY: sizes
sizes: analyse
.PHONY: show-analysis
show-analysis:
-@$(SHELL_INTERP) "test/open-file.sh" "stats/index.html"
.PHONY: dev
dev: coord
./bin/coord -txyC curve
.PHONY: dev1
dev1:
@mkdir -p bin
@$(CC) $(CFLAGS) -o bin/curve curve.c $(OPTS) $(LDFLAGS)
@./bin/curve
Test shell script
#!/bin/bash
#
# Trajectory planer test build/test script
# @author stfwi
# @file test/build-run.sh
#
OLDPWD=$(pwd)
ROOT=$(dirname "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )")
PRG=$ROOT/bin/coord
OUT=$ROOT/stats
CURHOST=$(echo $HOSTNAME|grep -Pwo '^[^\.]+')
CURHOST="x"
MAKE_PDF=no
OCTAVE_ARG2=""
#OCTAVE_OPTS="--no-gui --no-history --no-window-system"
OCTAVE_OPTS="--no-history --no-window-system"
COMPILE_EXAMPLE_ONLY="no"
COMPILE_CALC_TIMES_ONLY="no"
PRGOPTS="-*"
if [ "$1" == "calc-times" ]; then
DO_TESTS="yes"
COMPILE_CALC_TIMES_ONLY="yes"
COMPILE_EXAMPLE_ONLY="yes"
elif [ "$1" == "example" ]; then
DO_TESTS="yes"
COMPILE_EXAMPLE_ONLY="yes"
DO_ALL_COMPILER_SWITCHES="no"
elif [ "$1" == "run-all" ]; then
DO_TESTS="yes"
COMPILE_EXAMPLE_ONLY="no"
DO_ALL_COMPILER_SWITCHES="yes"
elif [ "$1" == "run" ]; then
DO_TESTS="yes"
COMPILE_EXAMPLE_ONLY="no"
DO_ALL_COMPILER_SWITCHES="no"
elif [ "$1" == "no" ]; then
DO_TESTS=""
COMPILE_EXAMPLE_ONLY="no"
DO_ALL_COMPILER_SWITCHES="no"
else
echo "Usage: build-run.sh [no|run|run-all|example|calc-times] [no|analyse|analyse-timing|save-only]"
echo ""
echo " 1st argument: C compiling and run tests. Default is 'no'."
echo " run : Run only tests with and without interpolation."
echo " run-all : Run with more compiler flags: UVW,ABC off, blending off, etc."
echo " example : Compile and run only the example trajectory."
echo " calc-times: 100 x compile example and force only timing output"
echo ""
echo " 2nd argument: Octave analysis. Default is 'no'."
echo " analyse: Analyse all data (positions, speeds, accels ...)."
echo " analyse-timing: Only performance statistics."
echo " save-only: Save a mat file for manual analysis."
echo ""
exit 1
fi
if [ "$2" == "save-only" ]; then
DO_STATS="yes"
OCTAVE_ARG2="save-only"
OCTAVE_SAVE_ONLY="yes"
elif [ "$2" == "analyse-timing" ]; then
DO_STATS="yes"
OCTAVE_ARG2="timing-stats"
elif [ "$2" == "analyse" ]; then
OCTAVE_ARG2=
DO_STATS="yes"
fi
function do_test() {
rm -f $PRG
echo "Compile test $1 ..."
cd $ROOT && make >/dev/null
echo "Run test $1 ..."
$PRG $PRGOPTS example > $OUT/$CURHOST-example--$1.txt 2>&1
if [ "$COMPILE_EXAMPLE_ONLY" != "yes" ]; then
#$PRG $PRGOPTS line > $OUT/$CURHOST-line--$1.txt 2>&1
#$PRG $PRGOPTS arc > $OUT/$CURHOST-arc--$1.txt 2>&1
$PRG $PRGOPTS curve > $OUT/$CURHOST-curve--$1.txt 2>&1
#$PRG $PRGOPTS straight-blending > $OUT/$CURHOST-straight-blending--$1.txt 2>&1
#$PRG $PRGOPTS push-line > $OUT/$CURHOST-push-line--$1.txt 2>&1
#$PRG $PRGOPTS push-arc > $OUT/$CURHOST-push-arc--$1.txt 2>&1
#$PRG $PRGOPTS rand-lines > $OUT/$CURHOST-rand-lines--$1.txt 2>&1
#$PRG $PRGOPTS rand-arcs > $OUT/$CURHOST-rand-arcs--$1.txt 2>&1
fi
cd $OLDPWD
}
function do_tests() {
do_test "noip"
if [ "$DO_ALL_COMPILER_SWITCHES" == "yes" ]; then
WITH_INTERPOLATION=1 do_test "all-on"
NO_BLEND=1 do_test "noip-noblend"
NO_MOTION_BLENDING=1 NO_SYNC=1 do_test "noip-noblend-nosync"
NO_ABC=1 do_test "noip-noabc"
NO_ABC=1 NO_UVW=1 do_test "noip-noabc-nouvw"
fi
}
function do_stats() {
OCTAVE="$(which octave)"
if [ "$OCTAVE" != "" ]; then
OCTAVE="$OCTAVE -qf $OCTAVE_OPTS"
if [ "$OCTAVE_SAVE_ONLY" != "yes" ]; then
rm -f $OUT/*.svg
rm -f $OUT/*.png
rm -f $OUT/*.html
fi
cd $OUT
[ "$OCTAVE_SAVE_ONLY" != "yes" ] && cp $ROOT/test/stats.css $OUT/
find . -type f -name '*.txt' -exec $OCTAVE $ROOT/test/stats.m {} "$OCTAVE_ARG2" \;
if [ "$MAKE_PDF" == "yes" ]; then
HTML2PDF="$(which wkhtmltopdf)"
if [ "$HTML2PDF" != "" ]; then
for F in *.html; do
$HTML2PDF -q "$F" "$F.pdf"
done
fi
fi
if [ "$OCTAVE_SAVE_ONLY" != "yes" ]; then
echo '<!doctype html><html><head>' > index.html
echo '<link rel="stylesheet" type="css" href="stats.css"/>' >> index.html
echo '</head><body><h1>Trajectory generator random motion test results</h1> <ul>' >> index.html
for F in *.html; do
if [ "$F" != "index.html" ]; then
echo '<li><a href="'"$F"'">' "$F" '</a></li>' >> index.html
fi
done
echo '</ul></body></html>' >> index.html
fi
cd $OLDPWD
fi
}
[ "$DO_TESTS" == "yes" ] && rm -f $ROOT/bin/*
mkdir -p $ROOT/bin
mkdir -p $OUT
if [ "$COMPILE_CALC_TIMES_ONLY" == "yes" ]; then
cd $ROOT && make >/dev/null
cd $OUT || exit 1;
for I in {1..25}; do
$PRG -p example > "$I.txt" 2>/dev/null
done
else
[ "$DO_TESTS" == "yes" ] && do_tests
[ "$DO_STATS" == "yes" ] && do_stats
fi
cd $OLDPWD
Test analysis (GNU Octave)
#!/usr/local/bin/octave -qf --no-gui --no-history --no-window-system
%
% Trajgen analysis script
%
% @author stfwi
% @file test/stats.m
%
global args;
for k=1:nargin, args{k}=argv(){k}; end;
clear k;
function f=infile()
f = regexprep(argv(){1}, "^\.\/", "");
end
function f=infilebase()
f = regexprep(regexprep(infile(), '\.[^\.]+$', ''), '.*?\/([^\/]+)', '$1');
end
function f=infilenoext()
f= regexprep (infile(), '\.[^\.]+$', '');
end;
function f=arg(name)
global args;
f = length(args) < 2 || length(args{2})==0 || index(args{2}, name) > 0;
end;
function open_html()
global html_fh;
global scv_header;
global build_info;
global platform_info;
html_fh = fopen([infilenoext() '.html'], 'w');
write_html([
'<!doctype html><html><head>' ...
'<title>Results for ' infilebase() '</title>' ...
'<link rel="stylesheet" type="text/css" href="stats.css"/>' ...
'</head><body>' ...
]);
write_html(['<h1>Results for ' infilebase() '</h1>']);
write_html(['<p class="platform-info"><em>Platform:</em> ' platform_info '</p>']);
write_html(['<p class="build-info"><em>Build information:</em> ' build_info '</p>']);
write_html('<p class="csv-header"><em>Data header information: </em><pre>');
for(k=1:length(scv_header))
s = scv_header{k};
s = regexprep(s, '\+([\d])', '$1');
s = regexprep(s, '\.0+,', ',');
s = regexprep(s, ':[\s]+', ':');
s = regexprep(s, ',[\s]+', ', ');
s = regexprep(s, ':0+,', ':0,');
write_text(s);
end
write_html('</pre></p>');
end
function write_text(s)
global html_fh;
s = strrep(s, '&', '&');
s = strrep(s, '<', '<');
s = strrep(s, '>', '>');
fprintf(html_fh, "%s\n", s);
end
function write_html(s)
global html_fh;
fprintf(html_fh, "%s\n", s);
end
function close_html()
global html_fh;
write_html('</body></html>');
fclose(html_fh);
end
set(0, 'defaultfigurevisible', 'off');
function savefig(tag, size)
global html_fh;
if(nargin <2), size='-S900,600'; end;
file = sprintf('%s-%s.png', infilenoext(), tag);
fig = gcf();
if(isfigure(fig))
warning ('off');
print(file, [size '-F:20']);
delete(fig);
end
fprintf(html_fh, '<p class="figure"><img src="%s" /></p>\n', file);
end
function stats = initargs()
global args;
global scv_header;
global build_info;
global platform_info;
global timing_only;
timing_only = 0;
scv_header = {};
build_info = '';
platform_info = '';
if(rows(args) < 1), error('Specify a file to analyse.'); end;
filename = args{1};
fh = fopen (filename, 'r');
s0 = fgetl(fh);
s1 = fgetl(fh);
k=0;
while(index(s1, '#') == 1),
skk = strtrim(substr(s0, 2));
if(index(skk, 'Platform:') > 0)
platform_info = strtrim(strrep(skk,'Platform:',''));
elseif(index(skk, 'Build:') > 0)
build_info = strtrim(strrep(skk,'Build:',''));
else
k = k+1;
scv_header{k} = skk;
end
s0=s1;
s1=fgetl(fh);
end;
s0 = strsplit(tolower(regexprep(strrep(s1, ' ', ''),'[^,_a-z0-9]', '')), ',');
csv = csvread(fh);
for k = 1:columns(s0), stats.(s0{k}) = csv(:,k); end;
fclose (fh);
clear csv s1 s0;
%% Some fixes
if(isfield(stats, 't'))
%% Fit time
dt = stats.t(end)-stats.t(end-1);
stats.t = (0:length(stats.t)-1)' * dt;
end
assignin('base', 'stats', stats);
end
%% Timing (calculation time) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function time_stats(calctime)
calctime = calctime(find(calctime > 0));
calctime = calctime(find(calctime > min(calctime)));
calctime = calctime(find(calctime < max(calctime)));
cmin = min(calctime);
cmax = max(calctime);
cmean= mean(calctime);
cmedian = median(calctime);
cstd = std(calctime);
num = rows(calctime);
write_html('<p class="textout"><em>Performance:</em><pre>');
write_text(sprintf('Maximum calculation time: %.1fus', cmax));
write_text(sprintf('Average calculation time: %.1fus', cmean));
write_text(sprintf('StdDev calculation time: %.1fus', cstd));
write_text(sprintf('Median calculation time: %.1fus', cmedian));
for(k=5:5:95)
write_text(sprintf('Ticks < %2d%% of max time : %.1f%%', k, ...
100*rows(find(calctime < cmax/100*k))/num));
end
write_html('</pre></p>');
stats = calctime;
n = rows(stats);
x = (1:n).*(100/n);
plot(x, stats, '-r', 'linewidth', 2);
grid on;
xlim([0,100]);
ylim([0,cmax]);
title(sprintf('Calculation times'));
xlabel('Tick (iteration) / % of total tick count');
ylabel('Calculation time / us');
savefig('calc-time-stats');
stats = sort(calctime);
n = rows(stats);
x = (1:n).*(100/n);
plot(x, stats, '-r', 'linewidth', 2);
grid on;
xlim([0,100]);
ylim([0,cmax]);
title(sprintf('Sorted calculation times'));
xlabel('Number of calculation cycles / % (sorted by calculation time)');
ylabel('Calculation time / us');
savefig('calc-time-stats-sorted');
end
%% Joint positions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function joint_positions(t,x,y,z)
if(y==0),
plot(t, x, '-k', 'linewidth', 2);
grid on;
title(sprintf('Joint position trace (x over time)'));
xlabel('time');
ylabel('x / 1');
elseif(z == 0),
plot(x, y, '-k', 'linewidth', 2);
grid on;
title(sprintf('Joint position trace (x/y)'));
xlabel('x / 1');
ylabel('y / 1');
axis('equal');
else
plot3(x, y, z, '-k', 'linewidth', 2);
grid on;
title(sprintf('Joint position trace (x/y/z)'));
xlabel('x / 1');
ylabel('y / 1');
zlabel('z / 1');
axis('equal');
end
savefig('joint-positions');
end
%% Speeds/accels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function joint_consistency(t,x,y,z)
if(y==0), y=zeros(size(x)); end;
if(z==0), z=zeros(size(z)); end;
dt = t(end)-t(end-1);
vx = diff(x)/dt; vx(1) = 0;
vy = diff(y)/dt; vy(1) = 0;
vz = diff(z)/dt; vz(1) = 0;
v = sqrt(vx.*vx+vy.*vy+vz.*vz);
ax = diff(vx)/dt; ax = [ ax; 0 ]; ax(1) = 0;
ay = diff(vy)/dt; ay = [ ay; 0 ]; ay(1) = 0;
az = diff(vz)/dt; az = [ az; 0 ]; az(1) = 0;
a = sqrt(ax.*ax+ay.*ay+az.*az);
t = ((1:rows(vx))') * dt;
subplot(2, 1, 1);
plot(t, v, '-k', 'linewidth', 2);
grid on;
title(sprintf('Trajectory velocity'));
xlabel('time / 1');
ylabel('velocity / 1');
subplot(2, 1, 2);
plot(t, a, '-k', 'linewidth', 2);
grid on;
title(sprintf('Trajectory acceleration'));
xlabel('time / 1');
ylabel('acceleration / 1');
savefig('traj-velocity-acceleration', '-S1024,768');
subplot(2, 1, 1);
plot(t, vx, '-k', 'linewidth', 2);
hold on;
if(length(find(vy ~= 0))>0), plot(t, vy, '--k', 'linewidth', 2); end;
if(length(find(vz ~= 0))>0), plot(t, vz, ':k', 'linewidth', 2); end;
grid on;
title(sprintf('Joint velocities'));
xlabel('time / 1');
ylabel('velocity / 1');
subplot(2, 1, 2);
plot(t, ax, '-k', 'linewidth', 2);
hold on;
if(length(find(ay ~= 0))>0), plot(t, ay, '--k', 'linewidth', 2); end;
if(length(find(az ~= 0))>0), plot(t, az, ':k', 'linewidth', 2); end;
grid on;
title(sprintf('Joint accelerations'));
xlabel('time / 1');
ylabel('acceleration / 1');
savefig('joint-velocity-acceleration');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
disp(sprintf('Analyse %s', infilebase()));
initargs();
if(length(args)>1 && length(args{2})>0 && arg('save-only')),
clear args;
save([infilenoext() ".mat"]);
else
with_timing = arg('timing') && isfield(stats, 't_calc_us');
with_positions = arg('positions') && isfield(stats, 'j0s');
with_speeds = (arg('speed') || arg('accel') || arg('velo') ) && isfield(stats, 'j0s');
if(with_timing || with_positions || with_speeds)
open_html();
if(with_timing), time_stats(stats.t_calc_us); end;
if(with_positions || with_speeds)
if(!isfield(stats, 't')), t=(0:length(stats.j1s)-1)'; end;
if(!isfield(stats, 'j1s')), stats.j1s = 0; end;
if(!isfield(stats, 'j2s')), stats.j2s = 0; end;
if(with_positions), joint_positions(stats.t, stats.j0s, stats.j1s, stats.j2s); end;
if(with_speeds), joint_consistency(stats.t,stats.j0s,stats.j1s,stats.j2s); end;
end
close_html();
end
end
Stylesheet of Test analysis
/**
* Analysis output style sheet
* @author stfwi
* @file test/stats.css
*/
* { font-family: monospace; font-size: 11pt; }
html { margin: 0; width: 100%; padding: 0mm; }
body { margin: 0; padding: 0; width: 100%; }
div,img,p,pre,h1 { position: relative; }
h1 { font-size: 13pt; width: 100%; text-align: center; }
pre { margin-top: 10pt; padding: 0; font-size: 10pt; padding-left: 10mm; }
p, h2, h3 { orphans: 3; widows: 3; }
p.csv-header + pre { font-size: 9pt; }
p { margin: 10pt 0 0 0; padding: 0; display: block; }
p.platform-info { font-size: 10pt; }
p.build-info { font-size: 10pt; }
p.figure { width: 100%; text-align: center; }
img { width: 90%; max-width: 95% !important; page-break-inside: avoid; }
a, a:visited { text-decoration: underline; color: #333399; }
h1, h2, h3 { page-break-after: avoid; }
@media screen {
html { background: #fafafa; }
body {
width: 800px !important;
min-height: 600px;
padding: 42px 80px 42px 80px;
margin: 10px;
background: white;
box-shadow: #444 2px 3px;
border: solid 1px #aaa;
border-radius: 2px;
}
}
@media print {
* {
background: transparent !important;
color: black !important;
text-shadow: none !important;
filter:none !important;
}
a[href]:after { content: " (" attr(href) ")"; }
abbr[title]:after { content: " (" attr(title) ")"; }
@page { margin: 5mm; }
}