Speed Measurement

Baseline System

There are many applications where we need to model the interaction between continuous behavior and discrete behavior. For this section, we’ll look at techniques used to measure the speed of a rotating shaft. For our discussion here, we will reuse the mechanical example we discussed previously in our discussion of Basic Equations:

model SecondOrderSystem "A second order rotational system"
  type Angle=Real(unit="rad");
  type AngularVelocity=Real(unit="rad/s");
  type Inertia=Real(unit="kg.m2");
  type Stiffness=Real(unit="N.m/rad");
  type Damping=Real(unit="N.m.s/rad");
  parameter Inertia J1=0.4 "Moment of inertia for inertia 1";
  parameter Inertia J2=1.0 "Moment of inertia for inertia 2";
  parameter Stiffness k1=11 "Spring constant for spring 1";
  parameter Stiffness k2=5 "Spring constant for spring 2";
  parameter Damping d1=0.2 "Damping for damper 1";
  parameter Damping d2=1.0 "Damping for damper 2";
  Angle phi1 "Angle for inertia 1";
  Angle phi2 "Angle for inertia 2";
  AngularVelocity omega1 "Velocity of inertia 1";
  AngularVelocity omega2 "Velocity of inertia 2";
initial equation
  phi1 = 0;
  phi2 = 1;
  omega1 = 0;
  omega2 = 0;
equation
  // Equations for inertia 1
  omega1 = der(phi1);
  J1*der(omega1) = k1*(phi2-phi1)+d1*der(phi2-phi1);
  // Equations for inertia 2
  omega2 = der(phi2);
  J2*der(omega2) = k1*(phi1-phi2)+d1*der(phi1-phi2)-k2*phi2-d2*der(phi2);
end SecondOrderSystem;

We will reuse this model by adding an extends clause to our model. This essentially imports everything from the model we are extending from. We’ll talk more about the extends clause later when we discuss Packages. For now, just think of it as copying the contents of another model into the current model.

Recall the solution for the SecondOrderSystem model looks like this:

../../../_images/SOSIP.png

In this case, we are simply plotting the solution that we computed. But in a real system, we can’t directly know the rotational velocity of a shaft. Instead, we have to measure it. But measurement introduces error and each measurement technique introduces different kinds of errors. In this section, we’ll look at how we can model different kinds of measurement techniques.

Sample and Hold

The first type of measurement we will examine is a sample and hold approach to measurement. Some speed sensors have circuits for measuring the rotational speed of the system. But instead of providing a continuous value for the speed, they sample it at a given point in time and then store it somewhere. This is called “sample and hold”. The following model demonstrates how to implement a sample and hold approach to measuring the angular velocity omega1:

model SampleAndHold "Measure speed and hold"
  extends BasicEquations.RotationalSMD.SecondOrderSystem;
  parameter Real sample_time(unit="s")=0.125;
  discrete Real omega1_measured;
equation
  when sample(0,sample_time) then
    omega1_measured = omega1;
  end when;
end SampleAndHold;

Note the presence of the discrete qualifier in the declaration of omega1_measured. This special qualifier indicates that the specified variable does not have a continuous solution. Instead, the value of this variable will make (only) discrete jumps during the simulation. It is not required to include the discrete keyword but it is useful because it provides additional information about the intent of the model that the compiler can check (e.g., making sure we never request the derivative of that variable).

Let us now examine the solution generated by this model:

../../../_images/SampleAndHold.png

The important thing to note in the solution is how the measured value is piecewise-constant. This is because the value of omega1_measured is set only when the when statement becomes active. The sample function is a special built-in function that first becomes true at the time indicated by the first argument (0 in this case) and then at regular intervals after that. The duration of these regular intervals is indicated by the second argument (sample_time in this case).

Interval Measurement

In the previous example, we weren’t actually making any estimates for the speed, we were simply reporting the value of the variable omega1 only at specific times. In other words, at the moment that we sampled omega1 our sample was completely accurate. But by “holding” our measured value (instead of continuing to track omega1), we introduced some artifact in the measurement.

In the remaining examples, we will focus on techniques used to estimate the speed of a rotating shaft. In these cases, we will never make direct use of the actual speed in our measurement. Instead, we will respond to events generated by the physical system and attempt to use these events to reconstruct an estimate of the rotational speed.

