Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.4 车载以太网与 DoIP

500MB固件用CAN传输——一个多小时

2015年,某德系OEM做了一个计算题。

ADAS域控制器的一次OTA固件包:500MB。CAN总线的有效带宽(扣除帧间间隔、CRC、ACK、填充位、协议开销):约500kbps。传输时间:500×8÷0.5=8000秒,超过两小时。实际更糟——CAN上有几十个ECU同时通信,你的固件帧要排在刹车信号、发动机状态、转向角度后面。

诊断日志——Level 3自动驾驶测试车每天产生TB级传感器原始数据。CAN-FD顶到8Mbps也差了两个数量级。

还要考虑另一个问题:CAN是信号总线。它的帧是广播的,只有ID没有地址、没有路由、没有会话。你想对某个ECU做刷写——你得先建立点对点连接,而CAN天生不支持。

答案是车载以太网。 不是“更快的CAN“——是换一个物种。

CAN是信号总线,以太网是数据网络

这是两个不同的物种。

CAN传输的是“信号“——一个物理量的值(转速1847rpm、档位P、刹车灯ON)。你可以看成一个分布式共享变量的读/写系统。地址是CAN ID,值是8字节payload。没有会话、没有连接、没有服务发现。发送方不知道谁在接收,接收方不知道谁在发送(只知道ID)。这是信号级的通信。

以太网传输的是“数据“——数据包有源IP和目的IP、有TCP/UDP端口、有应用层协议头(HTTP/SOME/IP/DoIP)。它支持寻址(router知道子网)、路由(RIP/OSPF决定路径)、分段重组(IP fragmentation)、流控(TCP滑动窗口)、QoS(DSCP优先级)。以太网不关心你发的是发动机转速还是NBA直播——它是数据的管道。

这个区别不是学术上的——它决定了整车的电子电气架构(E/E Architecture)。

经典CAN车型的EE架构是“信号矩阵“——一张巨大的DBC表(有时超过3000个信号),列出了所有ECU之间交换的所有信号。工程师在设计阶段就把“谁发什么“、“谁收什么”、“周期多少ms“全部写死。增加一个新ECU——你得更新全车几十个ECU的DBC矩阵。这是静态的、预定义的、脆弱的。

以太网车型的EE架构是“服务网络“——基于SOME/IP(Scalable Service-Oriented Middleware over IP)。ECU不需要在编译时知道谁会收它的数据。它只需要注册一个服务(如“CameraStreamService“),其他ECU通过SOME/IP-SD(Service Discovery)动态发现并订阅。新的传感器上线?Subscribe一把就是。这是动态的、可扩展的。

汽车从“装了CPU的机器“变成了“有四个轮子的分布式计算系统“。

一根线,全双工,100Mbps

传统以太网(100BASE-TX)需要两对差分线:一对发送、一对接收,外加屏蔽层,共四根导体。Cat5e线缆直径约5mm。一辆车上几十个以太网节点如果都用传统以太网——线束重量和成本无法接受。

100BASE-T1(IEEE 802.3bw)只用一对非屏蔽双绞线(UTP),在同一对线上做全双工传输。线径约0.35mm²,总直径约1.5mm——比传统以太网线细了70%。这是车轮拱板内能穿过去的线径。

发送和接收的分离靠回波抵消(Echo Cancellation)。原理是这样的:

PHY芯片内部的发送器把要发出的信号(S_TX)驱动到双绞线上。同时,接收器在这个双绞线上看到的是混合信号:S_TX(本地的发送信号,经过线缆阻抗变化的“回波“) + S_RX(远端发来的信号)。接收器需要从混合信号中减去S_TX,只留下S_RX。

回波抵消器是一个自适应FIR滤波器。 它把本地的数字发送信号(已知的)通过一个可调系数的数字滤波器,生成一个回波的估计值。从接收到的混合信号中减去这个估计值——剩余的就是远端信号加上残余误差。滤波器系数在链路建立时通过训练序列(伪随机序列)自适应收敛,数据传输中持续微调。

这个训练过程叫做“链路同步“。当两个100BASE-T1 PHY通过一对UTP连接后,它们彼此发送训练序列,互相独立地收敛自己的回波抵消器和自适应均衡器。收敛后,链路建立,开始传输有效数据。整个过程约100-200ms。

