软件在环(SiL)控制器

前面章节插值的示例里我们介绍了利用外部C函数去对数据进行管理和插值。本章节将继续探索如何在Modelica模型中集成用C代码编写的嵌入式控制器。

当利用Modelica搭建物理系统的数学模型时,有些时候,将(外部)控制策略与物理模型进行集成会非常有用。很多情况下,上述控制策略的存在形式均是用于嵌入式控制器的自动生成C代码。下面展示的例子将会回顾前面示例滞回。在此的基础上,例子里包含了一些有趣的转折。我们将使用外部C函数来实现磁滞特性,而非在Modelica中通过离散状态来实现。虽然这个示例非常简单,但是它包含了集成外部控制策略所需的所有步骤。

物理模型

首先,我们从此前的“物理模型”开始。在这个示例中,上述物理模型本质上与前面章节滞回中所搭建的模型基本相同。修改后的物理模型如下所示:

model HysteresisEmbeddedControl "A control strategy that uses embedded C code"
  type HeatCapacitance=Real(unit="J/K");
  type Temperature=Real(unit="K");
  type Heat=Real(unit="W");
  type Mass=Real(unit="kg");
  type HeatTransferCoefficient=Real(unit="W/K");
  parameter HeatCapacitance C=1.0;
  parameter HeatTransferCoefficient h=2.0;
  parameter Heat Qcapacity=25.0;
  parameter Temperature Tamb=285;
  parameter Temperature Tbar=295;
  Temperature T;
  Heat Q;
initial equation
  T = Tbar+5;
equation
  when sample(0, 0.01) then
    Q = computeHeat(T, Tbar, Qcapacity);
  end when;
  C*der(T) = Q-h*(T-Tamb);
end HysteresisEmbeddedControl;

对于上述模型的equation区域,如下所示:

equation
  when sample(0, 0.01) then
    Q = computeHeat(T, Tbar, Qcapacity);
  end when;
  C*der(T) = Q-h*(T-Tamb);
end HysteresisEmbeddedControl;

computeHeat函数每隔10毫秒计算一次所需的热量。我们马上将可以看到在控制器中实现了开关控制策略。即系统在无热量产生和全功率热量产生之间进行切换。正如在前面章节滞回中所述,上述方法可能会导致系统“抖振”。出于上述原因,我们在when语句中每10毫秒计算一次Q变量的值。这10毫秒间隔实质上是为了实现所谓的“调度”功能,用以决定执行哪种控制策略。

嵌入式控制策略

在Modelica中定义的computeHeat函数用于计算任意给定时间内传递给物理系统的热量。函数定义如下所示:

impure function computeHeat "Modelica wrapper for an embedded C controller"
  input Real T;
  input Real Tbar;
  input Real Q;
  output Real heat;
  external "C" annotation (Include="#include \"ComputeHeat.c\"",
    IncludeDirectory="modelica://ModelicaByExample.Functions.ImpureFunctions/source");
end computeHeat;

注意,在上述代码中同样也出现了external关键字。而与前面示例不同的是,external关键字后面没有相应C函数的名称。这就意味着,外部C函数与Modelica定义的函数有着完全相同的名字和参数。阅读上述C函数的源代码,我们会发现确实如此:

#ifndef _COMPUTE_HEAT_C_
#define _COMPUTE_HEAT_C_

#define UNINITIALIZED -1
#define ON 1
#define OFF 0

double
computeHeat(double T, double Tbar, double Q) {
  static int state = UNINITIALIZED;
  if (state==UNINITIALIZED) {
    if (T>Tbar) state = OFF;
    else state = ON;
  }
  if (state==OFF && T<Tbar-2) state = ON;
  if (state==ON && T>Tbar+2) state = OFF;

  if (state==ON) return Q;
  else return 0;
}

#endif

换言之,如果按上述方式定义外部C函数,在Modelica中定义的函数与C语言定义的函数之间就无需建立参数的映射关系,这样就可以省掉很多编程麻烦。

由上述代码可以看到,computeHeat函数中定义了static类型的变量statestatic关键字的使用表明变量state的值可以通过computeHeat函数在别的地方引用。这种类型的变量在嵌入式控制策略里非常普遍,因为需要将相应信息在不同调度函数内进行传递(例如在上述磁滞控制示例中)。

static类型变量的出现会引起潜在的问题,因为它意味着computeHeat函数对于相同的输入参数具有不同的返回值。从数学上来讲,这不是一个纯粹的数学函数。因为数学函数只依赖于其输入参数。在计算机科学领域,一般称上述函数为“非纯”函数。这也就意味着,每次调用上述函数其内存或变量都会产生相应的变化,从而影响函数的返回值。

在嵌入式控制策略设计阶段会遇到上述问题,在面向数学的环境中,例如Modelica环境,调用上述函数时需特别小心。因为,Modelica编译器默认所有的函数都是纯函数或者说无副作用的。如果出现了非纯函数或者说副作用,在仿真中就会出现问题。最好的结果是仿真效率非常低,最坏的结果是仿真结果完全不正确。

这种问题的出现,原因在于底层求解器在找到“正确”解前必须计算很多的“备选”解。如果生成的备选解需要求解器调用具有副作用的函数时,求解器将无法预测变量变更所造成的影响。

出于上述原因,computeHeat函数在定义时添加了impure限定词,如下所示:

impure function computeHeat "Modelica wrapper for an embedded C controller"
  input Real T;

以上述方式通知Modelica编译器这个函数具有副作用或返回值不仅仅依赖输入参数,而且当生成备选解时不能调用此函数。这样看起来会完全禁止其他函数调用上述函数。但其实并非如此。回顾上述要集成的控制策略:

equation
  when sample(0, 0.01) then
    Q = computeHeat(T, Tbar, Qcapacity);
  end when;
  C*der(T) = Q-h*(T-Tamb);
end HysteresisEmbeddedControl;

In particular, note that computeHeat is invoked only within the when statement and not as part of a “continuous” equation. As a result, we can be certain that computeHeat will only be invoked in response to an event but not when evaluating candidate solutions for the continuous variables.

仿真结果

在C语言定义的computeHeat函数中,实现了在设定点附近浮动+/-2度的算法,如下所示:

  if (state==OFF && T<Tbar-2) state = ON;
  if (state==ON && T>Tbar+2) state = OFF;

上述功能正正能有效地消除系统抖振。仿真结果中可以清晰的看到这点,如下图所示:

../../../_images/SIL.png