车载网络与通信:CAN总线驱动移植、CAN HAL实现、车载以太网(AVB/TSN)配置、V2X通信基础

各位同学,今天我们来聊聊车载网络。说实话,这部分内容在Android Automotive移植中属于「硬骨头」。为什么?因为车载网络不像Wi-Fi或蓝牙那样,上层有非常成熟的Android框架支撑。CAN总线、车载以太网这些东西,Android原生系统压根儿就不认识它们。你得自己从底层驱动一路搭到HAL层,再跟CarService对接。

我个人习惯把车载网络移植分成四个模块来讲:CAN总线驱动移植、CAN HAL实现、车载以太网(AVB/TSN)配置,以及V2X通信基础。咱们一个一个来。

核心观点:车载网络移植的本质,是把物理总线上的数据「翻译」成Android上层能理解的Service事件。翻译得不好,车机就收不到车速、转向角、电池状态这些关键信息。

14.1 CAN总线驱动移植

CAN总线,说白了就是汽车的「神经」。发动机转速、刹车状态、车门开关,全都在CAN总线上跑。你要做的第一件事,就是让Linux内核认识你的CAN控制器。

我在项目中遇到过一块i.MX8QM的开发板,板载的CAN控制器是FlexCAN。嗯,这里要注意,NXP的FlexCAN IP核在不同芯片上寄存器布局略有差异,不能直接拿参考代码往上怼。

14.1.1 设备树配置

先看设备树。这是CAN驱动移植的第一步,也是最容易出错的地方。我给大家一个典型的配置模板:

// arch/arm64/boot/dts/freescale/imx8qm-can.dtsi
&flexcan1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_flexcan1;
    clocks = &clk IMX8QM_CAN1_CLK,
             &clk IMX8QM_CAN1_IPG_CLK;
    clock-names = "can", "ipg";
    xceiver-supply = ®_can_3v3;
    status = "okay";
};

&pinctrl_flexcan1 {
    fsl,pins = <
        IMX8QM_FLEXCAN1_TX_AD_B1_10 0x21
        IMX8QM_FLEXCAN1_RX_AD_B1_11 0x21
    >;
};

这里有几个坑,我踩过:

  • 时钟源必须配对:CAN控制器通常需要两个时钟——CAN时钟和IPG时钟。搞反了,驱动能加载但收发数据全是乱码。
  • 收发器供电别漏:xceiver-supply这个属性,很多参考设备树里没有。没有它,CAN收发器不工作,总线上一片死寂。
  • 引脚复用要核对原理图:别信参考设计,一定要拿原理图对着改。我曾经因为TX/RX引脚搞反,调了整整两天。

14.1.2 内核配置与驱动验证

设备树改好后,内核配置要打开CAN支持:

CONFIG_CAN=y
CONFIG_CAN_FLEXCAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_BCM=y

编译烧录后,怎么验证驱动是否正常工作?我的习惯是三步走:

  1. 检查dmesg:看有没有"flexcan"相关的probe成功日志。
  2. 查看网络接口ip link show,应该能看到can0设备。
  3. 回环测试ip link set can0 type can bitrate 500000 loopback on,然后用candump和cansend做收发测试。
小技巧:调试CAN驱动时,我习惯在板子上同时接一个USB-CAN分析仪。这样能区分是驱动问题还是总线物理层问题。驱动发出来的数据,分析仪收不到,那就是驱动或硬件问题;分析仪能收到但上层应用收不到,那就是HAL层的问题。

14.2 CAN HAL实现

驱动层搞定了,接下来就是HAL层。Android Automotive的CAN HAL定义在hardware/interfaces/automotive/can/目录下。说白了,你要实现一个守护进程,把CAN总线上的原始帧转换成Android的HIDL事件。

我给大家画个架构图,方便理解:

CAN HAL 架构分层 Android CarService / 车载应用 ICarVehicle HAL (HIDL/AIDL) can-hal-daemon (帧解析 + 信号提取) Linux CAN驱动 (SocketCAN) 原始CAN帧 (11/29位ID + 数据) 车辆属性 (车速、转速、转向角...)

这个图你看懂了吗?底层是SocketCAN,中间是can-hal-daemon,上层是CarService。can-hal-daemon要做的事情,就是订阅特定的CAN ID,解析DBC文件定义的信号,然后通过HIDL接口上报给CarService。

我给大家看一段核心代码,这是从CAN帧里提取车速信号的逻辑:

// CanHalDaemon.cpp - 车速信号提取
void CanHalDaemon::onCanFrameReceived(const can_frame& frame) {
    // 假设车速信号在CAN ID 0x123,起始位bit 8,长度12位,小端序
    if (frame.can_id == 0x123) {
        uint8_t* data = frame.data;
        // 提取bit 8~19,共12位
        uint16_t raw_speed = (data[1] << 4) | (data[2] >> 4);
        // 根据DBC定义,分辨率为0.01 km/h
        float speed_kmh = raw_speed * 0.01f;

        // 通过HIDL上报
        auto status = mVehicleHal->setValue(
            VehiclePropType::VEHICLE_SPEED,
            speed_kmh
        );
        if (!status.isOk()) {
            ALOGE("Failed to report speed: %s", status.description().c_str());
        }
    }
}
注意:CAN帧的位提取操作,一定要用无符号类型。我见过有人用int8_t去提取,结果符号位扩展导致数据全错。另外,DBC文件里的起始位定义,各家车厂可能不一样——有的从LSB开始数,有的从MSB开始数。移植前一定要跟车厂的网络工程师确认清楚。

14.3 车载以太网(AVB/TSN)配置

车载以太网跟普通以太网最大的区别是什么?两个字:确定性。普通以太网是「尽力而为」的,但车载以太网要求「准时到达」。这就是AVB(Audio Video Bridging)和TSN(Time-Sensitive Networking)要做的事情。

在i.MX平台上配置AVB/TSN,主要涉及两个层面:

  • 内核层面:打开IEEE 802.1Qav、802.1Qbv等协议支持
  • 用户空间层面:使用tsntool或iproute2配置流规则

我记得第一次配置TSN时,被那个门控列表(Gate Control List)搞得头大。给大家看一个实际配置:

# 配置TSN的Qbv调度
# 假设周期为1ms,前500us给音视频流,后500us给控制流

# 创建优先级队列
tc qdisc add dev eth0 root handle 1: mqprio \
    num_tc 3 \
    map 0 1 2 2 2 2 2 2 \
    queues 1@0 1@1 1@2 \
    hw 0

# 配置门控列表
tc qdisc add dev eth0 parent 1:1 handle 10: taprio \
    num_tc 3 \
    map 0 1 2 2 2 2 2 2 \
    queues 1@0 1@1 1@2 \
    base-time 0 \
    sched-entry S 0x01 500000 \  # 前500us,只开队列0(音视频)
    sched-entry S 0x06 500000 \  # 后500us,开队列1和2(控制流)
    flags 0x2

这里有个关键点:base-time必须跟整个TSN网络的时钟同步。如果不同步,所有节点的门控时间就对不上,数据就会乱掉。所以AVB/TSN必须配合gPTP(IEEE 802.1AS)时钟同步协议一起用。

调试建议:我习惯先用两台设备做点对点测试。一台发流,一台收流,用wireshark抓包看时间戳。如果发现抖动超过100us,那大概率是时钟同步没配好,或者中断亲和性没设置对。

14.4 V2X通信基础

V2X(Vehicle-to-Everything)是车载通信的终极形态。目前主流的技术路线有两种:DSRC(专用短程通信)和C-V2X(蜂窝V2X)。在i.MX平台上做V2X移植,通常是通过USB或PCIe外挂一个V2X模块。

移植要点如下:

模块 接口类型 驱动方式 注意事项
DSRC模块 USB / SDIO 标准网络驱动 + 专用协议栈 需要处理IEEE 802.11p的OFDM参数
C-V2X模块 USB / PCIe MBIM或QMI协议 需要配置SIM卡和APN
GNSS模块 UART / I2C 标准串口驱动 + NMEA解析 定位精度直接影响V2X安全应用

V2X通信的协议栈通常包含三层:

  1. 接入层:负责物理层和MAC层,DSRC用802.11p,C-V2X用LTE-V2X或NR-V2X。
  2. 网络层:DSRC用WSMP(Wave Short Message Protocol),C-V2X用IPv6。
  3. 应用层:BSM(基本安全消息)、RSI(路侧信息)等。

我在移植C-V2X模块时遇到过一个坑:模块通过USB连接到i.MX后,系统识别为ttyUSB设备,但用AT指令死活拨不上号。后来发现是模块的固件版本太老,不支持MBIM协议。升级固件后,用mbimcli工具配置APN,一次成功。

一句话总结:V2X移植的核心不是驱动,而是协议栈的集成。驱动层通常厂商会提供,但协议栈跟Android CarService的对接,需要你自己写JNI桥接层。

好了,车载网络这块的内容就讲到这里。CAN总线驱动移植是基础,CAN HAL实现是桥梁,车载以太网解决的是带宽和确定性传输问题,V2X则是面向未来的通信能力。每一层都有坑,但只要你把底层数据流理清楚,上层对接就是水到渠成的事。


公众号:蓝海资料掘金营,微信deep3321