C/C++ user space HAL component compilation
- eneuro
- Offline
- Junior Member
- Posts: 21
- Thank you received: 0
I've created my own RT kins component for my CNC machine www.linuxcnc.org/index.php/english/forum...ine-coordinate#47061
using linuxcnc comp utility and successfuly changed linuxcnc axis configuration to activate those kins www.linuxcnc.org/index.php/english/forum...inate?start=10#47258 .
Now I'd like to create user space C/C++ machine visualisation OpenGL appliaction calling HAL to read pins provided in my kins RT component and I'm confused if I could use this comp utility to compile it while as I read in linuxdev-cnc/src/hal/hal.h it looks like I need
#define ULAPI
HAL uses the RTAPI/ULAPI interface. If RTAPI is #defined, (done
by the makefile) hal_lib.c would generate a kernel module hal_lib.o
that is insmoded and provides the functions for all kernel module
based components. The same source file compiled with the ULAPI
#define would make a user space hal_lib.o that is staticlly linked
to user space code to make user space executables.
How to tell comp utility I'm going to make user space HAL component-not RT component?
Maybe I could create my own application makefile and include headers for HAL library passing
-DULAPI
It looks like linuxcnc-dev/bin/comp creates its own Makefile this way:
makefile = os.path.join(tempdir, "Makefile")
f = open(makefile, "w")
print >>f, "%s: %s" % (binname, filename)
print >>f, "\t$(CC) $(EXTRA_CFLAGS) -URTAPI -U__MODULE__ -DULAPI -Os %s -o $@ $< -Wl,-rpath,$(LIBDIR) -L$(LIBDIR) -llinuxcnchal %s" % (
options.get("extra_compile_args", ""),
options.get("extra_link_args", ""))
print >>f, "include %s" % find_modinc()
f.close()
I looks like either RTAPI or ULAPI should be defined:
./src/rtapi/rtapi.h:#if ( !defined RTAPI ) && ( !defined ULAPI )
./src/rtapi/rtapi.h:#error "Please define either RTAPI or ULAPI!"
./src/rtapi/rtapi.h:#if ( defined RTAPI ) && ( defined ULAPI )
./src/rtapi/rtapi.h:#error "Can't define both RTAPI and ULAPI!"
Please Log in or Create an account to join the conversation.
- ArcEye
- Offline
- Junior Member
- Posts: 25
- Thank you received: 761
You have crossed the threshold of what can usefully be done with comp.
Once you want to start using C++ libraries, you really need to write the whole component from scratch.
Running comp without any arguements on a simple component will get you the generated C file which will give you a basis to work from
and capturing the output gcc string from a build will give you the basis of the build syntax
I have written a lot of Qt based C++ userspace components, for which I now have seperate libraries and Qt makespecs.
When I was writing in C and needed to add extra libraries I used the below from a script, which shows most of the defines and switches required
which may be of some use to you
g++ -Os -g -I. -I/usr/src/build/include -I/usr/realtime-2.6.32-122-rtai/include -I. -I/usr/realtime-2.6.32-122-rtai/include -D_FORTIFY_SOURCE=0 \
-mhard-float -fno-fast-math -mieee-fp -fno-unsafe-math-optimizations -D_GNU_SOURCE \
-I/usr/include/emc2 -Wframe-larger-than=2560 -DULAPI -Os \
-o $2 $2.cpp -Wl,-rpath,/lib -L/lib -llinuxcncini -llinuxcnchal -lnml -llinuxcnc -lposemath -$3
regards
Please Log in or Create an account to join the conversation.
- eneuro
- Offline
- Junior Member
- Posts: 21
- Thank you received: 0
Finally I was able to compile my simple user space HAL enabled application which creates a few HAL signals needed and I was able to run it as standalone from command line
or using HAL utilities$ ./proparabolic
$ halcmd loadusr proparabolic
I've used this simple command while catched first what comp writes to its temporary Makefile during RT HAL component compilation, but it looks like that user space HAL library is this linuxcnchal.so
In this application simply added this to ensure only ULAPI is defined whatever is specified in compilator options:$ make pro
g++ -I/usr/include/linuxcnc -I. -DULAPI -O4 proparabolic.cpp -o proparabolic -L/usr/lib -llinuxcnchal
// For user space HAL RTAPI
#ifdef RTAPI
#undef RTAPI
#endif
#ifndef ULAPI
#define ULAPI
#endif
#include "hal.h"
Now, I can monitor those HAL signals created in this application: eneurokinsml eneurokinsmr eneurokinsmz as expected when issue MDI G-codes with my eneurokins loaded in linuxcnc axis configuration:
However, I have no idea... how to read created HAL signal using this HAL interface in my C++ application
// signals
hal_signal_delete(eneurokins_ml_sig);
sigml= hal_signal_new(eneurokins_ml_sig, HAL_FLOAT );
hal_link(eneurokins_ml, eneurokins_ml_sig );
hal_signal_delete(eneurokins_mr_sig);
sigmr= hal_signal_new(eneurokins_mr_sig, HAL_FLOAT );
hal_link(eneurokins_mr, eneurokins_mr_sig );
hal_signal_delete(eneurokins_mz_sig);
sigmz= hal_signal_new(eneurokins_mz_sig, HAL_FLOAT );
hal_link(eneurokins_mz, eneurokins_mz_sig );
Functions: hal_signal_new and hal_link return int numbers.
Maybe missed something, while reading this hal.h , but ... I have no idea how to read those created float signals and store in C++ variable
I hope
will help find how to convert those signals to real C++ float/double numbers ?linuxcnc-dev$ grep -ir hal_signal .
Please Log in or Create an account to join the conversation.
- ArcEye
- Offline
- Junior Member
- Posts: 25
- Thank you received: 761
This extract from my library will give you an idea about writing to and reading pins
int set_common(hal_type_t type, void *d_ptr, QString& value)
{
// This function assumes that the mutex is held
int retval = 0;
double fval = 0;
long lval = 0;
unsigned long ulval = 0;
switch (type)
{
case HAL_BIT:
if( (value.contains("TRUE") || (value.contains("1"))))
*(hal_bit_t *) (d_ptr) = 1;
else if( (value.contains("FALSE") || (value.contains("0"))))
*(hal_bit_t *) (d_ptr) = 0;
else
retval = -EINVAL;
break;
case HAL_FLOAT:
fval = value.toFloat();
*((hal_float_t *) (d_ptr)) = fval;
break;
case HAL_S32:
lval = value.toLong();
*((hal_s32_t *) (d_ptr)) = lval;
break;
case HAL_U32:
ulval = value.toULong();
*((hal_u32_t *) (d_ptr)) = ulval;
break;
default:
/* Shouldn't get here, but just in case... */
retval = -EINVAL;
}
return retval;
}
int get_common(hal_type_t type, void *d_ptr, QString& value)
{
// This function assumes that the mutex is held
int retval = 0;
int bitval = 0;
double fval = 0;
long lval = 0;
unsigned long ulval = 0;
QString str;
switch (type)
{
case HAL_BIT:
bitval = *(hal_bit_t *) (d_ptr);
str.sprintf("%d", bitval);
break;
case HAL_FLOAT:
fval = *((hal_float_t *) (d_ptr));
str.sprintf("%f", fval);
break;
case HAL_S32:
lval = *((hal_s32_t *) (d_ptr));
str.sprintf("%ld", lval);
break;
case HAL_U32:
ulval = *((hal_u32_t *) (d_ptr));
str.sprintf("%lu", ulval);
break;
default:
/* Shouldn't get here, but just in case... */
retval = -EINVAL;
}
//qDebug() << "str string = " << str;
value = str;
//qDebug() << "value string = " << value;
return 0;
}
The mutex code is something like
// get mutex before accessing shared data
rtapi_mutex_get(&(hal_data->mutex));
...
get or set the value
...
rtapi_mutex_give(&(hal_data->mutex));
regards
Please Log in or Create an account to join the conversation.
- eneuro
- Offline
- Junior Member
- Posts: 21
- Thank you received: 0
Very nice, but I have NO pointers so far, only int numbers when signals are created using this hal_signal_new functions in user space
...
int get_common(hal_type_t type, void *d_ptr, QString& value)
{
...
int bitval = 0;
...
bitval = *(hal_bit_t *) (d_ptr);
...
HAL pins are created in kins RT kernel component:
struct haldata {
hal_float_t *md; // Machine diameter
hal_float_t *cx, *cy, *cz; // G-code cartesian coords
hal_float_t *ml, *mr, *mz; // Machine coords
} *haldata;
...
if((result = hal_pin_float_new("eneurokins.ml", HAL_IO, &(haldata->ml), comp_id)) < 0) goto error;
if((result = hal_pin_float_new("eneurokins.mr", HAL_IO, &(haldata->mr), comp_id)) < 0) goto error;
if((result = hal_pin_float_new("eneurokins.mz", HAL_IO, &(haldata->mz), comp_id)) < 0) goto error;
How to find those pointers needed for signal/pin get/set in C++?
Please Log in or Create an account to join the conversation.
- ArcEye
- Offline
- Junior Member
- Posts: 25
- Thank you received: 761
When you create any pin you have to malloc the memory required to hold the data it can carry, that is the pointer.
As I said my libraries were for interfacing with Qt, I created various typedefs
// These are used as the basic data units for any pins created
// via the functions in this class
typedef struct {
hal_bit_t *pin;
} bit_data_t;
typedef struct {
hal_s32_t *pin;
} s32_data_t;
typedef struct {
hal_float_t *pin;
} float_data_t;
and held the pins created in a local arrays for ease of access
// Access arrays
bit_data_t* bit_data[ARRAY_SIZE];
s32_data_t* s32_data[ARRAY_SIZE];
float_data_t* float_data[ARRAY_SIZE];
hal_bit_t* bit_param_data[ARRAY_SIZE];
hal_s32_t* s32_param_data[ARRAY_SIZE];
hal_float_t* float_param_data[ARRAY_SIZE];
// if(strcmp((char*)float_name[y], name) == 0) finds name
char bit_name [ARRAY_SIZE][NAME_LEN];
char s32_name [ARRAY_SIZE][NAME_LEN];
char float_name [ARRAY_SIZE][NAME_LEN];
char bit_param_name [ARRAY_SIZE][NAME_LEN];
char s32_param_name [ARRAY_SIZE][NAME_LEN];
char float_param_name [ARRAY_SIZE][NAME_LEN];
int bit_index; // index to the relevant array, left at last entry so size is known
int s32_index;
int float_index;
int bit_param_index;
int s32_param_index;
int float_param_index;
and this is routine to create a float pin and eneter it into the relevant array
int HAL_Access::createFloatPin(QString& name, bool in)
{
int retval;
QStrncpy(lname, name, name.length());
if(float_index >= ARRAY_SIZE)
{
rtapi_print_msg(RTAPI_MSG_ERR,"ERROR: pin %s creation not possible, local array full\n", lname);
return -1;
}
else
{
float_data_t *data = (float_data_t*)hal_malloc(sizeof(float_data_t));
if (data == 0)
{
rtapi_print_msg(RTAPI_MSG_ERR, "ERROR: hal_malloc() for pin %s creation failed\n", lname);
cleanUp();
}
else
{
float_data[float_index] = data;
strcpy((char*)float_name[float_index++], lname);
}
retval = hal_pin_float_newf(in ? HAL_IN : HAL_OUT, &(data->pin), comp_id, "%s.%s", prefix, lname);
if (retval < 0)
{
rtapi_print_msg(RTAPI_MSG_ERR,"ERROR: pin %s export failed with err=%i\n", lname, retval);
cleanUp();
return -1;
}
return 0;
}
}
If you write a simple comp file that creates a do nothing component with a couple of pins, run comp filename to generate a .c file
you can then see the pure C method of creating a pin.
struct __comp_state {
struct __comp_state *_next;
hal_float_t *out;
hal_float_t *in0;
hal_float_t *in1;
};
struct __comp_state *__comp_inst=0;
struct __comp_state *__comp_first_inst=0, *__comp_last_inst=0;
static void _(struct __comp_state *__comp_inst, long period);
static int __comp_get_data_size(void);
#undef TRUE
#define TRUE (1)
#undef FALSE
#define FALSE (0)
#undef true
#define true (1)
#undef false
#define false (0)
static int export(char *prefix, long extra_arg) {
char buf[HAL_NAME_LEN + 1];
int r = 0;
int sz = sizeof(struct __comp_state) + __comp_get_data_size();
struct __comp_state *inst = hal_malloc(sz);
memset(inst, 0, sz);
r = hal_pin_float_newf(HAL_OUT, &(inst->out), comp_id,
"%s.out", prefix);
if(r != 0) return r;
r = hal_pin_float_newf(HAL_IN, &(inst->in0), comp_id,
"%s.in0", prefix);
if(r != 0) return r;
r = hal_pin_float_newf(HAL_IN, &(inst->in1), comp_id,
"%s.in1", prefix);
if(r != 0) return r;
rtapi_snprintf(buf, sizeof(buf), "%s", prefix);
r = hal_export_funct(buf, (void(*)(void *inst, long))_, inst, 1, 0, comp_id);
if(r != 0) return r;
if(__comp_last_inst) __comp_last_inst->_next = inst;
__comp_last_inst = inst;
if(!__comp_first_inst) __comp_first_inst = inst;
return 0;
}
This in fact mallocs memory for the whole struct in one go, but the priciple is the same
regards
Please Log in or Create an account to join the conversation.
- ArcEye
- Offline
- Junior Member
- Posts: 25
- Thank you received: 761
int HAL_Access::getPValue(QString& name, QString& value)
{
int retval;
hal_param_t *param;
hal_pin_t *pin;
hal_type_t type;
void *d_ptr;
QStrncpy(lname, name, name.length());
// get mutex before accessing shared data
rtapi_mutex_get(&(hal_data->mutex));
// search param list for name
param = halpr_find_param_by_name(lname);
if (param == 0)
{
pin = halpr_find_pin_by_name(lname);
if(pin == 0)
{
rtapi_mutex_give(&(hal_data->mutex));
qDebug() << "pin " << lname << " not found";
return -EINVAL;
}
else // pin
{
type = pin->type;
// d_ptr = (void*)SHMPTR(pin->dummysig);
d_ptr = (void*)&pin->dummysig;
}
}
else // param
{
type = param->type;
d_ptr = SHMPTR(param->data_ptr);
}
retval = get_common(type, d_ptr, value);
rtapi_mutex_give(&(hal_data->mutex));
if (retval != 0)
qDebug() << "getPValue() " << lname << " failed";
return retval;
}
Please Log in or Create an account to join the conversation.
- eneuro
- Offline
- Junior Member
- Posts: 21
- Thank you received: 0
OK, I think this is what I missed during my signal creation-looking at this figure below from linuxcnc.org/docs/html/hal/basic_hal.html :When you create any pin you have to malloc the memory required to hold the data it can carry, that is the pointer.
my signal sources are those I/O pins created in RT kins module eneurokins.ml and I should create in my user space app input pin and connect to the same signal.
This way, I will have pointer to input pin needed to read this signal in my application while
linuxcnc.org/docs/html/man/man3/intro.3hal.html says:
In the HAL, a signal contains the actual data value that passes from one pin to another. When a signal is created, space is allocated for the data value. A pin on the other hand, is a pointer, not a data value. When a pin is connected to a signal, the pin’s pointer is set to point at the signal’s data value. This allows the component to access the signal with very little run-time overhead. (If a pin is not linked to any signal, the pointer points to a dummy location, so the realtime code doesn’t have to deal with null pointers or treat unlinked variables as a special case in any way.)
Thx for help
Please Log in or Create an account to join the conversation.
- ArcEye
- Offline
- Junior Member
- Posts: 25
- Thank you received: 761
A good place to start looking to see how hal pins etc can be created, found, read etc is the source for halcmd
Good Luck
Please Log in or Create an account to join the conversation.
- eneuro
- Offline
- Junior Member
- Posts: 21
- Thank you received: 0
It looks like those pointers are set to signal value, while it now reads HAL pins set in RT eneurokins component updated by linuxcnc axis in MDI mode for test G-code entered// Pins
typedef struct thaldata {
hal_float_t *md; // Machine diameter
hal_float_t *cx, *cy, *cz; // G-code cartesian coords
hal_float_t *ml, *mr, *mz; // Machine coords
} thaldata;
thaldata *haldata;
...
#define ML (*(haldata->ml))
...
double ml;
...
// input pins
if((ret = hal_pin_float_new("eneurokinsd.ml", HAL_IN, &(haldata->ml), comp_id)) < 0) goto error;
....
// signals
hal_signal_delete(eneurokins_ml_sig);
sigml= hal_signal_new(eneurokins_ml_sig, HAL_FLOAT );
hal_link(eneurokins_ml, eneurokins_ml_sig );
hal_link("eneurokinsd.ml", eneurokins_ml_sig );
...
// read
ml= ML;
// delay
// goto read
$ halcmd loadusr proparabolic
proparabolic: HAL component: proparabolic init: comp id: 18
EneuroKins HAL signals: eneurokinsml: 0.000 eneurokinsmr: 0.000 eneurokinsmz: 0.000
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 0.678 eneurokinsmz: 0.139
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 0.883 eneurokinsmz: 0.182
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 1.092 eneurokinsmz: 0.225
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 1.297 eneurokinsmz: 0.267
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 1.507 eneurokinsmz: 0.310
EneuroKins HAL signals: eneurokinsml: 46.365 eneurokinsmr: 1.710 eneurokinsmz: 0.352
...
Maybe I should add code for its mutexes, but in this machine visualisation software, where I do not expect high frame refresh rates while this milling process is not too fast and for the moment I need only read a few signals with tool position set by forward/inverse kinetics in this RT eneurokins component, there is no need to play with mutexes I guess in read code which is something like this when definitions are preprocessed in C++:
double mx= (*(haldata->mx));
Please Log in or Create an account to join the conversation.