Writing a component with instcomp
Introduction
instcomp is the Machinekit component pre-processor and compiler for instantiable components (.icomp)
It is the counterpart to the comp program, for creating legacy rt components.
Instcomp allows the user to write a short script which details what pins should be created, default and maximum numbers of pins and what functions should be created etc.
The function code is appended below the script delimiter ;; and this code is inserted unaltered into the
component function.
The correctness of this code is the sole responsibility of the user.
The preprocessor creates a complete C component by interpreting the script section and creates all the rtapi calls and functions required, without any knowledge being required on the part of the user.
Writing an Instantiated Component
The easiest way to demonstrate is by way of example, so we will use an existing component - debounce.icomp
debounce is used to eliminate false signals due to electrical noise.
Common applications are for limit and home switches, to prevent false triggers from induced voltages from 'dirty' spindle motors for instance.
The component will delay for a specified number of thread polls with an input pin HIGH, before it will pass that signal on to the output pin.
First line
component debounce "Debounce filter for Machinekit HAL";
The first line of a component, must specify its name. This name must be the same as the filename, minus the extension.
There may be an optional description and this and all other lines are terminated with a C style semi colon.
The pins
Pins and variables are the first thing detailed in the script.
They will be created and added to the instance structure which contains all the data relating to that instance of the component.
// Input Pins
pin in bit #.in[pincount];
pin out bit #.out[pincount];
pin io s32 #.state[pincount];
pin io s32 delay = 5;
Pins are described in a pin [direction] [type] [name] format.
This can be seen in the delay pin line, plus an initialisation to a default value.
NB.
Params are deprecated, if you want a pin value that can be initialised and altered by the user, use an io pin.
Params go back to a stage when normal pins could not be initialised at construction, which no longer applies.
The 3 top lines create arrays of pins for in, out and state.
The # is a placeholder for the pin number and pincount is a special integer instanceparam, which we will cover shortly.
If pincount is 4, the script instructs that 3 arrays of 4 pins, numbered from 0 - 3 be created.
Instanceparams
instanceparam int pincount = 8;
option MAXCOUNT 16;
instanceparams are the equivilent to rt module parameters, that is, they are arguments used in the creation and running of the component.
pincount is a reserved name, used by a lot of components, to tell the component the size of the pin arrays.
The value of the default is 8, the option MAXCOUNT tells instcomp that the largest number permitted is 16. However if the user creates the component with
newinst debounce db pincount=4
the component will be created with 3 arrays of 4 pins
Licence etc.
Next come any author credits and licence details
license "GPL";
author "John Kasunich, adapted by ArcEye";
Functions and delimiter
function _;
;;
The function is the part of the code that is attached to a thread and called at every thread period
You MUST declare at least one function.
The majority of components only have one function, which executes all the code.
In this case there is no need to uniquely name it, it can be represented by an underscore.
When loaded it will appear as component.[N.]funct
If you had 2 functions, one to read data and the other to write it out, you might declare it thus.
function readin ;
function writeout ;
The script section is terminated by the delimiter ;;
So the script section of this component in its entirity is
component debounce "Debounce filter for Machinekit HAL";
// Input Pins
pin in bit #.in[pincount];
pin out bit #.out[pincount];
pin io s32 #.state[pincount];
pin io s32 delay = 5;
instanceparam int pincount = 8;
option MAXCOUNT 16;
license "GPL";
author "John Kasunich, adapted by ArcEye";
function _;
;;
Function Code
Everything below the ;; delimiter, will be reproduced verbatim in the component.
In our example this is
FUNCTION(_)
{
hal_s32_t n;
// first make sure delay is sane
if (delay < 0)
delay = 1;
// loop through filters
for (n = 0; n < local_pincount; n++)
{
if(_in(n))
{
/* input true, is state at threshold? */
if (_state(n) < delay)
_state(n)++;
else
_out(n) = true;
}
else
{
if (_state(n) > 0)
_state(n)--;
else
_out(n) = false;
}
}
return 0;
}
FUNCTION()
FUNCTION() is a macro which expands into the function declaration.
The name of the function is inserted between the parentheses, in this case the default underscore.
There are convenience macros created by default for all the pins and variables in the instance structure.
These allow the user to address the pins by name without worrying about the pointer dereferencing required.
Arrays are addressed using parenthesis instead of square brackets.
local_xxxx
You will note that to iterate through the arrays of pins, a variable called local_pincount is used.
This is a standard local variable created in the instance structure whenever pincount appears in a component.
It holds the value that was passed to that instance.
Instanceparams are based upon kernel module params and have one major achilles heel, they are persistent for the
life of the base component and are not renewed at each instance creation.
This was never a problem when you could only load a legacy component once, but the advent of instantiated components
means you could load the same component several times, with different values to pincount say.
If you used the global instanceparam pincount in your function, to iterate through the array, you would get whatever value it was last set to.
So if you loaded an instance with less pins after this instance, you would be restricted to less pins than you actually created.
However if you loaded an instance with more pins after this instance, your function would overrun the bounds of its array and segfault,
possibly crashing the system.
To prevent this, instcomp sets the global instanceparams to 0 or -1 after they are copied locally.
Thus, if for instance pincount is not specified in a subsequent instance creation,
it will use the default set for the component, not whatever the last instance used.
Other standard local variables are local_argc and local_argv
which hold the values passed to the component at instantiation.
These can be used in components to pass complex string arguments.
An advanced example of the use of this mechanism can be found in
https://github.com/machinekit/machinekit/blob/master/src/hal/drivers/mesa-hostmot2/hm2_soc_ol.c
Any instanceparam that you declare in the script, will have a corresponding local_ version created and its value copied to it by instcomp.
_in, _out and _state
You may have noticed that the pin names get changed from in to _in etc.
This is because of how one of the conversion routines in instcomp treats names preceeded by a . (period)
They are a local reference to the pin, the pin name seen externally will be as intended.
The writer knew this would happen and wrote his function code accordingly, with underscores.
It does however raise a unrelated, very common naming issue that is best avoided.
The naming of pins as in or out clashes with the IO type of the pin, as well as possibly some default compiler routines.
The naming functions as read and write, definately clashes with basic compiler routines.
All are best avoided.
( Confusingly, some compilers used to create leading underscored local variables automatically, if you try to use a built-in function
as a variable name. )
The End Result
Just to show how the code you have seen above translates in the actual component, especially with regards to pin names, this is what you get when you load it.
user@INTEL-i7:/usr/src/machinekit/src/hal/i_components$ realtime restart
user@INTEL-i7:/usr/src/machinekit/src/hal/i_components$ halcmd newinst debounce db pincount=4
user@INTEL-i7:/usr/src/machinekit/src/hal/i_components$ halcmd show pin db
Component Pins:
Comp Inst Type Dir Value Name Epsilon Flags linked to:
78 80 bit IN FALSE db.0.in --l-
78 80 bit OUT FALSE db.0.out --l-
78 80 s32 I/O 0 db.0.state --l-
78 80 bit IN FALSE db.1.in --l-
78 80 bit OUT FALSE db.1.out --l-
78 80 s32 I/O 0 db.1.state --l-
78 80 bit IN FALSE db.2.in --l-
78 80 bit OUT FALSE db.2.out --l-
78 80 s32 I/O 0 db.2.state --l-
78 80 bit IN FALSE db.3.in --l-
78 80 bit OUT FALSE db.3.out --l-
78 80 s32 I/O 0 db.3.state --l-
78 80 s32 I/O 5 db.delay --l-
78 80 s32 OUT 0 db.funct.time ----
78 80 s32 I/O 0 db.funct.tmax ----
78 80 bit OUT FALSE db.funct.tmax-inc ----