Modules

Modules use code generation in order to allow people to use them by only editing the vehicle’s XML configuration as opposed to changing/adding to the code of the autopilot. This makes the paparazzi build process highly configurable. Typically, function calls are generated for initialization and in the main loop for timers (periodic in Paparazzi slang) and events. The event and periodic functions of the Modules get called at the end of event_task_ap and periodic_task_ap respectively.

Modules are used to configure all parts of Paparazzi other than the firmware sections. The build configuration for each module is described using XML description language and the located in the conf/modules subfolder. These modules implement not only the user implemented mission specific code but also the low level drivers and subsystems.

sw/airborne/modules contains the mission specific software such as image processing, video recording or relay or guidance commands.

Using existing modules

An autogenerated list of all the available modules in Paparazzi can be found on http://docs.paparazziuav.org/.

To add a module to an aircraft, add a section modules in his airframe XML file :

Arbitrary airframe file: conf/airframes/myplane.xml

<firmware name="fixedwing">

   <module name="my_module">
      <define name="BLA" value="1"/>
      <define name="BLUB"/>
      <configure name="FOO" value="BAR"/>
   </module>

 </firmware>

All modules must be included within firmware sections. The firmware implements some vehicle specific code using predefined Makefiles. The most common firmware types are rotorcraft and fixedwing, but more exist and are all located in the conf/firmware folder.

A module will consist of a module node that specifies the name of the module that should be included (in this case name="my_module"). The children define and configure are optional and module specific. They can be used to specify the value of certain variables used in the module. A define will generate compilation defines for all the targets of a module and can be used with or without an associated value. For more information on define and configure have a look at the section Airframe Defines and GCS Settings.

Make Your Own

It is very possible to make your own module and to share it with the world. To make it even easier there is a helper tool called create_mod_qt.py that can be found in sw/tools/create_module/. To run it you will need Python 3 and a few Python packages that can be installed by running in your terminal:

cd paparazzi/sw/tools/create_module
python3 -m pip install -r requirements.txt
python3 create_mod_qt.py

This program can also be launched directly from the Paparazzi Center, by selecting Tools -> Module Creator from the Menu bar. Try it, you’ll be surprised how easy it is to start your own module with the help of this tool.

Another way to go about creating your module is by copying another module’s files and editing the relevant parts. In order to do this, it is necessary to understand a bit more about how modules work in Paparazzi, starting with where all the files of a module are located.

Module Files Location

Modules consist of the following files: a module XML file, source and header files.

├── conf
│   ├── airframes
│   ├── autopilot
│   ├── flight_plans
│   ├── modules   <---- Contains module XML with description, defines, compilation instructions
│   ├── radios
│   ...
├── doc
├── sw
│   ├── airborne
│   │   └── modules   <---- Contains module source and header files
│   ├── ext
│   ...
...

Module XML

The module description files are located in conf/modules/. They contain information such as a description of the module, module settings that can be controlled from the GCS, which source files are needed to compile the module, and additional module specific compiler flags.

../_images/module_architecture_overview.png

Here is an example of a module XML file. Not all of the XML nodes that are shown are actually required, as will be explained in this section.

<!DOCTYPE module SYSTEM "module.dtd">
<module name="demo_module">

  <doc>
    <description>
      Demo module
    </description>
    <configure name="SOMETHING" value="S1|S2|S3" description="The thing to use"/>
    <define name="DEMO_MODULE_LED" value="LED_X" description="LED Selection"/>
  </doc>

  <settings>
    <dl_settings name="bla">
      <dl_setting min="0" max="5" step="1" var="bla_bla" shortname="bb"/>
    </dl_settings>
  <settings>

  <dep>
    <depends>module1,module2|module3,@functionality1</depends>
    <provides>functionality2</provides>
    <conflicts>module4,@functionality3</conflicts>
  </dep>

  <header>
    <file name="demo_module.h"/>
  </header>

  <init fun="init_demo()"/>

  <periodic fun="periodic_1Hz_demo()" freq="1." start="start_demo()" stop="stop_demo()" autorun="TRUE"/>
  <periodic fun="periodic_10Hz_demo()" period="0.1" start="start_demo()" stop="stop_demo()" autorun="FALSE"/>

  <makefile>
    <raw>
      #Example of RAW makefile part
    </raw>
    <define name="DEMO_MODULE_LED" value="2"/>
    <file name="demo_module.c"/>
  </makefile>

  <makefile target="demo">
    <define name="SOME_FLAG"/>
    <configure name="SOME_DEFINE" value="bla"/>
  </makefile>

</module>

The XML file starts with a module element that sets the name of the module (in this case demo_module). Optionally, this element can contain a dir attribute as well, to specify the location of the source files relative to sw/airborne/modules/. In this case the directory is not provided since the source files are located in a directory inside sw/airborne/modules/ that has the same name as the module name (sw/airborne/modules/demo_module/).

After a documentation and dependency section, the XML contains a header element, where the header files of the module are listed. Typically, you will only see one header file here that provides an easy-to-use access point for other modules.

The header element is often followed by an init and periodic element. These specify what functions in your module code should be called by the autopilot, and in case of the periodic function it also specifies its frequency in Hz. The other two function types that can be specified consist of event and datalink functions.

At the end of the XML file is the makefile element. This section describes how your source files should be compiled. Simple modules such as the demo_module only list one or more source files. More complicated modules such as cv_opencvdemo can specify additional compiler flags (to link OpenCV, for example) and can have different makefile sections depending on whether the autopilot is compiled for use on the drone (target="ap") or in simulation (target="nps").

The source and header files of your module can be found in sw/airborne/modules/<your_module_dir>/. We take a closer look at the content of these files in the Header and Source Sections.

Here is an overview of all possible Module XML nodes:

Node

Children

Description

module
(required)

name (required)

This parameter is the name of the module (mandatory)

dir

The name of the directory in sw/airborne/modules where the source code is located. If not specified, the name of the module is used as default directory name

doc
(optional)

description (required)

A description of the module. The content of the first line until the dot is treated as the brief description used as the name in the generated docs

define

