在上一篇,我们介绍了CAN模块,接下来我们介绍在CAN模块之上的模块Can Interface(CanIf)模块。在AUTOSAR软件架构中,CanIf也在BSW层,它处于CAN模块之上紧挨着CAN模块。CanIf是一个硬件独立层,具有到 CAN 驱动程序和 CAN 收发器驱动程序层以及 PDU 路由器、通信管理器和网络管理等上层的标准化接口。 所有需要 CAN 通信的上层都必须与负责 CAN 通信处理的 CanIf通信。 这包括消息的传输和接收以及 CAN 控制器的状态处理。下图显示了 CAN 接口在 AUTOSAR 架构中的位置。
CanIf 提供的服务可分为以下几大类:
- 初始化
- 数据发送
- 数据接收
- Can控制器模式控制
- PDU 模式控制
在介绍CanIf提供的服务之前,我们首先理解几个概念:Hardware Object Handles(HOH)、Hardware Transmit Handle(HTH) 和 Hardware Receive Handle(HRH)。
HOH是CAN Mailbox数据结构的抽象,它包括CanId、DLC和数据等CAN相关的参数。基于CAN硬件buffer抽象出来的在CanIf中被引用的硬件对象是独立于CAN硬件buffer。HOH被用做调用CanDrv的接口服务的传参,它有CanDrv的配置提供,被CanDrv用来识别CAN邮箱的通信buffer。
CanIf作为HOH的使用者,不会根据硬件特定信息对其进行解释。 因此,CanIf 保持独立于硬件
HOH分为两类:HTH和HRH
- HRH是对一个CAN控制器信箱的逻辑Hardware Receive Object的引用的handle;
- HTH是对一个CAN控制器信箱的逻辑Hardware Transmit Object的引用的handle;
一个HRH可以被配置用于接收:
- 一个CanId(FullCan)
- 一组CanIds(BasicCan)
- 一个范围/区域 CanIds(BasicCan)
- 所有CanIds
一个HTH可以被配置用于发送:
- 一个CanId
- 固定的一组CanIds
下图是一帧Can报文到硬件对象之间的映射关系
HTH需要在配置阶段静态分配到一个CanIfBufferCfg,CanIf Tx L-PDU不直接引用HTHs而是引用CanIfBufferCfg。
CanIf 存储有关为传输目的配置的可用硬件对象的信息。 函数CanIf_Transmit()将CanTxPduId映射到对应的HTH,调用函数Can_Write()。
如果全局启用总线镜像并且已通过调用 CAN 控制器的 CanIf_EnableBusMirroring() 激活,则 CanIf 应在使用 Can_Write() 在该控制器上传输之前存储每个帧的内容。帧内容只应在实际发送时提供给总线镜像模块。 因此,必须存储帧内容,以便它可以从 CanIf_TxConfirmation() 中提供给总线镜像模块。
初始化
EcuM调用CanIf的函数CanIf_Init()对整个CanIf进行初始化。 所有全局变量和数据结构都在初始化过程中被初始化,包括标志和缓冲区。 EcuM通过调用相应的初始化服务分别对CanDrvs和CanTrcvs进行初始化。 CanIf 期望 CAN 控制器在初始化过程完成后保持在 STOPPED 模式,就像在上电复位之后一样。 在这种模式下,CanIf 和 CanDrv 既不能发送也不能接收 CAN L-PDU。 如果需要在运行时重新初始化整个 CAN 模块,EcuM 应调用 CanSm 以通过调用 CAN 接口模块的 API 服务 CanIf_SetControllerMode () 来发起所需的CAN 控制器状态转换。 CanIf 将来自 CanSm 的调用映射到相应 CanDrvs 的调用。
数据发送
请求发送
CanIf 的发送请求函数 CanIf_Transmit() 是上层在 CAN 网络上发送 L-PDU 的通用接口。 上层通信层模块仅通过 CanIf 的服务发起传输,而无需直接访问 CanDrv。 如果 CanDrv 可以将 L-PDU 数据写入 CAN 硬件传输对象,则发起的传输请求成功完成。
CanIf在调用CanIf_Transmit()时执行以下行为:
- 检查CanIf的初始化状态;
- 识别CanDrv(仅当使用多个CanDrv时)
- 决定用于访问CAN硬件传输对象的HTH
- 调用CanDrv的Can_Write()函数
如果发送成功,CanIf_Transmit()返回E_OK。
发送请求时序图
发送数据流
传输请求服务 CanIf_Transmit() 是基于 L-PDU。 对 L-SDU 特定数据的访问由以下参数组织:
- Transmit L-PDU => L-SDU ID
- 引用一个数据结构,其中包含L-SDU相关数据:指向L-SDU的指针,指向MetaData的指针和L-SDU长度。
对 L-SDU 数据结构的引用在多个 CanIf 的 API 服务中用作参数,例如 CanIf_Transmit() 或回调服务 <User_RxIndication>()。在 L-PDU 被配置为触发传输的情况下,L-SDU 指针是空指针。
发送数据流示意图
发送缓存
在 CanIf 范围内,传输过程从调用 CanIf_Transmit() 开始,到调用上层模块的回调服务 <User_TxConfirmation>() 结束。在传输过程中,CanIf、CanDrv 和 CAN Mailbox 一起将要传输的 L-PDU 存储在一个位置。 根据传输方式,它们是:
- CAN硬件传输对象,或
- CanIf内部的发送L-PDU buffer(如果使能发送缓存)
如果使能发送缓存,如果Tx L-PDU在请求发送时被CanDrv拒绝,CanIf将会把Tx L-PDU储存在CanIf Transmit L-PDU buffer(即CanIfBufferCfg),但每个Tx L-PDU实体只会存一次。如果没有使能发送缓存,如果发送请求被拒绝,CanIf_Transmit()函数返回发送失败;若使能了发送缓存,发送请求因CanDrv CAN_BUSY被拒绝,L-PDU被缓存在CanIf的CanIfTxBuffer,此时尽管发送行为并未执行,但CanIf_Transmit()函数返回E_OK,在这种情况下,CanIf 通过 CanIf_TxConfirmation() 回调处理 L-PDU 的未完成传输,上层不必重试传输请求。
每个Tx L-PDU通过CanIfBufferCfg配置容器引用HTHs,即使不使用发送缓存也是这样配置,只不过这种情况中CanIfBufferCfg的参数CanIfBufferSize设置为0,此时CanIfBufferCfg配置容器仅仅是一个引用HTH的媒介。
当CanIfBufferSize配置大于0时,即可缓存CanIf Tx L-PDU或发送请求。如果L-PDU的发送需要保持特殊的次序,那么上层发送报文的请求必须依赖于前一帧报文发送成功的确认。
如果Tx L-PDU已经被缓存过,那么下次该L-PDU的缓存会直接覆盖上一次缓存的内容,因此发送出去的总是最新的数据。如果所有buffer都被占用,此时CanIf_Transmit()被调用发送一帧新的L-PDU(未被缓存过),那么该新的L-PDU或发送请求不会被缓存,CanIf_Transmit()返回E_NOT_OK。
CanIf在传输确认过程会判断CanIfTxBuffer中是否有缓存的L-PDU或发送请求,如果有则将其放入空闲的硬件传输对象。此过程是遵循优先级仲裁的,即优先级最高的先发送,CanIf对L-PDU和发送请求不做区分。当Can_Write()返回E_OK,CanIf立即将该L-PDU或发送请求从缓存清除(在发送确认返回之前)。
发送确认
如果前一个发送请求成功完成,CanDrv通过调用CanIf_TxConfirmation()通知CanIf。如果总线镜像全局使能且已调用CanIf_EnableBusMirroringSupport()对CAN控制器激活,那么在该CAN控制器上每帧经过CanIf_TxConfirmation()确认发送的报文,CanIf都应调用Mirror_ReportCanFrame()以存储报文内容和实际的CAN ID。
当CanIf_TxConformation()被调用时,CanIf需要识别被成功传输的L-PDU所连接的上层,并通过调用<User_TxConfirmation>(E_OK)通知上层传输完成,<User_TxConfirmation>()由上层实现。
上层通信层模块可以设计或配置为,用针对不同L-PDU或L-PDU组的单个或多个回调服务来处理发送确认。所有这些服务均在相应L-PDU发送请求的发送确认中由CanIf调用。Transmit L-PDU 能够发送与目标上层模块相关的不同的确认服务,此分配是在配置期间静态分配的。一个transmit L-PDU只能分配一个发送确认回调服务。
发送确认时序图(轮询模式)
数据接收
依照AUTOSAR基础软件架构,接收到的数据由上层通信栈(如:COM,CanNM,CanTp,DCM)解析,这意味着上层模块既不直接访问CanDrv的Rx缓存也不直接访问CanIf的Tx缓存。当CanIfPublicReadRxPduDataApi设为TRUE时,CanIf提供内部缓存Rx消息。当接收到一帧新L-PDU,CanDrv调用CanIf的CanIf_RxIndication()函数通知CanIf。对L-PDU数据的访问由以下参数组成:
- Hardware Receive Handle(HRH)
- Received CAN Identifier(CanId)
- Received Data Length
- Reference to Received L-PDU
Received L-PDU是独立于硬件的,位于通信系统的最底层CanDrv。HRH相当于CanDrv和使用L-PDU的上层之间的连接器。
在CanDrv调用CanIf_RxIndication()提示接收到L-PDU后,CanIf进行接收到收到报文提醒的后续处理。CanIf 无法识别 CanDrv 是使用临时缓冲还是直接硬件访问。 它期望在调用 CanIf_RxIndication() 时使用标准化的 L-PDU 数据。CAN硬件对象会被锁定直到数据拷贝到临时或上层模块buffer结束才结束。在CanIf的CanIf_RxIndication()函数返回后CAN硬件对象立即释放以避免(接收)数据丢失。属于所接收L-PDU的CanDrv、CanIf以及上层模块访问同一个临时的中间buffer,该临时中间buffer位于CAN控制器的CAN硬件对象或者位于CanDrv中的临时buffer。
报文接收数据流
数据接收提醒
函数CanIf_RxIndictaion()的参数引用了最新接收的CAN L-PDU。若函数CanIf_RxIndication()被调用,CanIf将评估接收到的CAN L-PDU并准备L-SDU用以后续上层通信模块访问。CAN L-PDU检测通过,若配置有<User_RxIndication>(), CanIf通过<User_RxIndication>()异步通知上层模块。详细过程如下:
如果全局使能总线镜像功能,并且通过调用CanIf_EnableBusMirroringSupport()针对CAN控制器激活,那么针对该CAN控制器接收的每一帧由CanIf_RxIndication()提醒的报文,CanIf应该调用Mirror_ReportCanFrame()。当CanIf_RxIndication()被调用,CanIf对接收的L-PDU进行软件过滤,若L-PDU没通过软件过滤,CanIf结束接收;若接收的L-PDU通过CanIf的软件过滤,CanIf进行数据长度校验;若通过数据长度校验,CanIf根据配置的数据长度拷贝数据到静态接收buffer。如果接收的L-PDU配置使用了MetaData还需要将CAN ID拷贝到CAN_ID-32类型的MetaDataItem。CanIf识别所接收的L-PDU是否配置了目标上层模块(CanIfRxPduUserRxIndicationUL),若配置目标上层模块,对对该L-PDU调用上层模块提供的提醒回调(CanIfRxPduUserRxIndicationName)。
一个L-PDU只可分配一个接收提醒回调服务函数
总结起来,在调用CanIf_RxIndication()阶段,CanIf做以下工作:
- 软件滤波(仅针对BasicCAN)
- 数据长度校验
- 缓存接收到的L-PDU
- 调用上层接收提醒回调服务
以上CanIf所作的工作都依赖于配置属性,若静态配置时没有配置,则跳过相应步骤
读取接收数据
上层模块用于读取从CAN网络接收到的L-PDU数据的API CanIf_ReadRxPduData()是共用的。上层模块只通过CanIf发起接受请求,并不直接访问CanDrv,当CanIf将接收到的L-PDU写入上层模块的buffer后,接受请求成功完成。函数CanIf_ReadRxPduData()可以在不依赖接收事件(RxIndication)的情况下读出数据。如果在配置时启用(参见CanIfPublicReadRxPduDataApi),则不一定要为L-SDU配置接收指示服务(参见CanIfRxPduUserRxIndicationUL)。如果需要,也可以启用接收指示。
通过这种方式CanIf的上层模块接收L-PDU的机制可以在配置时用参数CanIfRxPduUserRxIndicationUL和CanIfRxPduReadData根据上层模块的需求选择。
如果配置参数CanIfPublicReadRxPduDataApi设置为TRUE,CanIf应将每个接收到的L-SDU(CanIfRxPduReadData启用)存储到接收L-SDU缓冲器中。这意味着如果配置参数CanIfRxPduReadData设置为TRUE,CanIf必须为该接收L-SDU分配一个接收L-SDU buffer。调用CanIfRxIndication()后,通过软件滤波和数据长度校验CanIf应将接收到的L-SDU存放到这个接收L-SDU buffer,在调用CanIfRxReadData()时,分配的L_SDU buffer已有最近接收的L-SDU。
数据读取时序图
CAN控制器模式
CanIf提供服务,用以控制底层CAN控制器的通信模式。这意味着所有CAN控制器都由相应提供的API服务控制,以请求和读取当前控制器模式。当上层模块调用CanIf_SetControllerMode()请求时,CAN控制器状态可能会被更改,上层模块的请求,由CanIf通过CanDrv的API传递到相应CAN控制器。
对连接在一个CAN网络上的所有CAN控制器进行一致的管理是CanSm的任务。通过这种方式,CanSm负责将一个CAN网络的所有CAN控制器按照一定顺序设置为睡眠模式或唤醒它们。CanIf通过调用函数CanIf_SetControllerMode()或CanIf_ControllerBusOff()来接受每个状态转换请求。CanIf不决定CAN控制器请求的模式转换是否有效。CanIf仅通过获取当前模式和执行所请求的模式转换来与CanDrv交互。即网络相关状态机在CanSm中实现,CanIf仅存储请求的模式并执行请求的转换,根据CanSm请求的运行模式,CanIf转发请求CanDrvs。
控制器模式转换
基于切换请求,实际的切换的动作以异步的方式发生,如请求进入CAN_CS_SLEEP模式。状态成功切换到CAN_CS_SLEEP模式后,CanDrv调用函数CanIf_ControllerModeIndication()通知CanIf,CanIf调用<User_ControllerModeIndication>()将状态切换结果通过回调函数异步通知上层。上层模块可以通过CanIf_GetControllerMode()轮询控制器当前的模式。
并非所有CAN控制器都支持Sleep和Wake-Up模式。CanDrv通过其接口提供独立于硬件的操作模式来封装这些模式,这些模式必须由CanIf管理。
唤醒
CanIf识别内部发起的CAN控制器唤醒(内部唤醒)和CAN网络唤醒请求(外部唤醒)。内部唤醒调用CanIf的函数CanIf_SetControllerMode(ControllerId,CAN_CS_STARTED)发起;外部唤醒是CanDrv或CanTrcv发给ECU集成代码的CAN控制器事件。
无论使用何种唤醒方法(直接关于CAN控制器或CAN收发器),只要CAN控制器和CAN收发器设置为某种“监听唤醒”模式,ECU都支持通过CAN网络唤醒。这通常是一种睡眠模式,在这种模式下,通常的通信被禁用。只有这种模式才能确保CAN控制器停止运行。因此,可以使能唤醒中断。CAN网络唤醒包括两个步骤:
- 唤醒检测
- 唤醒验证
唤醒检测:如果使能CAN网络支持唤醒,通过服务CanIf_CheckWakeup(WakeupSource)检测到唤醒,就通知CanIf。在CAN总线“唤醒”事件的情况下,可能会在执行EcuM_CheckWakeup(WakeupSource)期间调用函数CanIf_CheckWakeup(WakeupSource)。当CanIf_CheckWakeup(EcuM_WakeupSourceType WakeupSource)被调用,CanIf通过函数 Can_CheckWakeup()或CanTrcv_CheckWakeup() "询问" CanDrvs / CanTrcvs具体那个CAN硬件设备引起的总线唤醒。如果Can_CheckWakeup()或CanTrcv_CheckWakeup()至少一个返回E_OK,那么CanIf_CheckWakeup()返回E_OK,若Can_CheckWakeup()或CanTrcv_CheckWakeup()均返回E_NOT_OK,那么CanIf_CheckWakeup()返回E_NOT_OK。被调用的通信服务属于配置期间定义的服务(见CanIfDispatchCfg)。通过这种方式,ECU和CanSm能够改变CAN控制器状态,并控制与总线关闭恢复或唤醒程序相关步骤的系统行为。
唤醒验证:当检测到总线唤醒事件,该唤醒事件会被直接通知到ECU状态管理器,若该唤醒事件需要验证,EcuM(或者一个CDD)将打开对应CAN控制器(CanIf_SetControllerMode())和CAN收发器(CanIf_SetTrcvMode())
PDU通道模式控制
PDU通道组
每个L-PDU被分配给一个专用的物理CAN通道,该通道连接到一个CAN控制器和一个CAN网络。通过这种方式,可以从处理逻辑上单个L-PDU信道组的角度来控制属于一个物理信道的所有L-PDU。这些逻辑组代表一个ECU连接到一个底层CAN网络的所有L-PDU。下图所示为通道L-PDU组以及和它相关上层和网络的示意图:
通道PDU组
一个L-PDU只能分配给一个通道组,典型的如PduR和网络管理模块负责控制PDU工作模式。
PDU通道模式
PDU通道模式切换只有在CAN控制器模式为CAN_CS_STARTED模式下才可以进行。PDU通道有四种模式:
- CANIF_OFFLINE
- CANIF_TX_OFFLINE
- CANIF_TX_OFFLINE_ACTIVE
- CANIF_ONLINE
下图所示为PDU通道模式转换图
CanIf提供接口函数CanIf_GetPduMode()可用于获取当前PDU通道模式;接口函数CanIf_SetPduMode()用于设置PDU通道模式。
CANIF_OFFLINE
当PDU通道模式为CANIF_OFFLINE时,此时通道进行通信。CanIf初始化完成时,CanIf此时就将所有通道切换到CANIF_OFFLINE状态,此外当调用CanIf_SetControllerMode(ControllerId, CAN_-
CS_SLEEP)时,CanIf将相应通道的PDU通道设为CANIF_OFFLINE模式。在CANIF_OFFLINE模式下,CanIf:
- 停止相关L-PDU的发送请求到CanDrv(CanIf_Transmit()调用返回E_NOT_OK给上层模块);
- 清除相关的CanIf发送buffer;
- 停止调用上层模块的接收提醒;
- 停止调用上层模块的发送确认;
即在此模式下,报文收发均被禁止。
CANIF_TX_OFFLINE
当调用CanIf_SetControllerMode(ControllerId, CAN_CS_STOPPED)或CanIf_ControllerBusOff(ControllerId)时,CanIf将相应通道的PDU模式设置为CANIF_TX_OFFLINE。在CANIF_TX_OFFLINE模式下CanIf:
- 停止相关L-PDU的发送请求到CanDrv(CanIf_Transmit()调用返回E_NOT_OK给上层模块);
- 清除相关的CanIf发送buffer;
- 停止调用上层模块的发送确认;
- 使能调用上层模块的接收提醒;
即在此模式下,可以进行报文接收,不可以进行报文发送。已经在CAN发送硬件对象中等待发送的发送L-PDU将在模式更改为CANIF_TX_OFFLINE或CANIF_OFFLINE模式后立即发送。
CANIF_OFFLINE_ACTIVE
如果CanIfTxOfflineActiveSupport = TRUE,则CanIf通过CANIF_TX_OFFLINE_ACTIVE模式提供成功传输的模拟。该模式通过调用CanIf_SetPduMode(控制器Id,CANIF_TX_OFFLINE_ACTIVE)使能,仅影响传输路径。对于分配给处于CANIF_TX_OFFLINE_ACTIVE模式的通道的每个L-PDU,CANIF应立即调用上层模块的发送确认回调服务,而不是在调用CanIf_Transmit()期间缓存或转发L-PDU至CanDrv。
该功能对于实现特殊操作模式(诊断被动模式)以避免总线阻塞而不影响通知机制是有用的。这种模式通常用于诊断用途。
CANIF_ONLINE
在CANIF_TX_OFFLINE模式下CanIf:
- 使能转发相关L-PDU的CanIf_Transmit()发送请求到CanDrv;
- 使能调用上层模块的接收提醒;
- 使能调用上层模块的发送确认;
即在此模式下,报文收发功能正常运行。
总结
CanIf的主要功能就是隔离底层硬件(驱动)区别,为上层提供统一的调用接口。其角色定位于"上传下达"的中转站。另外CanIf根据需求可配置的报文缓存。熟悉CanIf提供的常用API及其功能,即可满足日常工作中遇到问题分析的需求。