车载网络与通信:CAN总线驱动移植、CAN HAL实现、车载以太网(AVB/TSN)配置、V2X通信基础
各位同学,今天我们来聊聊车载网络。说实话,这部分内容在Android Automotive移植中属于「硬骨头」。为什么?因为车载网络不像Wi-Fi或蓝牙那样,上层有非常成熟的Android框架支撑。CAN总线、车载以太网这些东西,Android原生系统压根儿就不认识它们。你得自己从底层驱动一路搭到HAL层,再跟CarService对接。
我个人习惯把车载网络移植分成四个模块来讲:CAN总线驱动移植、CAN HAL实现、车载以太网(AVB/TSN)配置,以及V2X通信基础。咱们一个一个来。
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
编译烧录后,怎么验证驱动是否正常工作?我的习惯是三步走:
- 检查dmesg:看有没有"flexcan"相关的probe成功日志。
- 查看网络接口:
ip link show,应该能看到can0设备。 - 回环测试:
ip link set can0 type can bitrate 500000 loopback on,然后用candump和cansend做收发测试。
14.2 CAN HAL实现
驱动层搞定了,接下来就是HAL层。Android Automotive的CAN HAL定义在hardware/interfaces/automotive/can/目录下。说白了,你要实现一个守护进程,把CAN总线上的原始帧转换成Android的HIDL事件。
我给大家画个架构图,方便理解:
这个图你看懂了吗?底层是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());
}
}
}
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)时钟同步协议一起用。
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通信的协议栈通常包含三层:
- 接入层:负责物理层和MAC层,DSRC用802.11p,C-V2X用LTE-V2X或NR-V2X。
- 网络层:DSRC用WSMP(Wave Short Message Protocol),C-V2X用IPv6。
- 应用层:BSM(基本安全消息)、RSI(路侧信息)等。
我在移植C-V2X模块时遇到过一个坑:模块通过USB连接到i.MX后,系统识别为ttyUSB设备,但用AT指令死活拨不上号。后来发现是模块的固件版本太老,不支持MBIM协议。升级固件后,用mbimcli工具配置APN,一次成功。
好了,车载网络这块的内容就讲到这里。CAN总线驱动移植是基础,CAN HAL实现是桥梁,车载以太网解决的是带宽和确定性传输问题,V2X则是面向未来的通信能力。每一层都有坑,但只要你把底层数据流理清楚,上层对接就是水到渠成的事。