Custom C/C++ code¶
This guide describes how to create a Processing block binary file from C/C++ code that can be loaded into a (simulated) PMP motion controller or PMP EtherCAT SubDevice. The Deployment guide explains how to upload this binary file on a PMP device or simulator. C/C++ knowledge is a prerequisite.
During this guide you will learn how to:
Create a project to write C/C++ code that can be deployed on a PMP motion controllers.
Create a simple derived class implementation.
Build a binary file that can be deployed on a (simulated) PMP motion controller or PMP EtherCAT SubDevice.
Introduction¶
PMP provides a toolchain to build a binary file from custom C/C++ code that can be uploaded onto a motion controller and EtherCAT SubDevices. This makes it possible to implement custom functionality that is executed in real-time. The toolchain generates a base class and adds PMP-specific wrapper functionality that allows the motion controller to interact with the generated code. From a YAML interface description it is possible to create signals, inputs, updatables, events and filters in PMP. Typical use cases are control loops, compensation algorithms, coordinate transforms and custom state handling.
C/C++ control loop customization reference manual explains the build process, considerations and supported interfaces in detail.
Prerequisites¶
Before continuing with the user guide, please make sure that the following prerequisites are met:
PMP should be installed. The Complete setup can be used to install all required features, including C++ toolchain. Alternatively, the Custom setup can be used to save disk space. In this case make sure the sub-feature C++ toolchain under Control loop customization is selected.
GNU Make (version 3.81) should be installed. For Windows it can be downloaded from SourceForge or it can be installed via Chocolatey.
Visual Studion 2019 should be installed with workload
Desktop development with C++
in order to build processing blocks for Windows simulator. It can be downloaded from Microsoft.The Custom C++ code guide project files should be available. These files can be downloaded from Downloads.
Note
A generated binary can be used on both motion controllers and EtherCAT SubDevices. However, for EtherCAT SubDevices the interfaces (i.e. inputs, parameters, outputs, events and updatables) for processing blocks are predefined in the SubDevice firmware. For each processing block of the PMP EtherCAT SubDevice an interface description with the supported interfaces is provided in the product specific installer. This manual focusses on processing blocks for motion controllers.
Start project¶
As an example, we will create a feedforward block, which is connected to the position feedback control in the Deployment guide. The block will take velocity and acceleration setpoint inputs from the trajectory interpolator and calculate the feedforward output.
The processingblockinterfacegenerator command line tool is used to start a project in a specified folder. It can be found in the bin directory of PMP installation:
C:\Program Files\Prodrive Motion Platform\96.3.0.190c8728\bin\
Create a project via the startproject command of the tool by calling the tool with arguments
DIR
andNAME
. Open a command window in the bin directory and run the command:processingblockinterfacegenerator.exe startproject C:\temp\Feedforward Feedforward
This creates the initial project in the designated folder with file structure as stated in the reference manual. If the directory already exists an exception is thrown and the program is aborted.
By default, folders for all supported build targets as stated in the reference manual are generated. Build target folders that are not relevant can be deleted to reduce build time.
Open the
mkFileVariables
file and check that thePMP_INSTALL_DIR
variable is set correctly. It should be set to the bin directory of the PMP installation directory, if the processingblockinterfacegenerator is located in default location (the bin directory of the PMP installation) it is automatically set correctly.Attention
The path format should be with single backslashes, without trailing backslash, without quotes.
Open a command window in the processing block folder and run make to build the processing blocks and verify the project is setup correctly:
make
The make command first runs the generate command of the processingblockinterfacegenerator tool with arguments
DIR
andNAME
. This creates the files as stated in the reference manual, these generated files should not be modified.
After the generate step the processing block binary files are built for all targets. Now we have built a processing block without custom implementation. The next step is to add interfaces and an implementation.
Interface definition¶
The interface of the processing block is defined in processingblock_NAME_interface.yaml
. The interface can consist of:
Additionally a Description and ModelVersion. can be added, which can be read from the PMP API.
For the Feedforward block we define inputs for the velocity and acceleration, parameters for the coulomb friction, velocity, and acceleration compensation, and outputs for intermediate and end result, which can be connected to the feedback controller.
Update the
processingblock_feedforward_interface.yaml
:--- Description: "A simple feedforward implementation for applying velocity and acceleration based compensations." ModelVersion: "1.2.3.4" Inputs: - Name: "DemandVelocity" DataType: "Float" Description: "Velocity of the reference trajectory." - Name: "DemandAcceleration" DataType: "Float" Description: "Acceleration of the reference trajectory." Parameters: - Name: "Kfc" DataType: "Float" ResetValue: "0.0" Description: "Coulomb friction compensation gain." - Name: "Kfv" DataType: "Float" ResetValue: "0.0" Description: "Velocity compensation gain." - Name: "Kfa" DataType: "Float" ResetValue: "0.0" Description: "Acceleration compensation gain." Outputs: - Name: "KfcOutput" DataType: "Float" Description: "Coulomb friction compensation output." - Name: "KfvOutput" DataType: "Float" Description: "Velocity friction compensation output." - Name: "KfaOutput" DataType: "Float" Description: "Acceleration compensation output." - Name: "FeedforwardOutput" DataType: "Float" Description: "Total feedforward output." ...
Note
When creating a processing block for a PMP EtherCAT SubDevice the interface is fixed. Please refer to the product specific installer for the interface definition.
After updating the interface, run make in a command window to update the base classes, such that the interfaces become available for the implementation:
make
Note
When only updating the interface it is also possible to run make generate, which only executes the generation step, and does not build binary files.
Implementation¶
The implementation is added in the processingblock_feedforward.h
and processingblock_feedforward.cpp
files.
Open
processingblock_feedforward.h
, and declare persistent variables kfv, kfc and kfa:#include "processingblock_feedforward_base.h" class CProcessingBlockFeedforward : public CProcessingBlockFeedforwardBase { float kfv = 0.0; float kfc = 0.0; float kfa = 0.0; // PreInit() is called immediately after the component is created. // For motion controllers this is in the transition from config to run state. // For EtherCAT SubDevices this is during boot. // - No memory allocation is allowed. // - Inputs and parameters values are not available. // - Useful for setting values on outputs that should be available in config state (PreOperational for PMP // SubDevices). virtual void PreInit() override; // Init() is called at each transition from config to run state (PreOperational to SafeOperational for PMP SubDevices). // - Memory is allowed to be allocated. // - Inputs and parameters values are available. virtual void Init() override; // Output() is called in the IO-critical calculations, when new sensor data is available. // All calculations that depend on the newest input values and influence the output signals. virtual void Output() override; // Update() is called in the non-IO-critical calculations. // These calculations are done based on the 'old' input values. // These calculations are done outside the critical loop and can be seen as a preparation for the IO-critical // calculations. virtual void Update() override; };
Open
processingblock_feedforward.cpp
, thePreInit()
,Init()
,Output()
andUpdate()
methods are available for implementation.It is good practice to set signals to their default value in the
PreInit()
:void CProcessingBlockFeedforward::PreInit() { // Set values on outputs that should be available in config state (PreOperational for PMP SubDevices). KfcOutput.Write(0.0); KfvOutput.Write(0.0); KfaOutput.Write(0.0); FeedforwardOutput.Write(0.0); }
Note
If not defined, signals are initialized on 0.
Note
The
Init()
is not relevant in our case, but note that this is the only method in which memory allocation is allowed.The periodic operation of the processing block is defined in the
Output()
andUpdate()
methods. In order to minimize the IO-critical calculation we try to implement as much as possible in theUpdate()
task. All calculations that depend on input values should be implemented in theOutput()
function, to avoid a sample delay. For the feedforward logic all calculations depend on the inputs, therefore we only update parameters in theUpdate()
:void CProcessingBlockFeedforward::Update() { // Update parameters kfv = Kfv.Value(); kfc = Kfc.Value(); kfa = Kfa.Value(); }
We start the
Output()
task with reading the input values:void CProcessingBlockFeedforward::Output() { // Read inputs float demandVelocity = DemandVelocity.Read(); float demandAcceleration = DemandAcceleration.Read();
Then we determine the sign of the velocity and calculate the output values:
float velocitySign = (float)(demandVelocity > 0.0f) - (float)(demandVelocity < 0.0f); // Calculate output values float kfvOutput = kfv * demandVelocity; float kfcOutput = kfc * velocitySign; float kfaOutput = kfa * demandAcceleration; float feedforwardOutput = kfvOutput + kfcOutput + kfaOutput;
And last we set the output values, such that they become available outside the processing block:
KfvOutput.Write(kfvOutput); KfcOutput.Write(kfcOutput); KfaOutput.Write(kfaOutput); FeedforwardOutput.Write(feedforwardOutput); }
Now the implementation is complete. Run make again in a command window to generate the binary file:
make
The binary file can now be uploaded to the (simulated) motion controller or EtherCAT SubDevice, this is described in the Deployment guide.