Autosar Xcp移植

前言:
可能有兄弟胸中有点疑问, Vector ETAS等软件包都有XCP,你移植个吊,但是我的MICORSAR BSW中没发现XCP静态代码,倒是在CFG中发现了XCP配置选项,难道这部分代码全是动态生成?
好吧,不论如何现在也不知道怎么在工具里配置XCP,先移植Vector Xcp Basic, 顺便读下代码, 加深一下理解。这个软件包大家可以在网上找找,或者Vector官网找找, 免费的,可以满足基本的标定/测量需求。
本篇文章的主要目的是从配置和代码角度学习一下XCP协议,至于能否直接用于项目请君自行斟酌。
首先说明我们配置的是Xcp on CAN, 所以必须你的MCU要能收发标准CAN消息,当然只要你搞清楚了移植步骤,之后迁移到Xcp on CAN FD 或者 on Tcp/ip都是可以的。
Mcu 的CanDrv 至少要提供像CanTransmit(Canid, DLC, void * data)这样的函数, CAN接收到的MSG 需要传入XcpCommand((vuint32*)&XcpRequest[0])进行处理;所以即使你是STM32+FreeRtos这样的组合也是可以移植Xcp协议的。

  • 环境 硬件->NXP S32K148EVB 软件->Vector Davinci套件
  • 工具配置
    所以我们先新建立一个工程, 命名为XcpDemo, 我们把工程中BSW部分,MCAL部分都已经配置好了,我们这里只讨论如何添加Xcp这一个模块。
    我们首先要改下DBC文件,这样导入CFG中就能生成基本的通信配置
BU_: INCA MyECU


BO_ 1920 XcpResponse: 8 MyECU
 SG_ XcpRes : 0|64@1+ (1,0) [0|1.84467440737096E+019] ""  INCA

BO_ 1888 XcpRequest: 8 INCA
 SG_ XcpReq : 0|64@1+ (1,0) [0|1.84467440737096E+019] ""  MyECU

通过CANdb++添加如下两个Nodes, Msgs, Sigs CANID Master -> Slave 0x760
Slave->Master 0x780 两帧MSG各包含一个Signal SG_XcpRes和SG_XcpReq, 都占8Bytes。
打开Developer

在这里插入图片描述
新建如图所示两个SWCs 其中TestSWC是用来对标定和测量变量做测试的,内容如下。

#include "Dio.h"

#pragma default_variable_attributes = @ ".caliConst_Ram"

static uint16 cali_test1 = 3000;

#pragma default_variable_attributes =

#pragma default_variable_attributes = @ ".monitorRam"

static uint8 moni_u8 = 3;
static uint16 moni_u16 = 0;
static float32 moni_float = 1.2F;
static uint32 moni_u32 = 0;

#pragma default_variable_attributes = 
FUNC(void, TestSWC_CODE) Runnable_Test(void) /* PRQA S 0850 */ /* MD_MSR_19.8 */
{
/**********************************************************************************************************************
 * DO NOT CHANGE THIS COMMENT!           << Start of runnable implementation >>             DO NOT CHANGE THIS COMMENT!
 * Symbol: Runnable_Test
 *********************************************************************************************************************/
    if(cali_test1 > 3000){
        LedOn();
    }else{
        LedOff();
    }
    if(moni_u8 >= 255){
        moni_u8 = 0;
    }
    if(moni_u16 >= 255){
        moni_u16 = 0;
    }
    if(moni_u32 > 1000){
        moni_u32 = 0;
    }
    if(moni_float > 100.0F){
        moni_float = 0.0F;
    }

    moni_u8++;
    moni_u16++;
    moni_u32++;
    moni_float = moni_float + 1.0F;
/**********************************************************************************************************************
 * DO NOT CHANGE THIS COMMENT!           << End of runnable implementation >>               DO NOT CHANGE THIS COMMENT!
 *********************************************************************************************************************/
}