编码用的是PAM-3——每个符号三种电平:+1、0、-1。携带log₂(3)≈1.58个bit的信息。在66.7MHz符号率下,有效数据率 = 66.7Mbaud × 1.58bit ≈ 105Mbps。扣除8b/10b类编码开销(不是真正的8b/10b,而是类似的开销),净数据率=100Mbps。

为什么是PAM-3而不是PAM-2(两电平)或PAM-4(四电平)?PAM-2在相同符号率下只能跑66.7Mbps——不够100Mbps。PAM-4在相同符号率下能跑133Mbps,但四电平需要更精确的电平判决——在汽车的EMI环境下,PAM-4的眼图开口会闭合得更快。PAM-3是在“带宽效率“和“噪声容忍“之间的最优折中。

PAM-3的另一个精妙设计:它天然有直流平衡特性。三个电平(+1、0、-1)在统计上均匀分布,没有长期直流偏置——这意味着频谱集中在低频以上,几乎没有直流分量。不需要笨重的屏蔽层来抑制低频辐射。

在物理层上,100BASE-T1已经是一个完整的DSP系统。 自适应均衡、回波抵消、时钟恢复、PAM-3解码——全在PHY硅片内部完成,功耗约1-2W。你作为固件工程师看不到,也不需要看到。但你知道它在那里——在你插上以太网线的那一瞬间,PHY内部的DSP核正在微调几百个滤波器系数,用最小均方(LMS)算法在几十个毫秒内收敛到−40dB以下的回波抑制。

MAC层:从PHY到CPU的管道

PHY芯片的输出是MII(Media Independent Interface)——一组并行信号:4位宽的RXD[3:0]、4位宽的TXD[3:0]、RX_CLK、TX_CLK、RX_DV(数据有效)、TX_EN(发送使能)。MII的时钟在100Mbps模式下是25MHz——25MHz×4bit=100Mbps。

MAC层通常是MCU/SoC内部的一个硬件模块。它把MII的4位nibble流组装为以太网帧(添加前导码、SFB、加上FCS/CRC32校验、去除前导码)、管理发送和接收FIFO、处理流量控制PAUSE帧、生成发送完成/接收完成中断。

对于S32K之类的MCU,它可能只有一个10/100Mbps的以太网MAC(ENET模块)。MAC通过内部的DMA把接收到的帧搬到SRAM中的接收描述符(descriptor ring)指向的buffer中。你的代码从descriptor ring里拿到帧缓冲区的指针,开始解析以太网头、IP头、TCP/UDP头。

这是从PHY到socket的整个链路:

双绞线 (PAM-3模拟信号)
    ↓
PHY (回波抵消 + 自适应均衡 + 时钟恢复 + PAM-3解码)
    ↓
MII/RMII (4-bit/2-bit 并行数字)
    ↓
MAC (帧组装/拆解 + CRC校验 + DMA到描述符环)
    ↓
ENET中断 → ISR → 通知协议栈
    ↓
TCP/IP栈 (lwIP/uIP/自定义): 校验IP头、重组分片、匹配TCP端口、校验TCP checksum
    ↓
Socket层 (recv/send)
    ↓
你的应用代码

穿透:一个车载UDP Echo Server

下面是使用简化的协议栈风格(类似lwIP的raw API)在一个ECU上跑UDP echo server的示例。这不是标准lwIP——而是用裸机方式展示每一层的数据结构:

// ========== 以太网帧结构 (简化) ==========
#include <stdint.h>
#include <string.h>

// 以太网头: 14 字节
typedef struct {
    uint8_t  dst_mac[6];
    uint8_t  src_mac[6];
    uint16_t ethertype;   // 0x0800 = IPv4
} __attribute__((packed)) eth_hdr_t;

// IP 头: 20 字节 (无选项)
typedef struct {
    uint8_t  ver_ihl;     // Version(4) + IHL(4) = 0x45
    uint8_t  dscp_ecn;
    uint16_t total_len;
    uint16_t id;
    uint16_t flags_frag;
    uint8_t  ttl;
    uint8_t  protocol;    // 17 = UDP
    uint16_t hdr_checksum;
    uint32_t src_ip;
    uint32_t dst_ip;
} __attribute__((packed)) ip_hdr_t;

// UDP 头: 8 字节
typedef struct {
    uint16_t src_port;
    uint16_t dst_port;
    uint16_t length;      // UDP头+数据总长
    uint16_t checksum;
} __attribute__((packed)) udp_hdr_t;

// 完整的帧缓冲区
static uint8_t rx_frame[1536];  // 最大以太网帧

// ========== IP Checksum ==========
// RFC 1071: 16位反码求和
static uint16_t ip_checksum(void *data, uint16_t len)
{
    uint32_t sum = 0;
    uint16_t *p = (uint16_t *)data;
    while (len > 1) {
        sum += *p++;
        len -= 2;
    }
    if (len) sum += *(uint8_t *)p;           // 奇数字节 pad
    sum = (sum >> 16) + (sum & 0xFFFF);      // 进位加到低16位
    sum += (sum >> 16);                      // 再加一次进位
    return (uint16_t)(~sum);                 // 取反
}

// ========== IP 伪头 + UDP 校验和 ==========
typedef struct {
    uint32_t src_ip;
    uint32_t dst_ip;
    uint8_t  zero;
    uint8_t  protocol;
    uint16_t udp_len;
} __attribute__((packed)) udp_pseudo_hdr_t;

static uint16_t udp_checksum(ip_hdr_t *ip, udp_hdr_t *udp)
{
    uint16_t udp_total = ntohs(udp->length);  // 网络字节序→主机字节序
    uint8_t buf[sizeof(udp_pseudo_hdr_t) + udp_total];
    udp_pseudo_hdr_t *pseudo = (udp_pseudo_hdr_t *)buf;

    pseudo->src_ip   = ip->src_ip;
    pseudo->dst_ip   = ip->dst_ip;
    pseudo->zero     = 0;
    pseudo->protocol = 17;  // UDP
    pseudo->udp_len  = udp->length;

    memcpy(buf + sizeof(udp_pseudo_hdr_t), udp, udp_total);

    // 如果 UDP 数据长度是奇数,末尾补 0x00 (UDP 校验和规定)
    if (udp_total & 1)
        buf[sizeof(udp_pseudo_hdr_t) + udp_total] = 0x00;

    return ip_checksum(buf, sizeof(udp_pseudo_hdr_t) + udp_total + (udp_total & 1));
}

// ========== 字节序转换 ==========
static uint16_t ntohs(uint16_t n) {
    return ((n & 0xFF) << 8) | ((n >> 8) & 0xFF);
}
static uint16_t htons(uint16_t h) { return ntohs(h); }
static uint32_t htonl(uint32_t h) {
    return ((h & 0xFF) << 24) | ((h & 0xFF00) << 8)
         | ((h >> 8) & 0xFF00) | ((h >> 24) & 0xFF);
}

// ========== UDP Echo Server ==========
// 在 ENET 接收中断中调用
// rx_frame 已经通过 MAC DMA 填充好了完整的以太网帧

#define UDP_ECHO_PORT  0xE001  // 57345, 自选端口

// ECU 的 MAC 和 IP 地址 (假设已设定)
static uint8_t  my_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
static uint32_t my_ip   = 0xC0A8000A;  // 192.168.0.10 (网络字节序)

