5.3 AUTOSAR CP & AP
2003 年,一间会议室里的决定
2003 年,一群欧洲 OEM 和 Tier-1 的代表坐在会议室里。议题非常直接:
“我们需要一个标准。一个汽车 ECU 软件的通用框架。”
当时的情况是一盘散沙。每个 Tier-1 供应商为每个 OEM 单独开发 ECU 软件。标致-雪铁龙的发动机控制器和大众的发动机控制器硬件可以一模一样(比如都用英飞凌 TriCore 或瑞萨 RH850),但软件完全不兼容。
同样的 CAN 驱动要重写五遍。同样的诊断栈要重写五遍。同样的安全组件(WDOG、MPU、锁步核管理)要重演五次。
这是软件成本膨胀的根本原因。 不是工程师不努力。是每一次都从零开始。
与会者同意:定义一个标准。分层解耦。硬件无关的应用层可以在不同 ECU 上复用。基础的驱动层、通信栈、操作系统封装一次,用在所有 ECU 上。
这个标准叫做 AUTOSAR(AUTomotive Open System ARchitecture)。
蓝图:AUTOSAR Classic Platform(CP)的分层宇宙
CP 针对实时性高、资源有限的 ECU——发动机、变速箱、ABS、EPS、BCM 等。OS 是 OSEK/VDX 兼容的实时内核。
想象一座大厦,从上到下五层楼:
| 层 | 名称 | 角色 |
|---|---|---|
| 第 5 层 | 应用层(SWC) | 独立的应用逻辑:扭矩控制、轮速计算——不碰任何硬件 |
| 第 4 层 | RTE | SWC 的“软件总线“,连接 SWC 之间的数据交换 |
| 第 3 层 | 服务层(Services) | 系统状态管理、存储服务(NvM)、通信服务(DCM/COM) |
| 第 2 层 | ECU 抽象层 | 把微控制器外设抽象成高层接口——CAN Interface 在这里 |
| 第 1 层 | MCAL | 直接操作硬件寄存器——GPIO、ADC、PWM、SPI、CAN、GPT |
还有一层特殊的存在——复杂驱动(Complex Drivers),用于那些不能套进标准框架的特殊硬件:轮速传感器的模拟信号调理、发动机喷油芯片的精确时序控制。
关键原则:
- SWC 不能直接访问 BSW 或硬件。它只能通过 RTE 操作。
- BSW 层之间有严格的单向接口——COM → PduR → CanTp → CanIf → Can。
- 每个层只能访问它的下层。依赖方向严格向下。
站在最底层:MCAL 的 CAN 驱动
MCAL 是软件栈和硬件唯一的接触点。它直接读写外设寄存器。芯片厂商提供 MCAL——你是 ECU 集成工程师,通常不修改 MCAL 代码,但你必须理解它的接口。
一个简化的 CAN 驱动 MCAL 模块看起来是这样的:
/* Can.h - MCAL CAN驱动接口(简化) */
#ifndef CAN_H
#define CAN_H
#include "Can_Types.h"
/* 初始化CAN控制器 */
void Can_Init(const Can_ConfigType *config);
/* 发送一帧CAN报文 */
Std_ReturnType Can_Write(Can_HwHandleType hoh, const Can_PduType *pdu);
/* 接收中断回调——Can驱动调用,通知上层 */
void CanIf_RxIndication(PduIdType pduId, const PduInfoType *pduInfo);
/* 发送确认回调 */
void CanIf_TxConfirmation(PduIdType pduId);
#endif /* CAN_H */
/* Can.c - MCAL CAN驱动实现(简化) */
void Can_IrqHandler(uint8_t controller_id)
{
Can_PduType rx_pdu;
PduInfoType pdu_info;
/* 从硬件CAN控制器的接收FIFO读出 */
rx_pdu.id = CAN0->sFIFOMailBox[0].RIR >> 21;
rx_pdu.length = CAN0->sFIFOMailBox[0].RDTR & 0x0F;
uint32_t *src = (uint32_t *)&CAN0->sFIFOMailBox[0].RDLR;
uint32_t *dst = (uint32_t *)rx_pdu.sdu;
dst[0] = src[0];
dst[1] = src[1];
CAN0->RF0R |= CAN_RF0R_RFOM0;
/* 匹配CAN ID到PDU ID——用配置表 */
PduIdType pdu_id = pdu_id_from_can_id(rx_pdu.id);
/* 构造PduInfo */
pdu_info.SduDataPtr = rx_pdu.sdu;
pdu_info.SduLength = rx_pdu.length;
/* 通知上层——这是MCAL的唯一出口 */
CanIf_RxIndication(pdu_id, &pdu_info);
}
注意 CanIf_RxIndication——MCAL 不路由数据。它只是从硬件读出、包装好、递给上层。路由是上层的事。
追踪:一个 CAN 报文从物理总线到应用逻辑的完整旅程
假设 SWC “VehicleSpeed” 从 CAN 总线上读取车速信号。让这个报文带你走一遍 AUTOSAR 的五层大厦。
第 1 层——MCAL(CAN 驱动)
硬件 CAN 控制器的接收 FIFO 满时,触发 CAN 接收中断。MCAL 中的 Can_Irq.c 从硬件寄存器读出帧,调用 CanIf_RxIndication()——通知上层有报文到达。芯片厂商提供 MCAL,你通常不修改它。
第 2 层——CAN Interface(CanIf)
CanIf_RxIndication() 做的事:
- 匹配接收到的 CAN ID 到配置表中定义的 PDU ID。
- 把 CAN 帧的 8 字节数据拷贝到 PDU buffer 中。
- 调用
PduR_RxIndication(pduId, &pduInfo)。
第 3 层——PDU Router(PduR)
PDU Router 是通信栈的交换机。一条 PDU 从 CanIf 上来,PduR 根据 PDU ID 的配置决定路由到哪个上层模块。对于普通信号帧(不是诊断、不是 TP 分段),PduR 直接调用 Com_RxIndication(pduId, &pduInfo)。
第 4 层——COM Stack(Com)
Com 模块负责把 PDU 的原始字节流拆包成独立信号。这是整个通信栈中最体现 AUTOSAR 设计哲学的一层——信号和帧的彻底解耦。
在裸机编程中,你直接解析 CAN 帧:
/* 裸机方式:手工位掩码提取 */
uint16_t speed_raw = (can_data[0] << 8) | can_data[1];
float speed = speed_raw * 0.05625f;
在 AUTOSAR 中,COM 从配置(ARXML)中读取每个信号的定义,在运行时自动完成信号提取:
/* Com_SignalProcessing.c - COM信号提取(简化) */
void Com_RxIndication(PduIdType pduId, const PduInfoType *pduInfo)
{
ComPduIdType comPduId = Com_PduIdFromRouting(pduId);
const ComSignalConfig_t *signals = Com_GetSignalsForPdu(comPduId);
for (uint16_t i = 0; i < signals->num_signals; i++) {
const ComSignal_t *sig = &signals->signals[i];
uint8_t *data = pduInfo->SduDataPtr;
/* 从PDU字节流中提取信号的原始值 */
uint64_t raw = Com_ExtractBits(data,
sig->bitPosition,
sig->bitSize,
sig->endianness);
/* 物理值转换 */
float physical = (float)raw * sig->factor + sig->offset;
/* 更新RTE buffer */
Rte_Write_Signal(sig->handle, &physical);
}
}
/* 位提取函数——处理跨字节边界、大小端、符号扩展 */
uint64_t Com_ExtractBits(const uint8_t *buffer,
uint16_t bitPos, uint8_t bitSize,
Com_Endianness_t endian)
{
/* 计算起始字节和位偏移 */
uint16_t byteIdx = bitPos / 8;
uint8_t bitOff = bitPos % 8;
/* 按字节读取并拼接——支持跨字节边界 */
uint64_t value = 0;
for (uint8_t i = 0; i < (bitSize + bitOff + 7) / 8; i++) {
value |= (uint64_t)buffer[byteIdx + i] << (i * 8);
}
value >>= bitOff;
value &= (1ULL << bitSize) - 1;
/* 符号扩展:如果最高位是1且信号是有符号的 */
if (endian == COM_SIGNED && (value & (1ULL << (bitSize - 1)))) {
value |= ~((1ULL << bitSize) - 1);
}
return value;
}
这段代码是 COM 层存在的根本理由。在一个 ECU 中可能有 500+ 个 CAN 信号,分布在 50+ 个 PDU 中。让每个 SWC 应用工程师手工解析字节流不仅低效,更是 bug 的温床——信号布局的一个 bit 偏移错误可能导致错误的数据被分发到所有订阅的 SWC 中。
COM 用配置驱动的方式实现了一劳永逸的信号提取——配置在 ARXML 中定义,COM 的生成代码根据配置自动生成,应用工程师永远不需要写位掩码。
第 5 层——RTE 和 SWC
RTE 是 SWC 看到的“世界“。对于 SWC 来说,硬件不存在。CAN 不存在。Flash 不存在。只有 RTE——一组可读取和写入的端口(Port)。
/* SWC: VehicleSpeed_Implementation.c */
#include "Rte_VehicleSpeed.h"
/* 这个Runnable由RTE周期性触发——RTE根据OS schedule表调度它 */
void VehicleSpeed_MainFunction(void)
{
float speed = 0.0f;
boolean over_speed = FALSE;
/* 通过RTE读取端口——背后是COM层提供的信号值 */
Rte_Read_SpeedValue_VehicleSpeed(&speed);
/* 应用逻辑:超速判断 */
if (speed > 120.0f) {
over_speed = TRUE;
}
/* 通过RTE写入输出端口——RTE负责把值路由到订阅的SWC */
Rte_Write_OverSpeedWarning_VehicleSpeed(over_speed);
/* 如果需要触发诊断——调用RTE的客户端-服务器接口 */
if (over_speed) {
Rte_Call_SetEventStatus_OverSpeedMonitor(DTC_OVERSPEED, TRUE);
}
}
SWC 的实现者不需要知道“车速信号从哪来“——它是 CAN 帧里的一个信号?是 SPI 传感器直接读的?是内部计算出来的?是另一颗芯片通过以太网传来的?SWC 不在乎。它只认 RTE 端口。这是 AUTOSAR 分层解耦的最高境界。
整个路径:物理 CAN 总线 → MCAL 中断 → CanIf 匹配 → PduR 路由 → Com 拆包 → RTE 分发 → SWC 计算。每一层只做一件事,每一层只认识相邻的层。
ARXML:AUTOSAR 的灵魂——用配置代替代码
上面的 COM 信号提取不是靠硬编码,而是靠配置。AUTOSAR 的精髓不在 C 代码里——在 ARXML 文件里。ARXML 是 AUTOSAR 的配置语言,它用 XML 描述了 ECU 软件的每一个可配置参数。
一个 CAN 信号的完整 ARXML 描述:
<!-- 信号:车速,16位无符号,Big-Endian,位偏移0 -->
<Signal name="ComSignal_VehicleSpeed">
<bitPosition>0</bitPosition>
<bitSize>16</bitSize>
<endianness>BIG_ENDIAN</endianness>
<type>UINT16</type>
</Signal>
<!-- IPDU:车速信号所属的接收PDU -->
<IPdu name="ComIPdu_VehicleSpeedPdu">
<signalRef>/ComSignal_VehicleSpeed</signalRef>
<direction>RECEIVE</direction>
</IPdu>
<!-- 物理值转换:raw × 0.05625 + 0.0,范围 0-350 km/h -->
<CompuMethod name="ComSignal_VehicleSpeed_CompuMethod">
<factor>0.05625</factor>
<offset>0.0</offset>
<lowerLimit>0.0</lowerLimit>
<upperLimit>350.0</upperLimit>
</CompuMethod>
这段 XML 告诉 BSW 配置生成器(如 Vector DaVinci Configurator 或 EB tresos Studio):
- “车速信号“是一个 16 位的无符号整数,以 Big-Endian 排列,在 PDU 的第 0 位开始。
- 物理值 = 原始值 × 0.05625。
- 有效范围为 0-350 km/h——超出此范围的值将被 COM 标记为“无效信号“并触发 DEM(诊断事件管理器)。
BSW 配置生成器读取这段 XML,生成对应的 C 代码数据结构——ComSignal_t 结构体、信号提取参数表。 这就是 AUTOSAR 从“每个项目手写代码“到“配置驱动生成代码“的转变。你的手不再碰位掩码。你描述你想要什么,工具生成为你服务的代码。
AUTOSAR 的另一张面孔:Adaptive Platform(AP)
CP 是为经典实时 ECU 设计的。OS 是 OSEK/VDX。配置是静态的——所有任务和通信时间表在编译期就固定好了。
但汽车在变化。ADAS 域控制器、自动驾驶平台、V2X 通信单元——这些 ECU 需要:
- 强大的算力(多核 CPU、GPU、AI 加速器)
- 动态部署——任务可以在运行时启动或停止
- 服务发现——SOME/IP 协议让新加入的组件被自动发现
- OTA 升级——局部更新固件而不影响整车
AP 用 POSIX 操作系统(Linux、QNX)而不是 OSEK/VDX。支持 C++ 而不是纯 C。ARA(AUTOSAR Runtime for Adaptive)替代 RTE,提供面向服务的通信。
Application (C++) → ARA → Foundation Services → POSIX OS → Hardware
在 AP 中,SWC 之间的通信不是固定的“端口读/写“,而是服务调用(Service-Oriented Architecture, SOA)。一个 SWC 在启动时通过 SOME/IP 协议向网络中宣告自己提供的服务——例如“ADAS_SensorFusion 提供 SensorData 服务“。另一个 SWC 通过 SOME/IP 的服务发现机制找到这个服务,建立 TCP/UDP 连接,开始订阅数据流。
SOME/IP 服务发现流程:
ECU-A (提供者) ECU-B (消费者)
| |
| Offer Service (SensorData, UDP) |
| ─────────────────────────────> | (组播到整个子网)
| | "有人提供SensorData?我要了。"
| | Subscribe (SensorData, TCP)
| <───────────────────────────── |
| "好,开始给你发数据。" |
| SensorData stream =============> |
CP 是静态配置的。AP 是动态的。CP 面向高确定性——每一个 OS task 的执行周期和优先级在编译期完全固定。AP 面向高灵活性——ADAS 功能可以在车辆行驶中动态部署、动态升级、动态关联。
一辆高端车同时跑着 CP(发动机控制)和 AP(自动驾驶感知)。两个世界在同一个 CAN 网关后面共存——网关的 CP 侧处理实时 CAN/CAN-FD 报文,AP 侧通过 SOME/IP 向 ADAS 应用提供车辆状态数据。
你在 R5 核心上实打实的工作流
假设你的硬件是 SPC58NN84(PowerPC e200 核心,锁步),你需要在上面集成一个完整的 AUTOSAR CP 栈。典型的工作流是五个阶段。
阶段一:MCAL 配置。 在 Tresos Studio(EB)或 ISOLAR(Vector)中配置 MCAL 模块——CAN、SPI、GPT、ADC。这一步生成 MCAL 配置参数,告诉驱动每一个外设的具体设置。例如 CAN 控制器配置:波特率、每个邮箱的 CAN ID 过滤器、接收/发送类型。
阶段二:BSW 配置。 配置 ECU 状态管理(EcuM)、通信栈(Com/PduR/CanIf/CanTp)、诊断栈(DCM/DEM)。每个模块的参数通过 ARXML 文件定义。BSW 配置生成器把 ARXML 翻译成 C 配置结构体——你在编译时链接,而不是在运行时解析 XML。
阶段三:RTE 配置。 根据 SWC 的描述(SWC 有哪些端口,端口是发送还是接收,数据类型是什么),RTE 生成器为每个 SWC 生成 Rte_<SWC>.h 和 Rte_<SWC>.c。这些生成的文件把 SWC 的端口读写映射到 COM 的信号、NvM 的存储块、或操作系统的事件。
阶段四:SWC 编写。 应用工程师只写 SWC 的逻辑代码(C 语言或 MATLAB/Simulink 自动代码生成),不碰任何底层。SWC 的世界里只有 Rte_Read_xxx()、Rte_Write_xxx()、Rte_Call_xxx()——这些是 SWC 能做的唯一操作。
阶段五:系统集成。 将各部分编译链接——SWC 对象文件 + RTE 生成文件 + BSW 配置生成文件 + MCAL + OS——烧入 ECU,跑集成测试。
关键收益: 当你换另一个芯片平台时——比如从 SPC58NN84 换到英飞凌 TC399——应用 SWC 和大部分 BSW 可以复用。只需要重新做 MCAL 配置(不同的芯片不同的外设寄存器映射)和 OS 配置(不同的核数、不同的中断控制器)。这大幅降低了 Tier-1 的开发成本,也保证了跨 OEM 和跨硬件平台的软件一致性。
标准:不是束缚,是释放
在没有 AUTOSAR 的时代,每个 Tier-1 和 OEM 各自为政。好的工程师做出好的系统,但他们的知识和代码无法传承、无法复用。汽车电子软件领域像是在“小农经济“——每家都在做自己的东西,努力但低效。
AUTOSAR 做的事情是工业标准化。把 ECU 软件拆分成分层、分模块、可配置的组件——就像 ISO 把螺丝钉的螺纹规格标准化一样。这样一来:
- 芯片厂商可以只做 MCAL,不用管上层的应用逻辑。
- Tier-1 可以跨平台、跨 OEM 地复用应用 SWC。
- OEM 可以在不同 Tier-1 之间竞标,不担心被锁定到某个供应商的专有平台。
标准化不是束缚创造力——是释放更大的创造力。 当你不用为每个 ECU 重新写 CAN 驱动时,你的精力可以用在更高级的问题上:混合动力模式的能量管理、ADAS 传感器融合算法、OTA 升级的安全架构。
回头看这一章走过的路:
- 裸机:一个人掌控一切。简单、灵活、不可扩展。
- RTOS:在有限资源下引入任务调度和通讯机制。扩展性提高,但需要更强的系统设计能力。
- AUTOSAR:工业化、标准化、跨平台复用。释放整个行业的规模效应。
你在其中任何一个阶段工作,都是这个行业文明进步的一部分。
但 ECU 不只是“跑得动“。它还要能“说话“——当它出了问题,它要能告诉维修技师:我哪里不对、什么时候不对、附带什么上下文数据。
这就是诊断。UDS——统一的诊断服务。
本篇小结
今天我们做了一件事:理解AUTOSAR如何通过工业标准化,把一个Rte_Read()调用翻译成CAN总线上的物理帧——以及这种标准化如何释放整个行业的规模效应。
关键结论:
- AUTOSAR不是“又一个大框架“——是ECU软件的工业标准化:把ECU软件拆成分层、分模块、可配置的组件,像ISO把螺丝钉螺纹标准化一样。CAN驱动写一次,诊断栈写一次,安全组件封装一次——跨平台复用。
- CP(经典平台)和AP(自适应平台)分治了汽车电子的两个世界:CP面向实时MCU(确定性延迟、静态配置),AP面向高性能MPU(POSIX OS、动态服务发现)。
- AUTOSAR从裸机和RTOS的“手工经济“进化到“工业经济“:应用工程师只写SWC逻辑代码,不碰任何底层。换芯片平台时只需重配MCAL——释放出来的精力可以投向更高价值的系统级问题。
下一节,第六部分——软件的灵魂。诊断让ECU能“说话“、安全让ECU不可被攻破、OTA让ECU持续进化。从DTC的浮栅电子到HSM的熔丝阵列——穿透所有抽象层。
【下集预告】
2012 年,一辆豪华车在高速上突然失去动力。熄火,溜到路边,再也打不着。维修技师接上诊断仪,读取故障码——P0001。UDS 诊断仪在 30 分钟内锁定了 2 号缸喷油器线束脱焊。没有 UDS,这辆车要被拆开一半才能找到问题。
诊断不是“拆车“,而是“对话“。UDS 为 ECU 定义了一种语言——让一个沉默的金属疙瘩能够向世界报告自己的健康状况。每一个 DTC 不是一个简单的布尔值——它是一个有生命周期的状态机,从“当前操作周期检测到故障“,到“连续 3 个周期确认的真实故障“,到“写入非易失存储“,到最终被维修技师手动清除。它的旅程横跨了物理世界(传感器返回值)、AUTOSAR 软件栈(DEM/DCM)、和维修车间(诊断仪)。
下一章,我们进入 Part 6——诊断、安全、存储。汽车不仅仅是速度和操控。它还要能自检、不被攻击、不掉失数据。