可以看到我们定义了一个标定变量和四个观测变量(后面自己手动加上去的,如果用Simulink自动生成代码的话,可以将标定和测量变量都生成到同一个文件), 至于"#pragma default_variable_attributes = .monitorRam" 是IAR编译器特定的宏,同时需要修改链接文件.icf,定义flash和ram分区, 将标定和观测变量放到自己的分区中去,这里不展开讨论。
XcpIf这个SWC是Xcp接口
在这里插入图片描述在XcpIf 中定义3个Runable 它们的Trigger 分别为
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于Xcp需要使用BSW中ComM的接口, 所以需要定义一个Service Port, 并引用默认存在的ComM接口(C/S类型)
在这里插入图片描述
同时定义两个引用S/RInterface 的端口(Pport 向外发送Xcp Response, Rp 接收Tester发送的Xcp Request)
在这里插入图片描述
将两个SWC拖入ECU_Compoment中, 将XcpIf 的两个端口右键
在这里插入图片描述
在这里插入图片描述
代表这两个端口不做内部连接, 而是需要和外部其他ECU进行Sender/Reciver交互的, Rte 会自动识别并将这个两个Port的Read/Write操作转化为Com_ReadSignal/Com_SendSignal, 当然既然是Signal还有做DataMapping
在这里插入图片描述
这是Mapping好的Port中的Data Element 和对应的CAN Signal
在这里插入图片描述
想要RTE生成正确的代码还需要再CFG中将SWC和内部BSW建立连接
才能生成如下代码

#  define Rte_Read_Rp_XcpRequest_De_XcpRequest Rte_Read_XcpIf_Rp_XcpRequest_De_XcpRequest
#  define Rte_Read_XcpIf_Rp_XcpRequest_De_XcpRequest(data) (Com_ReceiveSignal(ComConf_ComSignal_XcpReq_oXcpRequest_oCAN_42940af5_Rx, (data))) /* PRQA S 3453 */ /* MD_MSR_19.7 */
#  define Rte_Call_ComM_UserRequest_RequestComMode(arg1) (ComM_RequestComMode((ComM_UserHandleType)0, arg1)) /* PRQA S 3453 */ /* MD_MSR_19.7 */

XcpDAQHandler为DAQ功能周期发送DTO,触发周期为1ms, 但是上位机可以通过Prescaler分别控制每个DAQList的上传周期。比如DAQList0->Prescaler = 10,那么XcpDAQHandler里会对DAQList0->Prescaler自减, 当其为0才真正通过CAN将DTO发送出去。
XcpRxHandler 触发为接收到ID 为0X760(Master->Slave)MSG

FUNC(void, RTE_CODE) Rte_COMCbk_XcpReq_oXcpRequest_oCAN_42940af5_Rx(void) /* PRQA S 0850 */ /* MD_MSR_19.8 */
{

  if (Rte_InitState == RTE_STATE_INIT)
  {
    /* scheduled trigger for runnables: XcpRxHandler */
    (void)SetEvent(OsTask_Xcp, Rte_Ev_Run_XcpIf_XcpRxHandler); /* PRQA S 3417 */ /* MD_Rte_Os */
  }
} /* PRQA S 6010, 6050 */ /* MD_MSR_STPTH, MD_MSR_STCAL */

可以看到这个CallBack是RTE自动生成的,会在CAN控制器接收到0x760消息的回调函数里进行匹配和逐级调用
XcpIfInit是协议初始化
在这里插入图片描述
3个Runables 需要Map到Task中去才能运行起来

