PIL 基本介绍
在航空和汽车等安全攸关的行业,如果采用基于模型的设计方法论(MBD),需要额外引入背靠背测试的概念,具体来说,使用模型开发的过程中,背靠背测试包含 SIL(Software-in-the-Loop)和 PIL(Processor-in-the-Loop)两种。其中 PIL 测试是验证模型和目标码(Object code,即编译链接之后的产物)等效性的有效手段,也是诸如 DO178C,ISO26262 等功能安全标准推荐的一种测试方法。
为了更好的理解本文,首先来看看什么是硬件支持包(注:PIL 功能通常是一个完整硬件支持包的其中一个功能),在 MBD 语境下,一个相对完整的硬件支持包通常包含以下组件:
Toolchain - 实现将 Simulink 模型或 M 代码一键自动化编译、链接和下载的核心手段
Device Driver - MCU/MPU/FPGA 等的外设或者片外设备,比如 ADC,PWM,GPIO,SCI 等
Code Replacement Library(CRL) - 将生成的标准代码替换成厂商的高效库函数/汇编码
Processsor-In-the-Loop(PIL) - 将算法模型生成可执行文件后(如.out)放到目标硬件上执行并和主机上的仿真结果进行对比的一种等效性测试手段
External Mode - 将(算法)模型生成可执行文件后独立运行在目标硬件上并和原模型保持连接,此时原模型可理解为一种上位机“界面”,上位机模型和下位机目标码可以联动。
注:MathWorks 官方推出的硬件支持包通常是兼顾芯片级和板级,以芯片级为主,板级主要是支持一些官方或热门的开发板。
硬件支持包的开发要求开发人员具备相应的知识和技能,以下是最基本的要求:
嵌入式软硬件知识,通常包括使用 MCU/MPU/FPGA,调试工具和常用设备等技能
嵌入式软件知识,通常包括熟悉嵌入式操作系统,编程,常用 IDE 的使用,通信协议,手写 makefile 等能力
Embedded Coder 定制化知识和代码生成的深刻理解
MATLAB 面向对象编程的知识
常见通信协议知识,包括 SCI,TCP/IP,CAN 等
只有在具备以上能力的前提下才能较好的开发出硬件支持包。另外,MathWorks 官方以及三方芯片厂商也会提供一些热门芯片的现成的硬件支持包,在确定开发之前,应该进行相关调研,如果市面上已经存在相应的支持包,建议直接使用现成的。比如 NXP 针对其各系列芯片就提供了对应的硬件支持包供客户选择。
本文将聚焦硬件支持包中 PIL 功能的开发,介绍如何开发自己的 PIL 支持包。
PIL 基本原理
首先介绍下 PIL 的基本概念,如图所示,PIL 测试供包含两次仿真,分别是:
1)将软件的Simulink模型首先在Simulink环境中运行一次,在给定激励下会产生一些输出,记录下来;
2)接着将软件的 Simulink 模型生成代码并编译链接成可执行目标码,下载至下位机板子上运行,在相同的激励下软件在板子上运行也会产生一些输出,也记录下来。
3)最后将两次输出的结果进行等效性比对。
目前这些基本操作在 MathWorks 提供的工具链中都是傻瓜式操作,大家可自行查阅文档学习,本文不做赘述。
如图所示,一个完整的 PIL 仿真过程由以下各阶段组成:
选择三种 PIL 仿真方式中的一种启动 PIL 仿真,至于哪三种方式可参考文档
验证上位机下位机连接配置
模型生成核心代码
实例化 PIL API 组件(即 PIL Framework,就是几个 PIL 专用的 MATLAB 类文件)
继续生成额外的 PIL 需要用到的辅助代码,配合模型生成的核心代码组成一个包含上位机下位机通信和控制的完整应用代码
基于注册好的 ToolchainInfo 对象中的编译信息,调用三方交叉编译器将完整的应用代码生成可执行的目标文件。同时调用上位机的 C/C++ 编译器 (MinGW 或 Visual Studio) 把 PIL Framework 中涉及的 C/C++ 代码编译成 C Mex S Function(可简单理解成加壳的 dll 或 so 文件),这些代码主要是上位机端的 C/C++ 通讯源码。
上位机 Simulink 模型下发指令启动下位机运行目标代码
Simulink Engine 通过 C Mex S Function 和下位机进行数据交互
上位机 Simulink 模型下发指令终止下位机目标代码的运行
Simulink 模型停止 PIL 仿真
PIL 开发方法论
第一步
软硬件开发环境
开发的第一步是搭建软硬件开发环境,具体来说:
● 硬件环境:开发板,调试器,外围电路,通讯接口等
● 软件环境:IDE,MATLAB 等,特别注意 IDE 一定要支持命令行调用,这是自动化的前提,有些 IDE 的非商业版本只允许界面操作,这个是做不了支持包的!
● 资料收集:各种可能涉及的参考资料,比如芯片的数据表,开发板的原理图,IDE 的使用手册等等
● 通讯:PIL 会涉及上位机和下位机通信,所以通讯协议和通讯的硬件部分必须匹配,比如我们希望所开发的支持包支持上位机下位机通过 CAN 进行程序的烧写和数据交互,那么要求板子上的硬件必须支持 CAN 通讯,并且上位机一般要通过 USB-CAN 卡与下位机相连,如下图所示。理论上,任何通讯协议都可以,MathWorks 内置有部分现成的上位机通讯协议,包括串口和 TCP/IP,下位机部分仍然需要自己编写。其他协议如 CAN 则需要自己编写上位机和下位机通讯驱动。
一个良好的软硬件开发环境可以通过写个 makefile 来点灯的例子进行验证。一旦完成点灯,意味着我们已经具备的基本的软硬件开发环境。
第二步
Toolchain 的开发和 Hardware Board 注册
从上文知道 PIL 仿真过程需要一键编译链接和下载目标码,这个动作是由 Toolchain 来实现的,因此开发 PIL 的第一步是开发 Toolchain。
这里先解释下 Toolchain 在本文的含义,我们知道嵌入式开发自动化脚本通常就是 makefile,而 Toolchain 大家可以认为就是 MATLAB 版本的 makefile,是用 M 语言按照一定的要求和格式进行编写,其中需要写明汇编器、编译器、链接器、下载器、格式转换器等工具的命令行调用方法,编译成目标码所需要使用的各种依赖库等。这样可以将其自动嵌入到代码生成的流程中去,实现所谓的“一键编译下载”。
为了更好的讲解后续内容,建议大家先跳到 第5步 - 细节学习 ,安装 C2000 的官方支持包,这是一个非常好的学习对象。这里就以 C2000 支持包为例讲解下 Toolchain 开发的要点。
C2000 支持包的安装目录(位置参考第 5 步中的截图)找到 tiCCS.m 文件,大家可以直接使用这个文件作为自己 Toolchain 开发的模板,这个文件中最核心的部分包括:
● coder.make.ToolchainInfo:定义 ToolchainInfo 对象,下图是该对象的透视图,可以说开发 Toolchain,本质就是操作这个 ToolchainInfo 对象
● 使用上述对象的 getBuildTool 方法注册各类工具,比如 Assembler,Compiler,Linker,Archiver
● 使用上述对象的 getPrebuildTool/getPostbuildTool 方法注册各类代码生成前后处理工具,比如编译后自动下载等
● 使用上述对象的 getBuildConfiguration 方法提供不同等级的 Build Configuration,比如保留调试能力的编译配置,高度优化的编译配置等,它们的本质区别就是编译器优化等级不同
一旦完成上述内容的开发,下一步就是注册该文件,这里我们以 TI TMS320 C6678 DSP 的 Toolchain 开发为例介绍:
● TI_TMS320C6678_toolchain.m – 所开发的toolchain 的 M 代码
● sl_customization.m – 用来注册硬件板子和 Toolchain 对象,这样用户可以在 Hardware Implementation 和 Code Generation 下面看到板子和 Toolchain 可供选择。
function sl_customization(cm) cm.registerTargetInfo(@loc_createDevice); end function thisDev = loc_createDevice thisDev(1) = RTW.HWDeviceRegistry; thisDev(1).Vendor = 'Texas Instruments'; thisDev(1).Type = 'TMS320C6678'; thisDev(1).Alias = {}; thisDev(1).Platform = {'Prod', 'Target'}; thisDev(1).setWordSizes([8 16 32 32 32 32 64 32]); thisDev(1).LargestAtomicInteger = 'Long'; thisDev(1).LargestAtomicFloat = 'Double'; thisDev(1).Endianess = 'Little'; thisDev(1).IntDivRoundTo = 'Zero'; thisDev(1).ShiftRightIntArith = true; end
● refreshTITMS320C6678Toolchain.m – 注册 Toolchain的 脚本,包括调用 TI_TMS320C6678_toolchain.m 并将其保存成 TI_TMS320C6678_toolchain.mat 格式,进一步调用 RTW.TargetRegistry.getInstance('reset') 注册 Toolchain。
function refreshTITMS320C6678Toolchain %Make sure we are in the right directory currentDir = pwd; toolchainPath = getpref(' MyToolchainName', 'TOOLCHAINPATH'); cd(toolchainPath); % Create a new configuration gtc= MyToolchainName_toolchain; tc=gtc(1); save([toolchainPath ' TI_TMS320C6678_toolchain.mat'], 'tc'); % Refresh customisations defined by rtwTargetInfo.m RTW.TargetRegistry.getInstance('reset'); % Return to directory cd(currentDir) end
一旦注册完成后我们就可以在模型参数配置界面上找到这个 Toolchain 作为代码生成的选项,其中:
Hardware Implementation->Hardware board 中可选择刚刚注册的板子:
Code Generation中可选择刚刚注册的板子:
最后构建一个极简的模型来验证该Toolchain能够完成模型的一键编译下载。
第三步
PIL 工具链的组成
接着就是 PIL 最核心的开发部分,图中红色部分就是我们需要开发的内容,其中 PIL API Components 即 MathWorks 官方提供的一套标准的 PIL 开发框架,包括:上位机和下位机端各自的通讯协议,Build Process 和 Launcher等,该框架的核心就是名为 Connectivity Configuration 的几个类文件组成,以下为开发要点:
● 开发 PIL Target 核心功能类(即 Connectivity Configuration 类的编写)
● 桌面端和 IDE 中分别调通上位机和下位机的通讯功能,移植到核心功能类中
● 下位机 Timer 配置并注册核心功能类中,主要服服务于后续下位机上任务运行时间的统计和分析
这里对上述要点进一步展开介绍下,还是以 TI TMS320 C6678 DSP 的支持包开发为例,下图是一个典型的 PIL Target 目录,这里便于我们进行讲解:
1)开发上位机和下位机通讯协议
比如截图中是使用串口协议,并把上位机的串口协议和下位机的串口协议代码分别放到 host_dll 和 target_src 中。如果是其他通讯协议,则把相应的协议代码分门别类放好即可。通讯协议代码需要按照一定的格式使用 rtiostream 函数进行封装。
关于 rtiostream 函数的使用请查阅文档示例或者 C2000 支持包安装目录中的代码。另外,对于串口和 TCP/IP,MATLAB 官方提供了现成的上位机协议 dll 供使用,具体位置请查阅帮助文档。
2)开发 PIL Target 核心功能类
其中 +tic6678codetargetpil 中存放了 PIL 的核心功能类,这些类文件都是通用的,大家可以直接使用官方 C2000 支持包安装目录中的 pil 代码作为模板进行微调基本就能满足要求。各个类的使用细节请直接参考文档。
3)注册硬件定时器 Timer
Timer 的注册有两种方式:1)crtool;2)Timer 类。C2000 支持包使用的是 crtool 方式,在文档中搜索:Specify Hardware Timer 关键字即可找到操作指南,这种方式略微复杂些。当然也可以使用类的方式进行 Timer 注册,以下为前述 C6678 示例中 Timer 的注册方式:
classdef Timer < rtw.connectivity.Timer % TIMER Get timing information for C6678 application % Copyright 2022 The MathWorks, Inc. methods function this = Timer(varargin) this.setTimerDataType('uint32'); % i.e. unsigned long % Look for an input providing ticks per second if (nargin > 0) ticksPerSecond = varargin{1}; else ticksPerSecond = round(1.0e9/6); end this.setTicksPerSecond(ticksPerSecond); % The timer counts upwards this.setCountDirection('up'); % Extract path information from MATLAB Preferences timerSrcFolder = fullfile(fileparts(mfilename('fullpath')),'..','rtiostream','rtiostreamserial','target_src'); % Configure source files required to access the timer timerHeaderFile = fullfile(timerSrcFolder, 'ProfilerTimer.h'); timerSourceFile = fullfile(timerSrcFolder, 'ProfilerTimer.c'); this.setSourceFile(timerSourceFile); this.setHeaderFile(timerHeaderFile); % Configure the expression used to read the timer readTimerExpression = 'profileTimerRead()'; this.setReadTimerExpression(readTimerExpression); end end end
4)注册整个 PIL 硬件支持包
function rtwTargetInfo(tr) %RTWTARGETINFO Target info callback,register individual targets with Coder Target % Copyright 2022 The MathWorks, Inc. tr.registerTargetInfo(@loc_createToolchain); tr.registerTargetInfo(@loc_createConfig); codertarget.TargetRegistry.addToTargetRegistry(@loc_registerThisTarget); codertarget.TargetBoardRegistry.addToTargetBoardRegistry(@loc_registerBoardsForThisTarget); end % ------------------------------------------------------------------------- % Create ToolchainInfoRegistry entries for TI TMS320C6678 function config = loc_createToolchain config = coder.make.ToolchainInfoRegistry; % initialize % Append to last in the toolchain registry base config(end).Name = 'TI TMS320C6678 Toolchain v1.0 | gmake (win64)'; toolchainPath = mfilename('fullpath'); [toolchainDir, ~] = fileparts(toolchainPath); config(end).FileName = fullfile(toolchainDir,'toolchain','TI_TMS320C6678_toolchain_win64.mat'); %MODIFY %List the platform or platforms supported by the custom toolchain %'*' means it supports any hardware device. config(end).TargetHWDeviceType = {'*'}; %config(end).Platform = {computer('arch')}; config(end).Platform = {'win64'}; end function ret = loc_registerThisTarget() ret.Name = 'TI TMS320C6678'; [targetFilePath, ~, ~] = fileparts(mfilename('fullpath')); ret.TargetFolder = targetFilePath; end % ------------------------------------------------------------------------- function boardInfo = loc_registerBoardsForThisTarget() target = 'TI TMS320C6678'; [targetFolder, ~, ~] = fileparts(mfilename('fullpath')); boardFolder = codertarget.target.getTargetHardwareRegistryFolder(targetFolder); % Point to folder registry/targethardware boardInfo = codertarget.target.getTargetHardwareInfo(targetFolder, boardFolder, target); end % ------------------------------------------------------------------------- % Specify settings for valid PIL configuration function config = loc_createConfig % Create object for serial connectivity configuration config(1) = rtw.connectivity.ConfigRegistry; % Assign connectivity configuration name config(1).ConfigName = 'TMS320C6678 PIL Serial'; % Associate the connectivity configuration with the connectivity % API implementation config(1).ConfigClass = 'tic6678codetargetpil.SerialConnectivityConfig'; % match TI C6678 toolchains config(1).Toolchain = {'TI TMS320C6678 Toolchain v1.0 | gmake (win64)'}; % Match only ert.tlc config(1).SystemTargetFile = {'ert.tlc'}; % Through the HardwareBoard and TargetHWDeviceType properties, % define compatible code for the target connectivity configuration % match only TI C6678 config(1).HardwareBoard = {}; config(1).TargetHWDeviceType = {'Texas Instruments->TMS320C6678'}; end
第四步
PIL 支持包的测试
可以自行构建一个简单的模型,使用 SIL/PIL Manager 进行测试,具体的操作技巧请查阅文档,这里不做赘述。
第五步
细节学习
限于篇幅和嵌入式本身的复杂性,本文无法面面俱到,最好的学习资料就是仔细研究一些现成的硬件支持包,比如 MathWorks 官方提供的 C2000 硬件支持包:
大家安装完成后,结合本文,仔细翻阅支持包安装目录,基本就能把各种细节摸清楚。帮助文档中也有 PIL 的示例和资料,相对来说篇幅较多也比较零散,需要耐心阅读。R2019a 引入了 target Package,即一组高度抽象的 API 来便利化硬件支持包的开发,由于其高度抽象性,建议仍然使用本文描述的方法进行 PIL 支持包开发,这样调试起来会方便很多。
说的再多,都不如找一块板子实际开发一遍,祝大家都能掌握PIL开发的方法论,在项目中引入PIL测试环节,以期提高产品质量,减少后期枯燥地调试工作。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !