AnyMath 文档

交互式执行模型

交互模式为模型的正常启动增加了双向通信通道。 AnyMath 和目标平台上的运行时。 通过这个渠道 AnyMath 接收所选信号的值并发送命令以更改配置的参数,而无需重新组装和加载模型。

在用户目标的当前实现中,此模式是围绕_XCP slave_构建的,它嵌入在目标平台上模型的运行时中。

基本概念

逻辑点是用户在执行过程中选择要监视的模型的信号或输出端口。 生成C代码时 AnyMath 在元数据中存储有关此类点的信息,并生成运行时可以从中读取值的Si数据。 在代码生成的Python表示中,这些数据反映在 代码信息 并以简化的方式为模板 CodeGenInfo。logging_signals.

可配置参数是模型参数,其值可以在启动后更改。 在生成的C代码中,此类参数通常落入模型参数的单独结构中。 [医]代码信息 这部分通过描述 可调参数. 对于交互模式,重要的是相应的C结构在目标应用程序的内存中可用,并且不会被优化器删除。

目标平台接口是一个物理或编程通道,通过该通道 AnyMath 与模型交换数据。 它可以是UART,TCP,UDP,USB CDC,调试通道或其他传输。 模型中存在可记录点这一事实并不会将数据传输到 AnyMath :在目标平台上需要一个运行时通信通道。

ELF文件是一个组装的工件,其中包含目标的Python代码用来查找目标应用程序内存中信号和参数的地址的符号。 对于交互式摘要模式 .精灵 它必须在客户端可用,并且不能从中删除有关必要符号的信息。

XCP协议

Xcp(通用测量和校准协议)是ASAM MCD-1XCP标准,用于在其运行期间访问内部应用数据。 该协议通常用于测量信号,校准参数和调试嵌入式应用程序,而无需在每次参数更改后重新组装和闪烁。

在本指南中 AnyMath 充当_XCP master_,目标平台上模型的运行时包含_XCP slave_。 主站管理会话:连接从站,配置信号传输,发送读写命令。 从站驻留在目标应用程序内部,执行主命令,并读取或修改模型的内存。

该标准的官方描述可在链接中找到 ASAM MCD-1XCP

XCP层:协议和传输

XCP将逻辑协议和传输层分开。

协议层描述了存在哪些命令以及它们的含义:连接,读取和写入内存,配置DAQ,启动和停止传输。 此级别不依赖于交换是通过UART、TCP、UDP、CAN、USB还是其他通道。

传输层描述了XCP数据包如何通过特定接口传输:如何打开连接,如何突出显示数据包边界,最大帧大小是多少,是否有确认,如何处理错误和超时。

这意味着您需要定义:

  1. 哪个XCP从站将嵌入到模型的运行时中。

  2. 什么样的交通工具 AnyMath 将与这个奴隶交换。

作为可执行文件的一部分,XCP slave的协议部分已经随目标基础结构一起提供。 对于新平台,您只需要选择或实现传输和平台层。

主、从和通信会话

XCP会话从主机到从机的连接开始。 连接后,主机将获得从机的基本功能:支持的传输、数据包大小、可用资源和限制。 然后,主控器可以执行命令。

对于交互模式 AnyMath 三组团队很重要:

*会话服务命令:连接、检查状态、完成交换; *内存访问命令:读取信号值或写入新的参数值; *DAQ命令:设置测量数据列表并开始定期传输。

在经典的XCP中,从站事先不知道用户想要观察哪些信号。 它提供对内存的通用访问,并且主机调整需要读取的地址和数据大小。 在 AnyMath 这些地址取自 .精灵 和代码生成元数据。

数据寻址

XCP通过内存地址处理目标应用程序对象。 为 AnyMath 这些对象是:

*记录信号的值; *可配置模型参数; *交互式执行模式运行时所需的服务结构。

