Motion controllers

This guides describes how to load a custom Processing block (from a Simulink model or custom C++ code) on a simulated PMP motion controller.

Introduction

Once the generated code binary is created via Simulink or C++, the file can be uploaded to the controller using a processing block template. This is done in the following steps:

  1. Create a processing block Template, and upload the generated library via the updatable of the template.

  2. Create one (or more) processing block instances based on the template.

  3. Connect inputs.

This can be done in two ways:

  • Via the PMP API.

  • Via LoadConfigurationFromFile.

In Deployment via the API sample code is given how to implement the steps stated above via the API to create an instance of the feedforward processing block and connect its inputs. Under Deployment via a configuration file it is shown how to achieve the same result via a configuration file.

Note

We recommend using an XML configuration file, as it reduces API calls, allows quicker initialization of the system, and a quicker configuration of a controller.

After the processing blocks are deployed on the controller, all PMP objects with corresponding attributes defined in the Simulink model or via the C++ interface description YAML are available.

The Feedforward processing block is connected to the trajectory interpolator and position control as followed:

Feedforward processing block interconnections

Feedforward processing block interconnections.

Prerequisites

Before continuing with the guide, please make sure that the following prerequisites are met:

  • PMP should be installed with Simulator and Tooling features. Follow the Installation quick-start guide if this is not yet the case. A Typical installation includes the required Simulator and Tooling features.

  • The Processing block deployment motion controller quick start project files should be available. These files can be downloaded from Downloads.

Deployment via the API

Feedforward is instantiated as a processing block on axis control level via the example code stated below.

Attention

