文章目录
前言
本文介绍CP AUTOSAR 架构下的CanIf组件,基于S32K312芯片、Vector提供的CBD包,使用DaVinci Configurator工具进行配置的经验。
CanIf组件位于Communication Hardware Abstraction层,是Can Driver的接口层。
CanIf组件实现MCU的CAN数据收发、模式控制,被上层CanTp、Dcm、CanNm等调用。
上图为CP AUTOSAR CAN网络架构。
一、概念
(一)、L-SDU、L-PDU、L-PDU Handle
L-SDU:CAN数据链路层的Service Data Unit,是L-PDU中的一个字段。
L-PDU:CAN数据链路层的Protocol Data Unit,由Identifier、DLC、L-SDU组成。
L-PDU Handle:L-PDU句柄,由CanIf定义并提供。
(二)、CanHardwareUnit、CanController、CanTransceiver、PhysicalChannel
PhysicalChannel:
CAN物理通道,表示CAN网络的接口,MCU的某一组CAN TX和RX引脚和一片CanTransceiver相连,CanTransceiver再和总线上的CANH和CANL相连,那这一段便是CAN驱动的物理通道。
CAN硬件单元的不同物理通道可以访问不同的CAN网络。
CanTransceiver:
CAN收发器,通常为一个芯片,MCU的CAN TX和RX输入输出信号为数字信号,需要CAN收发器经过电平转换传输到CAN总线上。
CanController:
CAN控制器,一个CAN控制器连接一个CAN物理通道,一般MCU里有若干个CAN控制器,每个控制器有一组CANTX和RX,实现数据的收发、波特率、波特率、采样点设置等。
CanHardwareUnit:
CAN硬件单元,硬件单元由一个或多个CAN控制器和一个或多个CAN邮箱组成,可以是片上的也可以是外部设备,由一个Can驱动程序表示,比如车上某一段CAN网络里某一个连接的ECU便是一个CAN硬件单元。
示例架构如下:
(三)、HardwareObject、HOH、HTH、HRH
HardwareObject:
Can驱动程序的硬件对象,定义为CAN硬件单元内的CAN RAM的消息缓冲区,比如一个硬件对象里属性包括了该对象属于哪个控制器、有哪个CAN邮箱、帧类型、过滤器、是接收还是发送、是BASIC还是FULL等。
HOH:
Hardware Object Handle,硬件对象句柄由Can驱动程序定义和提供,一个HOH代表一个硬件对象。
HTH:
Hardware Transmit Handle,硬件传输句柄由Can驱动程序定义和提供,一个HTH能代表一个或多个发送类别的LPDU。
HRH:
Hardware Receive Handle,硬件接收句柄由Can驱动程序定义和提供,一个HRH能代表一个或多个接收类别的LPDU。
通常硬件对象指的是CAN邮箱,在CanDrv里创建了一个硬件对象,该硬件对象里有一个或多个CAN邮箱,然后在CanIf里创建了若干个HTH和HRH,每个HTH和HRH链接CanDrv里一个硬件对象,然后又创建了若干个LPDU分配到对应的HTH和HRH。
二、CanIf与其他组件的关系
上层组件进行CAN收发不会之间调用Can驱动的程序,而是直接通过CanIf的接口去收发,所以CanIf里收发时就需要有策略或算法知道自己要发送接收的CANID对应的是Can驱动里哪个HTH或HRH。
CanIf上层组件有PduR、CanNm、CanTp、CanSm、EcuM、CanTsyn、J1939Tp、J1939Nm、Xcp等。
CanIf在EcuM里进行初始化。
CanSm调用CanIf的控制器管理。
CanIf下层组件则有Can、CanTrcv。
三、CanIf发送缓冲区和优先级反转
CanIf组件里的每个L-PDU支持带有发送缓冲区,CanIf_Transmit()里调用Can_Write()时如果返回BUSY并且CanIfBufferSize不为0的话,返回BUSY代表Can驱动里的HTH还没发送完,那么会把当前的L-PDU存到发送缓冲区里,等到该HTH发送完调用CanIf_TxConfirmation()时,在这里面如果发现发送缓冲区有数据的话便调用Can_Write()把发送缓冲区里的数据发送出去。
如果Can驱动里的HTH被多个L-PDU引用并且多个L-PDU的数据在缓冲区待发送的话,那么CanIf会根据L-PDU的CANID优先级来决定先等HTH邮箱释放后发送哪一个。
CanIf_Transmit()里更新发送缓冲区时,可能因为其他中断或抢占式任务也调用CanIf_Transmit()更新相同的缓冲区,那么会发生数据的不完整性,所以在更新缓冲区时需要调用Can_EnableControllerInterrupts()或Can_DisableControllerInterrupts()形成临界区来确保更新缓冲区不被打断。
如果发送缓冲区已满,但是新的数据需要填充缓冲区,那么新的数据会覆盖旧的数据来填充缓冲区。
FullCan类型的Pdu没有发送缓冲区。
如果低优先级的CAN消息因为HTH已经占用而没发出去,而同一个HTH的高优先级的CAN消息已经请求发送,那么便会出现高优先级的CAN消息进入发送缓冲区,便有了内部优先级反转情况,Vector的代码里可以使能Controller Driver Tx Cancellation,当发生这情况时,CanIf将调用Can驱动层的发生取消函数取消低优先级的请求,如果当前缓冲区空闲,则将放置缓冲区里否则丢弃,这样高优先级的消息请求便会排在前面。
四、CanIf接收过滤
如果多个L-PDU都是同一个HRH,那么该HRH接收到数据时进入CanIf_RxIndication(),会先判断收到的报文ID是不是在范围内,这个范围为CanIfHrhRangeRxPduLowerCanId和CanIfHrhRangeRxPduUpperCanId。CanId在范围内的话再通过滤波算法寻找对应的L-PDU,用到算法可能有linear search、table search、hash search,如果没有对应的L-PDU,则退出CanIf_RxIndication(),找到对应的L-PDU后再进入该L-PDU上层组件的指示函数里。
五、CanIf接收DLC检查
当HRH收到数据调用CanIf_RxIndication()时,CanIf_RxIndication()里找到对应的L-PDU后,如果DLCCheck有使能的话,那么会先判断HRH的数据长度是否和对应L-PDU的预期长度一样,不一样的话则抛弃该数据,一样的话进入该L-PDU上层组件的指示函数里。
有些客户需求会让你只要接收到对应的CANID都要进行处理,不管数据长度对不对,那这时候就不能开启DLCCheck,只要是预期的CAN报文,就把报文信息如ID、长度、数据传给上层组件的指示函数里。
可以使能Com层的每个PDU接收Callout,用户在里面自定义信号是不是预期的长度,是的话返回正确信号值就更新了。
如果DLC在传输中是动态变化的话比如诊断报文,那么与诊断报文有关的HRH就要关掉DLC检查不然传不到CanTp了。
六、PDU通道模式控制
CanIf支持调用CanIf_SetPduMode()为每个CAN控制器通道设置为CANIF_OFFLINE、CANIF_ONLINE、CANIF_TX_OFFLINE_ACTIVE、CANIF_TX_OFFLINE等模式,只有CAN控制器处于CAN_CS_STARTED时才允许被设置。
CanIf_SetPduMode()被CanSm组件所调用用来控制PDU通道模式。
CANIF_ONLINE:
调用CanIf_Transmit()时会将L-PDU传到CanDrv,发送完后会调用上层组件的发送确认函数,接收到数据后会调用上层组件的接收指示函数。
CANIF_TX_OFFLINE_ACTIVE:
调用CanIf_Transmit()时不会把数据传到Can驱动层,但是会调用CanIf_TxConfirmation通知上层组件已发送完。该功能用在某些特殊的诊断模式上。
CANIF_OFFLINE:
处于这个模式时L-PDU不能传输至Can驱动层,CanIf层的发送缓冲区也被清除,接收指示和发送确认函数都不会调用,Can驱动层还是会接收到数据。在调用CanIf_SetControllerMode(ControllerId,CANIF_CS_SLEEP)时和CanIf_Init()时进入。
CANIF_TX_OFFLINE:
在调用CanIf_SetControllerMode(ControllerId,CANIF_CS_STOPPED)或进入CanIf_ControllerBusOff()时设置为CANIF_TX_OFFLINE模式,这时L-PDU不能传输至Can驱动层,CanIf层的发送缓冲区也被清除,发送确认函数不会调用但接收指示函数会调用。
在CANIF_OFFLINE和CANIF_TX_OFFLINE下因为没有发送所以不会触发BusOff,但是如果硬件缓冲区还有数据在发送并且触发了BusOff,这时不会阻止BusOff的通知。
七、CAN控制器模式切换和BusOff切换序列图
CanIf支持用CanIf_SetControllerMode()切换CAN控制器的模式,一般被CanSm组件所调用。
支持的模式有:
CANIF_CS_UNINIT
CAN控制器未初始化,CAN寄存器也处于初始状态,CAN控制器不能参与总线的数据收发。
CANIF_CS_SLEEP
CAN控制器处于睡眠状态,睡眠状态下不参与总线的数据收发,允许被事件唤醒,唤醒后功能正常。
一般MCU的CAN控制器支持进入睡眠模式,如果CAN控制器不支持睡眠模式,那么软件应该模拟CAN控制器进入睡眠模式。
CANIF_CS_STARTED
CAN控制器已经有完整功能,参与总线的数据收发。
CANIF_CS_STOPPED
CAN控制器已经初始化,但不参与总线,也就是说不会数据收发也不会发错误帧和ACK。
CAN控制器设置为STARTED的序列图:
当触发BusOff后的序列图:
当BusOff恢复后的序列图:
八、动态CANID、CANFD
CanIf支持在发送L-PDU时,修改要发送PDU的CANID,就算L-PDU一开始就在配置表里已经配好发送ID了,但还是可以调用CanIf_SetDynamicTxId()来设置发送的CANID,如果MCU重新上电后,之前配置的动态ID无效还是按配置表来。
CAN控制器是否发送CANFD由Can驱动层判断,Can驱动判断CanIf传递的CANID。
九、TriggerTransmit
当CANIF_TRIGGER_TRANSMIT_SUPPORT使能时,用户调用CanIf_Transmit()时,可以不传入数据和长度,该请求传递到Can驱动里后,Can驱动里调用CanIf_TriggerTransmit(),CanIf_TriggerTransmit()再调用上层CallOut,上层里填充PDU的数据和长度,返回后Can驱动里再发送出去。CanIf_TriggerTransmit()被调用时传入参数SduDataPtr为NULL,触发传输模式激活,CanIf根据HANDLE判断上层是哪个模块,便调用哪个模块的XXTriggerTransmit()。
十、接收流程
(一)、接收指示中断模式
如图上所示接收数据传递流程如下:
1、CAN控制器里收到数据触发中断。
2、进入中断后Can驱动程序找到当前有数据的邮箱,并对当前邮箱进行保护锁定即当前邮箱不能接收数据,这个要看MCU支不支持,有些MCU没这功能的话可能程序里添加临界区保护或者就不做但是会有数据覆盖风险。
3、这时候Can驱动程序应该开辟临时数组读取邮箱里的数据再传递给CanIf或者通过指针指向邮箱再传递给CanIf这样的好处是省掉Copy的环节提升效率,一般都先把邮箱里数据复制到临时数组再传递。图上的Data normalization不知道是需要通过软件去配置使能还是什么也没在Can驱动规范找到相关描述。
4、Can驱动将CANID、数据长度、数据、HRH索引通过CanIf_RxIndication()传递给CanIf。
5、CanIf进行CANID过滤(如果有配置使能的话),如果是无效的CANID则不处理,否则进行下一步。
6、CanIf进行DLC检查(如果有配置使能的话),如果是无效的DLC则不处理,否则进行下一步。
7、CanIf找到对应的L-PDU后将L-PDU索引、L-SDU、L-SDU长度传递给上一层的指示函数比如CanTp。
8、上一层指示函数处理完后,整个流程结束,Can驱动对当前的邮箱取消保护锁定(如果有的话)。
(二)、接收指示轮询模式
与接收指示中断模式类似,只不过CAN控制器的邮箱有数据后,处理动作是放在Can_MainFunction_Read()里,因为是轮询的,所以Can驱动最好有保护锁定。
(三)、读取接收数据
如果CanIfPublicReadRxPduDataApi有使能,那么CanIf_RxIndication()里在CANID检查和DLC检查都通过后,会将Can驱动传来的数据先复制到L-PDU缓冲区里,结束后能通过CanIf_ReadRxNotifStatus()知道发生过指示后,再通过CanIf_ReadRxPduData()就能读出之前缓冲区里的数据了。
十一、发送流程
(一)、传输请求
如上图分别是单个CAN控制器和多个CAN控制器的发送请求。
当调用CanIf_Transmit()后将L-PDU和对应的HTH传给Can驱动后,Can驱动会检查HTH的CAN邮箱是否在忙也就是是否还在发数据,如果在忙的话CanIf层将L-PDU保存在发送缓冲区待下次发送。
(二)、传输指示
上图为传输指示的中断模式和轮询模式,数据传到Can驱动的邮箱后,邮箱里的数据发送成功后如果是中断模式进入邮箱的发送成功中断再调用CanIf_TxConfirmation(),如果是轮询模式在Can_MainFunction_Write()里检测到有发送完成标志再调用CanIf_TxConfirmation()。
十二、CAN控制器和CAN收发器休眠唤醒流程
Vector里CanIf文档里这样描述Can的休眠唤醒流程的,本文暂不详细描述,直接上文档里的内容:
十三、主要代码描述
(一)、void CanIf_Init( void )
CanIf组件的初始化。
(二)、Std_ReturnType CanIf_SetControllerMode( uint8 ControllerId,ControllerStateType ControllerMode )
用来设置CAN控制器的模式。
(三)、Std_ReturnType CanIf_GetControllerMode( uint8 ControllerId,ControllerStateType* ControllerModePtr )
获取CAN控制器的模式。
(四)、Std_ReturnType CanIf_Transmit( PduIdType CanTxPduId,const PduInfoType* PduInfoPtr )
传输L-PDU数据。
CanTxPduId:TX L-PDU的索引。
PduInfoPtr:里面包含了传输数据的地址指针和长度,需要注意长度不能超过静态配置的最大长度,长度由上层定义好传入CanIf里。
(五)、Std_ReturnType CanIf_SetPduMode( uint8 ControllerId,CanIf_PduSetModeType PduModeRequest )
设置控制器的PDU模式。
(六)、CanIf_NotifStatusType CanIf_ReadTxNotifStatus( PduIdType CanTxPduId )
获取是否发生过TX的确认通知。
(七)、Std_ReturnType CanIf_ReadRxPduData( PduIdType CanTxPduId,PduInfoType* PduInfoPtr )
读取接收到的L-PDU数据。
(八)、void CanIf_TxConfirmation(PduIdType CanTxPduId)
CanTxPduId:发送成功的TX - PDU的句柄。
TX-PDU发送成功后被Can驱动调用用来通知CanIf层,CanIf再通知上层。
(九)、void CanIf_RxIndication(CanIf_HwHandleType Hrh,Can_IdType CanId,uint8 CanDlc,const uint8* CanSduPtr)
Hrh:接收到的RX - PDU的句柄。
CanId:接收到的RX - PDU的CANID。
CanDlc:接收到的RX - PDU的数据长度。
CanSduPtr:接收到的数据指针。
RX-PDU接收成功后被Can驱动调用用来通知CanIf层,CanIf再通知上层。
(十)、Std_ReturnType CanIf_CheckWakeup(EcuM_WakeupSourceType WakeupSource)
用来检查CAN控制器或者CAN收发器有没有唤醒事件,如果任意有则返回OK。
(十一)、void CanIf_ConfirmPnAvailability(uint8 TransceiverId)
该函数指示某个CAN收发器开启了PN功能,里面调用了上层函数比如CanSm用来通知CanNm已经开启了PN功能。
(十二)、void CanIf_ConfirmCtrlPnAvailability(uint8 TransceiverId)
该函数指示某个CAN控制器开启了PN功能,里面调用了上层函数比如CanSm用来通知CanNm已经开启了PN功能。是回调函数被CanDrv和CanTrcv调用。
(十三)、Std_ReturnType CanIf_SetTrcvMode(uint8 TransceiverId, CanTrcv_TrcvModeType TransceiverMode)
设置CAN收发器为NORMAL、STANDBY、SLEEP模式之一。
(十四)、Std_ReturnType CanIf_GetTrcvWakeupReason(uint8 TransceiverId, CanTrcv_TrcvWakeupReasonType* TrcvWuReasonPtr)
获取收发器的唤醒源。
十四、DaVinci Configurator主要配置
(一)、CanIfPublicCfg部分:
Bus Mirroring Support:使用总线对镜像的支持,需要与Can驱动一起支持。
Meta Data Support:使用SDU元数据进行动态ID处理。
Multiple Drv Support:允许支持多个Can驱动。
Tx Buffering:开启发送缓冲区。
Wakeup Check Validation Support:开启唤醒验证。
Wakeup Check Validation By NM:只对NM报文进行唤醒验证。
(二)、CanIfPrivateCfg部分:
Data Checksum Rx Support:
Data Checksum Tx Support:
使能发送接收进行CHECKSUM,如果校验失败还可以在Dispatch Data Checksum Rx Error Indication Name里添加通知函数。可分别在CanIf_RxIndicationSubDataChecksumRxVerify()和CanIf_TransmitSubDataChecksumTxAppend()中实现校验和算法。
Dlc Check:使能DLC检查。
Software Filter Type:选择CANID滤波算法类型。
Transceiver Handing:使能支持CAN收发器功能。
Tx Buffer Fifo Support:使能TX缓冲区类型为FIFO,最终在CanIfBufferCfg那选择哪一种类型。
Tx Buffer Prio By Can Id Support:使能TX缓冲区类型为优先级高的排前面,最终在CanIfBufferCfg那选择哪一种类型。
Wakeup Support:使能支持CAN的唤醒事件,使能后才会调用CanIf_CheckWakeup()。
Wakeup Vaildate All Rx Msgs:使能后所有从Can驱动收来的报文都会有唤醒事件否则只有RX L-PDU才会有唤醒事件。
(三)、CanIfInitCfg部分:
CanIfBufferCfgs:
这是发送缓冲区的配置。
Buffer Hth Ref:映射哪个Can驱动的HTH。
Buffer Size:有多少个发送类型L-PDU就输入多少,表示HTH在忙时能存的LPDU最大数量。
Tx Buffer Handing Type:发送缓冲区的类型。
CanIfInitHohCfgs:
这里需要与Can驱动那对应,Can驱动有多少个HTH和HRH,这里也要添加对应的数量。
HRH列表可以添加要接收的CANID范围。
CanIfRxPduCfgs:
CanIfRxPduCfgs处添加RX L-PDU列表,从图上可知改RX PDU映射Can驱动的哪个HRH、要接收的PDU CANID是什么、DLC最大长度是多少、该PDU是否进行DLC检查,注意如果LPDU的DLC是动态可变的要把DLC检查关掉、是否放进缓冲区供CanIf_ReadRxPduData()读取、接收到数据后上层指示函数是什么等。
Rx Pdu Ref是映射EcuC组件创建的PDU容器。
CanIfTxPduCfgs:
CanIfTxPduCfgs处添加TX L-PDU列表,从图上可知改TX PDU映射Can驱动的哪个TX缓冲区,因为CanIf有发送缓冲区的机制,所以这里不是映射HTH而是在缓冲区那里映射HTH、最大DLC长度是多少、要发送的PDU CANID是什么、CANID的类型是什么、是否允许配置动态ID、发送成功后上层确认函数是什么等。
Tx Pdu Ref是映射EcuC组件创建的PDU容器。
(四)、CanIfDispatchCfg部分:
此处可以添加一些CallOut,如收发器检测到唤醒帧指示上层函数、启用了PN功能确认指示上层函数、产生BUSOFF指示上层函数、收发器模式切换指示上层函数、唤醒事件验证上层函数等,这里除了添加AUTOSAR指定的上层接口外,还可以添加自己写的CDD接口用来,例如自己重写个BUSOFF处理函数实现自定义处理方式。
(五)、CanIfTrcvDrvCfg部分:
此处是CAN收发器部分如果有用到CAN收发器添加CanTrcv组件的通道,如果有用到收发器的唤醒源则勾选。
(六)、CanIfTrcvDrvCfg部分:
此处是CAN控制器部分添加Can组件的通道。
(七)、CanIfCtrlDrvCfgs部分:
Controller Driver Tx Cancellation:
使能后允许取消当前发送请求,可以避免发生CanIf层内部优先级反转。
CanIf组件的配置需要和EcuC和Can组件配合。
十五、使用范例
(一)、初始化完后执行写操作
/**忽略其他配置**/
PduInfoType Pdu;
uint8 sdu[8u] = {1,2,3,4,5,6,7,8};
Pdu.SduDataPtr = sdu;
Pdu.SduLength = 0x08u;
Can_Init(Can_Config_Ptr);
CanIf_Init();
Can_SetControllerMode(0u,CAN_T_START);
CanIf_Transmit(0u, &Pdu);
十六、参考资料
AUTOSAR_SWS_CANInterface
AUTOSAR_SRS_CAN
AUTOSAR_EXP_LayeredSoftwareArchitecture
TechnicalReference_CanIf
ISO7498
ISO11898
ISO11519
ISO15765
ISO14229
can2.0
can_fd_spec
总结
本文没有对CanIf的PN功能进行描述,也没描述Mirror,也没描述元数据,也没详细描述CAN控制器的休眠唤醒流程,也没描述多分区,也没描述跟CAN收发器的交互,另外本文文字描述多点,更像是本人的使用笔记,仅供参考,如有不对地方欢迎指教。