Real Time Systems

Exercise 3: Controller Implementation

2019 Version


If you have not done the Buttons exercise from Exercise 2, we suggest that you start by finishing it.

The aim of exercise three is:

Most of the exercise concerns control of the ball and beam process.

Ball and Beam process

Instead of using the real process you will use a virtual, simulated ball and beam process. A Swing-based GUI to the controllers is available. A reference signal generator class is also provided. During Laboratory 1 the real ball and beam process will be used.


Periodic Threads

So far you have implemented periodic tasks only using sleep, i.e., as

  long h = ...; // Sampling interval
  try {
    while (!Thread.interrupted()) {
      periodicActivity();
      Thread.sleep(h);
    }
  } catch (InterruptedException x) {
    // Requested to stop
  }

where h is the desired period in milliseconds. The problem with this approach is that it does not take the execution time of the periodic activity into account. The execution time includes the actual execution, which may be time-varying due to, e.g., caches, data-dependent operations, and the time when the thread is suspended by other threads.

A better, although far from perfect, way to implement periodic threads is the following

  long h = ...; // Sampling interval
  long duration;
  long t = System.currentTimeMillis();
  try {
    while (!Thread.interrupted()) {
      periodicActivity();
      t = t + h;
      duration = t - System.currentTimeMillis();
      if (duration > 0) {
        Thread.sleep(duration);
      }
    }
  } catch (InterruptedException x) {
    // Requested to stop
  }
  1. Why is this approach better than the first? Why is it still not perfect?
The first approach assumes that the compoutation time is 0. The second approach takes the computation time into consideration.

In the following you should use this approach as soon as you implement periodic controller tasks.


Control of the Beam Process

In this exercise your task is to implement a PI controller and use it to control the angle of the ball and beam process, i.e., without any ball. The controller should be split up in two parts: the class PI which only contains the basic PI algorithm and the class BeamRegul which implements the control of the beam process with the use of a PI controller.

For the implementation you should use two predefined classes: PIParameters containing the parameters of a PI controller and PIGUI that implements a graphical user interface for the PI through which you can change controller parameters.

Class PI

The PI controller should be implemented in the class PI as a monitor, i.e., a passive object with only synchronized methods: PI code skeleton

Class PIGUI

The PIGUI class provides a Swing JFrame through which you can type in new values for the PI-parameters. If an invalid entry is made, e.g. a non-number, the change is ignored. In order to make it possible to change several parameters at the same time, a change does not take place until the Apply button has been pressed. The integratorOn is set to false if Ti is set to 0.0. The PI parameters in PIGUI get their initial values set through the constructor of PIGUI, which has the signature:

  public PIGUI(PI PIReference, PIParameters initialPar, String name);
  1. Implement the PI class using the already presented code skeleton. Use forward Euler approximation for the discretization of the integral part. The PI algorithm is available in the slides from Lecture 8. Write a small dummy main method that creates an instance of the class. The files that you need are PIGUI.java and PIParameters.java.
public class PI {
  private PIParameters p;

  private double I; // Integrator state

  private double v; // Desired control signal
  private double e; // Current control error

  //Constructor
  public PI(String name) {
	  PIParameters p = new PIParameters();
	  p.Beta = 1.0;
	  p.H = 0.1;
	  p.integratorOn = false;
	  p.K = 1.0;
	  p.Ti = 0.0;
	  p.Tr = 10.0;
	  new PIGUI(this, p, name);
	  setParameters(p);

	  this.I = 0.0;
	  this.v = 0.0;
	  this.e = 0.0;
  }

  public synchronized double calculateOutput(double y, double yref) {
	  this.e = yref - y;
	  this.v = p.K * (p.Beta * yref - y) + I; // I is 0.0 if integratorOn is false
	  return this.v;
  }

  public synchronized void updateState(double u) {
	  if (p.integratorOn) {
		  I = I + (p.K * p.H / p.Ti) * e + (p.H / p.Tr) * (u - v);
	  } else {
		  I = 0.0;
	  }
  }

  public synchronized long getHMillis() {
  	return (long)(p.H * 1000.0);
  }

  public synchronized void setParameters(PIParameters newParameters) {
  	p = (PIParameters)newParameters.clone();
	if (!p.integratorOn) {
		I = 0.0;
	}
  }
}

Class BeamRegul

BeamRegul is the controller thread that implements the control of the beam process through the use of the PI class. The class ReferenceGenerator provides the reference signal with the method

  public synchronized double getRef();