Make sure to upload the binary of the correct build target. For Windows simulator we use windows-x86_64-release.

  1. Create the template and upload the processing block binary file to the template updatable:

    1
    2
    3
    var feedforwardTemplate = topController.CreateTemplate("FeedforwardTemplate", Pmp.TemplateType.ProcessingBlock);
    feedforwardTemplate.Updatable.LoadContentsFromFile("C:\\your\\path\\to\\processingblock-deployment-motion-controller\\configuration-files\\Feedforward-windows-x86_64.bin");
    feedforwardTemplate.Updatable.WaitComplete(10.0);
    
    1
    2
    3
    auto feedforwardTemplate = topController->CreateTemplate("FeedforwardTemplate", Pmp::ETemplateType::ProcessingBlock);
    feedforwardTemplate->GetUpdatable()->LoadContentsFromFile("C:\\your\\path\\to\\processingblock-deployment-motion-controller\\configuration-files\\Feedforward-windows-x86_64.bin");
    feedforwardTemplate->GetUpdatable()->WaitComplete(10.0);
    
    1
    2
    3
    feedforwardTemplate = topController.CreateTemplate("FeedforwardTemplate", Pmp.TemplateType.ProcessingBlock)
    feedforwardTemplate.Updatable.LoadContentsFromFile("C:\\your\\path\\to\\processingblock-deployment-motion-controller\\configuration-files\\Feedforward-windows-x86_64.bin")
    feedforwardTemplate.Updatable.WaitComplete(10.0)
    
    1
    2
    3
    feedforwardTemplate = topController.CreateTemplate('FeedforwardTemplate', Pmp.TemplateType.ProcessingBlock);
    feedforwardTemplate.Updatable.LoadContentsFromFile('C:\\your\\path\\to\\processingblock-deployment-motion-controller\\configuration-files\\Feedforward-windows-x86_64.bin');
    feedforwardTemplate.Updatable.WaitComplete(10.0);
    
  2. Print the template version:

    1
    2
    var version = feedforwardTemplate.Updatable.Version;
    Console.WriteLine("Template version: " + version.Major + "." + version.Minor + "." + version.Patch + "." + version.Build);
    
    1
    2
    auto version = feedforwardTemplate->GetUpdatable()->GetVersion();
    std::cout << "Template version: " << version.Major << "." << version.Minor << "." << version.Patch << "." << version.Build << std::endl;
    
    1
    2
    version = feedforwardTemplate.Updatable.Version
    print(f"Template version: {version.Major}.{version.Minor}.{version.Patch}.{version.Build}")
    
    1
    2
    version = feedforwardTemplate.Updatable.Version;
    fprintf('Template version: %i.%i.%i.%s\n', version.Major, version.Minor, version.Patch, version.Build);
    

    The version should be equal to the version reported in Simulink or in the C++ interface description YAML (padded with zeros at the end).

  3. Instantiate the processing block based on the template:

    1
    2
    var axis = topController.AxisControls["AxisX"];
    var feedforward = axis.CreateProcessingBlock("Feedforward", feedforwardTemplate);
    
    1
    2
    auto axis = topController->GetAxisControl("AxisX");
    auto feedforward = axis->CreateProcessingBlock("Feedforward", feedforwardTemplate);
    
    1
    2
    axis = topController.AxisControls["AxisX"]
    feedforward = axis.CreateProcessingBlock("Feedforward", feedforwardTemplate)
    
    1
    2
    axis = topController.AxisControls.Item('AxisX');
    feedforward = axis.CreateProcessingBlock('Feedforward', feedforwardTemplate);
    
  4. After the processing block is instantiated, connect the inputs of the feedforward processing block to the corresponding signals of the trajectory interpolator:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    var trajectoryInterpolator = axis.TrajectoryInterpolator;
    feedforward.Inputs["DemandVelocity"].Source = trajectoryInterpolator.Signals["DemandVelocity"];
    feedforward.Inputs["DemandAcceleration"].Source = trajectoryInterpolator.Signals["DemandAcceleration"];
    
    var positioncontrol = axis.ProcessingBlocks["PositionControl"];
    positioncontrol.Inputs["FeedforwardControl"].Source = feedforward.Signals["FeedforwardOutput"];
    
    feedforward.Signals["Kfv"].ValueDouble = 1.5;
    feedforward.Signals["Kfc"].ValueDouble = 2.0;
    feedforward.Signals["Kfa"].ValueDouble = 0.5;
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    auto trajectoryInterpolator = axis->GetTrajectoryInterpolator();
    feedforward->GetInput("DemandVelocity")->SetSource(trajectoryInterpolator->GetSignal("DemandVelocity"));
    feedforward->GetInput("DemandAcceleration")->SetSource(trajectoryInterpolator->GetSignal("DemandAcceleration"));
    
    auto positioncontrol = axis->GetProcessingBlock("PositionControl");
    positioncontrol->GetInput("FeedforwardControl")->SetSource(feedforward->GetSignal("FeedforwardOutput"));
    
    feedforward->GetSignal("Kfv")->WriteDouble(1.5);
    feedforward->GetSignal("Kfc")->WriteDouble(2.0);
    feedforward->GetSignal("Kfa")->WriteDouble(0.5);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    trajectoryInterpolator = axis.TrajectoryInterpolator
    feedforward.Inputs["DemandVelocity"].Source = trajectoryInterpolator.Signals["DemandVelocity"]
    feedforward.Inputs["DemandAcceleration"].Source = trajectoryInterpolator.Signals["DemandAcceleration"]
    
    positioncontrol = axis.ProcessingBlocks["PositionControl"]
    positioncontrol.Inputs["FeedforwardControl"].Source = feedforward.Signals["FeedforwardOutput"]
    
    feedforward.Signals["Kfv"].ValueDouble = 1.5
    feedforward.Signals["Kfc"].ValueDouble = 2.0
    feedforward.Signals["Kfa"].ValueDouble = 0.5
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    trajectoryInterpolator = axis.TrajectoryInterpolator;
    feedforward.Inputs.Item('DemandVelocity').Source = trajectoryInterpolator.Signals.Item('DemandVelocity');
    feedforward.Inputs.Item('DemandAcceleration').Source = trajectoryInterpolator.Signals.Item('DemandAcceleration');
    
    positioncontrol = axis.ProcessingBlocks.Item('PositionControl');
    positioncontrol.Inputs.Item('FeedforwardControl').Source = feedforward.Signals.Item('FeedforwardOutput');
    
    feedforward.Signals.Item('Kfv').ValueDouble = 1.5;
    feedforward.Signals.Item('Kfc').ValueDouble = 2.0;
    feedforward.Signals.Item('Kfa').ValueDouble = 0.5;
    
  5. Now everything is configured go to run state to activate the processing block:

    1
    topController.Run();
    
    1
    topController->Run();
    
    1
    topController.Run()
    
    1
    topController.Run();
    
  6. After usage we go back to config state. In config state the processing block instance and template can be cleaned up. First disconnect inputs, then clean up the processing block, and clean up the template last:

    1
    2
    3
    4
    5
    6
    topController.Stop();
    
    // Clean-up the processing block instance and template
    positioncontrol.Inputs["FeedforwardControl"].Source = null;
    feedforward.Dispose();
    feedforwardTemplate.Dispose();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    topController->Run();
    
    // Add your application here
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    
    // Disable top-controller
    topController->Stop();
    
    // Clean-up the processing block instance and template
    positioncontrol->GetInput("FeedforwardControl")->SetSource(nullptr);
    feedforward->Destroy();
    feedforwardTemplate->Destroy();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    topController.Run()
    
    # Add your application here
    time.sleep(1)
    
    # Disable top-controller
    topController.Stop()
    
    # Clean-up the processing block instance and template
    positioncontrol.Inputs["FeedforwardControl"].Source = None
    feedforward.Dispose()
    feedforwardTemplate.Dispose()
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    topController.Run();
    
    % Add your application here
    pause(1);
    
    % Disable top-controller
    topController.Stop();
    
    % Clean-up the processing block instance and template
    positioncontrol.Inputs.Item('FeedforwardControl').Source = [];
    feedforward.Dispose();
    feedforwardTemplate.Dispose();
    