主机看到的地址必须正确转换为从机内部的指针。 平台宏对此负责。 XCP_地址_GET. 在没有虚拟存储器的微控制器上,地址通常可以直接使用。 在具有PIE/ASLR地址的主机平台上 .精灵 可能存在偏移,因此平台层必须考虑到上传图像的基地址。

因此,交互式执行模式很重要 .精灵 带符号:目标的Python代码必须找到数据地址,XCP从站必须能够使用这些地址访问目标应用程序的内存。

DAQ、ODT和事件

DAQ(数据采集)是一种XCP机制,用于将数据从从机流式传输到主机。 主站预先配置正在发送的数据的组成,从站在事件发生时进行传输。

DAQ设置由几个级别组成: *DAQ列表-由事件激活的数据列表; *ODT(对象描述符表—-适合单个DTO包的一组元素; *ODT条目-一个可测量的对象:地址、大小和附加属性; *事件通道-从站必须收集和发送数据的运行时事件。

在模型中 AnyMath 这样的事件通常是一个模型步骤的完成。 运行时原因 rtExtModeUpload,并由XCP从机发送数据。

配置XCP时,重要的是要考虑尺寸:

*用户可以在同一时间记录多少信号; *一个DTO数据包可以容纳多少字节; *一个ODT中允许有多少个ODT记录; *平台上有多少DAQ列表可用; *DAQ数据包中是否需要时间戳以及需要多少字节。

如果限制太小,一些信号将无法包含在流中。 如果限制太大,XCP从机将占用更多RAM,这对于小型Mcu至关重要。

记录参数

以交互方式更改参数是将新值写入目标应用程序的内存。 主控选择参数地址并发送记录数据。 从站检查命令并将字节写入内存。

为了正确的操作,有必要:

*参数实际上在内存中,并没有被优化器删除; *与代码生成信息匹配的数据大小和类型; *写入与从另一个线程或中断处理程序(ISR)进行的不安全读取不同时发生; *模型的运行时已准备好在接下来的步骤中使用新值。

如果参数是模型结构的一部分,则更改通常从模型下次访问此结构时开始影响计算。 如果在初始化期间将值复制到本地状态,则对源参数的简单XCP写入可能没有预期的效果。

目标配置

要配置XCP,您需要定义:

  1. 传输:UART,TCP,UDP或其他通道。

  2. DAQ限制:列表、Odt和ODT元素的数量。

  3. 将XCP地址转换为指针的方法。

  4. 同步模型:是否需要互斥锁,其中线程或中断处理程序(线程/ISR)是可能的。

一般数据流

交互式执行模式作为一个由几个部分组成的链:

  1. AnyMath 生成模型的C代码和有关信号、参数和入口点的元数据。

  2. 目标生成交互模式的运行时模板。

  3. XCP从站和平台传输正在添加到项目中。

  4. 工具链收集工件和 .精灵 与模型的符号。

  5. Target的Python代码解析 .精灵 并获取记录点和参数的地址。

  6. AnyMath 通过所选接口连接到XCP从站。

  7. 模型的调度程序执行step函数并调用DAQ事件。

  8. XCP从站从内存中读取值并将它们传递给 AnyMath .

  9. 更改参数时 AnyMath 发送写命令,XCP从站将新值写入模型的内存。

Python-目标代码

交互模式目标通常继承自 基本目标XCPTarget的. 基本目标 设置管理模型操作的方法,以及 XCPTarget的 提供Python交换代码:配置端点、创建流队列、启动数据读取和更改参数。

基本继承的一个例子

from targets.base_target import BaseTarget
from targets.xcp_target.xcp_target import XCPTarget


class MyTarget(BaseTarget, XCPTarget):
    def __init__(self) -> None:
        XCPTarget.__init__(self)
        ...

generate_executable_code 我们需要准备一个交互模式草案。:

检查 model_settings.is_ext_mode; *选择具有调用的运行时模板 rtExtMode,rtExtMode; *保存模型的源代码; *通过添加共享XCP从源 get_xcp_slave_src_list; *添加平台传输文件; *为头文件和编译定义添加必要的路径; *提供装配 .精灵 用符号。

start_model的 我们需要准备一个连接 AnyMath 已组装并运行的模型:

  1. 通过以下方式设置端点 XCPTarget的。set_endpoint.

  2. 找到最终结果 .精灵.

  3. 通过以下方式获取有关信号和参数的数据 get_engee_elf_info.

  4. 打电话 XCPTarget的。setup_流.

  5. 回来吧 目标反应数据_stream_q, controll_stream_q发现_信号. start_stream 通常原因 XCPTarget的。_start_stream, change_param 原因 set_param,而 停止模型 停止流过 XCPTarget的。停止流.

目标的Python代码的方法方案的一个例子

from targets.xcp_target.engee_elf_info import get_engee_elf_info
from targets.xcp_target.xcp_target import get_xcp_slave_src_list


async def start_model(self, model: EngeeModel) -> TargetResponse:
    XCPTarget.set_endpoint(self, interface="uart", url=self.target_block.com_port)

    elf_data = get_engee_elf_info(
        elf_path=self.elf_path,
        code_info=self.code_info,
    )

    data_q,control_q,discovered=await XCPTarget。setup_streaming(
        自我,
        elf_数据,
        自我。model_settings,
    )

    返回目标响应(
        细节="start_model",
        data_stream_q=data_q,
        control_stream_q=control_q,
        discovered_signals=发现,
    )


async def start_stream(self,simulation_uuid:str)->结果:
    等待XCPTarget。_start_stream(self,simulation_uuid)
    返回结果。成功()


async def change_param(self,block_key,param,data)->TargetResponse:
    等待自我。set_param(block_key,param,data)
    返回TargetResponse(detail="Param已更改")


async def stop_model(self)->TargetResponse:
    等待XCPTarget。stop_stream(self)
    返回TargetResponse(detail="stop_model")

运行时-交互式执行模式模板

交互式执行模式运行时构建在独立模式调度程序之上,但将API调用添加到交互式模式适配器(external-mode adapter ext_work。h.

最低订购量:

  1. 初始化模型。

  2. 打电话 [医][医][医][医][医][医].

  3. 等待启动命令通过 rtExtModeWaitForStartPkt.

  4. 在循环中执行模型的阶跃函数。

  5. 呼叫步骤后 rtExtModeUpload 发布DAQ事件。

  6. 在每一步,调用 rtExtModeOneStep 来处理主命令。

  7. 请求停止时,呼叫 终止;终止.

  8. 正常完成后,致电 rtExtModeShutdown 如果平台支持正确关闭(干净关闭)。

重要的是 rtExtModeOneStep 他经常被叫来. 如果调度程序在驱动程序中阻塞了很长时间,同时等待外设或睡眠功能, AnyMath 它将接收具有延迟的参数更改的数据和命令。

运行时循环方案的示例

#include "ext_work.h"

RTWExtModeInfo ext_mode_info;
boolean_T stop_requested = false;

model_initialize();

rtExtModeCheckInit(1);
rtExtModeWaitForStartPkt(&ext;_mode_info, 1, &stop;_requested);
rtERTExtModeStartMsg();

while (!stop_requested) {
    run_due_model_steps();
    rtExtModeUpload(0, getCurrentTimestamp());
    rtExtModeOneStep(&ext;_mode_info, 1, &stop;_requested);
    wait_until_next_tick();
}

model_terminate();
rtExtModeShutdown(1);

在Engee的xcp奴隶。综合服务

共享的XCP从站包含在提供的可执行文件中。 工程师-设备经理. 目标的Python代码通过以下方式将其添加到项目中 get_xcp_slave_src_list,并分别添加平台层。

XCP从属结构:

  • 包括/ext_work。h -调用模型运行时模板的API;

  • src/ext_work。c -之间的适配器 rtExtMode,rtExtMode* 和XCP奴隶;

  • src/xcp_daq.c, src/xcp_daq.h —XCP命令、DAQ列表、读取信号和写入参数的处理;

  • src/xcp_frame/xcp_frame_uart。c -uart框架;

  • src/xcp_frame/xcp_frame_tcp。c -tcp的成帧;

  • src/xcp_frame/xcp_frame_udp。c -UDP成帧;

  • 包括/xcp_config。h -XCP slave在编译阶段的配置;

  • 包括/rtiostream。h -传输I/O接口。

主要功能 rtExtMode,rtExtMode*:

  • [医][医][医][医][医][医] -初始化xcp从站并打开传输;

  • rtExtModeWaitForStartPkt -等待主人的启动命令;

  • rtExtModeUpload -发布DAQ事件;

  • rtExtModeOneStep -执行一个非阻塞命令处理通行证;

  • 需要的;需要的 -在暂停状态下提供命令;

  • rtExtModeShutdown -重置从状态并关闭运行时;

  • [医][医][医][医][医][医]rtERTExtModeStartMsg的 左为与生成的交互模式API(外部模式)的兼容性。

Xcp的编译定义

必须在项目中选择运输。:

-DXCP_UART_TRANSPORT

-DXCP_TCP_TRANSPORT

-DXCP_UDP_TRANSPORT

对于嵌入式平台,他们通常会添加:

-DXCP_CUSTOM_PLATFORM

您可以为x86主机平台使用现成的平台层。:

-DXCP_PLATFORM_X86

xcp_config。h 它还包含可定制的尺寸。:

  • XCP_MAX_ODT_ENTRIES_COUNT -一个ODT中的最大元素;

  • XCP_MAX_DAQ_列表 -DAQ列表的最大值;

  • XCP_DAQ_TIMESTAMP_SIZE -DAQ包中时间戳的大小;

  • XCP_DAQ_DATA_SIZE -一个DTO中最大有用的DAQ数据;

  • XCP_MAX_SHORT_DOWNLOAD_DATA_LEN -使用单个命令记录参数的最大数据;

  • XCP_MAX_CTO_LEN -CTO命令/响应包的大小;

  • XCP_MAX_DTO_LEN -dto数据传输包的大小。

对于小型Mcu,可以减少这些值,但之后您需要检查所选日志点是否符合DAQ限制。

用户平台

如果xcp从属构建未在现成的x86层上运行,则需要启用 -DXCP_CUSTOM_PLATFORM 并提供 xcp_platform_custom。h.

最低平台要求集包括以下宏和指令的实现:

  • XCP_PRINTF -诊断输出或空宏;

  • XCP_MUTEX_DEFINE;

  • XCP_MUTEX_INIT;

  • XCP_MUTEX_LOCK;

  • XCP_MUTEX_UNLOCK;

  • XCP_地址_GET;

  • XCP_睡眠; *包装/对齐宏: XCP_PRAGMA_PACK_BEGIN, XCP_PRAGMA_PACK_END, XCP_ATTRIBUTE_ALIGNED, XCP_ATTRIBUTE_打包.

基本示例 xcp_platform_custom。h

#ifndef XCP_PLATFORM_CUSTOM_H
#define XCP_PLATFORM_CUSTOM_H

#include <stdint.h>
#include "rtwtypes.h"

#define XCP_PRINTF(...)

#define XCP_MUTEX_DEFINE(lock)
#define XCP_MUTEX_INIT(lock)
#define XCP_MUTEX_LOCK(lock)
#define XCP_MUTEX_UNLOCK(lock)

#define XCP_ADDRESS_GET(addressExtension, address) \
    ((uint8_T *)((uintptr_t)(address)))

#define XCP_SLEEP(seconds, microseconds)  platform_sleep(seconds, microseconds)

#define PRAGMA(n) _Pragma(#n)
#define XCP_PRAGMA_PACK_BEGIN(n) PRAGMA(pack(push, n))
#define XCP_PRAGMA_PACK_END()    PRAGMA(pack(pop))
#define XCP_ATTRIBUTE_ALIGNED(n)
#define XCP_ATTRIBUTE_PACKED

#endif

XCP_地址_GET -关键任务宏。 它将xcp命令中的地址转换为目标应用程序地址空间中的真实指针。

对于没有虚拟内存的微控制器,直接地址到指针转换通常就足够了。 对于PIE/ASLR主机应用程序,来自ELF文件的地址可以设置为偏移量,并且必须将下载的映像的基地址添加到其中。 这已在 ritm-xcpslave/平台_x86.

互斥锁取决于执行模型:

*如果仅从一个主循环调用整个XCP从站,则关键部分可能为空。; *如果I/O、DAQ或调度程序从不同的任务、线程或中断处理程序运行,则需要真正的锁。; *阻塞不应永久暂停模型基本步骤的执行。

海关运输

传输必须实现API [医]流星雨:

int rtIOStreamOpen(int argc, void *argv[]);
int rtIOStreamSend(int streamID, const void *src, size_t size, size_t *sizeSent);
int rtIOStreamRecv(int streamID, void *dst, size_t size, size_t *sizeRecvd);
int rtIOStreamClose(int streamID);

规则 [医][医][医][医]:

*返回 RTIOSTREAM_NO_ERROR 如果运输工作正常; *如果您现在无法发送数据,请安装 *sizeSent=0 *如果所选成帧层不支持部分发送,请勿确认部分帧发送。; *在真正的运输错误的情况下,返回 RTIOSTREAM_ERROR.

规则 [医][医][医][医]:

*该功能必须是非阻塞的或具有较短的时间限制。; *如果没有数据,请安装 *sizeRecvd=0 还回去 RTIOSTREAM_NO_ERROR; *返回的字节比请求的少:帧解析器将通过以下调用完成数据包的读取; *在传输错误的情况下,返回 RTIOSTREAM_ERROR.

对于UART,当前帧格式使用起始字节。 0xFF,有效载荷的长度,数据本身和XOR CRC。 UART传输必须在没有回声、文本处理或行尾字符转换的情况下传输字节.

复制到项目的内容

对于交互式执行项目,您需要:

  • ritm-xcpslave/include/*。h;

  • ritm-xcpslave/src/ext_work。c;

  • ritm-xcpslave/src/xcp_daq。c;

  • ritm-xcpslave/src/xcp_daq。h;

  • ritm-xcpslave/src/xcp_frame/xcp_frame。h; *一个帧处理程序文件: xcp_frame_uart。c, xcp_frame_tcp。cxcp_frame_udp。c; *基于平台的 xcp_platform_custom。h 或现成的 平台_x86; *实施 [医][医][医][医][医], [医][医][医], [医][医][医], [医][医][医][医][医]; *如有必要,您自己的 rtwtypes。hext_mode_types。h.

精灵和符号

用于交互式执行 AnyMath 必须将记录的点和模型参数与目标应用程序内存中的地址相匹配。 为此,目标的Python代码使用 .精灵get_engee_elf_info.

在实践中,有必要确保以下内容:

*决赛 .精灵 必须在客户端可用; *信号和参数符号不应被删除 条带; *优化器不应删除日志记录和更改参数所需的结构。; *结构层必须与来自 代码信息; *对于非标准架构,您需要检查ELF解析,指针大小和寻址规则。

清单

  1. 模型具有逻辑点和至少一个验证信号。

  2. XCP slave已添加到项目中。

  3. 选择一种传输方式:UART、TCP或UDP。

  4. 传输和平台的编译定义。

  5. 实施或选择 xcp_platform_custom。h.

  6. 已实施 [医]流星雨 为所选择的接口。

  7. 运行时模板导致 [医][医][医][医][医][医], rtExtModeWaitForStartPkt, rtExtModeUpload, rtExtModeOneSteprtExtModeShutdown.

  8. .精灵 它包含必要的字符,并且可用于Python目标。

  9. XCPTarget的。setup_流 返回队列和找到的信号列表。

  10. 在演示模型上测试了启动、信号读取、参数变化和停止.