The events that we will be responding to are generated by the discrete elements attached to the spinning shaft. For example, a typical way to produce these events is to use a “tooth wheel encoder”. A tooth wheel encoder includes a gear on the rotating shaft. On either side of the gear, we place a light source and a light sensor. As the gear teeth pass in front of the light source, they block the light. The result is that the signal from the light sensor will include an approximate square wave. The leading edge of these square waves are the events we will be responding to.

The first estimation method we will examine computes the speed of the shaft by measuring the time interval that passes between events. Knowing that these events occur whenever the shaft has rotated by an angle of \(\Delta\theta\), we can estimate the speed as:

\[\hat{\omega} = \frac{\Delta\theta}{\Delta t}\]

This technique for speed estimation can be represented in Modelica as:

model IntervalMeasure "Measure interval between teeth"
  extends BasicEquations.RotationalSMD.SecondOrderSystem;
  parameter Integer teeth=200;
  parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
  Real next_phi, prev_phi;
  Real last_time;
  Real omega1_measured;
initial equation
  next_phi = phi1+tooth_angle;
  prev_phi = phi1-tooth_angle;
  last_time = time;
equation
  when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
    omega1_measured = tooth_angle/(time-pre(last_time));
    next_phi = phi1+tooth_angle;
    prev_phi = phi1-tooth_angle;
    last_time = time;
  end when;
end IntervalMeasure;

where tooth_angle represents \(\Delta\theta\). Note how tooth_angle is not something the user needs to specify. Instead, the user specifies the number of teeth using the teeth parameter. The tooth_angle parameter is then computed using the value of teeth (note that while we have hand coded the value of \(pi\) here, we’ll learn how to avoid this later in the book when we talk about Constants).

Let’s take a close look at the when statement in this model:

equation
  when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
    omega1_measured = tooth_angle/(time-pre(last_time));
    next_phi = phi1+tooth_angle;
    prev_phi = phi1-tooth_angle;
    last_time = time;
  end when;

Here, we use the vector expression syntax used previously in the Bouncing Ball example. Recall that the when statement becomes active if any of the conditions become true. In this case, the when statement becomes active if the angle, phi1, becomes greater than next_phi or less than prev_phi.

Another thing to note is the use of the pre operator throughout the when statement. When an event occurs in a model, there is a chance that the value of some variables may change discontinuously. During an event, while we are trying to resolve what values all the variables should have as a result of the event, the pre operator allows us to reference the value of a variable prior to the event. The pre operator is used in this model to refer to the previous (pre-event) values of next_phi, prev_phi and last_time. The pre operator is necessary because all of these variables are affected by the statements inside the when statement. So, for example, last_time (without the pre operator) refers to the value of last_time at the conclusion of the event while pre(last_time) refers to the value of last_time prior to any event occurring.

Use of the pre operator

In general, if a variable changes as a result of a when statement becoming active, you almost always want to use the pre operator when referring to that variable in the conditional expression associated with the when statement (as we have done in the previous example). This makes it clear that you are responding to what was happening before the when statement was triggered.

Let’s take a look at the speed estimates provided by this approach:

../../../_images/IntervalMeasure.png

There are two important properties of this estimation algorithm that we can immediately see in these results. The first is that the estimate is unsigned. In other words, we cannot tell from a device like a tooth wheel encoder which direction the shaft is rotating. Also, low speeds and changes in rotational direction can degrade the accuracy of the estimate significantly. The results are also very sensitive the number of teeth involved. If we were to reduce the number of teeth used in our encoder by setting teeth to 20, we’d get very different results.

../../../_images/IntervalMeasure_Coarse.png

To understand exactly why the measured signal is so inaccurate, it helps to consider the following plot which shows how the angle, phi1 compares to the angles associated with the adjacent teeth, next_phi and prev_phi.

../../../_images/IntervalMeasure_Coarse_phi.png

In this plot, we can clearly see how relatively low speeds and speed reversals create irregular events that introduce significant estimation error.

Pulse Counting

The interval measuring technique mentioned above requires hardware that can perform speed calculations on hardware interrupts. Another approach to estimating speed is the count how many events occur within a given (fixed) time interval and use that as an estimate of speed. Using this method, only the summation of events occurs when the events occur and the calculations are deferred to a regularly scheduled update.