Deployment via a configuration file

The configuration file can be loaded into PMP via the API function LoadConfigurationFromFile or via the PMP Tooling. We will reuse the configuration XML file from the Closed-loop system quick start guide, which already includes a simple position feedback control, and add our generated feedforward control. The added lines are highlighted.

Update the configuration file

The link to the binary files is added via file path. Alternatively, the binary content can be converted to base64 format and embedded in the configuration file as described in Base64 conversion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<Controller Name="Arcas 5EG-1">

  <!-- Template to create a simple position feedback controller -->
  <Template Name="PositionControlTemplate" TemplateType="ProcessingBlock">
    <Updatable Name="Updatable">
      <FilePath RelativeTo="File">PositionControlSimple-windows-x86_64.bin</FilePath>
    </Updatable>
  </Template>

  <Template Name="PlantSecondOrderMechanicalTemplate" TemplateType="ProcessingBlock">
    <Updatable Name="Updatable">
      <FilePath RelativeTo="File">PlantSecondOrderMechanical-windows-x86_64.bin</FilePath>
    </Updatable>
  </Template>

  <Template Name="FeedforwardTemplate" TemplateType="ProcessingBlock">
    <Updatable Name="Updatable">
      <FilePath RelativeTo="File">Feedforward-windows-x86_64.bin</FilePath>
    </Updatable>
  </Template>

  <!-- Axis control configuration -->
  <AxisControl Name="AxisX" Template="LogicalAxisControlStandard3rdOrderTemplate">

    <!-- Instantiate PlantSecondOrderMechanicalTemplate under AxisX -->
    <ProcessingBlock Name="Plant" Template="PlantSecondOrderMechanicalTemplate" CalculationStart="True">
      <Input Name="Actuator" Source="AxisX/PositionControl/ControlOutput"/>
      <Input Name="SamplePeriod" Source="SamplePeriod"/>
      <!-- Mass with viscous friction -->
      <Signal Name="Mass" Unit="kg">10</Signal>
      <Signal Name="Damping" Unit="Ns/m">1</Signal>
      <Signal Name="Stiffness" Unit="N/m">0</Signal>
    </ProcessingBlock>

    <TrajectoryGenerator>
      <!-- Limited by bearings, mechanics, back-emf -->
      <Signal Name="MaximumVelocity" Unit="m/s">1</Signal>
      <!-- Limited by max current of drive or max force/torque of motors -->
      <Signal Name="MaximumAcceleration" Unit="m/s^2">10</Signal>
      <!-- Limited by motor inductance, max voltage of drive and control bandwidth -->
      <Signal Name="MaximumJerk" Unit="m/s^3">100</Signal>
    </TrajectoryGenerator>

    <TrajectoryInterpolator>
      <!-- The following tuning configuration was created for purposes of this demo only -->
      <Input Name="DemandPositionOffset" Source="AxisX/PositionControl/DemandPositionOffset"/>
    </TrajectoryInterpolator>

    <CommandQueue>
      <!--Position control ClosedLoop state -->
      <Input Name="IsClosedLoop" Source="AxisX/PositionControl/IsClosedLoop"/>
    </CommandQueue>

    <!-- Create PositionControl ProccesingBlock -->
    <ProcessingBlock Name="PositionControl" Template="PositionControlTemplate">
      <!-- High when axis control state machine requests transition to closed loop (see IsClosedLoop signal) -->
      <Input Name="CloseLoopRequest" Source="AxisX/CommandQueue/CloseLoopRequest"/>
      <!-- Position setpoint is connected to trajectory generator -->
      <Input Name="DemandPosition" Source="AxisX/TrajectoryInterpolator/DemandPosition"/>
      <!-- Position feedback from the plant is connected to position controller -->
      <Input Name="PositionSensor" Source="AxisX/Plant/Position"/>
      <!-- Feedforward output from the Feedforward model is connected to FeedforwardControl -->
      <Input Name="FeedforwardControl" Source="AxisX/FeedForward/FeedforwardOutput" />
      <Filter Name="PID_LowPass">
        <!-- PD controller - for the purposes of this demo only -->
        <Signal Name="ProportionalGain">700</Signal>
        <Signal Name="IntegratorFrequency">0</Signal>
        <Signal Name="LowPassFrequency">1000</Signal>
        <Signal Name="LowPassDamping">0.707</Signal>
        <Signal Name="DifferentiatorFrequency">0.1</Signal>
      </Filter>
    </ProcessingBlock>

    <ProcessingBlock Name="FeedForward" Template="FeedforwardTemplate">
      <Input Name="DemandVelocity" Source="AxisX/TrajectoryInterpolator/DemandVelocity"/>
      <Input Name="DemandAcceleration" Source="AxisX/TrajectoryInterpolator/DemandAcceleration"/>
      <!-- Gains -->
      <Signal Name="Kfa">0.5</Signal>
      <Signal Name="Kfc">2</Signal>
      <Signal Name="Kfv">1.5</Signal>
    </ProcessingBlock>

  </AxisControl>
