速度的测量

基准系统

在许多应用中,我们需要对连续行为与离散行为之间的相互作用进行建模。在本节中,我们将看到用于测量旋转轴速度的技术。在这里的讨论里,我们将重用前面在基本方程中讨论过的机械中的例子

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;

我们将在新模型中增加一个extends(扩展)条款,以重用旧的模型。这个语句实质上是导入被扩展模型的一切内容。我们将在之后讨论时进一步介绍extends条款。现在不妨暂时把这个语句的作用当成把另一个模型的内容直接复制到当前模型内。

记得SecondOrderSystem模型的解如下:

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

在此情况下,我们仅仅是把计算出来的解绘制出来了。但在实际系统中,我们不可能直接知道一个轴的旋转速度。相反,我们必须进行测量。不过,测量误差会引入误差。而不同的测量技术则会引入不同类型的误差。在本节中,我们将介绍如何能够模拟各种不同的测量技术。

取样保持

我们将考察的第一种测量方法是用采样和保持的方法。某些速度传感器具有测量系统旋转速度的电路。而这些传感器并非提供连续的速度值。传感器在给定时间点对速度进行取样,然后将其保存在某个地方。这种方法被成为“取样保持”(sample and hold)。下面的模型展示了如何实现采用采样保持的方法测量角速度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;

请注意,变量omega1_measured的声明内有discrete(离散)这个限定词。这个特殊的限定词表示指定变量不具有连续的解。相反,这个变量的值(只)会在仿真过程中离散跃变。加入discrete关键字并不是不需的,但此关键字有其用处。它可以提供了模型意图的进一步信息,帮助编译器检查模型的正确性。(例如用户不能求该变量的导数。)

现在让我们来研究这个模型生成的解:

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

值得注意的是,解中测得的值是分段恒定的。这是因为只有仅当when被激活时,omega1_measured的值才会被设置。sample函数是一个特殊的内建函数。此函数在其第一个参数(在本例中值为0)所示时刻首次为真,之后在恒定间隔里所变为真。恒定间隔的长度则有第二个参数决定(本例中为变量sample_time)。

间隔测量

在前面的例子里,我们其实没有对速度进行估测,而是直接报告了变量omega1在特定时刻的值。换句话,在我们在采样时刻得到omega1的采样值是完全准确的。不过通过“保持”(holding)测量值(而非继续跟踪omega1变量),我们便引入了误差。

在余下的例子中,我们重点放在用于估计旋转轴速度的技术。在这些例子中,我们不再会在测量中直接使用实际速度。相反,我们将立足于物理系统所产生的事件,尝试使用这些事件去重建旋转速度的估计值。

这些我们要处理的事件产生自连接在旋转轴的离散元件。例如,一种典型生产这些事件的方法是使用“齿轮编码器”。齿轮编码器包括一个在旋转轴上的齿轮。在齿轮的两侧,我们将放置光源和光传感器。随着齿轮的齿通过在光源的前面,齿轮会挡住光线。其结果是,光传感器的信号将给出一个近似方波信号。这些方波的前缘便是我们需要响应的事件。

我们研究的第一种测算方法是通过测量事件之间经过的时间间隔来计算轴的转速。既然我们知道,每当轴旋转\(\Delta\theta\)角度时这些事件便会发生,我们可以如此测算转速:

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

这种速度测算方法可用Modelica表示为:

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;

其中tooth_angle(齿角)表示\(\Delta\theta\)。注意tooth_angle并不需要用户指定。用户使用teeth参数指定齿轮的齿数。tooth_angle参数的值会由参数teeth计算(注意,虽然我们在这里手工输入了\(\pi\)的值,我们将在本书的常数中学习如何在以后避免这种情况)。

让我们来仔细看一下这个模型中的when语句:

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;

在这里,我们使用了之前在弹跳球例子中用过的矢量表达式语法。请记住,如果其中的任何条件的变为真,那么when语句便会被激活。在这里,一旦角度phi1大于next_phi或小于prev_phi,那么when语句便会被激活。