void enet_rx_handler(uint8_t *frame, uint16_t len)
{
    // 1. 解析以太网头
    eth_hdr_t *eth = (eth_hdr_t *)frame;
    if (ntohs(eth->ethertype) != 0x0800) return;  // 不是 IPv4, 丢弃

    // 2. 解析 IP 头
    ip_hdr_t *ip = (ip_hdr_t *)(frame + sizeof(eth_hdr_t));
    if (ip->protocol != 17) return;  // 不是 UDP, 丢弃
    if (ip->dst_ip != my_ip) return; // 不是发给我的, 丢弃

    // 3. 校验 IP 头 checksum
    uint16_t ip_hdr_csum = ip_checksum(ip, (ip->ver_ihl & 0x0F) * 4);
    if (ip_hdr_csum != 0x0000 && ip_hdr_csum != 0xFFFF) return;

    // 4. 解析 UDP 头
    udp_hdr_t *udp = (udp_hdr_t *)((uint8_t *)ip + sizeof(ip_hdr_t));
    if (ntohs(udp->dst_port) != UDP_ECHO_PORT) return;  // 不是我们的端口

    // 5. 提取 UDP 数据
    uint8_t  *udp_data = (uint8_t *)udp + sizeof(udp_hdr_t);
    uint16_t  udp_datalen = ntohs(udp->length) - sizeof(udp_hdr_t);

    // 6. 构造 Echo 响应: 交换 src/dst
    //    直接在原帧上修改, 避免复制

    // 6a. 交换 MAC 地址
    uint8_t tmp_mac[6];
    memcpy(tmp_mac, eth->dst_mac, 6);
    memcpy(eth->dst_mac, eth->src_mac, 6);
    memcpy(eth->src_mac, my_mac, 6);

    // 6b. 交换 IP 地址
    uint32_t tmp_ip = ip->dst_ip;
    ip->dst_ip = ip->src_ip;
    ip->src_ip = my_ip;
    ip->ttl = 64;     // 新 TTL
    ip->id = 0x0001;  // 随便一个 ID
    ip->hdr_checksum = 0;
    ip->hdr_checksum = ip_checksum(ip, sizeof(ip_hdr_t));

    // 6c. 交换 UDP 端口
    uint16_t tmp_port = udp->dst_port;
    udp->dst_port = udp->src_port;
    udp->src_port = htons(UDP_ECHO_PORT);
    // UDP payload 不变 —— 正是 echo
    udp->checksum = 0;
    udp->checksum = udp_checksum(ip, udp);

    // 7. 通过 ENET MAC 发送出去
    uint16_t eth_len = sizeof(eth_hdr_t)
                     + sizeof(ip_hdr_t)
                     + ntohs(udp->length);
    enet_send_frame(frame, eth_len);
}

recvfrom() 返回的那一刻,这个UDP数据报已经完成了以下旅程:100BASE-T1 PHY把PAM-3模拟波形解码成数字bit→MAC层验证FCS→DMA引擎把帧从PHY的RX FIFO搬到主内存的DMA描述符指向的缓冲区→LWIP协议栈剥掉以太网头、IP头、UDP头→数据载荷拷贝到你的buf[]数组。所有这些,在你调用recvfrom()的几微秒之前已经在硬件里完成了。

当你从诊断仪上ping这个ECU然后发一个UDP echo——诊断仪发出一个以太网帧,经过交换机、网关、车载以太网,在物理层被PHY的PAM-3解调和回波抵消解析为MII nibble流,在MAC层被组装为完整的帧,通过DMA送入SRAM,ENET中断触发ISR,ISR里你的enet_rx_handler被调用——然后你交换MAC/IP/端口,把数据原封不动地发回去。 整个过程,如果是100Mbps的链路且帧长64字节,从接收完成到发送完成——大约2-3毫秒(主要瓶颈是中断延迟和软件处理)。

以太网解决的是“怎么传“。但车载以太网还需要回答“传什么“和“为什么传“。一个关键场景是诊断。你的车出了故障灯,4S店的诊断仪要读出故障码、要做ECU刷写、要跑执行器测试。CAN可以做到,但500MB的固件包,用CAN传要一个多小时。这就是DoIP出现的理由——不是替代UDS,是给UDS换一条更快的高速路。

DoIP:诊断从CAN搬家到以太网

DoIP(Diagnostics over IP,ISO 13400)是把UDS诊断协议迁移到TCP/IP的方案。

在CAN上做UDS诊断——你被8字节帧长掐住喉咙。一个0x22服务读某个DID的响应如果超过8字节,得用ISO 15765-2的流控帧机制做多帧传输。效率极低:诊断仪发0x30流控帧→ECU分配连续帧(CF)发送→每帧之间还要隔一个最小时隙(Stmin)。刷写固件时,500MB要穿越大约1600万次CAN帧交互。

在DoIP上:诊断仪通过UDP广播(目的端口13400)发现车辆上的DoIP节点,发送Vehicle Identification Request,接收Vehicle Identification Response——得到VIN、逻辑地址、进一步的连接信息。然后诊断仪针对选定的目标ECU建立TCP连接(端口13400)。此后所有UDS请求/响应通过TCP流传输——UDS服务原封不动,但没有了8字节帧长限制。一个0x22 ReadDataByID的响应可以放大到一整个TCP segment,4096字节直接发过来。

DoIP DISCOVERY 消息格式(简化):

诊断仪发UDP广播:

Payload Type: 0x0001 (Generic DoIP header)
Version:      0x02
Inverse Ver:  0xFD
...
Payload:      Vehicle Identification Request

ECU响应UDP Unicast:

Payload Type: 0x0004 (Vehicle ID Response / Announcement)
VIN:          "WDD..."
Logical Addr: 0x0010 (诊断地址)
EID:          6 bytes (Entity ID)
GID:          6 bytes (Group ID)
Further Actions: 0x00 = 不需要进一步路由激活

诊断仪随后对Logical Address 0x0010发起TCP连接。连接建立后,后续UDS数据格式:

DoIP Header (8 bytes):
  Version:          0x02
  Inverse Version:  0xFD
  Payload Type:     0x8001 (Diagnostic Message)
  Payload Length:   N

UDS Message (N bytes):
  SA:  Source Address
  TA:  Target Address
  ... UDS Service + Parameter ...

ECU刷写:500MB固件通过DoIP在2-3分钟内完成下载(100Mbps以太网有效净负载约70Mbps,500MB÷(70/8)≈57秒。加上ECU擦除Flash和写入的时间,总计2-3分钟)。并行诊断:诊断仪可以同时对多个ECU建立TCP连接——这在CAN上不可能(CAN是广播总线,一个物理段上同时只能一个节点发帧,多帧通信需要调度)。

DoIP不是“UDS的升级版“——它是把诊断从“总线拓扑“释放到“网络拓扑“。

Gateway ECU:两个世界的翻译官

一辆实际智能汽车的EE架构是这样的:

CAN域(实时控制):动力总成CAN(500kbps)、底盘CAN(500kbps)、车身CAN(125kbps)、舒适CAN(125kbps)。每个CAN段由独立的CAN控制器驱动。帧在各自段内广播。段与段之间通过Gateway ECU中的CAN路由功能转发——只有需要的信号被转发,不需要的留在本段内。

以太网域(数据):信息娱乐域、ADAS域各自有以太网交换机(Switch),形成星型拓扑。上面跑SOME/IP、DoIP、AVB/TSN。带宽100Mbps-1Gbps。

Gateway ECU坐在两个域的交界处。它的CAN控制器挂在动力CAN段上接收发动机转速帧;它的以太网MAC挂在车载以太网交换机上接收摄像头视频流。它的SoC(通常是高性能ARM核,如Cortex-A53/A72)运行完整的协议栈——CAN端是信号路由(AUTOSAR COM→PduR→CanIf→Can),以太网端是TCP/IP+SOME/IP+DoIP。

当你从诊断仪通过DoIP读取发动机转速时:

诊断仪 → TCP连接 → Gateway(以太网端口)
  → Gateway的SOME/IP层把DoIP请求解封为UDS请求
  → Gateway的PDU Router把UDS请求路由到CAN域
  → Gateway的CAN控制器(Body CAN)发送CAN帧:
    ID=0x7DF, data=03 22 F4 0C 00 00 00 00
    (UDS物理请求 0x22 ReadDataByIdentifier DID=0xF40C)
  → 发动机ECU回复CAN帧:
    ID=0x7E8, data=10 14 62 F4 0C 00 00 00
    (UDS首帧响应, 表示还有流控)
  → Gateway通过ISO 15765-2流控协议接收完整响应
  → Gateway把UDS响应组装为DoIP消息, 通过TCP发回诊断仪

来追踪一个具体的包。发动机ECU(CAN域)发出了一个DTC——“冷却液温度过高”,CAN ID=0x18FEDF00。网关的CAN控制器收到这个帧,触发接收中断。MCU把这个CAN帧的8字节数据通过SPI或内部总线传给网关的应用处理器。应用处理器上的AUTOSAR COM栈把CAN信号翻译成UDS DTC格式。然后这个DTC被打包成一个SOME/IP通知消息——UDP载荷,目标IP是诊断仪。这个IP包穿过网关的以太网MAC、100BASE-T1 PHY,通过单对双绞线到达诊断仪。整个旅程:CAN帧→CAN控制器→MCU→应用处理器→以太网栈→100BASE-T1 PHY→诊断仪。不到10毫秒。

Gateway是翻译官——它让“信号总线“和“数据网络“能互相理解。这不是技术债务的妥协——这是正确架构的必然。实时控制需要确定性延迟和抗故障鲁棒性,CAN给你这两个。数据需要带宽和路由灵活性,以太网给你这两个。两者不互斥——它们分工。

为什么以太网不会取代CAN(在可预见的未来)

你可能会想:如果以太网这么快、这么灵活、支持服务发现——为什么不把车上所有通信都切到以太网上?

