19、以太网与车载以太网驱动:AVB/TSN协议简介、QNX以太网驱动、Android Ethernet HAL、DoIP(诊断 over IP)实现

说实话,车载以太网这个话题,我每次讲起来都挺感慨的。早些年做嵌入式,大家觉得CAN总线就够用了,谁能想到现在一辆智能座舱车里,光摄像头数据流就能把CAN总线撑爆。以太网,尤其是车载以太网,已经是绕不开的核心技术了。

这一章,我带你从协议层到底层驱动,再到上层应用,把车载以太网这条线捋清楚。内容不少,但都是实战中必须啃下来的硬骨头。

19.1 AVB/TSN协议:为什么车载要搞一套新标准?

先问个问题:普通以太网为什么不能直接用在车上?

原因很简单——确定性。普通以太网是“尽力而为”的传输,你发一个包,什么时候到,不确定。但车里不行。比如ADAS的摄像头数据,延迟超过几毫秒,后果很严重。

AVB(Audio Video Bridging)和后来的TSN(Time-Sensitive Networking)就是来解决这个问题的。说白了,它们给以太网加了一套“交通规则”,让高优先级的数据能走快车道。

核心要点:AVB/TSN不是推翻以太网重来,而是在标准以太网上增加了一套时间同步和流量调度机制。

我个人习惯把AVB/TSN的关键技术归纳为三块:

  • 时间同步(802.1AS):所有节点共享一个精确时钟,精度通常在亚微秒级。我在项目中遇到过,如果时钟不同步,音视频流直接卡顿,根本没法用。
  • 流量整形(802.1Qav / 802.1Qbv):给不同数据流分配不同的优先级和时间槽。比如,控制指令走最高优先级,音视频走次高,普通数据走最低。
  • 预留带宽(802.1Qat):流传输前先“申请”带宽,网络保证不丢包。这有点像你订酒店,先预约,到了就有房。

嗯,这里要注意:AVB和TSN其实是一脉相承的。AVB是早期版本,TSN是后来标准化更完善的版本。现在车载领域,TSN已经是主流了。

车载以太网AVB/TSN协议栈 应用层 DoIP / SOME/IP / AVTP 传输层 TCP / UDP 网络层 IPv4 / IPv6 AVB/TSN 核心协议 802.1AS (时间同步) | 802.1Qav/Qbv (流量整形) | 802.1Qat (流预留) MAC层 + 物理层 100BASE-T1 / 1000BASE-T1 (单对线) 图:车载以太网协议栈分层结构

19.2 QNX以太网驱动:从io-pkt到多网卡管理

QNX的以太网驱动,核心是io-pkt这个进程。你想想看,QNX是微内核架构,驱动都在用户态跑,io-pkt就是那个管理所有网络接口的“大管家”。

我在项目中遇到过一个问题:一块板子上有两个网卡,一个接车内以太网,一个接诊断接口。默认情况下,io-pkt会把两个网卡都识别成en0、en1,但如果你不配置路由表,数据包可能走错口。这个坑,我踩过。

QNX以太网驱动的典型加载流程:

# 启动 io-pkt 并加载特定网卡驱动
io-pkt -d e1000 -p tcpip

# 查看网络接口状态
ifconfig en0

# 配置静态IP
ifconfig en0 192.168.1.100 netmask 255.255.255.0 up

嗯,这里有个关键点:QNX的网卡驱动通常以共享库(.so)形式存在,io-pkt启动时通过-d参数加载。如果你要自己写一个车载以太网PHY的驱动,本质上就是实现一个符合QNX NDIS(Network Driver Interface Specification)接口的库。

实战技巧:调试QNX以太网驱动时,我最常用的命令是io-pkt -v,它会打印详细的驱动加载日志。如果PHY芯片没初始化成功,这里会直接报错,省去你很多排查时间。

19.3 Android Ethernet HAL:让上层应用感知不到底层差异

Android Automotive里,以太网的管理是通过Ethernet HAL来抽象的。说白了,HAL层的作用就是让Framework层不用关心底层是Wi-Fi、蜂窝还是以太网,统一通过ConnectivityService来管理。

Android Ethernet HAL的核心接口:

接口 功能 我遇到的坑
getInterfaceList() 获取所有以太网接口 返回的接口名必须与内核一致,否则上层匹配不上
setLinkUp()/setLinkDown() 启用/禁用接口 曾经因为PHY的link状态没稳定就调用setLinkUp,导致上层认为网卡已死
getLinkProperties() 获取IP、网关、DNS等 如果DHCP获取失败,要返回默认静态IP,否则应用层直接无网络

我曾经在调试一个项目时,发现Android系统里以太网插上去没反应。查了半天,原来是HAL层实现的getInterfaceList()返回的接口名是“eth0”,但内核里实际注册的是“end0”。就这一个字符的差异,折腾了我两天。你想想看,这种问题多冤。

19.4 DoIP(诊断 over IP)实现:把UDS搬到以太网上

DoIP,全称Diagnostics over Internet Protocol。传统诊断走CAN,但CAN的带宽有限,刷写一个大的固件包可能要几十分钟。DoIP用以太网,速度直接提升几个数量级。

DoIP的核心逻辑其实不复杂:

  • 物理层:走TCP/IP,通常端口号是13400。
  • 会话层:先建立TCP连接,然后通过DoIP头部消息进行路由激活。
  • 应用层:UDS(ISO 14229)报文被封装在DoIP消息体中传输。

一个典型的DoIP报文结构:

| 协议版本 (1B) | 反向版本 (1B) | 负载类型 (2B) | 负载长度 (4B) | 负载数据 (N B) |
|     0x02      |     0xFD      |    0x8001     |   0x00000004  |  UDS 诊断请求  |

我在实现DoIP驱动时,最头疼的是路由激活阶段。客户端发来路由激活请求,服务端要验证客户端的逻辑地址是否合法。如果验证失败,连接直接断开。我曾经因为逻辑地址的配置表写错了一个字节,导致诊断仪死活连不上车机。后来我加了一条规则:所有逻辑地址配置必须通过脚本自动生成,杜绝手动填写。

警告:DoIP的TCP连接要保持心跳。如果长时间没有诊断请求,服务端会主动断开连接。我在项目中设置的心跳间隔是5秒,超过30秒无活动则断开。这个参数可以根据实际需求调整,但不要太长,否则会占用连接资源。

最后说一句,DoIP的驱动实现,在QNX和Android上思路是相通的。底层都是socket编程,上层都是UDS协议解析。区别在于QNX里你可能要自己管理socket的生命周期,而Android里可以通过Ethernet HAL把DoIP作为一个特殊的网络服务注册上去。

好了,这一章的内容就到这里。车载以太网这块,从AVB/TSN的协议理解,到QNX和Android的驱动实现,再到DoIP的实战,每一步都有不少细节。希望我踩过的那些坑,能帮你少走一些弯路。


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