CAPL编程实现诊断刷写,车联网FOTA流程自动化测试(代码篇)
原创内容,转载请注明出处
接上篇,本文主要讲CAPL编程详细实现,软件环境CANoe 11.0
一、Simulation Setup
1、建模之前,首先创建一个.DBC文件。如果不会,可以用一个已有的DBC文件修改。新建待仿真的空节点,如下图,只有节点名称无任何信号。然后加载到Setup

2、新插入节点,选择Insert Network Node, 然后右击新建的节点配置该节点属性。

选择DBC中创建的节点名,此处很有用

设置节点属性为OSEK_TP节点(添加osek_tp.dll即可,在canoe安装目录下查找,我的是 "C:\Program Files\Vector CANoe 11.0\Exec32")
我的整个模型建完如下图(ECU太多,未截图完整):
可能大家会有疑问,关于这个网络模型的合理性。
疑问1. 如此多的节点,运行负载如何,会不会不足以支撑,变得不够实时性?
答:我的硬件是CANoe89系列,是最强悍的一款。完全可以支撑这么多节点。 而且按CANoe官方介绍的说法,理论上这种模型可支持无限多个节点,只是会降低速率。当然canoe对PC的运存要求比较高,需一台强悍的电脑承载。
ISO11898标准规定标准的1M/s CAN网络的最大总线长度40m, 最多允许存在30个节点,各节点支路最长为0.3m,如果网络以较低的速度运行则可支持更多的节点,总线长度也可增加。
高速总线的标准最大速率500k/s, 而支持超过30个节点的低速总线的速率为125k/s或更低, 低速CAN网络普遍能支持50个或更多的节点。
疑问2.目前才20几个ECU,复杂度不算太高,当ECU数量更多时,是否会造成编码量过大,可维护性变得极差?
答:上一篇的介绍过系统框架和通信模型,此模型非常简便的支持节点热增减,各ECU之间的耦合度降到最低,互不牵连。设计时抽取了通用接口,即使是二次开发也是非常简单的。
二、代码实现
此处选择GW节点作为样例讲解。其中涉及的环境变量和系统变量在代码中出现时再做说明
1、ECU应用层行为仿真
/*@!Encoding:936*/
includes
{
#include "GenericNode.cin" //此处是一个造好的轮子,可见canoe提供的\OSEK_TP_MultiChannel Demo
}
variables
{
msTimer PhysRespTimer; //物理寻址应答定时器
msTimer FuncRespTimer; //功能寻址应答定时器
msTimer GWMessageTimer; //ECU外发消息定时器,周期性的往总线发报文
message 0x111 GW_message; //此处是随便举例的报文,假设GW的tx报文就是id=0x111
message 0x222 NWM_message; //监控唤醒状态
; //100ms周期
}
//每100ms发送一帧gw报文到总线,ecu信号仿真
on timer GWMessageTimer
{
output(GW_message);
setTimer(GWMessageTimer, cycPepsTime);
}
//模拟按键弹起,物理寻址
on timer PhysRespTimer
{
//注意此处的系统变量格式, ECUName::链路名::变量名, 本篇章节一介绍的在setup处建立节点时,要求配置选择数据库的节点名将在此处生效
@sysvar::GW::Conn1::sysSendData = ;
}
//模拟按键弹起,功能寻址
on timer FuncRespTimer
{
@sysvar::GW::Conn2::sysSendData = ; //注意此处链路名与上一函数不一样,区分物理寻址和功能寻址主要体现在这里
}
//监控一个环境变量,整车电源模式。 备注:环境变量可在DBC中创建
on envVar PEPS_PwrMode
{
varPowerMode = getValue(PEPS_PwrMode); //先略过此变量的定义位置,全局变量记录电源状态
GW_message.PEPS_PowerMode = varPowerMode;
)
{
BCM_ATWS = ; //车身安全锁报警状态变量,略过定义处
}
)//休眠
{
InactiveGW();
}
else
{
ActiveGW();
}
}
//模拟按键按下,物理寻址
void diagPhysRespMessage()
{
if(IsResponse){
@sysvar::GW::Conn1::sysSendData = ;
setTimer(PhysRespTimer, N_As);
}
}
//模拟按键按下,功能寻址
void diagFuncRespMessage()
{
if(IsResponse){
@sysvar::GW::Conn2::sysSendData = ;
setTimer(FuncRespTimer, N_As);
}
}
on message NWM_message
{
)
{
GW_message.PEPS_PowerMode = ;
ActiveGW(); //设备被唤醒,升级定时器触发后 激活信号
}
}
//处理来自诊断仪的物理寻址访问GW请求
on message 0x701 //此处是捏造的物理寻址诊断ID,根据产品实际的来变更
{
diagReqMsg=this;
writeDbgLevel(level_1, "---physical diagnostic request, id = 0x%x", diagReqMsg.id);
SetValue(); //获取当前应回复值
diagParseReqMessage(); //解析请求内容
diagPhysRespMessage(); //应答请求
}
//处理来自诊断仪的功能寻址访问GW请求
on message 0x7EE //此处是捏造的功能寻址诊断ID,根据产品实际的来变更
{
diagReqMsg=this;
writeDbgLevel(level_1, "---functional diagnostic request, id = 0x%x", diagReqMsg.id);
diagParseReqMessage();
diagFuncRespMessage();
}
//初始化仿真的通信信号值
void InitGWValue()
{
putValue(PEPS_PwrMode, );
GW_message.PEPS_PowerModeValidity = ;
GW_message.PEPS_RemoteControlState = ;
}
//初始化数据
void InitValue()
{
//以下是从配置文件读取 GW接到诊断请求时的应答的数据
getProfileString("GW", gEntry_1, gDefautStr, cOEMInfo, gLenEntry_1, gFileName);
putValue(GWOEMNumber, cOEMInfo); //EPS OEM NO.
}
//获取ECU的回复参数
void SetValue()
{
getValue(GWOEMNumber, cOEMInfo);
}
on start
{
InitGWValue();
ActiveGW();
}
//停止仿真通信报文
void InactiveGW()
{
cancelTimer(GWMessageTimer);
IsBUSActive = ;
}
//仿真通信报文
void ActiveGW()
{
setTimer(GWMessageTimer, cycPepsTime);
IsBUSActive = ;
}
on preStart
{
InitValue();
}
//获取实时更新的OEM版本号
on envVar GWOEMNumber
{
];
getValue(GWOEMNumber, cOEMInfo);
snprintf(dest, elcount(dest), "\"%s\"", cOEMInfo);
writeProfileString("GW", gEntry_1, dest, gFileName);
}
//数据对外发送的统一变量,所有ECU发送数据时通过它外传
on envVar varDataToTransmit
{
getValue(varDataToTransmit, cEnvVarBuffer);
}
以上代码,实现了ECU的通信信号仿真,不同的ECU之间的差异在于信号数量不一样、物理请求与功能请求的应答的链路的ECUName不一致, 诊断ID不一致。其余逻辑上完全一致。所以说二次开发很简单,只需要复制代码后 修改此三处即可完成新节点的增加
2.通用接口实现
includes
{
#include "GenericConn1.cin"
#include "GenericConn2.cin" //造好的轮子 建立链路,分别实现物理寻址与功能寻址
#include "Common.cin" //通用接口封装在此处
}
variables
{
] = "%NODE_NAME%"; //此变量是获取当前通信节点的名称,此处与通信链路中的ECUName很自然的关联起来了
,
kExtendedBased = ,
kNormalFixed = ,
kMixed = ,
//......略去下面很多代码
}
diagParseReqMessage()实现,解析总线上的诊断请求报文
/***********************************************************
* description : 解析收到的报文
* creation date: 2018/11/13
* author : XXX
* revision date:
* revision log :
* modifier :
***********************************************************/
void diagParseReqMessage()
{
byte fBValue;
byte hNibble; //高四位
byte lNibble; //低四位
byte sid = 0x0;
byte reserveSid = 0x0; //针对多帧请求的服务有效,特别预留
int remainderBLen; //剩余未传输字节
;
;
//获取首字节信息
fBValue = diagReqMsg.);
writeDbgLevel(level_1, "---The First Byte: 0x%02x", fBValue);
hNibble = (fBValue>>) & 0xf;
lNibble = fBValue & 0xf;
//writeDbgLevel(level_1, "high 4 bits=%d, low 4 bits=%d", hNibble, lNibble);
IsResponse= ; //初始化时默认不发送应答,需要发送应答时置位1
//解析高字节信息
if(0x0 == hNibble) //单帧
{
SF_DL = lNibble;
sid = diagReqMsg.);
writeDbgLevel(level_1, "SF: SF_DL=%d, sid=0x%x", SF_DL, sid);
if(0x2e==sid){//写入服务
subServiceId = ((diagReqMsg.)<<)&);
writeDbgLevel(level_1, "---SF:sid=0x%02x, ssid=0x%x---", sid, subServiceId);
}
else if(0x31==sid) //擦写 05 71 01 FF 01 04 AA AA
{
checkSum = (diagReqMsg.)<<) | (diagReqMsg.)<<)
|(diagReqMsg.)<<) | diagReqMsg.);
writeDbgLevel(level_1, "---SF:crc or flush, 0x%x---", checkSum);
}
diagProcessSFRequest(sid); //根据实际服务回复应答内容
}
else if(0x1 == hNibble) //多帧首帧
{
FF_DL = ((lNibble<<)&);
reserveSid = diagReqMsg.);
remainderFrameCnt = ; //回复0值
consecutiveFrameCnt = ; //置0连续帧
remainderBLen = (FF_DL - );
writeDbgLevel(level_1, "---MF:sid=0x%02x", reserveSid);
if(reserveSid==0x2e){
subServiceId = ((diagReqMsg.)<<)&);
writeDbgLevel(level_1, "---MF:ssid=0x%x---", subServiceId);
}
else if(reserveSid==0x36) //经验, 将数据放置在左边,可避免少写=的异常
{
transferDataSN = diagReqMsg.);
writeDbgLevel(level_1, "---MF:data sn=0x%x---", transferDataSN);
}
else if(reserveSid==0x31) //校验
{
checkSum = (diagReqMsg.)<<) | (diagReqMsg.)<<)
|(diagReqMsg.)<<) | diagReqMsg.);
writeDbgLevel(level_1, "---MF:crc or flush, 0x%x---", checkSum);
IsCRCDone = ; //已校验过 刷写完成
}
== )
{
remainderFrameCnt = remainderBLen/;
}
else
{
remainderFrameCnt = remainderBLen/ + ;
}
writeDbgLevel(level_1, "MF: FF_DL=%d,remainder frame count=%d", FF_DL, remainderFrameCnt);
}
else if(0x2 == hNibble) //连续帧
{
SN = lNibble;
consecutiveFrameCnt += ;
writeDbgLevel(level_1, "CF: SN=%x, current count=%d", SN, consecutiveFrameCnt);
sid = 0x0;
}
else if(0x3 == hNibble) //流控帧
{
FS = lNibble;
BS = diagReqMsg.);
STmin = diagReqMsg.);
writeDbgLevel(level_1, "FC: FS=%d, BS=%d, ST min=%d", FS, BS, STmin);
sid = 0x0;
}
else
{
writeDbgLevel(level_1, "error frame");
}
//响应多帧请求
)
{
if(remainderFrameCnt == consecutiveFrameCnt)
{
diagProcessMFRequest(reserveSid); //封装具体的应答逻辑,可以根据诊断协议获知
IsResponse= ;
consecutiveFrameCnt = ;
}
}
}
以上就完成了车内ECU的仿真,启动CANoe后,仿真的ECU就可以验证TBOX的FOTA流程正确性啦。 本方案只算个半成品,只模拟了正向刷写的过程,实际刷写过程中,会有很多异常场景出现。所以需要根据产品的OTA规范来封装重试机制,否定应答处理机制等。
还可以配合开发控制面板或模拟器,同步车身的状态,控制车内信号的变化等。
CAPL编程实现诊断刷写,车联网FOTA流程自动化测试(代码篇)的更多相关文章
- CAPL编程实现诊断刷写,车联网FOTA流程自动化测试(方案篇)
原创内容,转载请注明出处 本文围绕车联网的ECU,TBOX的FOTA升级业务展开描述.主要讲如何通过CANoe编程实现自动化测试, 验证TBOX在FOTA业务过程中作为一个诊断仪刷写整车其它ECU的流 ...
- 初版storm项目全流程自动化测试代码实现
由于项目需要,写了版针对业务的自动化测试代码,主要应用场景在于由于业务日趋复杂,一些公共代码的改动,担心会影响已有业务.还没进行重写,但知识点还是不少的与大家分享实践下.首先,介绍下整个流处理的业务流 ...
- paip.输入法编程--英文ati化By音标原理与中文atiEn处理流程 python 代码为例
paip.输入法编程--英文ati化By音标原理与中文atiEn处理流程 python 代码为例 #---目标 1. en vs enPHati 2.en vs enPhAtiSmp 3.cn vs ...
- CANoe 入门 Step by step系列(二)CAPL编程【转】
CAPL就是Communication Application Programming Laguage的缩写,CAPL类似于C语言的语法,因此所有的语法请参考C语言教程,这里不在这里进行详述,关于C语 ...
- 【转载】CANoe 入门 Step by step系列(二)CAPL编程
来源:http://www.cnblogs.com/dongdonghuihui/archive/2012/09/26/2704619.html CAPL就是Communication Applica ...
- 【强烈强烈推荐】《ORACLE PL/SQL编程详解》全原创(共八篇)--系列文章导航
原文:[强烈强烈推荐]<ORACLE PL/SQL编程详解>全原创(共八篇)--系列文章导航 <ORACLE PL/SQL编程详解> 系列文章目录导航 ——通过知识共享树立个人 ...
- Javascript高级编程学习笔记(6)—— 流程控制语句
话不多说,我们直接开始进入今天的主题 流程控制语句 首先什么是流程控制语句呢? 顾名思义,就是控制流程的语句. 在JS中语句定义了ECMAScript中的主要语法,让我们可以使用一系列的关键字来完成指 ...
- CoreJavaE10V1P3.8 第3章 Java的基本编程结构-3.8 控制流程(Control Flow)
通过使用条件语句.循环语句可以实现流程的控制. 3.8.1 块作用域(Block Scope) 块(Block)就是由一对花括号包围起来的部分.他指定了一个变量的生存范围,与一个方法的操作范围. Ja ...
- 《天书夜读:从汇编语言到windows内核编程》二 C语言的流程与处理
1) Debug与Release的区别:前者称调试版,后者称发行版.调试版基本不优化,而发行版会经过编译器的极致优化,往往与优化前的高级语言执行流程会大相径庭,但是实现的功能是等价的. 2) 如下fo ...
随机推荐
- Part4_lesson2---ARM处理器这个硬件启动流程分析
1.启动方式 s3c2440:可以从Nor flash启动,Nor flash一般是2MB:也可以从Nandflash启动,它一般是256MB,我们习惯上把我们的uboot,内核以及文件系统都放到Na ...
- HttpSession解决表单的重复提交
1). 重复提交的情况: ①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面, 此时地址栏还保留着 Serlvet 的那个路径, 在响 ...
- C#request和response的中文乱码问题
request乱码指的是:浏览器向服务器发送的请求参数中包含中文字符,服务器获取到的请求参数的值是乱码: response乱码指的是:服务器向浏览器发送的数据包含中文字符,浏览器中显示的是乱码: ...
- mysql 全文搜索 FULLTEXT
到 3.23.23 时,MySQL 开始支持全文索引和搜索.全文索引在 MySQL 中是一个 FULLTEXT 类型索引.FULLTEXT 索引用于 MyISAM 表,可以在 CREATE TABLE ...
- Django实战之古风博客
感谢 感谢杨青 大大的古风模板,设计的很棒,给个赞. 如有侵权,请联系我 运行环境 python3.6 Django==1.11.4 django-ckeditor==5.4.0 django-js- ...
- 解决dragsort鼠标拖动与onclick事件共存
- js作用域解析原理
当代码进入到<script>标签或者在调用一个方法,那么就会进入作用域,在解析代码的时候就会做以下两件事情: ①去找var 和function关键字进行js预解析如果有var把值全部定义成 ...
- JavaScript中创建自定义对象的方法
本文内容参考JavaScript高级程序设计(第3版)第6章:面向对象的程序设计 ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”我所理解的就是对象就是一个结构 ...
- bzoj 4182
首先很容易看出这是一个树上多重背包问题 设状态$f[i][j]$表示以$i$为根的子树中利用的体积是$j$ 但是题目中有要求:选择的点集必须是一个联通块 这要怎么处理? 点分治! 首先我们利用点分治的 ...
- php代码审计5审计命令执行漏洞
命令执行漏洞:通过易受攻击的应用程序在主机操作系统上执行任意命令,用户提供的数据(表单,cookie,http头等)未过滤 挖掘思路:用户能够控制函数输入,存在可执行代码的危险函数 命令执行和代码执行 ...