Autosar CAN 报文接收 下集预告核间通讯
“ 本文参考Tc397手册,ILLD库以及autosar 的一些代码”
接上篇autosar can tx 发送。《Autosar CAN 报文发送 下期预告CAN_RX》
了解硬件
首先了解一下硬件本身支持多少路CAN,分别是什么样的关系。在实际使用中可能需要考虑到。下图来自3xx用户手册。
从上面图加上描述可以看出,tc397有三个module 每个module有4路nodes. 所以一共可以支持12路can。本文主要介绍CAN的接收过程。一般来说有polling 和 interrupt两种模式。这里介绍中断模式。因为polling模式比较容易丢帧。必须和其他ECU配合的好的情况下,才不容易丢帧。
接收中断是怎么回事(这里和autosar没关系)
上面说到一个module可以支持4路can. 下面是一个module支持的中断。那么他们分别是什么意思呢。
Interrupt Router Input | Connected to CAN Interrupt Output |
SRC_CANzINT0 | INT_O0 MCMCAN |
SRC_CANzINT1 | INT_O1 MCMCAN |
SRC_CANzINT2 | INT_O2 MCMCAN |
SRC_CANzINT3 | INT_O3 MCMCAN |
SRC_CANzINT4 | INT_O4 MCMCAN |
SRC_CANzINT5 | INT_O5 MCMCAN |
SRC_CANzINT6 | INT_O6 MCMCAN |
SRC_CANzINT7 | INT_O7 MCMCAN |
SRC_CANzINT8 | INT_O8 MCMCAN |
SRC_CANzINT9 | INT_O9 MCMCAN |
SRC_CANzINT10 | INT_O10 MCMCAN |
SRC_CANzINT11 | INT_O11 MCMCAN |
SRC_CANzINT12 | INT_O12 MCMCAN |
SRC_CANzINT13 | INT_O13 MCMCAN |
SRC_CANzINT14 | INT_O14 MCMCAN |
SRC_CANzINT15 | INT_O15 MCMCAN |
0,1,2,3 || 4,5,6,7 || 8,9,10,11 || 12,13,14,15
这是四组分别对应的是四个nodes. 作用是一样的。这里介绍第一组。0,1,2,3
这里对应的是
- 接收中断
- 发送中断
- busoff中断
- rx fifo中断
这里我们先把rx的中断优先级和中断服务函数注册一下,后面详细描述。
注册中断优先级与对应的服务函数
* IFX_INTERRUPT(isr, vectabNum, priority)
* - isr: Name of the ISR function.
* - vectabNum: Vector table number.
* - priority: Interrupt priority. Refer Usage of Interrupt Macro for more details.
IFX_INTERRUPT(canIsrRxHandler, 0, ISR_PRIORITY_CAN_RX);
这个IFX_INTERRUPT 是编译器提供的,下面是对应的一段汇编,仅供参考。可忽略。
#define IFX_INTERRUPT_INTERNAL(isr, vectabNum, prio) \
__asm__ (".ifndef .intr.entry.include \n"\
".altmacro \n"\
".macro .int_entry.2 intEntryLabel, name # define the section and inttab entry code \n"\
" .pushsection .\\intEntryLabel,\"ax\",@progbits \n"\
" __\\intEntryLabel : \n"\
" svlcx \n"\
" movh.a %a14, hi:\\name \n"\
" lea %a14, [%a14]lo:\\name \n"\
" ji %a14 \n"\
" .popsection \n"\
".endm \n"\
".macro .int_entry.1 prio,vectabNum,u,name \n"\
".int_entry.2 intvec_tc\\vectabNum\\u\\prio,(name) # build the unique name \n"\
".endm \n"\
" \n"\
".macro .intr.entry name,vectabNum,prio \n"\
".int_entry.1 %(prio),%(vectabNum),_,name # evaluate the priority and the cpu number \n"\
".endm \n"\
".intr.entry.include: \n"\
".endif \n"\
".intr.entry "#isr","#vectabNum","#prio );\
仅仅上面这样一下收到报文就可以触发中断了?显然不是这么简单。在mcal配置的话,确实就是这么简单,但是我们这里从最根本的寄存器说起。
上面提到的三个module 在内核里面对应的是有三个地址。
/** \brief CAN object */
#define MODULE_CAN0 /*lint --e(923, 9078)*/ ((*(Ifx_CAN*)0xF0200000u))
#define MODULE_CAN1 /*lint --e(923, 9078)*/ ((*(Ifx_CAN*)0xF0210000u))
#define MODULE_CAN2 /*lint --e(923, 9078)*/ ((*(Ifx_CAN*)0xF0220000u))
/** \} */
我们说第一个。
这里我们找到RX中断SRC的 地址
/** \brief 5B4, CAN0 Service Request 1 */
#define SRC_CAN_CAN0_INT1 /*lint --e(923, 9078)*/ (*(volatile Ifx_SRC_SRCR*)0xF00385B4u)
/** Alias (User Manual Name) for SRC_CAN_CAN0_INT1.
* To use register names with standard convension, please use SRC_CAN_CAN0_INT1.
*/
#define SRC_CAN0INT1 (SRC_CAN_CAN0_INT1)
就在这个node下进行接收数据。我们使用什么方式呢?使用下面这个DRXE
Message stored to Dedicated Rx Buffer Interrupt Enable
0 B Interrupt disabled
1 B Interrupt enabled
把这个位置位,也就是说当rx buffer 有新的报文填入,那么就会有中断触发。
这里就对应到了上面的中断优先级和中断服务函数。重写一遍。
* IFX_INTERRUPT(isr, vectabNum, priority)
* - isr: Name of the ISR function.
* - vectabNum: Vector table number.
* - priority: Interrupt priority. Refer Usage of Interrupt Macro for more details.
IFX_INTERRUPT(canIsrRxHandler, 0, ISR_PRIORITY_CAN_RX);
所以总结一下,当 rx buffer 有新报文填入,这时候会触发中断,这个中断的优先级就是 ISR_PRIORITY_CAN_RX
这个中断对应的中断服务函数就是canIsrRxHandler
当然触发接收中断的方式有很多,还有上面的fifo 等机制。这里抛砖引玉详细的可以按照这种方式去看一下芯片手册。至此中断服务函数算是从物理线束跑到了中断服务函数。那么下面开始介绍一下autosar 在中断服务函数调到之后做了些什么。为了方便起见。这里的终端服务函数该名称autosar定义的名称
#define canIsrRxHandler Can_17_McmCan_IsrReceiveHandler
下面函数名统一成
/*******************************************************************************
** Traceability : [cover parentID={B91B38C0-0F62-4a9a-BF22-9EC231B6521E}] **
** Syntax : void Can_17_McmCan_IsrReceiveHandler **
** ( **
** const uint8 HwKernelId **
** const uint8 NodeIdIndex **
** ) **
** **
** Description :The function should handle receive interrupts from **
** dedicated receive buffers during CAN controller **
** STARTED state. **
** For dedicated reception the hardware filter code alone is **
** considered, the receive mask available shall not be used **
** during the filtering or processing of the message. **
** In case of dedicated each hardware object can be configured**
** as INTERRUPT or POLLING. However as the interrupt lines are**
** shared, if one of the HRH is configured as INTERRUPT all **
** dedicated objects on reception would trigger an interrupt. **
** [/cover] ** **
** Parameters(in) :HwKernelId - The CAN controller which is to be processed,**
** is associated with the passed Kernel, **
** NodeIdIndex - The CAN node which is to be processed **** **
*******************************************************************************/
void Can_17_McmCan_IsrReceiveHandler(const uint8 HwKernelId,
const uint8 NodeIdIndex)
CAN RX Mcal里面做了什么
通过上一章节,中断服务函数顺利进入。我们从代码出发,先看形参
void Can_17_McmCan_IsrReceiveHandler(const uint8 HwKernelId,
const uint8 NodeIdIndex)
从形参可以看出来是上文提到的内核module 和 每个module有四个node. 这里的意思是 这一个具体的node 触发的中断。比如 KernelId == 0, NodeId == 0, 则就是我们配置的那个中断触发的。
首先根据具体的node 来获取 配置参数,以及内核node本身的地址参数等。
然后检查刚才配置的buffer里面是否有数据。并且清除调DRX寄存器。
判断状态START之后,进入真正的接收函数。
** Parameters (in) : HwControllerId - Associated CAN Controller **
** CheckBuffType - Data received buffer type **
** ProcessingType - Rx processing type **
** CoreConfigPtr - Pointer to CAN driver configuration **
static void Can_17_McmCan_lReceiveHandler(const uint8 HwControllerId,
const Can_17_McmCan_RxBufferType CheckBuffType,
const Can_17_McmCan_ProcessingType ProcessingType,
const Can_17_McmCan_CoreGlobalType *const CoreGlobalPtr,
const Can_17_McmCan_CoreConfigType *const CoreConfigPtr)
还是可以通过形参去看。
形参里面除了配置文件,还多出来了buffer 和 处理类型。这指的是什么呢。答:下面会通过配置根据这些类型进行针对性的读取数据。
{
CAN_17_MCMCAN_RX_DED_BUFFER,
CAN_17_MCMCAN_RX_FIFO0,
CAN_17_MCMCAN_RX_FIFO1,
CAN_17_MCMCAN_RX_ALL
} Can_17_McmCan_RxBufferType;
typedef enum
{
CAN_17_MCMCAN_RX_FIFO_POLLING,
CAN_17_MCMCAN_RX_FIFO_INTERRUPT,
CAN_17_MCMCAN_RX_FIFO_NOT_CONFIGURED
} Can_17_McmCan_RxFIFOProcessingType;
这两个枚举放在这里就清楚了吧。实际上就是进行一系列的判断。来决定用哪一个回调函数来收取保温内容。
根据我们前面配置的接受模式是中断。并且buffer.通过CheckBuffType
switch (CheckBuffType)
我们这边就进一步确定了如何去接收数据。
** Parameters (in) : HwControllerId - Associated CAN Controller **
** ProcessingType - Rx processing type **
** CoreConfigPtr - Pointer to CAN driver configuration **
** CoreGlobalPtr - Pointer to global structure **
** Parameters (in-out) : none **
** Parameters (out) : none **
** **
** Return value : none **
** **
*******************************************************************************/
static void Can_17_McmCan_lRxDedicatedHandler(const uint8 HwControllerId,
const Can_17_McmCan_ProcessingType ProcessingType,
const Can_17_McmCan_CoreConfigType *const CoreConfigPtr,
const Can_17_McmCan_CoreGlobalType *const CoreGlobalPtr)
通过寄存器的各个位来知道有没有新的数据。和定位。
Ifx_CAN_N_NDAT1 NDAT1; /**< \brief 198, New Data 1 ${i}*/
Ifx_CAN_N_NDAT2 NDAT2; /**< \brief 19C, New Data 2 ${i}*/
上面一直都在准备,都在确定新的数据和各种有效性。直到这里才开始真正的从buffer中读取到协议栈的buffer里。
static void Can_17_McmCan_lRxExtractData(const uint8 HwControllerId,
const uint8 RxBuffIndex,
const Can_17_McmCan_RxBufferType RxBuffer,
const Can_17_McmCan_CoreGlobalType *const CoreGlobalPtr,
const Can_17_McmCan_CoreConfigType *const CoreConfigPtr)
这里通过两段代码获取到can内核ram的buffer地址。
if (TRUE ==
CoreConfigPtr->CanControllerConfigPtr[HwControllerId].CanFDSupport)
{
/* Load the data receive message buffer address */
RxReadStartAddr = RxReadStartAddr + (RxReadOffsetAddr *
(uint32)CAN_17_MCMCAN_MSG_RAM_ELEMENT_SIZE_FD);
}
else
#endif
{
/* Load the data receive message buffer address */
RxReadStartAddr = RxReadStartAddr + (uint32)
(RxReadOffsetAddr * CAN_17_MCMCAN_MSG_RAM_ELEMENT_SIZE_NFD);
}
注意这里的CANFD 和 CAN 是不一样的长度。但是下面的寄存器等访问,读写都是一致的。
首先对数据长度进行解析
/* Extract DLC information */
RxMsgDLC = RxMsgRAMPtr->R1.B.DLC;
最终会把内核的里数据,ID等信息传给 autosar定义的pdu里面
/* Copy Message from RAM section to local data buffer */
while (RxMsgCnt < RxMsgDLC)
{
/* copy data from byte0 to n the buffer */
RxMessageData[HwControllerId][RxMsgCnt] = RxMsgRAMPtr->DB[RxMsgCnt].U;
/* Increment to point next byte */
RxMsgCnt++;
}
/* Call CanIf_RxIndication function with receive message information */
RxMailBoxInfo.CanId = RxMsgId;
RxMailBoxInfo.ControllerId = HwControllerId;
RxPduInfo.SduLength = RxMsgDLC;
/*MISRA2012_RULE_1_3_JUSTIFICATION:Address of auto variable is used
only to read the values. The address is not used to perform any
pointer arithmetic. hence no side effect has been seen.*/
RxPduInfo.SduDataPtr = &RxMessageData[HwControllerId][0U];
所以一般的可以去查看RxMailBoxInfo 这个信息。这里面是否有想要的内容,内容对不对。从内核到本地ram再到传给BSW 协议栈的pdu信息。
直到这里,内核的ram数据已经完全的被取出来。过程有很多检查和CANFD 等check这里省略了 。只留下了主干。下一步到了BSW协议栈。
CAN RX BSW里面做了什么
FUNC(void, CANIF_CODE) CanIf_RxIndication(
P2CONST (Can_HwType, AUTOMATIC, CANIF_APPL_DATA) Mailbox,
P2CONST (PduInfoType, AUTOMATIC, CANIF_APPL_DATA) PduInfoPtr
)
这里面先看形参, mailbox 和 pduinfo
这个mailbox 是和具体的pdu对应的。pduinfo 里面包含的是数据的具体信息。
如果不加入个人集成代码的话,直接到下面一步骤。
/* HIS METRIC PATH,v(G),STMT,LEVEL,RETURN VIOLATION IN CanIf_RxIndication_Internal: Function contains very simple "else if" statements and "switch-cases".
* HIS metric compliance would decrease readability and maintainability. Also Function contains more than one return statement.
* This is needed mainly because of DET. */
/* BSW-415 */
void CanIf_RxIndication_Internal( const Can_HwType * Mailbox,
const PduInfoType * PduInfoPtr)
这一步主要干什么呢。
检查controller mode 是否正确。
if((uint8)CANIF_CS_STARTED == (((ControllerState_ps->Ctrl_Pdu_mode)& CANIF_BIT_MASK_PDU_MODE)>>CANIF_Ctrl_BIT_SHIFT))
检查接收路径是否激活,等等规范性,正确性检查。
最终会通过下面两个文件找到对应配置的pdu.
CanIf_PBcfg.c
CanIf_Cfg.h
这里假设找到了。
才开始复制到真正的canif buffer里面
/***********************************************************************************************************************
** Function name : CanIf_Prv_WriteRxBuffer **
** Syntax : void CanIf_Prv_WriteRxBuffer(Const uint8* CanSduPtr,Const CanIf_Cfg_RxPduType_tst* RxPduCfgPtr,**
** Const CanDlc,Const CanId) **
** Service ID[hex] : -- **
** Sync/Async : -- **
** Reentrancy : -- **
** Parameters (in) : CanSduPtr, RxPduCfgPtr **
** Parameters (inout): None **
** Parameters (out) : None **
** Return value : None **
** Description : Internal function called by API CanIf_RxIndication to write data to receive buffer **
**********************************************************************************************************************/
#define CANIF_START_SEC_CODE
#include "CanIf_MemMap.h"
#if (CANIF_PUBLIC_READRXPDU_DATA_API == STD_ON && CANIF_CFG_READRXPDU_DATA_IN_RXPDUS == STD_ON)
void CanIf_Prv_WriteRxBuffer(const uint8 * CanSduPtr,
const CanIf_Cfg_RxPduType_tst * RxPduCfg_pcst,
const PduLengthType CanDlc, const Can_IdType CanId
)
这个函数没什么好介绍的。正常的检测,copy函数。
之后陷入更进一步的函数。这里决定了下一步BSW的走向。
Std_ReturnType CanIf_XCore_LocalCore_RxIndication(const CanIf_Cfg_RxPduType_tst * CanIf_RXPduConfig_pst,
const PduInfoType * CanIf_ULPduinfo_pst)
最重要的一步,这是个函数指针,指向不同的函数。
/* Notification to Autosar types */
CanIf_Prv_ConfigSet_tpst->RxAutosarUL_Ptr[RxPduConfig_pst->IndexForUL_u8].CanIfRxPduIndicationName(RxPduConfig_pst->RxPduTargetId_t, CanIf_ULPduinfo_pst);
主要的全局变量有
const CanIf_ConfigType * CanIf_Prv_ConfigSet_tpst;
static const CanIf_RxCbk_Prototype CanIf_Prv_ULName_ta__fct[] =
{
{&CanNm_RxIndication},
{&CanTp_RxIndication},
{&PduR_CanIfRxIndication},
{&Xcp_CanIfRxIndication},
};
这个在文件
CanIf_PBcfg.c
删改你一个是具体的配置,一个是canif的走向。所以当APP层收不到报文的时候,可以查一下这几个回调函数,有没有被调用到。如果没有的话,就按照上面文章一步一步去查。如果这一步有被执行到。那么基本和canif以及下面的mcal没有关系了。
好,这里我们假设调用到了PduR_CanIfRxIndication.也就是路由。
/**
**************************************************************************************************
* PduR_dCanIfRxIndication - PDU Router CanIfRxIndication
Function to be invoked DET enable for PduR_CanIfRxIndication
* This function is called by the CanIf after a L-PDU has been received.
*
* \param PduIdType CanIf_RxPduId
* const uint8 *ptr
*
* \retval None
* \seealso
* \usedresources
**************************************************************************************************
*/
void PduR_dCanIfRxIndication( PduIdType id, const PduInfoType * ptr )
这里面主要决定路由到com层的具体哪里。涉及到很多配置与全局变量。
全局变量可以到这里找
PduR_PBcfg.c
这里给出了两个路径,一个是大数据com 一个是普通的com. 我们暂且认为路由到了普通的com.
const PduR_upIfRxIndicationFuncType PduR_upIfRxIndicationTable[] =
{
{&PduR_RF_Com_RxIndication_Func},
{&PduR_RF_LdCom_RxIndication_Func}
};
到这里面已经没有了报文的概念,完全的就是独立的单个的pdu.
/*
**********************************************************************************************************************
Function name : Com_RxIndication
Description : Service called by the lower layer after an I-PDU has been received.
Parameter : idRxPdu_uo,pduInfoPtr_pcst
Return value : None
**********************************************************************************************************************
*/
#define COM_START_SEC_CODE
#include "Com_MemMap.h"
void Com_RxIndication(PduIdType idRxPdu_uo, const PduInfoType * pduInfoPtr_pcst)
首先是对PDU本身属性进行检查,对PDU 的开关进行检查,检查是不是又group的存在。等等,检查完之后,要对pdu进行拆分成不同的signal.
因为上层是使用具体的signal.
/*
/*
**********************************************************************************************************************
Function name : Com_Prv_ProcessSignal
Description : Process rx-signals of the received I-PDU.
Parameter : idRxPdu_uo - ID of the received I-PDU.
: pduInfoPtr_pcst - Contains the length (SduLength) of the received I-PDU and
a pointer to a buffer (SduDataPtr) containing the I-PDU.
Return value : None
**********************************************************************************************************************
*/
#define COM_START_SEC_CODE
#include "Com_MemMap.h"
void Com_Prv_ProcessSignal(PduIdType idRxPdu_uo, const PduInfoType * pduInfoPtr_pcst)
这里面就会根据DBC的信息,LSB,MSB 长度,等等信息来解读每一个signal.获取每一个signal的信息。
/* Set the _SIGNOTIF flag to invoke configured signal-ComNotification */
Com_SetRamValue(RXSIG,_SIGNOTIF,rxSigRamPtr_pst->rxSigRAMFields_u8,processSignal_b);
在取出所有的信息内容之后,调用com配置的回调函数。这个回调函数是针对每一个signal的。这里有一个重要的全局变量。也是配置产生的。
const Com_Prv_xRxSigCfg_tst Com_Prv_xRxSigCfg_acst[COM_NUM_RX_SIGNALS]
这里面记录了所有的回调函数,
这里比如。
下面函数就是某一个signal在com里的回调。那么他实现了什么呢。
Rte_COMCbk_facaiziyou_signal
comstatus = Com_ReceiveSignal(((VAR(Com_SignalIdType, AUTOMATIC))66), &facai);
if ( ((VAR(StatusType, AUTOMATIC))E_OK) != comstatus )
{
read_ok = ((VAR(boolean, AUTOMATIC))FALSE);
}
/* Box: Data Conversion begin */
/* Box: floating point data conversion for Reception direction begin */
data = ( ( ((VAR(float32, AUTOMATIC))Rte_FPConv) + -2500.000000 ) / 5.000000 );
/* Box: floating point data conversion for Reception direction end */
/* Box: Data Conversion end */
/* Box: receive end */
/* Box: process begin */
if ( TRUE == read_ok )
{
/* source OsApplications: OsApp_SysCore */
/* providing OsApplication: OsApp_ComCore */
/* destination OsApplication: OsApp_ComCore */
(void)IocWrite_Rte_Rx_000660(data);
}
通过receive 把数据读到了
IocWrite_Rte_Rx_000660
为什么这里是读到了Ioc呢。因为配置了不同的core.这里不详细介绍了。下次开一篇单独说一下。核间通讯
这里可以简单地理解 signal 到了一个全局变量。这个全局变量叫做IocWrite_Rte_Rx_000660
好了,终于结束了。谢谢观看
文章转载自公众号:汽车与基础软件