TASK(OsTask_Xcp) /* PRQA S 3408, 1503 */ /* MD_Rte_3408, MD_MSR_14.1 */
{
  EventMaskType ev;


  /* call runnable */
  XcpIfInit();

  for(;;)
  {
    (void)WaitEvent(Rte_Ev_Run_XcpIf_XcpDAQHandler | Rte_Ev_Run_XcpIf_XcpRxHandler); /* PRQA S 3417 */ /* MD_Rte_Os */
    (void)GetEvent(OsTask_Xcp, &ev); /* PRQA S 3417 */ /* MD_Rte_Os */
    (void)ClearEvent(ev & (Rte_Ev_Run_XcpIf_XcpDAQHandler | Rte_Ev_Run_XcpIf_XcpRxHandler)); /* PRQA S 3417 */ /* MD_Rte_Os */

    if ((ev & Rte_Ev_Run_XcpIf_XcpDAQHandler) != (EventMaskType)0)
    {
      /* call runnable */
      XcpDAQHandler();
    }

    if ((ev & Rte_Ev_Run_XcpIf_XcpRxHandler) != (EventMaskType)0)
    {
      /* call runnable */
      XcpRxHandler();
    }
  }
} /* PRQA S 6010, 60

在生成的XcpIf模板中进行代码编写

FUNC(void, XcpIf_CODE) XcpDAQHandler(void) /* PRQA S 0850 */ /* MD_MSR_19.8 */
{
    uint8 i = 0;
    for (i = 0; i < xcp.Daq.DaqCount; i++)
    {
        vuint8 event = xcp.Daq.u.DaqList[i].eventChannel;
        XcpEvent(event);
    }
}
FUNC(void, XcpIf_CODE) XcpIfInit(void) /* PRQA S 0850 */ /* MD_MSR_19.8 */
{
    Rte_Call_ComM_UserRequest_RequestComMode(COMM_FULL_COMMUNICATION);
    XcpInit();
}

FUNC(void, XcpIf_CODE) XcpRxHandler(void) /* PRQA S 0850 */ /* MD_MSR_19.8 */
{
    uint8 XcpRequest[8] = {0};
    Rte_Read_Rp_XcpRequest_De_XcpRequest(XcpRequest); /*将CMD读到XcpRequest中*/
    XcpCommand((vuint32*)&XcpRequest[0]);
}
对三个Runable进行代码添加
其中XcpCommand XcpInit xcp.Daq.u.DaqList 等都是在Vector 提供的XcpBaic 代码包中定义和实现的,这里只需正确使用。
#include "Appl_Cbk.h"
#include "CanIf.h"
#include "XcpBasic.h"
#include "Os.h"

这是XcpIf.c 自己添加的头文件

#define XCP_RESPONSE_CANIF_PDUID 0
static vuint8 xcpResourceMask = 0;

void Xcp_Cbk_XcpResponseTxSuccess(void)
{
    XcpSendCallBack();
}

void ApplXcpSend( vuint8 len, BYTEPTR msg )
{
    PduInfoType PduInfoPtr;
    if(len > 8){
      len = 8;
    }
    PduInfoPtr.SduLength = len;
    PduInfoPtr.SduDataPtr = msg;
    CanIf_Transmit(XCP_RESPONSE_CANIF_PDUID, &PduInfoPtr);
}

void ApplXcpSendFlush(void)
{
  
}

void ApplXcpInit()
{

}

vuint8 ApplXcpGetSeed( vuint8 resourceMask, vuint8 *seed )
{
  /*RM_CAL_PAG|RM_DAQ|RM_PGM|RM_STIM 可分别返回不同SEED*/
  xcpResourceMask = resourceMask;
  seed[0] = 0;
  seed[1] = 1;
  seed[2] = 2;
  seed[3] = 3;
  seed[4] = 4;
  seed[5] = 5;
  return 6;
}

vuint8 ApplXcpUnlock( const vuint8 * key, vuint8 length )
{
  /* Ckeck the key */
  if((length == 2) && (key[0] == 0xBE) && (key[1] == 0xEF)){
    xcp.ProtectionStatus &= ~xcpResourceMask; /* Reset the appropriate resource protection mask bit */
    return 1;
  }else{
    return 0; /*Key not correct*/
  }
}