Describe the possible define flags for this module with default values and a short description (usually called from the airframe firmware section

configure

Describe the possible configuration options for this module with default values and a short description (usually called from the airframe firmware section

section

Describe the parameters that can be added as a section in the airframe configuration file

settings
(0 or more)

target

A list of targets allowed or forbidden for which embedded settings should be used

dl_settings

Creates a tab with arbitrary name that can be specified with name="your-tab-name"

dl_setting (child of dl_settings)

Setting description, see Settings section for details

dep
(0 or 1)

depends

Comma separated list of required modules

Allows to specify OR dependencies with pipe (|) similar to Debian depends, ex: module1,module2|module3 would make it depend on module1 AND (module2 OR module3)

The elements can be a module name (as set in the module XML name node) or a functionality (a keyword specified in a provides node), which has to be preceded by @

provides

Advertises the functionality that the module provides (e.g. actuators, imu)

conflicts

Comma separated list of conflicting modules

The elements can be a module name (as set in the module XML name node) or a functionality (a keyword specified in a provides node), which has to be preceded by @

autoload
(0 or 1)

name

The name of the module which should also be automatically loaded

header
(0 or 1)

file

The name of the header to automatically include in modules.h

init
(0 or more)

fun

Initialization function name, called once at startup

periodic
(0 or more)

fun (required)

Periodic function name

period

Period of the function in seconds, cannot be higher than the main frequency (if not specified, use freq parameter)

freq

Frequency of the function in Hz, cannot be higher than main frequency (used if period is not defined; if nor period nor freq are defined, the maximum frequency is used by default)

delay

Integer that can be used to impose a sequence in the periodic functions (use values between 0. and 1.)

start

Function to be executed before the periodic function starts

stop

Function to be executed after the periodic function stops (never called if autorun=LOCK)

autorun

TRUE to make the periodic function starts automatically after init, FALSE to make it way for a user command to start, LOCK to make it always true (default is LOCK)

event
(0 or more)

fun

Event function name called in each cycle of the main AP loop

datalink
(0 or more)

message

Name of the datalink (uplink) message to be parsed

fun

Name of the function called when a message arrived

makefile
(0 or more)

target

A list of build targets separated with pipes (ex: <makefile target="tunnel|foo">) (default is ap|sim|nps)

define

Each define node specifies a CFLAGS for the current targets

  • name : name of the define (ex: name="USE_MODULE_LED" ->
    target.CFLAGS += -DUSE_MODULE_LED) (required)
  • value : the value to associate
    (ex: name="DEMO_MODULE_LED" value="2" ->
    target.CFLAGS += -DDEMO_MODULE_LED=2)
  • type : the type of define, possible values are “define” or “D”,
    “include” or “I” (ex: name="$(ARCH_SRC)" type="include" ->
    target.CFLAGS += -I$(ARCH_SRC) default is “define”

file

  • name : the name of the c file (located in
    sw/airborne/modules/<dir_name>) to add in the Makefile
    (ex: name="demo_module.c" ->
    target.srcs += modules/<dir_name>/demo_module.c)
  • dir : select a directory for this file only
    (overrides thedefault directory)
  • cond : allows for the conditional compilation of file depending
    on the condition specified (ex. cond="ifdef FOO" ->
    ifdef FOO
    ...
    endif
    As the file node refers to compilation elements, ifdef,
    ifeq etc. must be specified in value of the cond attribute

file_arch

  • name : the name of the c file (located in
    sw/airborne/arch/<ARCH>/modules/<dir_name>) add in the Makefile
    (ex: name="demo_module_hw.c" ->
    target.srcs += arch/<ARCH>/modules/<dir_name>/demo_module_hw.c)
  • dir : select a directory for this file only
    (overrides the default directory)
  • cond : allows for the conditional compilation of file depending
    on the condition specified (ex. cond="ifdef FOO" ->
    ifdef FOO
    ...
    endif
    As the file node refers to compilation elements, ifdef,
    ifeq etc. must be specified in value of the cond attribute

raw

Allows to define a raw makefile section

Starting and Stopping a module

Together with the periodic function, the module XML can specify a START and STOP function. These are called when the module is started or stopped, respectively. The autorun attribute in the module XML’s periodic element controls whether your module is started automatically or manually; you can manually start and stop modules from the GCS by going to Settings -> System -> Modules, selecting START or STOP and clicking the green checkmark. You can find an example of start and stop functions functions in sw/airborne/modules/loggers/file_logger.c, where they are used to open and close the log file.

If modules are loaded with periodical functions that are not locked, a new tab will automatically appear in the setting page of the GCS that allows you to start and stop them.

An other possibility is that any file that includes the header “modules.h” can start or stop the periodic tasks.

Module Header File

The module header is located in sw/airborne/modules/<module-dir>/<module-header.h>, and functions like a normal .h file. The main difference is that any function or variable that is referenced by an XML file needs to be defined as extern so that the compiler can find the definition.

By convention any variable and function that is defined in a module header, especially if used outside of the module by another module or XML, should be prefixed with the module name or some other unique identifier to help avoid name collision.

Module Source File

The autopilot will regularly call functions that are part of your module, such as a module periodic function. Which functions are called is defined by the module XML file described earlier.

The section Module XML lists the types of functions you can register in the module XML: init, periodic, event and datalink, of which init and periodic are the most common. The init function is called once at startup. You can use this function to initialize important variables of your module, or memory intensive structures such as large arrays, or for instance to subscribe to new video frames. Once the autopilot is fully initialized, it will enter an infinite loop in which it will continuously read new sensor data, feed this to the guidance and stabilization controllers, and send new commands to the actuators. From this loop, the autopilot can also call your module’s periodic function at a frequency specified in the module XML. Within this function, you can for instance get the drone’s state and use this to calculate new setpoints for the guidance controller.

Because the periodic function is called from within the autopilot’s control loop, you should take care that the function does not take too much time to run. The autopilot runs by default at 512~Hz, which means that it has slightly less than 2~ms to run your module code, the code of the other modules and the control loops and estimators. If your periodic function takes too long, the autopilot will run at a lower frequency than intended, which can lead to instability. In practice you have to make things pretty bad before this becomes a problem, but you should be careful when using large or nested loops in your periodic function, and video processing is best performed in the video callback function, as this callback runs in a separate thread.

Warning

If your periodic function takes too long, the autopilot will run at a lower frequency than intended, which can lead to instability

Airframe Defines and GCS Settings

A module will most likely contain tunable parameters, such as gain or threshold values. While these numbers can be written directly in the source code, this will make it difficult to tune them later, as every time that they are changed you will need to rebuild and reupload to your drone. Paparazzi provides two systems to simplify parameter tuning: defines and settings.

Defines allow you to set constant values from the airframe file. See, for example, the following abstract of the bebop_course_orangeavoid.xml airframe:

<airframe name="bebop_avoider">
  <firmware name="rotorcraft">
    <target name="ap" board="bebop">
      <define name="COLOR_OBJECT_DETECTOR_LUM_MIN1" value="40"/>
      <!-- ... -->
    </target>
    <!-- ... -->
    <define name="ARRIVED_AT_WAYPOINT" value="0.5"/>
    <!-- ... -->
    <module name="cv_detect_color_object">
      <define name="COLOR_OBJECT_DETECTOR_CAMERA1" value="front_camera"/>
      <!-- ... -->
    </module>
  </firmware>
  <!-- ... -->
  <section name="GUIDANCE_H" prefix="GUIDANCE_H_">
    <define name="CLIMB_VSPEED" value="1.0"/>
  </section>
  <!-- ... -->
</airframe>

As you can see, defines can be set at multiple places in the airframe file. The behavior is mostly the same in these cases, with the following exceptions:

  • Defines placed in the <target> elements are only set when the autopilot is built for that target, i.e. "ap" for the real drone and "nps" for the simulator. This allows you to, for instance, use different color filter settings on the real and simulated drone.

  • Placing a define inside a <module> element has no special effect! The define is also visible in other modules, so be sure to use a unique name. Typically, defines are prefixed with the name of the module (e.g. COLOR_OBJECT_DETECTOR_ to make them unique. The only reason these defines are placed inside the module element is to improve readability.

  • <section> elements allow you to specify a prefix, this prefix is placed in front of all define names inside this section. In the example, the CLIMB_VSPEED define is available in the code as GUIDANCE_H_CLIMB_VSPEED.

During compilation, these defines are turned into preprocessor macros and can be referred to directly from your code.

Airframe defines allow you to set constant parameters at compile-time, but in some cases it would be easier if you could change these values during the flight. This is possible with the settings mechanism. Settings are defined in the module XML file. Take for example conf/modules/cv_detect_color_orange.xml:

<module name="cv_detect_color_object">
<!-- ... -->
<settings>
  <dl_settings name="ColorObjectDetector">
    <dl_setting var="cod_lum_min1" min="0" step="1" max="255" shortname="y_min1"/>
    <!-- ... -->
  </dl_settings>
</settings>
</module>

Settings listed in the module XML can be tuned from the Ground Control Station by going to the Settings tab and then selecting the tab belonging to your module, as defined in the dl_settings element (here ColorObjectDetector). To read the current value of a parameter from the drone, click its value (the number) in the GCS. Te set a value on the drone, adjust the slider, then click the green checkmark to upload this new value to the drone . Click the value number again to make sure the setting was updated if a question mark appears to the left of the slider. The updated value should appear to the left of the slider.

Use the dl_setting element in your module XML to add a setting to your module. The var attribute specifies the variable this setting should be written to; this variable should be globally accessible (defined as extern in the h file). The min, step and max attributes let you specify a range of possible values for this setting. Using shortname you can control the name under which this setting is listed in the GCS. The module attribute can be added to specify the file where the variable is coming from. A corresponding #include “m.h” will be auto-generated in the corresponding C code.

In case of more complicated logic that needs to be triggered any time that a GCS variable is changed (like resetting certain variables, or changing the value of more variables at once) a handler attribute can be added to specify a function (or a macro) to be called whenever the setting is changed. This macro is associated with a module and must be named <module-name>_<handler-name>().

As an example, take a look at an excerpt from conf/modules/digital_cam.xml:

<dl_settings name="dc">
  <dl_setting max="255" min="0" step="1" module="digital_cam/dc" var="0" handler="send_command" shortname="Shutter">
</dl_settings>

The module attribute is specified as module="digital_cam/dc". While in the XML the handler function is specified as send_command, in the source code the module name must be added in front of the function name, as can be seen in sw/airborne/modules/digital_cam/dc.h.

extern void dc_send_command(uint8_t cmd);

It is possible to combine the define and settings mechanisms, where the define provides a default value that can be adjusted later using settings. This often uses the following pattern:

#ifndef MY_DEFINE
#define MY_DEFINE 0
#endif
int my_setting = MY_DEFINE;

In this example, MY_DEFINE provides the initial value of my_setting. MY_DEFINE can be set from the airframe file, but if it is not defined there this code will give it a default value of 0. The actual parameter is stored in my_setting, for which a <dl_setting> element is included in the module’s XML file.

Third Party Modules

It is possible to include third party modules in an airframe, or modules that are not located within the Paparazzi folder itself. The extra directories can be added with the environment variable PAPARAZZI_MODULES_PATH, where items are : separated and modules are in subfolders of a modules folder. Ex. PAPARAZZI_MODULES_PATH=/home/me/pprz_modules. This directory should look like this:

├── pprz_modules
│  ├── modules
│  │  ├── module1
│  │  │  ├── module1.xml
│  │  │  ├── module1.h
│  │  │  └── module1.c
│  │  ├── module2
│  │  │  ├── module2.xml
│  │  │  ├── module2.h
│  │  │  └── module2.c
|  │  ...
|  ...
...