前言:
可能有兄弟胸中有点疑问, 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__ */
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » Autosar XCP 的移植

    发表评论