Navigation

Atomic functions

Atomic function is C-ATOM’s atomic building block for functionality. You may use atomic function from existing library (Atomic functions catalog) or create your own.

Understanding atomic function

Atomic function is a software building block for extending existing CatPilot and C-ATOM functionality for specific purposes. Atomic function starts from its specification declaration (functional specification, specification). This specification represents single source of truth for function description and implementation. Integration of the atomic function happens inside SWSYS (SWSYS Introduction), FLOW (FLOW Introduction) and IBR (IBR Introduction) configuration layers.

../_images/atomic-generation.jpg

Depending on the nature of the atomic function several levels exist:

  • generic - C-ATOM level, basic mathematical operations, navigational calculations, etc (stored inside atomics/core inside C-ATOM);

  • board specific - CatPilot level, functions that connect hardware resources like sensors to the C-ATOM’s scope;

  • project specific - barely reusable function related to the target system, e.g., specific algorithm for the specific vehicle.

Function specification might be found in declaration.py source inside files tree of atomics catalog on the specified level.

Atomic functions related artifacts are produced by the python tool fspecgen.py which is located at catpilot/c-atom/tools/fspecgen.py.

Currently these outputs are available:

  • C code: atomic function <> communicating middleware source code;

  • C code: function’s implementation initial stubs;

  • C code: function’s parameters parsing source code;

  • C code: prototypes and specification structures;

  • CMakeLists: atomic function’s software building configuration;

Atomic function specification

As an example here is a function specification declaration for PID regulator:

``c-atom/atomics/core/cont/pid/declaration.py``
from fspeclib import *

Function(
    name='core.cont.pid',
    title=LocalizedString(
        en='PID controller'
    ),
    parameters=[
        Parameter(
            name='Kp',
            title='Proportional coefficient',
            value_type='core.type.f64',
            tunable=True,
            default=1,
            constraints=[
                ThisValue() >= 0
            ]
        ),
        Parameter(
            name='Ki',
            title='Integral coefficient',
            value_type='core.type.f64',
            tunable=True,
            default=0,
            constraints=[
                ThisValue() >= 0
            ]
        ),
        Parameter(
            name='Kd',
            title='Derivative coefficient',
            value_type='core.type.f64',
            tunable=True,
            default=0,
            constraints=[
                ThisValue() >= 0
            ]
        ),
        Parameter(
            name='integral_min',
            title='Lower bound of integral part of error',
            value_type='core.type.f64',
            tunable=True,
            default=0,
            constraints=[
                ThisValue() <= 0
            ]
        ),
        Parameter(
            name='integral_max',
            title='Upper bound of integral part of error',
            value_type='core.type.f64',
            tunable=True,
            default=1,
            constraints=[
                ThisValue() >= 0
            ]
        ),
        Parameter(
            name='output_min',
            title='Lower bound of controller output',
            value_type='core.type.f64',
            tunable=True,
            default=0,
            constraints=[
                ThisValue() <= 0
            ]
        ),
        Parameter(
            name='output_max',
            title='Upper bound of controller output',
            value_type='core.type.f64',
            tunable=True,
            default=1,
            constraints=[
                ThisValue() >= 0,
            ]
        ),
    ],
    inputs=[
        Input(
            name='input',
            title='Input',
            value_type='core.type.f64'
        ),
        Input(
            name='feedback',
            title='Feedback',
            value_type='core.type.f64'
        ),
        Input(
            name='enable',
            title='Enable',
            value_type='core.type.bool',
            mandatory=False
        ),
        Input(
            name='preset',
            title='Preset source',
            description='Used to preset integral part at the moment of the PID enabling',
            value_type='core.type.f64',
            mandatory=False
        ),
    ],
    outputs=[
        Output(
            name='output',
            title='Output',
            value_type='core.type.f64'
        ),
        Output(
            name='enabled',
            title='Enabled',
            value_type='core.type.bool',
        )
    ],
    state=[
        Variable(
            name='integral',
            title='Accumulated Integral term',
            value_type='core.type.f64'
        ),
        Variable(
            name='previous_error',
            title='Previous error',
            value_type='core.type.f64'
        ),
        Variable(
            name='time_from_last_iteration',
            title='Time from last iteration',
            value_type='core.type.f64'
        ),
        Variable(
            name='activated',
            title='PID was activated before by enable signal',
            value_type='core.type.bool'
        )
    ],
    parameter_constraints=[
        ParameterValue('output_max') > ParameterValue('output_min'),
        ParameterValue('integral_max') > ParameterValue('integral_min')
    ],
    injection=Injection(
        timedelta=True
    )
)