The ReferenceGenerator generates a square-wave signal. It has a GUI through which the amplitude and period (in seconds) can be changed.

  1. Implement BeamRegul by extending Thread according to the BeamRegul code skeleton. The IO interface to the virtual process uses the same approach as in the Buttons exercise. Assume that the max/min control signal is 10/-10 V.
import SimEnvironment.*;

public class BeamRegul extends Thread {
	private ReferenceGenerator referenceGenerator;
	private PI controller;

	private AnalogSource analogIn;
	private AnalogSink analogOut;
	private AnalogSink analogRef;

	//Define min and max control output
	private double uMin = -10.0;
	private double uMax = 10.0;

	//Constructor
	public BeamRegul(ReferenceGenerator ref, Beam beam, int pri) {
		referenceGenerator = ref;
		controller = new PI("PI");
		analogIn = beam.getSource(0);
		analogOut = beam.getSink(0);
		analogRef = beam.getSink(1);
		setPriority(pri);
	}
	//Saturate output at limits
	private double limit(double u, double umin, double umax) {
		if (u < umin) {
			u = umin;
		} else if (u > umax) {
			u = umax;
		}
		return u;
	}

	public void run() {
		long t = System.currentTimeMillis();
		while (true) {
			// Read inputs
			double y = analogIn.get();
			double ref = referenceGenerator.getRef();

			synchronized (controller) { // To avoid parameter changes in between
				// Compute control signal
				double u = limit(controller.calculateOutput(y, ref), uMin, uMax);

				// Set output
				analogOut.set(u);

				// Update state
				controller.updateState(u);
			}
			analogRef.set(ref); // Only for the plotter animation

			t = t + controller.getHMillis();
			long duration = t - System.currentTimeMillis();
			if (duration > 0) {
				try {
					sleep(duration);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

The class Beam is a simulated "virtual" ball and beam process without any ball. It shows a plotter and an animation of the process.

Simulated Beam Process Plot

  1. Compile and run the system, The files that you need are Beam.java Main.java and ReferenceGenerator.java. Like in the buttons exercise you also need virtualsimulator.jar in the classpath, i.e.,
  2. Start with a pure P-controller. Test what happens for different values of K. Explain what happens for large values of K (> 10). How does this compare to reality?
  3. Add integral action. Is it a good idea (the beam is modeled as a pure integrator)?
  4. With your current solution it is most likely possible for the parameters to become updated between calculateOutput and updateState. Since both these calls are needed to execute the controller this should be prevented. Modify your code to prevent this. Hint: Use a synchronized block. Which object should you synchronize on? Remember that Java synchronized locks are reentrant.

Cascade Control of the Ball and Beam Process

NOTE: This exercise is a preparatory exercise that must be done in order to be allowed to do Laboratory 1. No solution is available.

The next task is to implement a controller for the entire Ball and Beam Process (with the Ball). More information about the model for this process is available in BallAndBeamModel.pdf. The control structure that you should use is a cascade controller. This structure is suitable when an intermediate process output is available, that lies between the process input and the process output that you want to control. In the ball and beam process we want to control the position of the ball. However, we also have access to the angle of the beam. In a cascade controller the intermediate process output is used to close an inner control loop, according to the Figure below:

Block Diagram

The output from the outer controller now becomes the reference for the inner controller. The advantages of a cascaded controller are that

When a cascade structure is used it is common to only have integral action in the outer loop. When we use cascaded control on the ball and beam process we use the same sampling interval for both controllers, and implement them in a single periodic thread. It is also quite common to use a faster sampling interval for the inner controller. When implementing the two loops in a single controller thread it is important to minimize the computational delay (control delay, latency) from the sampling of the position to the generation of the control signal.

  1. Now you should implement a controller for the Ball and Beam process. Name the class BallAndBeamRegul. The class should contain two controllers, a PI controller for the inner loop and a PID controller for the outer loop.
  1. Test and tune your controller manually. Use the parameter ranges above.
  1. Optional. Assume that the inner controller is a pure P-controller with gain K=10. The transfer function for the beam process is G_beam(s) = 4.4 / s. Compute the transfer function, G_inner(s), from angle reference to beam angle in the figure above. For design of the outer controller you now have a new open-loop system, G_inner(s)*G_ball(s), where G_ball(s) = -7 / s². Study the Nyquist and Bode plots of this transfer function using Matlab. Why is derivative action important in the outer loop? Matlab commands:
  >> K = 10;
  >> s = tf('s');
  >> Gbeam = 4.4 / s;
  >> Ginner = K*Gbeam / (1 + K*Gbeam);
  >> Gball = 7 / s^2; % assume negative controller gain
  >> nyquist(Ginner*Gball);
  >> margin(Ginner*Gball); % what are the stability margins?