另外需要注意是在整个when语句里使用的pre操作符。模型内发生事件时,变量值可能会非连续地改变。在一个事件中,当我们试图计算所有变量受事件影响后的取值时,pre操作符允许我们引用变量在事件之前的值。这里的pre操作符在该模型中的作用是引用next_phiprev_phi以及last_time在之前(事件前)的值。由于所有变量都受到在when内部语句的影响,因此pre操作符是必须的。所以,例如last_time(没有pre操作符)指代的是last_time在事件结束时的取值。而pre(last_time)指代的则是last_time在任何事件发生前的取值。

pre操作符的使用

一般而言,如果一个变量的变化是由被激活的when语句造成的,那么在when语句使用的条件表达式与上述会被更改的变量相关联时,你几乎总是需要使用pre操作符来指代这个变量(正如我们在前例中做的一样)。这清楚地表明,你是回应when语句触发之前系统的状态。

让我们看看用上述方法给出的速度测算值:

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

从以上结果我们可以马上看到这个测算算法的两个重要特性。首先,测算结果是无符号的。也就是说,我们无法使用类似齿轮编码器的装置得知轴转动的方向。其次,低转速和旋转方向的改变可以显著降低测算的准确性。结果对齿轮的齿数也非常敏感。如果我们把编码器中齿的数量即参数teeth减少为20,我们将得到十分不同的结果。

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

为何测量信号如此地不准确呢?为了明白这一点,大家可以观察下列图表,对比变量phi1和下列相邻齿的角位置next_phi以及prev_phi

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

在这个图里,我们可以清楚地看到,较低的转速以及反转都会产生不规则的事件。这会从而引入显著估计误差。

脉冲计数

上述时间间隔测量技术需要能在硬件中断时执行速度计算的硬件。另一种估测速度的方法需要对(固定)时间间隔内发生的事件数目进行计数,并以此作为速度的估算值。使用这种方法,在事件发生时仅仅会进行事件总数的求和运算,实际的计算则只会在定期更新时进行。

基于本节前面的例子,我们似乎会很自然地用以下形式在模型里实现这种估测技术:

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;

然而,此模型有一个问题。注意count变量实际上有两个方程。如果我们尝试编译这样的模型就会导致方程数量超过变量数量。(也就是说问题是奇异的。)

那么,我们应该如何处理这种情况呢?之所以需要两个不同的方程,是因为在更新count时,我们需要对不同的事件作出不同的反应。我们可以尝试用如下形式将需求表达为一个when语句,:

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;

但是,这种代码很快就会变得难以阅读。幸运的是,我们可以通过把所有的when陈述中放在algorithm(算法)区域以解决这个问题。

algorithm区域的特性是对于任何在其内部进行赋值的变量而言,它都仅被视为是一个等式。譬如说,这个特性让count变量可以被多次赋值。使用algorithm区域时,读者必须明白赋值的顺序不再无足轻重。如果有冲突出现时(例如在同一个algorithm区域内一个变量被赋了两个值),那么只有最后一个会被使用。对于algorithm区域我们还需要注意另外一点,就是它并不支持一般意义的等式。相对地,大家必须使用赋值语句

在这个方面上,algorithm区域的工作方式与大多数编程语言非常相像。在算法区域的语句是顺序执行的,且每个语句不被解析为等式而是作为给变量赋值的表达式。具有某些编程背景的人们可能会觉得Modelica面向公式的一面既迷惑又陌生。对于他们而言,熟悉的赋值语句可能是很诱人的。但需要注意,之所以要避免algorithm区域的一大原因是,它会干扰Modelica语言编译器进行的符号运算。这既会导致仿真性能不佳,又会让你在建模时丧失灵活性。因此,大家最好尽可能地使用equation区域。

在本例里algorithm区域的使用并不会带来显著的影响。以下是将前述测算方法用algorithm区域重构后的结果:

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;

测算方法的仿真结果见下图:

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

同样,我们可以看到这种方法并不能确定旋转的方向。 由下图,我们可以对每个采样间隔内发生事件的数量有一个大概的感觉:

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

在一般情况下,间隔内计数越高,测算结果就越准确。

结论

本节说明如何可以通过when这一语言特性对发生在系统中的物理事件作出响应。这类事件及其对系统的影响,对比我们之前讨论过的连续时间动力学特性,其实同样重要。由于完整系统通常包括连续的和不连续行为,Modelica对上述事件的发现和应对能力是其适合用于系统建模的重要原因。。