Creating atomic function

1. Define a level of your atomic function: generic or board specific - if you want to contribute it to the C-ATOM or CatPilot accordingly.

  1. Go to directory [level_root]/atomics/[class_name]/

  2. Create directory with the name of your function, e.g. func1

  3. Create a file [level_root]/atomics/[namespace_name]/[group_name]/func1/declaration.py

  4. Copy and pase basic specification structure:

Replace [namespace_name] and [group_name] accordingly.

from fspeclib import *

Function(
    name='[namespace_name].[group_name].func1',
    title=LocalizedString(
        en='Functional function'
    ),
    parameters=[
        ...
    ],
    inputs=[
        ...
    ],
    outputs=[
        ...
    ],
    state=[
        ...
    ],
    parameter_constraints=[
        ...
    ],
    injection=Injection(
        ...
    )
)
  1. Describe parameters:

parameters=[
    Parameter(
        name='p1',
        title='Parameter 1',
        value_type='core.type.f64',
        tunable=True,
        default=1,
        constraints=[
            ThisValue() >= 0
        ]
    ),
    Parameter(
        name='p2',
        title='Parameter 2',
        value_type='core.type.f64',
        tunable=True,
        default=1,
        constraints=[
            ThisValue() >= 0
        ]
    ),
    ...
],
  1. Describe inputs:

inputs=[
    Input(
        name='input',
        title='Input',
        value_type='core.type.f64'
    ),
    ...
]
  1. Describe outputs:

outputs=[
    Output(
        name='output',
        title='Output',
        value_type='core.type.f64'
    ),
    ...
],
  1. Describe States variables:

state=[
    Variable(
        name='integral',
        title='Accumulated Integral term',
        value_type='core.type.f64'
    ),
    ...
]
  1. Describe parameters constrains:

parameter_constraints=[
   ParameterValue('p1') > ParameterValue('p2'),
],
...
  1. Specify injection options:

injection=Injection(
 timedelta=True
)
  1. Generate code by the command:

./catpilot/c-atom/tools/fspecgen.py --code --cmake --f_specs_dirs project:./atomics/ catpilot:catpilot/atomics/ catom:catpilot/c-atom/atomics/
  1. Inside generated stub [namespace]_[func1]_exec.c file implement function as usual C language function: process inputs, states and produce output.

void someNamespace_func1_exec(
        const someNamespace_func1_inputs_t *i,
        someNamespace_func1_outputs_t *o,
        const someNamespace_func1_params_t *p,
        someNamespace_func1_state_t *state,
        const someNamespace_func1_injection_t *injection
)
{
    ...
}

Integrating atomic function

For integrating new atomic function inside your project you should do the following:

1. Extend build script to run of fspecgen.py for code generation for the directories with all your atomic functions (atomics target in the example below).

  1. Make sure you are generating atomic functions registry code by the option --registry_c ./atomics_reg.c (atomics target).

3. Make sure you are generating XML file for describing building block if you want to maintain graphical representation inside C-ATLAS. (bblocks target) .. note:: C-ATLAS web app uses this makefile target for fetching building blocks, so keep the naming.

Makefile section below is stored at the root of UAS-CatPilot repository.

atomics:
    @echo Generating atomic functions
    @./catpilot/c-atom/tools/fspecgen.py --catom_path catpilot/c-atom --code --cmake --registry_c ./atomics_reg.c --atomics_dirs project:./atomics catpilot:catpilot/atomics catom:catpilot/c-atom/atomics

xmlinline:
    @echo Inlining XML configs
    @./catpilot/c-atom/tools/xml2c_inliner.py --cfg_path config/quad/ --out xml_inline_cfgs.c

bblocks:
    @./catpilot/c-atom/tools/fspecgen.py --code --cmake --bbxml bblocks.xml --atomics_dirs project:./atomics catpilot:catpilot/atomics catom:catpilot/c-atom/atomics

Target xmlinline is used for inlining XML into Read-Only microcontroller. This tool speeds up putting of the configuration XMLs into microcontrollers without IP network stack.