</Controller>
  1. Create a new template with the type ProcessingBlock and add the binary file with its relative file path:

    1
    2
    3
    4
    5
    <Template Name="FeedforwardTemplate" TemplateType="ProcessingBlock">
      <Updatable Name="Updatable">
        <FilePath RelativeTo="File">Feedforward-windows-x86_64.bin</FilePath>
      </Updatable>
    </Template>
    
  2. Create an instantiation of the feedforward processing block based on the template:

    1
    2
    3
    4
    5
    6
    7
    8
    <ProcessingBlock Name="FeedForward" Template="FeedforwardTemplate">
      <Input Name="DemandVelocity" Source="AxisX/TrajectoryInterpolator/DemandVelocity"/>
      <Input Name="DemandAcceleration" Source="AxisX/TrajectoryInterpolator/DemandAcceleration"/>
      <!-- Gains -->
      <Signal Name="Kfa">0.5</Signal>
      <Signal Name="Kfc">2</Signal>
      <Signal Name="Kfv">1.5</Signal>
    </ProcessingBlock>
    

    The values for the Feedforward gains are set directly and signals are connected to inputs as described in Feedforward processing block interconnections..

  3. Connect the FeedforwardControl input of the PositionControl processing block to the FeedforwardOutput signal of the Feedforward processing block:

    1
    <Input Name="FeedforwardControl" Source="AxisX/FeedForward/FeedforwardOutput" />
    

Load the configuration file

In this guide the loading and verification steps are done using the PMP Tooling, alternatively this can be done using the API as described in the Closed-loop system quick start guide.

Right-click on the top-controller and select Load configuration from files.

Upload configuration XML file.

Upload configuration XML file

Verification

After the processing blocks are deployed on the controller, all PMP objects with corresponding attributes are present:

Feedforward processing block integration in PMP.

Feedforward processing block integration in PMP

Follow the Motion simulation chapter to create a Scope and execute a relative move command.

Note

Make sure to add the FeedforwardOutput signal to the scope.

Check the response of the Plant.

Closed loop system with Feedforward.

Closed loop system with Feedforward