MTABYTEPTR ApplXcpGetPointer( vuint8 addr_ext, vuint32 addr ) {

  addr_ext = addr_ext;
  // /*S32K为小端格式*/
  // vuint32 new_addr = ( ((addr & 0XFF000000) >> 24)  | ((addr & 0X00FF0000) >> 8) |
  //                     ((addr & 0X0000FF00) << 8) | ((addr & 0X000000FF) << 24));
  /*如果上位机默认按小端格式传输则下位机无需更改字节序*/
  return (MTABYTEPTR)addr;
}

void ApplXcpInterruptDisable()
{
  SuspendAllInterrupts();
}

void ApplXcpInterruptEnable( void )
{
  ResumeAllInterrupts();
}

ApplXcpSend 这一类函数都是在XcpBasic.h中申明,但是没有具体实现,需要用户自己实现的函数,它们只在Xcp协议内部使用,大家可以参考代码包中的例程。
这里说下为啥ApplXcpSend 调用CanIf_Transmit 而不是之前生成的
Rte_Write_XcpIf_Pp_XcpResponse_De_XcpResponse(uint64 data);
因为这个函数实际会调用Com_SendSignal , 如果发送Response走Com->PduR->CanIf->CanDrv
的话Com中的Signal DLC是事先定好的,比如8Bytes那么每次都会发8Bytes,而Xcp Response 是需要根据不同响应动态改变DLC的,且Com发送PDU是有Periodic 和 Trigger两种模式,前一种模式是按照DBC中设定的周期周期发送,后一种无需计算周期直接发送,这里不如直接用CanIf层进行发送来的方便, 但是在Com中需配置
在这里插入图片描述
也就是SG_XcpRes 发送成功的Notification函数Xcp_Cbk_XcpResponseTxSuccess
在里面放上XcpBasic.c中实现的XcpSendCallBack才正确。
配置就说这么多,这样框架就搭好了,大家自行琢磨。

  • 代码

在这里插入图片描述
我们可以看到这是Vector 提供的代码, 其主要逻辑就是在XcpBasic.c中实现,大家可以把它们集成到自己的工程中去,进行编译。

我们来理一下Xcp协议栈的运行步骤
Tester-> send msg 0x760 + 8bytes -> ECU CAN Transiver -> Ecu CAN Interrupt -> CanIf_RxIndication -> PduR_RxIndication -> Com_RxIndication -> 对Signal XcpRequest 进行解包并拷贝至信号的缓存区-> 调用Rte_COMCbk_XcpReq_oXcpRequest_oCAN_42940af5_Rx callback激活Task(Xcp_Task) -> 调用Runable XcpRxHandler() -> Rte_Read_Rp_XcpRequest_De_XcpRequest 将缓存区的Signal 数据读出并传入XcpCommand -> Xcp 协议栈进行响应-> 如需向Tester 发出Response, 调用ApplXcpSend接口。
那么我们首先应该分析XcpCommand这个最重要的函数, 它其实就是一个状态机

