【AUTOSAR】 Classic Platform COM 模块–从入门到放弃
这份材料主要依据
AUTOSAR_CP_SWS_COM.pdf(R23-11)整理,另外参考了 RTE(AUTOSAR_CP_SWS_RTE.pdf)、PDU Router(AUTOSAR_CP_TPS_ECUConfiguration.pdf)、BswM(AUTOSAR_CP_SWS_BSWModeManager.pdf)、System Template(AUTOSAR_CP_TPS_SystemTemplate.pdf)以及 Transformer、LdCom 等规范。内容偏入门,重点是把"COM 到底在干什么、哪些配置项会影响行为"讲清楚。
目录
- COM 模块是干什么的
- 基本概念:Signal、Signal Group 与 I-PDU
- 打包、解包与字节序
- 什么时候发:传输属性与传输模式
- 收到之后:过滤与超时监控
- 和周边模块怎么配合
- 常用 API 与回调
- 上手建议
1. COM 模块是干什么的
AUTOSAR 的分层结构里,每一层只跟紧邻的层打交道,跨层的细节都被接口挡在后面。应用层的软件组件(SWC)脑子里装的是"车速"“转速”"车门开没开"这类带物理含义的值,而总线那头只认字节。COM 就是负责把这两种表达方式对接起来的那一层。
它的位置夹在 RTE 和 PDU Router(PduR)之间:
- 往上,SWC 通过 RTE 读写信号。跨 ECU 通信时 RTE 会直接调用 COM 的接口,比如
Com_SendSignal、Com_ReceiveSignal;软件集群(Software Cluster)则通过 ComProxy 访问 COM。 - 往下,COM 不直接操作 CAN、LIN 或以太网驱动。它收发的单位统一是 I-PDU,全部交给 PduR:要发送时调
PduR_ComTransmit;总线收到报文时 PduR 反过来回调Com_RxIndication;底层发完了再回调Com_TxConfirmation。 - 旁边还有一组管状态的模块。BswM 根据 ECU 当前状态决定哪些 I-PDU 组该开、哪些该关(
Com_IpduGroupStart/Com_IpduGroupStop),也能按需打开或关掉接收超时监控(Com_EnableReceptionDM/Com_DisableReceptionDM);EcuM 负责上电时Com_Init、下电时关闭通信。
有一点要说清楚:COM 从不自己判断"现在该不该通信"。它只是按上层给的指令干活——什么时候开、什么时候关、发送周期多长,全部来自配置和模式管理模块。
把这些关系画出来就是这样:
从图上能看出 COM 的价值:它在"信号"和"I-PDU"这两套数据视图之间做了隔离。上层改个信号名,不用动总线配置;总线换一帧格式,也不用动应用代码。
2. 基本概念:Signal、Signal Group 与 I-PDU
2.1 信号(ComSignal)
ComSignal 是 COM 眼里最小的数据单位,对应通信矩阵里的一行,比如车速VehicleSpeed、发动机转速EngineSpeed。
每个信号在配置时会绑定到某个 I-PDU,并说明自己在那帧里的位置:从第几个字节的第几位开始、占多少位。位置信息来自通信矩阵,工具(DaVinci Configurator、EB tresos 之类)最终把它翻译成Com_Cfg.c里的打包描述。
2.2 信号组(ComSignalGroup)与 Shadow Buffer
有些数据必须成组出现。发动机转速和时间戳就是一对:如果转速更新了、时间戳没跟上,接收端会拿到"新转速 + 旧时间戳"这种自相矛盾的组合。信号组就是为这种场景准备的——一组信号要么一起更新,要么一起不更新。
实现手段是给每个信号组配一块 Shadow Buffer(影子缓冲区)。发送和接收两条路径上的用法不太一样。
发送侧:
- 应用先逐个调用
Com_UpdateShadowSignal(),把各个子信号写进 Shadow Buffer。此时真正的 I-PDU 发送缓冲区还没动过。 - 所有子信号写完后,再调一次
Com_SendSignalGroup()。COM 在这一个调用里把 Shadow Buffer 整体拷进 I-PDU,不存在拷到一半被打断的可能。 - 之后该不该发、什么时候发,仍然由这个 PDU 的传输模式决定。
接收侧:
- 收到含信号组的 I-PDU 时,COM 先把整组数据解包进接收侧的 Shadow Buffer。
- 应用调用
Com_ReceiveSignalGroup(),这个调用会锁定 Shadow Buffer 并拿到一份快照。 - 然后用
Com_ReceiveShadowSignal()逐个读子信号。因为读的是同一份快照,读出来的值必然来自同一帧。
顺序别搞反:接收侧必须"先锁再读"。否则读到一半又来了新帧,前后两次读到的数据就分属不同帧,等于白锁。
2.3 I-PDU(ComIPdu)
ComIPdu 是 COM 交给 PduR 的数据块,里面装着一到多个信号或信号组。一个 ComIPdu 通常就对应一帧 CAN 报文、一帧 LIN 报文,或者一个以太网 payload。
发不发、多久发一次、发几次——这些参数都挂在 I-PDU 上,不挂在信号上。这一点在排查发送异常时很关键。
2.4 Update Bit(更新位)
Update Bit 是可选功能,作用是在 PDU 里额外占一位,明确告诉对面"这个信号我这轮更新过"。
- 发送侧:上层调用
Com_SendSignal时,COM 自动把该信号对应的 Update Bit 置 1;等这个 I-PDU 真正发出去、或者被Com_TriggerTransmit取走之后,Update Bit 清零。 - 接收侧:COM 收到 I-PDU 后先看 Update Bit。为 1,说明对方确实更新了这个信号,正常解包并通知上层;为 0,则认为对方这轮没动它,直接跳过。
它的价值在于区分"信号值变了"和"信号值恰好和上轮一样"。像 E2E 里带计数器的场合,没有 Update Bit 就很难判断对面是真的重发还是压根没发。
3. 打包、解包与字节序
3.1 支持的数据类型
- 整型:
boolean、uint8、uint16、uint32、uint64、sint8、sint16、sint32、sint64 - 浮点:
float32、float64 - 字节数组:
uint8[n],分固定长度(UINT8_N)和动态长度(UINT8_DYN)两种
3.2 字节序转换
COM 要处理两层字节序的差异:CPU 内存里怎么放,和总线上约定怎么放。这两者不一定一致,转换由 COM 完成,应用层不用管。
- 小端(Little-Endian,Intel 格式):低位字节在低地址,规范里写作
MostSignificantByteLast。配置项startPosition指的是信号**最低有效位(LSB)**所处的位置。 - 大端(Big-Endian,Motorola 格式):高位字节在低地址,规范里写作
MostSignificantByteFirst。startPosition指的是信号**最高有效位(MSB)**所处的位置。 - Opaque(不透明类型):用于
uint8[n]数组这类不关心字节序的数据。COM 原样搬运,不做任何颠倒。
字节序最容易踩的坑,就是把startPosition的含义记混。同一个起始位数值,在小端和大端里指的是信号的两端,配错了偏移量会整体偏掉。
位编号方面,AUTOSAR 统一采用 Sawtooth(锯齿)模式、位序递减:一帧的第 0 字节里,bit 0 是 LSB、bit 7 是 MSB;信号填充时从 LSB 往 MSB 逐位递增映射。
3.3 符号扩展
当有符号信号的位宽小于承载它的变量类型时(例如线上传 10 bit 的sint10,本地用sint16接收),COM 在解包时会自动做符号扩展。
举个例子:收到的 10 bit 值是11 1111 1101b,按有符号数读出来是 -3。拷进 16 bit 变量时,如果高位补 0 就变成正数 1013 了,所以 COM 把高位全部补 1,得到1111 1111 1111 1101b,在 16 bit 下依然是 -3。
这个动作不需要应用层参与,但理解它有助于解释"为什么接收缓冲区里读到的负值,和原始帧里的字节看着对不上"。
4. 什么时候发:传输属性与传输模式
一个 I-PDU 什么时候被发出去,由两件事共同决定:信号级的传输属性(Transfer Property)和PDU 级的传输模式(Transmission Mode)。前者回答"上层写了个新值,要不要马上发",后者回答"这个 PDU 平时按什么节奏发"。
4.1 传输属性(Transfer Property)
每个 ComSignal / ComSignalGroup 在配置时都得选一个:
PENDING(挂起):调Com_SendSignal只更新 COM 内部缓冲区,不触发发送。数据等着这个 PDU 周期性发出去,或者被同一个 PDU 里别的 TRIGGERED 信号顺带带出去。TRIGGERED(触发):调Com_SendSignal会触发该 PDU 立即发送,最迟在下一次Com_MainFunctionTx里执行。TRIGGERED_ON_CHANGE(变化触发):只有新值和 COM 缓冲区里的旧值(或初始值)不一样时才触发发送。值没变就不发,能省不少总线负载。TRIGGERED_WITHOUT_REPETITION/TRIGGERED_ON_CHANGE_WITHOUT_REPETITION:触发行为同上两条,但忽略配置的重复发送次数(ComTxModeNumberOfRepetitions),只发一次。
第 3 条的判断基准是 COM 缓冲区里的值,不是总线上的值。中间如果发送被取消过,比较基准可能和预期不一致。
4.2 传输模式(Transmission Mode)
每个 ComTxIPdu 会配置一种传输模式:
| 传输模式 | 常量 | 发送行为 |
|---|---|---|
| 直接/多次模式 | DIRECT | 由事件(如 TRIGGERED 信号更新)触发,立即连发1 + N次,N 为ComTxModeNumberOfRepetitions |
| 周期模式 | PERIODIC | 按固定间隔ComTxModeTimePeriod循环发送 |
| 混合模式 | MIXED | 两者结合:平时按周期发;一旦有 TRIGGERED 信号更新,额外插入一次直接发送 |
| 无发送模式 | NONE | COM 不主动发起发送。常见于由 PduR 轮询调用的Com_TriggerTransmit场景,或用作 TMS 条件为假时的禁用状态 |
4.3 动态切换:TMS
每个 Tx I-PDU 都可以在ComTxModeTrue和ComTxModeFalse两套参数之间动态切换,这套机制叫 TMS(Transmission Mode Selection,传输模式选择)。
评估原理:
- I-PDU 里可以配置若干参与 TMS 评估的信号,每个信号带一个
ComFilter条件。 - 只要有一个参与信号的
ComFilter评估为真,该 I-PDU 的 TMS 状态就是 True,采用ComTxModeTrue;所有参与信号都评估为假时,状态为 False,采用ComTxModeFalse。 - TMS 状态一旦变化,COM 会在当前或下一次
Com_MainFunctionTx中立刻按新模式发包,不需要额外触发。
典型用法是让一个 PDU 在"空闲时慢发、有事件时快发"之间切换,把周期性通信的确定性和事件驱动通信的实时性放在同一帧上兼顾。
5. 收到之后:过滤与超时监控
5.1 接收过滤(Reception Filtering)
收到新信号时,COM 可以按配置先做一次筛选,只有通过过滤的数据才会更新缓冲区并通知上层。常用的ComFilterAlgorithm有:
ALWAYS:始终放行。NEVER:始终丢弃。MASKED_NEW_EQUALS_X:(New_Value & Mask) == X时放行。MASKED_NEW_DIFFERS_X:(New_Value & Mask) != X时放行。MASKED_NEW_DIFFERS_MASKED_OLD:(New_Value & Mask) != (Old_Value & Mask)时放行,也就是只有关心的那些位变了才放行。NEW_IS_WITHIN/NEW_IS_OUTSIDE:数值落在(或超出)区间[Min, Max]时放行。ONE_EVERY_N:每收到 N 次数据只放行一次,用于降低上层处理频率。
5.2 接收截止时间监控(Rx DM)
总线断了、节点掉线、报文丢了,这些在实车上都可能发生。Rx DM 就是用来发现这类问题的。
监控参数(配在 ComRxIPdu 上):
ComFirstTimeout:从 I-PDU 启动(Com_IpduGroupStart)到收到第一帧报文的允许等待时间。ComTimeout:连续两帧报文之间的最大允许间隔。
超时之后 COM 做两件事:
- 处理数据。按
ComRxDataTimeoutAction的配置决定:保持旧值(NONE)、替换为指定的替代值ComTimeoutSubstitutionValue(REPLACE),或者替换成初始值ComSignalInitValue(SUBSTITUTE)。 - 通知上层。调用预先配置的回调
<ComUser_CbkRxTOut>,RTE 侧对应Rte_COMCbkRxTOut,把超时事件交给应用处理。
需要注意的是,Rx DM 默认是开的,但可以按 I-PDU 组关掉再打开(Com_DisableReceptionDM/Com_EnableReceptionDM),比如某些模式切换过程中不希望报超时。
5.3 发送截止时间监控(Tx DM)
Tx DM 监控的是另一个方向的问题:发送请求发出去之后,底层(PduR / CAN 驱动)有没有在规定时间内回Com_TxConfirmation。
如果在ComTimeout周期内没等到确认,COM 会取消超时定时器并调用<ComUser_CbkTxTOut>,把发送失败这件事告知应用层。它的典型用途是发现"CAN 控制器忙不过来了"或"总线一直仲裁失败"这类发送侧异常。
6. 和周边模块怎么配合
6.1 COM 与 RTE 的收发流程
发送(Sender-Receiver,跨 ECU):
- 应用 SWC 调用
Rte_Write_p_data(value)。 - RTE 按配置映射,调用
Com_SendSignal(SignalId, &value)。 - COM 做字节序转换、符号扩展,打包进 I-PDU 缓冲区,置 Update Bit。
- COM 按该信号的 Transfer Property 判断是否需要立即发送,需要就调
PduR_ComTransmit(PduId, &PduInfo)。 - 底层发送成功,PduR 回调
Com_TxConfirmation(),COM 再通过Rte_COMCbkTAck()通知 RTE。
接收:
总线收到报文,PduR 调用
Com_RxIndication(PduId, &PduInfo)。COM 按固定顺序做预处理:
- 重置该 I-PDU 的 Rx DM 定时器
- 检查 Update Bit
- 字节序转换与符号扩展
- 无效值检查(Data Invalidation)
- 接收过滤(Reception Filtering)
- 重置信号级 DM 定时器
- 通过回调(如
Rte_COMCbkRxAck())通知 RTE
SWC 调用
Rte_Read_r_data(&value)读出最新的信号值。
这个顺序是规范定死的。测到"值更新了但回调没来"这类现象时,按这七步从头对一遍,基本能定位到问题出在哪一步。
6.2 COM Based Transformer(ComXf)
面对结构体或组合数据类型,逐个处理 Primitive 信号效率不高。AUTOSAR 提供了 ComXf(COM 转换器):
- 定位:ComXf 位于 RTE 内部/上层,把应用层的结构体对象按信号组格式序列化成线性字节数组。
- 和普通 COM 的区别:普通 COM 逐个处理 Primitive 信号;ComXf 配合
Com_SendSignalGroupArray()/Com_ReceiveSignalGroupArray(),直接以连续内存数组的形式把数据交给 COM,省掉逐信号的打包开销。
6.3 Large Data COM(LdCom)
传大块数据时(刷写 Data Block、诊断大数据包、传感器点云),传统 COM 的打包、解包、过滤、Update Bit 处理会带来不小的 CPU 和内存开销,得不偿失。LdCom 就是为这类场景准备的:
- 零拷贝、直接传递:绕过信号打包和过滤流程,在 RTE 与 PduR 之间直接传字节数组指针。
- 轻量:只支持基于事件的非周期大块传输,开销显著更低。
6.4 E2E 保护配合
传输安全关键(ASIL B~D)的信号时,数据里要嵌入 CRC 校验码和序列计数器(Sequence Counter)。做法是在 COM 上层挂一个E2E Transformer(E2EXf):它在序列化数据的前端或后端写入 CRC 与 Counter,再交给 COM(或 ComXf)填入 I-PDU 空间。这样即便中间经过的总线、网关都不可信,端到端仍能检测出数据被篡改或重复——也就是所谓"黑通道(Black Channel)"保护。
7. 常用 API 与回调
7.1 上层(应用 / RTE)调用 COM
| 函数 | 原型 | 说明 |
|---|---|---|
Com_SendSignal | Std_ReturnType Com_SendSignal(Com_SignalIdType SignalId, const void* SignalDataPtr) | 更新指定信号的发送缓冲区,并按该信号的 Transfer Property 决定是否触发 I-PDU 发送 |
Com_ReceiveSignal | Std_ReturnType Com_ReceiveSignal(Com_SignalIdType SignalId, void* SignalDataPtr) | 从接收缓冲区读出指定信号的最新值 |
Com_SendSignalGroup | Std_ReturnType Com_SendSignalGroup(Com_SignalGroupIdType SignalGroupId) | 把 Shadow Buffer 整体原子地刷进 I-PDU |
Com_ReceiveSignalGroup | Std_ReturnType Com_ReceiveSignalGroup(Com_SignalGroupIdType SignalGroupId) | 锁定并快照信号组数据到接收侧 Shadow Buffer |
Com_UpdateShadowSignal | Std_ReturnType Com_UpdateShadowSignal(Com_SignalIdType SignalId, const void* SignalDataPtr) | 写发送侧 Shadow Buffer 中的子信号 |
Com_ReceiveShadowSignal | Std_ReturnType Com_ReceiveShadowSignal(Com_SignalIdType SignalId, void* SignalDataPtr) | 读接收侧 Shadow Buffer 中的子信号 |
Com_IpduGroupStart | void Com_IpduGroupStart(Com_IpduGroupIdType IpduGroupId, boolean Initialize) | 启动指定的 I-PDU 组 |
Com_IpduGroupStop | void Com_IpduGroupStop(Com_IpduGroupIdType IpduGroupId) | 停止指定的 I-PDU 组,同时取消组内挂起的发送请求和 DM 定时器 |
Com_EnableReceptionDM | void Com_EnableReceptionDM(Com_IpduGroupIdType IpduGroupId) | 使能指定 I-PDU 组的接收超时监控 |
Com_DisableReceptionDM | void Com_DisableReceptionDM(Com_IpduGroupIdType IpduGroupId) | 关闭指定 I-PDU 组的接收超时监控 |
7.2 底层(PduR)调用 COM
| 函数 | 原型 | 说明 |
|---|---|---|
Com_RxIndication | void Com_RxIndication(PduIdType RxPduId, const PduInfoType* PduInfoPtr) | PduR 收到总线报文后调用,把原始 I-PDU 字节流交给 COM |
Com_TxConfirmation | void Com_TxConfirmation(PduIdType TxPduId, Std_ReturnType result) | 底层发送完成后,经 PduR 告知 COM 发送结果 |
Com_TriggerTransmit | Std_ReturnType Com_TriggerTransmit(PduIdType TxPduId, PduInfoType* PduInfoPtr) | 由 LIN、FlexRay 等轮询式总线调用,请求 COM 把最新 I-PDU 数据拷到给定缓冲区 |
7.3 COM 通知上层的回调
这些回调由工具根据配置生成,最终接到 SWC 的运行实体上:
<ComUser_CbkRxAck>(RTE 中为Rte_COMCbkRxAck):信号/信号组成功接收并读取后回调。<ComUser_CbkTxAck>(RTE 中为Rte_COMCbkTAck):发送请求拿到底层Com_TxConfirmation确认后回调。<ComUser_CbkRxTOut>(RTE 中为Rte_COMCbkRxTOut):接收超时(Rx DM)触发时回调。<ComUser_CbkTxTOut>:发送确认超时(Tx DM)触发时回调。
8. 上手建议
COM 的配置项不少,但真正决定行为的其实就两条线索:
- 数据怎么走:应用变量 → RTE 接口 → COM 打包与字节序转换 → I-PDU 缓冲区 → PduR 路由 → 总线驱动。这条线决定了"值对不对"。
- 什么时候走:BswM / ComM 的模式切换 → I-PDU 组的启停 → 截止时间监控的开关。这条线决定了"发不发、什么时候发"。
调试的时候按这两条线分开看,比堆在一起翻配置快得多。
给几个具体建议:
- 对着生成的代码看配置。工具最终会产出
Com_Cfg.h和Com_Cfg.c,里面能看到每个信号的实际ComSignalType、ComBitPosition、ComBitSize、字节序,以及每个 I-PDU 的ComTxMode。配合 CANoe 抓报文对照,比只看 ARXML 直观。 - 发不出去先查三处:I-PDU 组开了没有、Transfer Property 是不是配成了
PENDING、TMS 当前落在ComTxModeFalse还是True。 - 收不到先查三处:Rx DM 是不是已经超时并替换了数据、
ComRxDataTimeoutAction配的是哪种动作、接收过滤是否把值挡掉了(尤其MASKED_*系列,掩码配错会很隐蔽)。 - 信号组务必"先锁再读",并且不要在
Com_ReceiveSignalGroup和Com_ReceiveShadowSignal之间插入可能阻塞或耗时的操作。 - Update Bit 不是默认能力,它是配置项,且会真实占用 PDU 空间。设计通信矩阵时要提前和信号对齐好位宽。