两个根本原因。

一、确定性。 CAN的优先级仲裁是硬确定性的——最高优先级的帧在最坏情况下的延迟可以被精确计算(帧长+3位帧间间隔)。TCP/IP栈的延迟是非确定性的——重传超时(RTO)从几百毫秒到数秒不等,取决于拥塞控制算法(Reno/Cubic/BBR)的动态调节。刹车信号不能等待TCP重传超时。不是“太慢“——是“不可预测“。

二、成本和复杂性。 一个CAN节点的物料成本(收发器+控制器)不到$1。一个100BASE-T1节点需要PHY($3-5)、MAC、交换机端口、更复杂的线束和连接器——成本高出一个数量级。用在动力总成和底盘上几十个ECU上,这多出来的成本在规模上是不可接受的。CAN用$1的收发器解决$1问题的能力——正是它活过40年的原因。

CAN管“活着“——发动机不熄火、刹车不失灵、转向不锁死。以太网管“聪明“——自动驾驶感知、云端更新、信息娱乐。这不是谁取代谁。是谁在各自的约束空间里把自己的事做到最好。

你的角色也在变

十年前,你是“CAN工程师“——你对着DBC文件和CANoe做信号收发,你的调试工具是示波器差分探头。今天,当车载以太网进入量产——你需要同时理解差分信号和TCP重传超时,需要同时诊断CAN error frame和SOME/IP service discovery failure,需要同时调试FlexCAN descriptor ring和lwIP的pbuf内存池。

但你不变成一个纯IT工程师。你比网络工程师多一层理解:你知道100BASE-T1的PAM-3眼图在车内20A PWM驱动的EMI环境下会怎么闭合。你知道DoIP刷写时如果邻域的DCDC大功率充电模块启动——共模噪声会让TCP丢包率飙升到30%,触发连续重传。你知道在-40°C冷启动时PHY的PLL锁定时间从150ms延长到400ms——而你的bootloader超时设置是300ms。

你不是简单地“把IT技术搬到车上“——你是在车的物理约束内重新验证每一个IT技术的假设。 网络工程师假设“电缆是Cat5e,环境温度是25°C“。你面对的现实是“电缆是单对UTP穿引擎舱,环境温度是-40到125°C,隔壁是50A的EPS电机PWM驱动“。同样的TCP协议栈——不同的物理世界,不同的答案。

这又回到了本书的主题:打通。打通CAN和以太网。打通物理层和应用层。打通信号总线和数据网络。你不是在两个世界中选一个——你是站在它们的交界处,让它们共同工作。


本篇小结

今天我们做了一件事:理解车载以太网如何进入汽车,以及它为什么不会取代CAN——而是与CAN分工互补。

关键结论:

  1. 100BASE-T1用一对非屏蔽双绞线跑100Mbps全双工:回波抵消分离收发信号,PAM-3编码——这个DSP魔法让汽车的“数据神经系统“成为可能。
  2. SOME/IP和DoIP赋予了以太网“服务发现“和“大文件诊断“能力:SOME/IP让服务自己“喊I’m Here“,DoIP让诊断仪通过IP地址一次刷入几百MB固件。
  3. Gateway是翻译官:CAN帧到IP包、信号到服务——跨协议桥接让“信号总线“和“数据网络“能互相理解。这不是技术债务——是正确架构的必然。

下一节,分布式系统最根本的难题——让全车100个ECU在微秒级精度上共享同一道时间轴。PTP用四个时间戳和PI控制器,做到了惠更斯400年前用横梁做过的事。

【下集预告】

以太网解决了带宽。SOME/IP解决了服务发现。DoIP解决了大数据诊断。Gateway解决了异构网络桥接。

但分布式系统还有一个更根本的问题没有解决:时间。

一辆车上100个ECU,各自有自己的晶振,各自以略微不同的频率振荡。什么是“同时“?摄像头在第T_camera时刻拍到的行人,和毫米波雷达在第T_radar时刻扫到的目标——是同一个物体吗?“在高速公路上100km/h,两个tick之间车移动了1.4cm”——如果时间戳差1ms,目标位置就错了28cm。传感器融合算法的输入是错的——输出就是对致命事故的误判。

回答这个问题,需要让全车所有ECU的时钟互相同步——不是同步到毫秒,是同步到微秒甚至纳秒。