Wikipedia: PID Controller

In this example we'll use the asc::History class to create a simple controller for a spring-mass-damper system.

Control.h

#pragma once

#include "ascent/history/History.h"
#include "ascent/Link.h"

class Control : public asc::Module
{
public:
   double x{}, xd{}, xdd{}; // We are going to control a simple mass-spring-damper system: x'' + 2*zeta*wn*x' + wn*wn*x = f/m
   double x_target{}; // Desired x value

   double f{}; // force applied

   double m = 1.0; // mass
   double wn = sqrt(40); // Hz
   double zeta = 0.7;

   asc::History error; // History of the error.

   double Kp{}; // Proportional gain
   double Ki{}; // Integral gain
   double Kd{}; // Derivative gain

   Control(size_t sim);

protected:
   void update();

   void postcalc();
};

Control.cpp

#include "Control.h"

Control::Control(size_t sim) : asc::Module(sim),
   error(sim)
{
   addIntegrator(x, xd);
   addIntegrator(xd, xdd);

#define ascNS Control
   ascVar(x)
   ascVar(x_target)
}

void Control::update()
{
   xdd = -2.0*zeta*wn*xd - wn*wn*x + f / m; // This may seem simpler than the Spring-Damper example, but it is far less flexible.
}

void Control::postcalc()
{
   error.push_back(x_target - x);

   f = Kp*error.back() + Ki * error.integral() + Kd*error.derivative();
}

Initializing and Running

Main.cpp

Setup tracking, run the simulation, and generate output file.

#include "Control.h"

int main()
{
   Control control(0);
   control.name<Control>("control");

   control.Kp = 300.0;
   control.Ki = 300.0;
   control.Kd = 10.0;

   control.x_target = 1.0;

   control.track("t");
   control.track("x");
   control.track("x_target");

   control.run(0.01, 1.0);

   control.outputTrack();

   return 0;
}

Generated File

control.csv

t,control x,control x_target
0,0.000000,1.000000
0.01,0.000000,1.000000
0.02,0.014562,1.000000
0.03,0.055761,1.000000
0.04,0.118031,1.000000
0.05,0.195357,1.000000
continued...

Plotted Results: