AnyMath 文档

独立模式下的模型执行

本文介绍了独立执行模式的运行时:模型的c函数在生成、组装和加载后在目标平台上的正确调用。 这里不考虑交互式执行。

从模型生成C代码

在目标开始工作之前, AnyMath 生成模型的C代码。 此数据为目标输入。

生成的模型C代码包含实现模型计算的函数和结构:

*初始化函数; *一个或多个需要以指定周期调用的step函数; *完成功能; *模型的源文件; *关于采样周期、信号、参数和模型入口点的元数据。

目标不再生成模型的内容部分。 他的任务是将已经生成的C代码嵌入到目标平台的运行时中。 在这种情况下,运行时是围绕模型的绑定:入口点、步骤规划器、计时器操作、驱动程序、构建文件以及(如果需要交互模式)数据交换通道。

例如,对于 Arduino目标创建一个草图 STM32是围绕STM32CubeMX和HAL代码的项目,用于 x86是一个常规的可执行应用程序。

执行设置在哪里?

Python目标类创建一个项目,但没有定义模型在每个时间步骤的行为。 此行为由目标在其中生成的运行时代码设置 generate_executable_code.

运行时代码是围绕生成的模型的c/C++绑定。 它通常包括:

*程序或固件的入口点; *平台定时器的初始化; *调用模型初始化函数; *以所需频率调用模型步进函数的调度程序; *检查模拟的结束时间; *调用模型完成函数; *处理步骤执行时间违规。

大多数情况下,运行时代码是从Jinja2模板创建的。 Jinja2不是一个要求:您可以使用任何其他模板引擎或以编程方式生成文件。 但现有的目标使用Jinja2,辅助功能 render_template_to_file 它已经是专门为Jinja2模板设计的。

模型入口点

生成C代码后 AnyMath 为目标提供模型入口点的描述。 入口点是运行时必须在正确的时刻调用的C函数。

在文章中 代码生成器功能这是从生成的C接口的一侧描述的:外部代码连接 modelname.h,调用初始化函数一次,然后周期性地调用所需步骤的步骤函数,完成后调用完成函数。 Target做同样的事情,但不是手动的:它生成在目标平台上执行这些调用的运行时模板。

入口点的主要类型:

  • 初始化 -初始化模型状态,在第一步之前调用一次;

  • 步骤 -计算模型的步骤函数列表;

  • 终止;终止 -模型的完成,在定期停止调用,如果它可用于平台。

目标通过 [医]代码信息,这是从创建 model_code。c_code。c_code_info.

接收示例 [医]代码信息

codegen_info = create_codegen_info(
    model_code.c_code.c_code_info,
    model_settings.is_ext_mode,
)

以下字段通常传递给运行时模板:

  • model_name -模型和主头文件的名称;

  • 初始化。cname,cname -Si初始化函数的名称;

  • 步骤 -步骤功能列表;

  • 步。cname,cname -特定步骤函数的名称;

  • 步。base_rate_scale -步长周期与基期的比率;

  • 终止。cname,cname -c完成函数的名称;

  • 基准/基准 -基期以秒为单位;

  • 基础,基础 -以微秒为单位的基期;

  • 停止时间停止时间 -最后的模拟时间,如果指定。

基本调度程序

独立执行调度器必须重复一个基本周期。:

  1. 记当前基本步骤的开始时间。

  2. 调用该基本步骤应执行的所有步骤函数。

  3. 增加基步计数器。

  4. 检查是否已达到结束模拟时间。

  5. 等待下一个基本步骤或修复违反执行时间的问题。

主要规则是针对单个基本步进计数器检查所有步进函数。 如果功能有 base_rate_scale==1 它在每个基本步骤中都被调用。 如果 base_rate_scale==10,然后在每十个基本步骤调用该函数。

最小Jinja2调度程序模板的示例

#include "{{ model_name }}.h"

static uint64_t step_number = 0;
static const uint32_t base_rate_us = {{ base_rate_us }};

int主(无效)
{
    平台_timer_init(base_rate_us);
    {{ init.cname }}();

    虽然(1){
        uint32_t step_start_us=platform_time_us();

{% for step in steps %}
        如果((step_number % {{ step.base_rate_scale }}ULL)==0ULL){
            {{ step.cname }}();
        }
{% endfor %}

        step_number++;

{% if stop_time %}
        if((step_number*base_rate_us)> {{ stop_time_us }{ULL){
            {{ terminate.cname }}();
            休息;
        }
{% endif %}

        platform_wait_until_next_tick(step_start_us,base_rate_us);
    }

    返回0;
}

多个步骤函数

一个模型可以有一个或多个阶跃函数。 当模型具有多个计算步长值(样本时间)时,将使用多个步长函数。 运行时应使用其周期调用每个函数,但根据最快的基周期进行同步。

阶跃函数的数量取决于多频码生成的设置。 在单任务版本中,生成器将仅创建一个阶跃函数,其中已包含较慢频率的条件。 在多任务版本中,生成器创建几个阶跃函数,每个阶跃函数都对应于自己的采样频率;然后外部目标绑定必须以正确的周期触发其中的每个阶跃函数。

对于目标必须遵循以下规则:而不是假设模型中的单个步骤,使用列表 CodeGenInfo。步骤. 如果有一个元素,则执行一个步骤;如果有多个元素,则运行时顺序处理所有元素,将其应用于每个元素 base_rate_scale.

例如:

*基期为1毫秒; *步骤A有 base_rate_scale=1 它被称为每1毫秒; *步骤B有 base_rate_scale=10 它被称为每10毫秒。; *步骤C有 base_rate_scale=100 它被称为每100毫秒。

从调用步骤函数的顺序 CodeGenInfo。步骤. 函数不能以任何其他顺序调用。

不能只调用第一步函数。 这样的运行时间仅适用于最简单的模型,并且会导致具有多个采样步骤的模型出现错误。

调用多个步骤的示例

for (;;) {
    if ((step_number % 1ULL) == 0ULL) {
        model_step_1ms();
    }

    if ((step_number % 10ULL) == 0ULL) {
        model_step_10ms();
    }

    if ((step_number % 100ULL) == 0ULL) {
        model_step_100ms();
    }

    step_number++;
    wait_until_next_1ms_tick();
}

模板字典扩展

[医]代码信息 它包含大多数运行时模板所需的常规数据。 但特定平台通常需要额外的参数:定时器模式,电路板名称,布局文件路径,CPU频率,堆栈大小,RTOS任务设置。

在这种情况下,您可以展开传递给Jinja2模板的字典。 他们通常采取 codegen_info。model_dump(),添加平台字段并将结果传递给 render_template_to_file.

向模板上下文添加edm-Target块参数的示例

codegen_info = create_codegen_info(
    model_code.c_code.c_code_info,
    model_settings.is_ext_mode,
)

context = codegen_info.model_dump()
context.update(
    {
        "cpu_frequency_hz": self.target_block.cpu_frequency_hz,
        "timer_prescaler": self.target_block.timer_prescaler,
        "linker_script": self.target_block.linker_script,
    }
)

render_template_to_file(
    self.main_template,
    self.project_path / "src" / "main.c",
    context,
)

在模板中使用附加字段的示例

#define CPU_FREQUENCY_HZ {{ cpu_frequency_hz }}UL
#define TIMER_PRESCALER  {{ timer_prescaler }}UL

extern const char linker_script_name[] = "{{ linker_script }}";

如果不使用Jinja2,原理保持不变:首先,形成关于模型和平台的结构化数据集,然后基于它创建运行时和构建文件。

TET和步骤时间违规

Tet(任务执行时间)是一个基本运行时步骤的执行时间。 通常,tet从基本步骤开始到完成所有必要步骤功能和开销处理的时刻进行测量。

对于独立执行模式,重要的是tet小于或等于模型的基期。:

TET <= base_rate_us

如果TET长于基期,则运行时将没有时间以指定的速度完成模型。 此条件通常称为_overrun_。

违反TET使模型工作不正确:

*离散块开始执行的频率低于模型中指定的频率; *时间延迟、滤波器、调节器和信号发生器接收不正确的时间网格; *缓慢的计算步骤可能会随着相对于预期时间的漂移而触发; *外部设备接收命令的时间比模型建议的晚; *累积延迟会使仿真结果变得不可预测。

为了防止违反TET,建议:

  1. 在每个基本步骤测量TET。

  2. 设置标志 超负荷_flagTet>base_rate_us.

  3. 如果平台未明确设计用于追赶,请勿在批处理中执行跳过的步骤。

  4. 记录所选处理策略:继续执行、停止模型、跳过步骤或仅诊断指示。 TET测量示例

uint32_t step_start_us = platform_time_us();

run_due_model_steps();

uint32_t step_end_us = platform_time_us();
uint32_t tet_us = step_end_us - step_start_us;

if (tet_us > base_rate_us) {
    overrun_flag = true;
} else {
    overrun_flag = false;
    platform_sleep_us(base_rate_us - tet_us);
}

在裸机和RTOS平台上,最好使用具有足够分辨率的计时器测量TET。 如果模型的基周期为1ms,则分辨率为1ms的计时器对于诊断来说已经太粗糙了:它不会显示运行时距离违反周期有多近。

模型时间和时间戳

运行时必须区分平台的物理时间和模型时间。

平台的物理时间取自硬件定时器、RTOS tick或系统时钟,用于等待下一步和测量TET。

模型时间通常从步进计数器计算:

model_time = step_number * base_rate

这足以检查成就。 停止时间 时独立运行,并针对需要知道当前车型时间的用户驾驶员。

基于模型时间计算的时间戳示例

uint32_t getCurrentTimestampUs(void)
{
    return step_number * base_rate_us;
}

double getCurrentTimestamp(void)
{
    return step_number * base_rate;
}

新平台选择什么

在实现运行时模板之前修复解决方案。:

*哪个时间源设置基期; *执行步骤函数的位置:在主循环中、在中断中、在RTOS任务中或在单独的进程中; *什么计时器用于测量TET; *超限期间会发生什么; *停止是如何实现的; *它叫吗? 终止;终止 在正常完成; *step函数提供哪些平台驱动程序; *模板上下文中是否需要附加字段。

对于目标的第一个版本,简单的主循环或周期性任务通常就足够了。 最主要的是正确调用所有的步骤函数使用他们的 base_rate_scale,承受基期并明确处理TET违规。