1. comp: инструмент для создания модулей HAL
1.1 Вступление
Написание компонента HAL может утомительным процессом,большинство из них обращается к функциям rtapi_ и hal_ и ассоциированным проверкам ошибок.
comp создаст весь этот код для вас автоматически.
Компилирование компонента HAL гораздо проще с использованием comp, в не зависимости от того находится ли компонент в дереве исходного кода EMC2 или нет.
Например, "ddt" занимает около 80 строк кода. Его эквивалентный компонент очень короткий, если написан с использованием препроцессора comp:
("ddt" это компонент для вычисления производной заданной величины)
Код: Выделить всё
component ddt "Compute the derivative of the input function";
pin in float in;
pin out float out;
variable float old;
function _;
license "GPL";
;;
float tmp = in;
out = (tmp - old) / fperiod;
old = tmp;
И этот компонент может быть очень легко скомпилирован и установлен: просто помещаем
ddt.comp в
src/hal/components и запускаем "make", или помещаем в любое место в системе и запускаем
comp --install ddt.comp.
1.2 Определения
- component - компонент это модуль реального времени, который загружается при помощи halcmd loadrt. Один .comp файл задает один компонент.
- instance - компонент может иметь ноль и более сущностей. Каждая сущность компонента создается идентичной (ни имеют одинаковые пины, параметры, функции и данные) но ведут себя независимо когда их пины, параметры, функции и данные содержат разные значения.
- singleton - компонент может быть "singleton" (синглтоном), в этом случае создается только одна сущность. Редко бывает смысл создавать компонент синглтон, кроме тех случаев, когда в системе может быть только один объект такого типа (например, компонент назначение которого предоставлять пин текущего UNIX времени, или драйвер для внутреннего PC спикера).
1.3 Создание сущности
Для синглтона, одна сущность создается когда компонент загружается.
Для не синглтона, параметр "count" определяет как много нумерованных сущностей будет создано.
1.4 Параметры
Компонентам перелается параметр "period" который содержит время в наносекундах последнего выполнения компонента. Это может быть полезно для компонентов которым нужна временная информация.
1.5 Синтакс
Файл .comp состоит из набора деклараций, за которыми следует строка ";;", после которой идет код на C реализующий функции модуля.
Объявления (declaration) включают:
- component HALNAME (DOC);
- pin PINDIRECTION TYPE HALNAME ([SIZE]|[MAXSIZE : CONDSIZE]) (if CONDITION) (= STARTVALUE) (DOC);
- param PARAMDIRECTION TYPE HALNAME ([SIZE]|[MAXSIZE : CONDSIZE]) (if CONDITION) (= STARTVALUE) (DOC) ;
- function HALNAME (fp | nofp) (DOC);
- option OPT (VALUE);
- variable CTYPE NAME ([SIZE]);
- description DOC;
- see_also DOC;
- license LICENSE;
- author AUTHOR;
Круглые скобки обозначают необязательные элементы. Вертикальная черта обозначает альтернативы. Слова большими буквами обозначают произвольный текст, как ниже:
- HALNAME
Идентификатор.
Когда используется для создания HAL идентификатора все подчеркивания заменяются знаками "-", и все завершающие тире и точки убираются, таким образом "this_name_" будет преобразован в "this-name", и если имя равно "_", то конечная точка будет также удалена, таким образом "function _" дает функцию HAL типа "component.<num>" вместо "component.<num>.".
Префикс hal_, если присутствует, удаляется из начала имени компонента когда создаются пины, параметры и функции.
В идентификаторе HAL для пинов или параметров, # обозначает массив, и должен быть использован в сочетании с объявлением размера [SIZE]. Знаки # заменяются числом (начинающимся с 0) с количеством знаков равным количеству знаков #.
Когда используется для создания идентификатора С, следующие изменения применяются к HALNAME:
Все символы # и любые ".", "_" или "-" сразу перед ними убираются. Все оставшиеся "." и "-" заменяются на "_". Повторяющиеся знаки "_" заменяются одиночными "_".
Конечный "_" остается, таким образом идентификатор HAL который вступал бы в противоречие с зарезервированными именами или ключевыми словами (например "min") может быть использован.
| HALNAME | C Identifier | HAL Identifier |
| x_y_z | x_y_z | x-y-z |
| x-y.z | x_y_z | x-y.z |
| x_y_z_ | x_y_z_ | x-y-z |
| x.##.y | x_y(MM) | x.MM.z |
| x.## | x(MM) | x.MM |
- if CONDITION
Выражение предполагающее переменную персонализацию которая не равна нулю при когда пин или параметр должен быть создан.
- SIZE
Число задающее размер массива. Элементы массива нумеруются от 0 до SIZE-1
- MAXSIZE : CONDSIZE
Число задающее максимальный размер массива, за которым следует выражение подразумевающее переменную персонализацию и которое всегда имеет значение меньше MAXSIZE. Когда массив создается его размер будет равен CONDSIZE.
- DOC
Строка, которая описывает элемент. Строка может быть в стиле С "в двойных кавычках", или в стиле Python - в тройных кавычках, которые могут содержать перенос строки и символы кавычек, например:
param rw bit zot=TRUE
"""The effect of this parameter, also known as "the orb of zot",
will require at least two paragraphs to explain.
Hopefully these paragraphs have allowed you to understand "zot"
better.""";
Строка документации должна быть в формате "groff -man". Для большей информации об этом формате см. man groff_man(7). Помните, что comp интерпретирует экранирование обратными слешами в строках, поэтому чтобы, например, чтобы установить курсивный шрифт для слова example пишите "\\fIexample\\fB".
- TYPE
Один из типов HAL: bit, signed, unsigned или float. Старые названия s32 and u32 тоже могут быть использованы, но signed и unsigned предпочтительны.
- PINDIRECTION
+Один из следующих: in, out или io. Компонент устанавливает значение для пина out, читает значения из пина in, и может как читать так и писать в пин io.
- PARAMDIRECTION
Одно из следующих: r или rw. Компонент может читать значения параметров типа r и может как читать так и устанавливать значения для параметров rw.
- STARTVALUE
Устанавливает начальное значение параметра или пина. Если не указан, тогда значение по умолчанию 0 или FALSE, в зависимости от типа элемента.
- fp
Указывает на то, что функция выполняет операции с плавающей запятой.
- nofp
Указывает на то, что выполняются только целочисленные вычисления. Если не один не указан, то подразумевается fp. Ни comp, ни gcc не могут определить использование вычислений с плавающей запятой в функциях, которые маркированы nofp.
- OPT, VALUE
В зависимости от опции название OPT, корректные значения VALUE разные. На текущий момент определены опции:- option singleton yes
(default: no)
Не создавать параметр count, и всегда создавать только одну сущность. С опцией singleton элементы называются component-name.item-name, а без нее элементы для нумерованных сущностей называются component-name.<num>.item-name.
- option default_count
number (default: 1)
Обычно, параметр count равен 0. Если указан, count будет равен этому значению.
- option count_function yes
(default: no)
Обычно, количество создаваемых сущностей указано в параметре count, если указан count_function и не указан параметр count, будет использовано значение возвращаемое функцией int get_count(void),
- option rtapi_app no
(default: yes)
Обычно, функции functions rtapi_app_main и rtapi_app_exit генерируются автоматически. С опцией rtapi_app no, они не создаются, и должны быть представлены в С коде.
Когда создаете свою собственную rtapi_app_main, вызовите функцию int export(char *prefix, long extra_arg) для того, чтобы зарегистрировать пины, параметры и функции для prefix.
- option data
type (default: none) deprecated устаревшая
Если указана, каждая сущность компонента будет иметь ассоциированный блок данных типа type (который может быть простым типом как float или именем типа созданного при помощи typedef).
В новых компонентах вместо этого должны быть использованы переменные.
- option extra_setup yes
(default: no)
Если указана, вызывается функция установленная в EXTRA_SETUP для каждой сущности. Если используется автоматически заданные rtapi_app_main, extra_arg равен номеру этой сущности.
- option extra_cleanup yes
(default: no)
Если указана, вызывается функция установленная в EXTRA_CLEANUP из автоматически созданной rtapi_app_exit или, если обнаружена ошибка в автоматически созданной rtapi_app_main.
- option userspace yes
(default: no)
Если указана, этот файл описывает компонент пользовательского пространства (userspace), а не компонент реального времени. Userspace компонент может не иметь функций заданных директивой function. Вместо этого, после того как созданы все сущности, вызывается C функция user_mainloop(). Когда эта функция возвращает значение, компонент завершает работу. Обычно, user_mainloop() будет использовать FOR_ALL_INSTS() для выполнения обновления всех сущностей, а потом будет засыпать на короткое время. Другое частое применение user_mainloop() - может вызываться функция обрабатывающая события GUI.
- option userinit yes
(default: no)
Если указана, функция userinit(argc,argv) вызывается перед rtapi_app_main() (и стало быть перед вызовом hal_init()). Эта функция может обрабатывать аргументы из командной строки или выполнять другие действия. Ее возвращаемое значение void; она может вызвать call exit() если она решит уничтожится вместо того, чтобы создать компонент HAL (например, из-за того что аргументы командной стоки были не правильными).
Если не указанно VALUE опции, тогда это эквивалентно указанию option … yes. Результат назначения неподходящего значения опции не определен.
Результат использования любых других опций не определен.
- LICENSE
Указывает лицензию модуля для документации и для объявления модуля MODULE_LICENSE(). Например, чтобы указать, что модуль лицензируется GPL, используйте:
license "GPL";
Для дополнительной информации по значению MODULE_LICENSE() и дополнительных идентификаторах лицензий см. <linux/module.h>.
Начиная с emc 2.3, декларация лицензии обязательна.
- AUTHOR
Указывает автора модуля для документации.
1.6 Per-instance data storage
variable
CTYPE NAME;
variable
CTYPE NAME[SIZE];
variable
CTYPE NAME = DEFAULT;
variable
CTYPE NAME[SIZE] = DEFAULT;
Declare a per-instance variable NAME of type CTYPE, optionally as an array of SIZE items, and optionally with a default value DEFAULT. Items with no DEFAULT are initialized to all-bits-zero. CTYPE is a simple one-word C type, such as float, u32, s32, int, etc.
Access to array variables uses square brackets.
C++-style one-line comments (// …) and C-style multi-line comments (/* … */) are both supported in the declaration section.
1.7 Other restrictions on comp files
Though HAL permits a pin, a parameter, and a function to have the same name, comp does not.
1.8 Convenience Macros
Based on the items in the declaration section, comp creates a C structure called struct state. However, instead of referring to the members of this structure (e.g., *(inst->name)), they will generally be referred to using the macros below. The details of struct state and these macros may change from one version of comp to the next.
FUNCTION(name)
Use this macro to begin the definition of a realtime function which was previously declared with "function NAME". The function includes a parameter "period" which is the integer number of nanoseconds between calls to the function.
EXTRA_SETUP()
Use this macro to begin the definition of the function called to perform extra setup of this instance. Return a negative Unix errno value to indicate failure (e.g., return -EBUSY on failure to reserve an I/O port), or 0 to indicate success.
EXTRA_CLEANUP()
Use this macro to begin the definition of the function called to perform extra cleanup of the component. Note that this function must clean up all instances of the component, not just one. The "pin_name", "parameter_name", and "data" macros may not be used here.
pin_name
parameter_name
For each pin pin_name or param parameter_name there is a macro which allows the name to be used on its own to refer to the pin or parameter.
When pin_name or parameter_name is an array, the macro is of the form pin_name(idx) or param_name(idx) where idx is the index into the pin array. When the array is a variable-sized array, it is only legal to refer to items up to its condsize.
When the item is a conditional item, it is only legal to refer to it when its condition evaluated to a nonzero value.
variable_name
For each variable variable_name there is a macro which allows the name to be used on its own to refer to the variable. When variable_name is an array, the normal C-style subscript is used: variable_name[idx]
data
If "option data" is specified, this macro allows access to the instance data.
fperiod
The floating-point number of seconds between calls to this realtime function.
FOR_ALL_INSTS() {
…} For userspace components. This macro uses the variable struct state *inst to iterate over all the defined instances. Inside the body of the loop, the pin_name, parameter_name, and data macros work as they do in realtime functions.
1.9 Components with one function
If a component has only one function and the string "FUNCTION" does not appear anywhere after ;;, then the portion after ;; is all taken to be the body of the component's single function.
1.10 Component Personality
If a component has any pins or parameters with an "if condition" or "[maxsize : condsize]", it is called a component with "personality". The "personality" of each instance is specified when the module is loaded. "Personality" can be used to create pins only when needed. For instance, personality is used in the logic component, to allow for a variable number of input pins to each logic gate and to allow for a selection of any of the basic boolean logic functions and, or, and xor.
1.11 Compiling .comp files in the source tree
Place the .comp file in the source directory emc2/src/hal/components and re-run make. Comp files are automatically detected by the build system.
If a .comp file is a driver for hardware, it may be placed in emc2/src/hal/components and will be built except if emc2 is configured as a userspace simulator.
1.12 Compiling realtime components outside the source tree
comp can process, compile, and install a realtime component in a single step, placing rtexample.ko in the emc2 realtime module directory:
comp --install rtexample.comp
Or, it can process and compile in one step, leaving example.ko (or example.so for the simulator) in the current directory:
comp --compile rtexample.comp
Or it can simply process, leaving example.c in the current directory:
comp rtexample.comp
comp can also compile and install a component written in C, using the --install and --compile options shown above:
comp --install rtexample2.c
man-format documentation can also be created from the information in the declaration section:
comp --document rtexample.comp
The resulting manpage, example.9 can be viewed with
man ./example.9
or copied to a standard location for manual pages.
1.13 Compiling userspace components outside the source tree
comp can process, compile, install, and document userspace components:
comp usrexample.comp
comp --compile usrexample.comp
comp --install usrexample.comp
comp --document usrexample.comp
This only works for .comp files, not for .c files.
1.14 Examples
1.14.1 constant
This component functions like the one in "blocks", including the default value of 1.0. The declaration "function _" creates functions named "constant.0", etc.
component constant;
pin out float out;
param r float value = 1.0;
function _;
license "GPL";
;;
FUNCTION(_) { out = value; }
1.14.2 sincos
This component computes the sine and cosine of an input angle in radians. It has different capabilities than the "sine" and "cosine" outputs of siggen, because the input is an angle, rather than running freely based on a "frequency" parameter.
The pins are declared with the names sin_ and cos_ in the source code so that they do not interfere with the functions sin() and cos(). The HAL pins are still called sincos.<num>.sin.
component sincos;
pin out float sin_;
pin out float cos_;
pin in float theta;
function _;
license "GPL";
;;
#include <rtapi_math.h>
FUNCTION(_) { sin_ = sin(theta); cos_ = cos(theta); }
1.14.3 out8
This component is a driver for a fictional card called "out8", which has 8 pins of digital output which are treated as a single 8-bit value. There can be a varying number of such cards in the system, and they can be at various addresses. The pin is called out_ because out is an identifier used in <asm/io.h>. It illustrates the use of EXTRA_SETUP and EXTRA_CLEANUP to request an I/O region and then free it in case of error or when the module is unloaded.
component out8;
pin out unsigned out_ "Output value; only low 8 bits are used";
param r unsigned ioaddr;
function _;
option count_function;
option extra_setup;
option extra_cleanup;
option constructable no;
license "GPL";
;;
#include <asm/io.h>
#define MAX 8 int io[MAX] = {0,};
RTAPI_MP_ARRAY_INT(io, MAX, "I/O addresses of out8 boards");
int get_count(void) {
int i = 0;
for(i=0; i<MAX && io
; i++) { /* Nothing */ }
return i;
}
EXTRA_SETUP() {
if(!rtapi_request_region(io[extra_arg], 1, "out8")) {
// set this I/O port to 0 so that EXTRA_CLEANUP does not release the IO
// ports that were never requested.
io[extra_arg] = 0;
return -EBUSY;
}
ioaddr = io[extra_arg];
return 0; }
EXTRA_CLEANUP() {
int i;
for(i=0; i < MAX && io; i++) {
rtapi_release_region(io, 1);
}
}
FUNCTION(_) { outb(out_, ioaddr); }
1.14.4 hal_loop
component hal_loop;
pin out float example;
This fragment of a component illustrates the use of the hal_ prefix in a component name. loop is the name of a standard Linux kernel module, so a loop component might not successfully load if the Linux loop module was also present on the system.
When loaded, halcmd show comp will show a component called hal_loop. However, the pin shown by halcmd show pin will be loop.0.example, not hal-loop.0.example.
1.14.5 arraydemo
This realtime component illustrates use of fixed-size arrays:
component arraydemo "4-bit Shift register";
pin in bit in;
pin out bit out-# [4];
function _ nofp;
license "GPL";
;;
int i;
for(i=3; i>0; i--) out(i) = out(i-1);
out(0) = in;
1.14.6 rand
This userspace component changes the value on its output pin to a new random value in the range [0,1) about once every 1ms.
component rand;
option userspace;
pin out float out;
license "GPL";
;;
#include <unistd.h>
void user_mainloop(void) {
while(1) {
usleep(1000);
FOR_ALL_INSTS() out = drand48();
}
}
1.14.7 logic
This realtime component shows how to use "personality" to create variable-size arrays and optional pins.
component logic "EMC2 HAL component providing experimental logic functions";
pin in bit in-##[16 : personality & 0xff];
pin out bit and if personality & 0x100;
pin out bit or if personality & 0x200;
pin out bit xor if personality & 0x400;
function _ nofp;
description """
Experimental general `logic function' component. Can perform `and', `or'
and `xor' of up to 16 inputs. Determine the proper value for `personality'
by adding:
.IP \\(bu 4
The number of input pins, usually from 2 to 16
.IP \\(bu
256 (0x100) if the `and' output is desired
.IP \\(bu
512 (0x200) if the `or' output is desired
.IP \\(bu
1024 (0x400) if the `xor' (exclusive or) output is desired""";
license "GPL";
;;
FUNCTION(_) {
int i, a=1, o=0, x=0;
for(i=0; i < (personality & 0xff); i++) {
if(in(i)) { o = 1; x = !x; }
else { a = 0; }
}
if(personality & 0x100) and = a;
if(personality & 0x200) or = o;
if(personality & 0x400) xor = x;
}
A typical load line for this component might be
loadrt logic count=3 personality=0x102,0x305,0x503
which creates the following pins:
A 2-input AND gate: logic.0.and, logic.0.in-00, logic.0.in-01
5-input AND and OR gates: logic.1.and, logic.1.or, logic.1.in-00, logic.1.in-01, logic.1.in-02, logic.1.in-03, logic.1.in-04,
3-input AND and XOR gates: logic.2.and, logic.2.xor, logic.2.in-00, logic.2.in-01, logic.2.in-02