Выполнение модели в интерактивном режиме
|
Страница в процессе разработки. |
Интерактивный режим добавляет к обычному запуску модели двусторонний канал связи между Engee и рантаймом на целевой платформе. Через этот канал Engee получает значения выбранных сигналов и отправляет команды изменения настраиваемых параметров без повторной сборки и загрузки модели.
В текущей реализации пользовательских таргетов этот режим строится вокруг XCP slave, который встраивается в рантайм модели на целевой платформе.
Основные понятия
Логируемая точка — это сигнал или выходной порт модели, который пользователь выбрал для наблюдения во время выполнения. При генерации Си-кода Engee сохраняет информацию о таких точках в метаданных и формирует Си-данные, из которых рантайм может читать значения. В Python-представлении кодогенерации эти данные отражаются в CodeInfo и в упрощенном для шаблонов CodeGenInfo.logging_signals.
Настраиваемый параметр — это параметр модели, значение которого можно изменить после запуска. В сгенерированном Си-коде такие параметры обычно попадают в отдельную структуру параметров модели. В CodeGenInfo эта часть описана через tunable_params. Для интерактивного режима важно, чтобы соответствующая Си-структура была доступна в памяти целевого приложения и не была удалена оптимизатором.
Интерфейс целевой платформы — это физический или программный канал, по которому Engee обменивается данными с моделью. Это может быть UART, TCP, UDP, USB CDC, отладочный канал или другой транспорт. Сам факт наличия логируемых точек в модели не передает данные в Engee: нужен канал связи во время выполнения на целевой платформе.
ELF-файл — это собранный артефакт с символами, по которому Python-код таргета находит адреса сигналов и параметров в памяти целевого приложения. Для интерактивного режима итоговый .elf должен быть доступен на стороне клиента и из него не должна быть удалена информация таким образом, чтобы исчезли нужные символы.
Протокол XCP
XCP (Universal Measurement and Calibration Protocol) — это стандарт ASAM MCD-1 XCP для доступа к внутренним данным приложения во время его работы. Протокол обычно используют для измерения сигналов, калибровки параметров и отладки встраиваемых приложений без повторной сборки и перепрошивки после каждого изменения параметра.
В этом руководстве Engee выступает как XCP master, а рантайм модели на целевой платформе содержит XCP slave. Master управляет сеансом: подключается к slave, настраивает передачу сигналов, отправляет команды чтения и записи. Slave находится внутри целевого приложения, выполняет команды master и читает или изменяет память модели.
Официальное описание стандарта доступно по ссылке ASAM MCD-1 XCP.
Уровни XCP: протокол и транспорт
XCP разделяет логический протокол и транспортный уровень.
Протокольный уровень описывает, какие команды существуют и что они означают: подключение, чтение памяти, запись памяти, настройка DAQ, запуск и остановку передачи. Этот уровень не зависит от того, идет обмен по UART, TCP, UDP, CAN, USB или другому каналу.
Транспортный уровень описывает, как XCP-пакеты передаются по конкретному интерфейсу: как открыть соединение, как выделить границы пакета, какой максимальный размер кадра, есть ли подтверждения, как обрабатываются ошибки и таймауты.
Это означает, что нужно определить:
-
Какой XCP slave будет встроен в рантайм модели.
-
Через какой транспорт Engee будет обмениваться с этим slave.
В составе исполняемого файла протокольная часть XCP slave уже поставляется вместе с инфраструктурой таргета. Для новой платформы нужно выбрать или реализовать только транспорт и платформенный слой.
Master, slave и сеанс связи
XCP-сеанс начинается с подключения master к slave. После подключения master получает базовые возможности slave: поддерживаемый транспорт, размеры пакетов, доступные ресурсы и ограничения. Затем master может выполнять команды.
Для интерактивного режима Engee важны три группы команд:
-
служебные команды сеанса: подключиться, проверить состояние, завершить обмен;
-
команды доступа к памяти: прочитать значение сигнала или записать новое значение параметра;
-
команды DAQ: настроить списки измеряемых данных и запустить их регулярную передачу.
В классическом XCP slave не знает заранее, какие именно сигналы хочет наблюдать пользователь. Он предоставляет универсальный доступ к памяти, а master настраивает, какие адреса и размеры данных нужно читать. В Engee эти адреса берутся из .elf и метаданных кодогенерации.
Адресация данных
XCP работает с объектами целевого приложения через адреса памяти. Для Engee такими объектами являются:
-
значения логируемых сигналов;
-
настраиваемые параметры модели;
-
служебные структуры, нужные рантайм интерактивного режима выполнения.
Адрес, который видит master, должен быть корректно преобразован в указатель внутри slave. За это отвечает платформенный макрос XCP_ADDRESS_GET. На микроконтроллере без виртуальной памяти адрес часто можно использовать напрямую. На хост-платформах с PIE/ASLR адреса из .elf могут быть смещениями, поэтому платформенный слой должен учитывать базовый адрес загруженного образа.
Из-за этого для интерактивного режима выполнения важен .elf с символами: Python-код таргета должен найти адреса данных, а XCP slave должен уметь по этим адресам обратиться к памяти целевого приложения.
DAQ, ODT и события
DAQ (data acquisition) — это механизм XCP для потоковой передачи данных от slave к master. Master заранее настраивает состав отправляемых данных, а slave передает их при наступлении события.
DAQ-настройка строится из нескольких уровней:
-
DAQ list — список данных, который активируется по событию;
-
ODT (object descriptor table) — группа элементов, которые помещаются в один DTO-пакет;
-
ODT entry — один измеряемый объект: адрес, размер и дополнительные атрибуты;
-
event channel — событие рантайма, по которому slave должен собрать и отправить данные.
В модели Engee таким событием обычно является завершение шага модели. Рантайм вызывает rtExtModeUpload, и XCP slave отправляет данные.
При конфигурации XCP важно учитывать размеры:
-
сколько сигналов пользователь может записывать одновременно;
-
сколько байт помещается в один DTO-пакет;
-
сколько ODT-записей разрешено в одном ODT;
-
сколько DAQ-списков доступно на платформе;
-
нужен ли timestamp в DAQ-пакете и сколько байт он занимает.
Если лимиты слишком маленькие, часть сигналов не удастся включить в поток. Если лимиты слишком большие, XCP slave будет занимать больше RAM, что критично для маленьких MCU.
Запись параметров
Изменение параметра в интерактивном режиме — это запись нового значения в память целевого приложения. Master выбирает адрес параметра и отправляет данные записи. Slave проверяет команду и записывает байты в память.
Для корректной работы нужно, чтобы:
-
параметр действительно находился в памяти и не был удален оптимизатором;
-
размер и тип данных совпадали с информацией кодогенерации;
-
запись не происходила одновременно с небезопасным чтением из другого потока или обработчика прерываний (ISR);
-
рантайм модели был готов использовать новое значение на следующих шагах.
Если параметр является частью структуры модели, изменение обычно начинает влиять на расчет со следующего обращения модели к этой структуре. Если значение копируется в локальное состояние при инициализации, простая XCP-запись в исходный параметр может не дать ожидаемого эффекта.
Конфигурация таргета
Для настройки XCP нужно определить:
-
Транспорт: UART, TCP, UDP или другой канал.
-
Лимиты DAQ: количество списков, ODT и ODT entries.
-
Способ преобразования XCP-адреса в указатель.
-
Модель синхронизации: нужны ли мьютексы, где возможны потоки или обработчики прерываний (thread/ISR).
Общий поток данных
Интерактивный режим выполнения работает как цепочка из нескольких частей:
-
Engee генерирует Си-код модели и метаданные о сигналах, параметрах и точках входа.
-
Таргет генерирует рантайм-шаблон интерактивного режима.
-
В проект добавляются XCP slave и платформенный транспорт.
-
Тулчейн собирает артефакт и
.elfс символами модели. -
Python-код таргета разбирает
.elfи получает адреса логируемых точек и параметров. -
Engee подключается к XCP slave через выбранный интерфейс.
-
Планировщик модели выполняет step-функции и вызывает DAQ-события.
-
XCP slave читает значения из памяти и передает их в Engee.
-
При изменении параметра Engee отправляет команду записи, а XCP slave записывает новое значение в память модели.
Python-код таргета
Таргет с интерактивным режимом обычно наследуется от BaseTarget и XCPTarget. BaseTarget задает методы для управления работой модели, а 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*; -
сохранить исходники модели;
-
добавить общие исходники XCP slave через
get_xcp_slave_src_list; -
добавить платформенные файлы транспорта;
-
добавить нужные include paths и compile definitions;
-
обеспечить сборку
.elfс символами.
В start_model нужно подготовить связь Engee с уже собранной и запущенной моделью:
-
Настроить endpoint через
XCPTarget.set_endpoint. -
Найти итоговый
.elf. -
Получить данные о сигналах и параметрах через
get_engee_elf_info. -
Вызвать
XCPTarget.setup_streaming. -
Вернуть
TargetResponseсdata_stream_q,controll_stream_qиdiscovered_signals.
start_stream обычно вызывает XCPTarget._start_stream, change_param вызывает set_param, а stop_model останавливает поток через XCPTarget.stop_stream.
Пример схемы методов 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(
self,
elf_data,
self.model_settings,
)
return TargetResponse(
detail="start_model",
data_stream_q=data_q,
controll_stream_q=control_q,
discovered_signals=discovered,
)
async def start_stream(self, simulation_uuid: str) -> Result:
await XCPTarget._start_stream(self, simulation_uuid)
return Result.success()
async def change_param(self, block_key, param, data) -> TargetResponse:
await self.set_param(block_key, param, data)
return TargetResponse(detail="Param is changed")
async def stop_model(self) -> TargetResponse:
await XCPTarget.stop_stream(self)
return TargetResponse(detail="stop_model")
Рантайм-шаблон интерактивного режима выполнения
Рантайм интерактивного режима выполнения строится поверх планировщика независимого режима, но добавляет вызовы API адаптера интерактивного режима (external-mode adapter) из ext_work.h.
Минимальный порядок:
-
Инициализировать модель.
-
Вызвать
rtExtModeCheckInit. -
Дождаться команды старта через
rtExtModeWaitForStartPkt. -
В цикле выполнять step-функции модели.
-
После шага вызвать
rtExtModeUpload, чтобы опубликовать DAQ-событие. -
На каждом шаге вызвать
rtExtModeOneStep, чтобы обработать команды master. -
При запросе остановки вызвать
terminate. -
При штатном завершении вызвать
rtExtModeShutdown, если платформа поддерживает корректное завершение (clean shutdown).
Важно, чтобы rtExtModeOneStep вызывался регулярно. Если планировщик надолго блокируется в драйвере, ожидании периферии или sleep-функции, Engee будет хуже получать данные и команды изменения параметров.
Пример схемы рантайм-цикла
#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);
XCP slave в Engee.Интеграции
Общий XCP slave находится в составе поставляемого исполняемого файла engee-device-manager. Python-код таргета добавляет его в проект через get_xcp_slave_src_list, а платформенный слой добавляет отдельно.
Структура XCP slave:
-
include/ext_work.h— API, который вызывает рантайм-шаблон модели; -
src/ext_work.c— адаптер междуrtExtMode*и XCP slave; -
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; -
include/xcp_config.h— compile-time конфигурация XCP slave; -
include/rtiostream.h— интерфейс транспортного ввода-вывода.
Основные функции rtExtMode*:
-
rtExtModeCheckInit— инициализирует XCP slave и открывает транспорт; -
rtExtModeWaitForStartPkt— ждет команду старта от master; -
rtExtModeUpload— публикует DAQ-событие; -
rtExtModeOneStep— выполняет один неблокирующий проход обработки команд; -
rtExtModePauseIfNeeded— обслуживает команды в состоянии паузы; -
rtExtModeShutdown— сбрасывает состояние slave и закрывает рантайм; -
rtExtModeParseArgsиrtERTExtModeStartMsgоставлены для совместимости с генерируемым API интерактивного режима (external-mode).
Определения компиляции для 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_LISTS— максимум DAQ-списков; -
XCP_DAQ_TIMESTAMP_SIZE— размер timestamp в DAQ-пакете; -
XCP_DAQ_DATA_SIZE— максимум полезных DAQ-данных в одном DTO; -
XCP_MAX_SHORT_DOWNLOAD_DATA_LEN— максимум данных для записи параметра одной командой; -
XCP_MAX_CTO_LEN— размер пакета команд/ответов CTO; -
XCP_MAX_DTO_LEN— размер пакета передачи данных DTO.
Для маленьких MCU эти значения можно уменьшать, но после этого нужно проверить, что выбранные логируемые точки помещаются в DAQ-лимиты.
Пользовательская платформа
Если сборка XCP slave выполняется не на готовом x86-слое, нужно включить -DXCP_CUSTOM_PLATFORM и предоставить xcp_platform_custom.h.
Минимальный набор требований к платформе включает реализацию следующих макросов и директив:
-
XCP_PRINTF— вывод диагностики или пустой макрос; -
XCP_MUTEX_DEFINE; -
XCP_MUTEX_INIT; -
XCP_MUTEX_LOCK; -
XCP_MUTEX_UNLOCK; -
XCP_ADDRESS_GET; -
XCP_SLEEP; -
макросы упаковки/выравнивания:
XCP_PRAGMA_PACK_BEGIN,XCP_PRAGMA_PACK_END,XCP_ATTRIBUTE_ALIGNED,XCP_ATTRIBUTE_PACKED.
Пример базового 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_ADDRESS_GET — критически важный макрос. Он преобразует адрес из XCP-команды в реальный указатель в адресном пространстве целевого приложения.
Для микроконтроллеров без виртуальной памяти обычно достаточно прямого преобразования адреса в указатель. Для хост-приложений с PIE/ASLR адрес из ELF-файла может быть задан как смещение (offset), и к нему нужно прибавлять базовый адрес загруженного образа. Это уже реализовано в ritm-xcpslave/platform_x86.
Мьютексы зависят от модели выполнения:
-
если весь XCP slave вызывается только из одного основного цикла, критические секции могут быть пустыми;
-
если ввод-вывод, DAQ или планировщик работают из разных задач, потоков или обработчиков прерываний, нужна реальная блокировка;
-
блокировка не должна надолго приостанавливать выполнение базового шага модели.
Пользовательский транспорт
Транспорт должен реализовать rtIOStream 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);
Правила для rtIOStreamSend:
-
возвращайте
RTIOSTREAM_NO_ERROR, если транспорт исправен; -
если сейчас нельзя отправить данные, установите
*sizeSent = 0; -
не подтверждайте частичную отправку кадра, если выбранный слой кадрирования не поддерживает частичные отправки;
-
при реальной ошибке транспорта возвращайте
RTIOSTREAM_ERROR.
Правила для rtIOStreamRecv:
-
функция должна быть неблокирующей или иметь короткий временной лимит;
-
если данных нет, установите
*sizeRecvd = 0и вернитеRTIOSTREAM_NO_ERROR; -
можно вернуть меньше байтов, чем запрошено: парсер кадров дочитает пакет следующими вызовами;
-
при ошибке транспорта верните
RTIOSTREAM_ERROR.
Для UART текущий формат кадра использует стартовый байт 0xFF, длину данных (payload), данные и 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.cилиxcp_frame_udp.c; -
платформенный
xcp_platform_custom.hили готовыйplatform_x86; -
реализация
rtIOStreamOpen,rtIOStreamSend,rtIOStreamRecv,rtIOStreamClose; -
при необходимости свои
rtwtypes.hиext_mode_types.h.
ELF и символы
Для интерактивного выполнения Engee должен сопоставить логируемые точки и параметры модели с адресами в памяти целевого приложения. Для этого Python-код таргета использует .elf и get_engee_elf_info.
На практике необходимо обеспечить следующее:
-
итоговый
.elfдолжен быть доступен на стороне клиента; -
символы сигналов и параметров не должны быть удалены посредством
strip; -
оптимизатор не должен удалить структуры, которые нужны для логирования и изменения параметров;
-
слой структур должен соответствовать информации из
CodeInfo; -
для нестандартной архитектуры нужно проверить разбор ELF, размер указателя и правила адресации.
Проверочный список
-
В модели есть логируемая точка и хотя бы один проверочный сигнал.
-
В проект добавлен XCP slave.
-
Выбран один транспорт: UART, TCP или UDP.
-
Определена компиляция транспорта и платформы.
-
Реализован или выбран
xcp_platform_custom.h. -
Реализован
rtIOStreamдля выбранного интерфейса. -
Рантайм-шаблон вызывает
rtExtModeCheckInit,rtExtModeWaitForStartPkt,rtExtModeUpload,rtExtModeOneStepиrtExtModeShutdown. -
.elfсодержит нужные символы и доступен Python-таргету. -
XCPTarget.setup_streamingвозвращает очереди и список найденных сигналов. -
На демо-модели проверены старт, чтение сигнала, изменение параметра и останов.