Building on the previous examples in this section, the following seems like a natural way to create a model that implements this estimation technique:

model Counter "Count teeth in a given interval"
  extends BasicEquations.RotationalSMD.SecondOrderSystem;
  parameter Real sample_time(unit="s")=0.125;
  parameter Integer teeth=200;
  parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
  Real next_phi, prev_phi;
  Integer count;
  Real omega1_measured;
initial equation
  next_phi = phi1+tooth_angle;
  prev_phi = phi1-tooth_angle;
  count = 0;
equation
  when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
    next_phi = phi1+tooth_angle;
    prev_phi = phi1-tooth_angle;
    count = pre(count) + 1;
  end when;
  when sample(0,sample_time) then
    omega1_measured = pre(count)*tooth_angle/sample_time;
    count = 0 "Another equation for count?";
  end when;
end Counter;

However, there is a problem in this model. Note that there are actually two equations for count. Trying to compile such a model will lead to a situation where there are more equations than variables (i.e., the problem is singular).

So what can we do about this? We need two different equations because the updates to count occur in response to different events. We could try to formulate everything under a single when statement, like this:

when {phi1>=pre(next_phi),phi1<=pre(prev_phi),sample(0,sample_time)} then
  if sample(0,sample_time) then
    omega1_measured := pre(count)*tooth_angle/sample_time;
    count :=0;
  else
    next_phi := phi1 + tooth_angle;
    prev_phi := phi1 - tooth_angle;
    count :=pre(count) + 1;
  end if;
end when;

But this kind of code quickly becomes hard to read. Fortunately, we can address this situation by placing all the when statements in an algorithm section.

The nature of an algorithm section is that it is treated as one single equation for any variables that are assigned within it. This allows multiple assignments to count, for example. When using an algorithm section, it is very important to understand that the order of assignment becomes important. If a conflict should arise (e.g., a variable is assigned two values within the same algorithm section), the last one is the one that will be used. Another thing to note about algorithm sections is that you cannot write general equations. Instead, you must write assignment statements.

In this way, an algorithm section is very much like the way most programming languages work. The statements in the algorithm section are executed in order and each statement isn’t interpreted as an equation, but rather as an assignment of an expression to a variable. The familiarity of assignment statements may make using algorithm sections attractive to people with a programming background who find the otherwise equation oriented aspects of Modelica disorienting and unfamiliar. But be aware that one big reason to avoid algorithm sections is because they interfere with the symbolic manipulation performed by the Modelica compiler. This can result in both poor simulation performance and a loss of flexibility in how you compose your models. So it is best to use an equation section whenever possible.

In our case, there are no significant consequences to using the algorithm section. Here is an example of how the previous estimation algorithm could be refactored using an algorithm section:

model CounterWithAlgorithm "Count teeth in a given interval using an algorithm"
  extends BasicEquations.RotationalSMD.SecondOrderSystem;
  parameter Real sample_time(unit="s")=0.125;
  parameter Integer teeth=200;
  parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
  Real next_phi, prev_phi;
  Integer count;
  Real omega1_measured;
initial equation
  next_phi = phi1+tooth_angle;
  prev_phi = phi1-tooth_angle;
  count = 0;
algorithm
  when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
    next_phi := phi1 + tooth_angle;
    prev_phi := phi1 - tooth_angle;
    count := pre(count) + 1;
  end when;
  when sample(0,sample_time) then
    omega1_measured := pre(count)*tooth_angle/sample_time;
    count :=0;
  end when;
end CounterWithAlgorithm;

The simulated results of this estimation technique can be seen in the following plot:

../../../_images/SpeedCounter.png

Again, we see that this approach cannot determine the direction of rotation. With the following plot, we can get a sense of how many events occur within each sample interval:

../../../_images/SpeedCounter_count.png

In general, the higher the count gets in an interval, the more accurate the estimate.

Conclusion

This section demonstrates how we can use the when construct to respond to physical events that occur in our system. These kinds of events and the impact they have on our system are just as important as the continuous dynamics we’ve covered previously. The ability to capture and respond to these physical events is an important part of why Modelica is so well suited to model complete systems since those system frequently include both continuous and discrete behavior.