void XcpCommand( const vuint32* pCommand )
{
  const tXcpCto* pCmd = (const tXcpCto*) pCommand; /* PRQA S 0310 */ /* MD_Xcp_0310 */
  vuint8 err;
  if (CRO_CMD==CC_CONNECT)
  {

    CRM_CMD = 0xFF; /* No Error */
    xcp.CrmLen = 1; /* Length = 1 */

#if defined ( XCP_ENABLE_DAQ )
    if ( (xcp.SessionStatus & (SessionStatusType)SS_RESUME) == 0 )
    {
      XcpFreeDaq();
  #if defined ( XCP_ENABLE_SEND_QUEUE )
      xcp.SendStatus = 0; /* Clear all transmission flags */
  #endif
    }
#endif /* XCP_ENABLE_DAQ */

#if defined ( XCP_ENABLE_SEED_KEY )
    /* Lock all resources. */
    xcp.ProtectionStatus = (vuint8)RM_CAL_PAG|RM_DAQ|RM_PGM|RM_STIM;
#endif
    xcp.SessionStatus = (SessionStatusType)SS_CONNECTED;
    xcp.CrmLen = CRM_CONNECT_LEN;

    /* Versions of the XCP Protocol Layer and Transport Layer Specifications. */
    CRM_CONNECT_TRANSPORT_VERSION = (vuint8)( (vuint16)XCP_TRANSPORT_LAYER_VERSION >> 8 );
    CRM_CONNECT_PROTOCOL_VERSION =  (vuint8)( (vuint16)XCP_VERSION >> 8 );

    CRM_CONNECT_MAX_CTO_SIZE = kXcpMaxCTO;
    CRM_CONNECT_MAX_DTO_SIZE_WRITE(kXcpMaxDTO); /* PRQA S 3109 */ /* MD_MSR_14.3 */

#if defined ( XCP_ENABLE_CALIBRATION_PAGE )
    CRM_CONNECT_RESOURCE = RM_CAL_PAG;            /* Calibration */
#else
    CRM_CONNECT_RESOURCE = 0x00;                  /* Reset resource mask */
#endif
#if defined ( XCP_ENABLE_DAQ )
    CRM_CONNECT_RESOURCE |= (vuint8)RM_DAQ;       /* Data Acquisition */
#endif

    CRM_CONNECT_COMM_BASIC = 0;
#if defined ( XCP_ENABLE_COMM_MODE_INFO )
    CRM_CONNECT_COMM_BASIC |= (vuint8)CMB_OPTIONAL;
#endif
#if defined ( XCP_CPUTYPE_BIGENDIAN )
    CRM_CONNECT_COMM_BASIC |= (vuint8)PI_MOTOROLA;
#endif
    goto positive_response;
  }

  else{

    if ( (xcp.SessionStatus & (SessionStatusType)SS_CONNECTED) != 0 ){

#if defined ( XCP_ENABLE_SEND_QUEUE )
      if ( (xcp.SendStatus & (vuint8)(XCP_CRM_PENDING|XCP_CRM_REQUEST)) != 0 )
      {
        xcp.SessionStatus |= (SessionStatusType)SS_ERROR; 
        END_PROFILE(1); /* Timingtest */

        /* No response */
        return;
      }
#endif

#if defined ( XCP_ENABLE_GET_SESSION_STATUS_API )
        xcp.SessionStatus |= (SessionStatusType)SS_POLLING;
#endif

      CRM_CMD = 0xFF; /* No Error */
      xcp.CrmLen = 1; /* Length = 1 */

      switch (CRO_CMD){

          case CC_SYNC:
            xcp.CrmLen = CRM_SYNCH_LEN;
            CRM_CMD    = PID_ERR;
            CRM_ERR    = CRC_CMD_SYNCH;
            break;

#if defined ( XCP_ENABLE_COMM_MODE_INFO )
          case CC_GET_COMM_MODE_INFO:
            {
              xcp.CrmLen = CRM_GET_COMM_MODE_INFO_LEN;
              CRM_GET_COMM_MODE_INFO_DRIVER_VERSION = (vuint8)( ((CP_XCP_VERSION & 0x0F00) >> 4) |
                                                                (CP_XCP_VERSION & 0x000F)         );
              CRM_GET_COMM_MODE_INFO_COMM_OPTIONAL = 0;
              CRM_GET_COMM_MODE_INFO_QUEUE_SIZE = 0;
              CRM_GET_COMM_MODE_INFO_MAX_BS = 0;
              CRM_GET_COMM_MODE_INFO_MIN_ST = 0; /*查UDS相关概念*/
            }
            break;
#endif 

          case CC_DISCONNECT:
            xcp.CrmLen = CRM_DISCONNECT_LEN;
            XcpDisconnect();
            break;
    ...代码太多就不拷贝了
    大家可以仔细阅读
}

这里总结下Xcp 标定/测量的流程
首先肯定要建立连接
M->S FF 00 00 00 00 00 00 00 请求建立连接
S->M FF 连接成功
M->S F8 00 01 00 00 00 00 00 解锁RM_CAL_PAG 标定权限
S->M FF 06 01 02 03 04 05 06 Slave发回6Bytes 种子
M->S F7 02 BE EF 00 00 00 00 发回KEY进行解锁
S-M FF 解锁成功,可以标定
M->S SET_MTA 传入需要标定变量的地址(Master根据A2L文件)
M->S Download Size+Data 这样存在RAM中的标定变量就可以被改写了

测量的话分为Polling 和 DAQ, 其中Polling较为简单,但效率较低,不适合速度快一点的采集,DAQ模式较为复杂,这里就不具体分析代码了,只有弄明白什么是DAQList,Odt ,OdtEntryAddress, OdtEntrySize以及它们在这套代码中是如何分配内存的就可以了
需注意由于这里采用CAN通信,一个DTO最多发送8个字节, 其中第一个字节是Odt编号,也就说真正用于发送Data的只有7个字节,而Xcp不像UDS或Tcp那样可以发连续帧,有FlowCtrl
一个Dto发送一个Odt包含这个Odt中所有EntryAddres中存放的EntrtySize大小的数据
所以一个Odt中最多可包含MaxDto-1个Entry 并且要Size 都为1Bytes
也就说我有4个 uint32 moni_vars 那么一个Odt肯定放不下, 需要四个Odt
DAQ 简要的流程是 FreeDAQ -> Allocate DAQList -> Allocate Odt -> Allocate Entry ->
WriteDAQ (为每个Entry 写上变量的具体地址和大小,比如 uint32 moni_var1 adress:0x1FFF8000 size:4bytes)-> Start DAQ(这里可以分别为每个DAQList 设置Prescaler, 即采样周期)
相当于先在ECU中建立好一张采样表,并设定周期开始采样。

其他需要注意的是一定要打开XCP_ENABLE_SEND_QUEUE这个宏,否则DAQ会丢帧,另外根据工具(上位机)的不同需要注意一下上位机传数据的大小段格式。
附上Xcp_Cfg.h

#if !defined(__XCP_CFG_H__)
#define __XCP_CFG_H__


/* define here configuration parameters for customizing XcpBasic driver */
#define V_MEMROM0
#define kXcpMaxCTO     8
#define kXcpMaxDTO     8
#define V_MEMROM0
#define XCP_DISABLE_COMM_MODE_INFO
#define XCP_DISABLE_SERV_TEXT
#define XCP_DISABLE_SERV_TEXT_PUTCHAR
#define XCP_DISABLE_SERV_TEXT_PRINT
#define XCP_DISABLE_CALIBRATION_PAGE
#define XCP_TRANSPORT_LAYER_VERSION 0x100
#define XCP_DISABLE_UNALIGNED_MEM_ACCESS
#define XCP_ENABLE_USE_BYTE_ACCESS
#define XCP_CPUTYPE_LITTLEENDIAN
#define XCP_ENABLE_SEND_EVENT /*send event支持*/
#define XCP_ENABLE_CALIBRATION
#define XCP_ENABLE_PARAMETER_CHECK
#define XCP_ENABLE_SEED_KEY
#define XCP_ENABLE_DAQ
#define XCP_DISABLE_DAQ_HDR_ODT_DAQ
#define XCP_ENABLE_DAQ_PROCESSOR_INFO
#define XCP_ENABLE_DAQ_RESOLUTION_INFO
#define XCP_ENABLE_DAQ_PRESCALER
#define XCP_ENABLE_DAQ_OVERRUN_INDICATION
#define XCP_ENABLE_SEND_QUEUE
#define kXcpDaqMemSize (1024*2)


#endif /* __XCP_CFG_H__ */
  • 10
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值