13、车载以太网与诊断:EthernetManager、车载诊断协议(OBD-II)、CAN总线数据读取、故障码解析
各位同学,今天我们来聊聊车载以太网和诊断这块硬骨头。说实话,我刚入行那会儿,觉得诊断就是插个OBD盒子读读故障码,简单得很。直到有一次,我在实车上调试一个多媒体系统,车机死活连不上诊断仪,查了三天才发现是以太网物理层的极性搞反了……嗯,从那以后我再也不敢小看这块内容了。
车载诊断,说白了就是车跟人之间的「对话」——车告诉你它哪里不舒服,你帮它对症下药。而以太网和CAN总线,就是这场对话的「电话线」。今天我们就从Android Automotive的角度,看看怎么用代码跟车「聊天」。
13.1 车载以太网:EthernetManager 实战
先说说以太网。你可能觉得奇怪,车里面为什么要用以太网?CAN总线不是挺好的吗?其实,随着ADAS、高清地图、OTA升级这些功能上来,CAN那点带宽(最高1Mbps)根本不够用。以太网一上来就是100Mbps、1Gbps,这才是现代车载应用的「高速公路」。
在Android Automotive中,以太网的管理是通过 EthernetManager 来做的。我个人习惯先检查设备是否支持以太网:
EthernetManager ethernetManager = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
if (ethernetManager != null) {
// 获取所有以太网接口
List<EthernetInterface> interfaces = ethernetManager.getInterfaces();
for (EthernetInterface iface : interfaces) {
Log.d("Ethernet", "接口名称: " + iface.getName());
Log.d("Ethernet", "MAC地址: " + iface.getMacAddress());
Log.d("Ethernet", "连接状态: " + iface.isConnected());
}
}
这里有个坑,我踩过。有些车机的以太网接口是「虚拟」的——它底层可能走的是USB转以太网,或者通过PCIe桥接。你调用 getInterfaces() 可能返回空列表。这时候别慌,先确认一下硬件是否真的接好了。
/vendor/etc/ethernet.xml 里。你可以用adb pull出来看看,里面定义了接口名称、IP分配方式等。我调试时经常先看这个文件,能省不少时间。
13.2 车载诊断协议(OBD-II)基础
OBD-II,全称On-Board Diagnostics II,是1996年之后几乎所有乘用车都强制支持的诊断标准。你想想看,一辆车上有几十个ECU(电子控制单元),每个都可能出问题。OBD-II就是让这些ECU统一「说话」的协议。
OBD-II的物理层其实有好几种:
| 协议类型 | 物理层 | 速率 | 常见车型 |
|---|---|---|---|
| ISO 15765-4 | CAN总线 | 250/500 kbps | 大多数2008年后车型 |
| ISO 14230-4 | K线 | 10.4 kbps | 老款欧洲车 |
| ISO 9141-2 | K线 | 10.4 kbps | 老款亚洲车 |
| SAE J1850 | VPW/PWM | 10.4/41.6 kbps | 老款美系车 |
现在的新车,99%都走CAN总线了。所以我们在Android Automotive上做诊断,核心就是通过CAN接口收发OBD-II报文。
02 01 0D 00 00 00 00 00,其中02是数据长度,01是服务ID(显示当前数据),0D是PID(车速)。响应格式类似,但第一个字节会加上0x40变成0x41。
13.3 CAN总线数据读取:从Socket到应用层
在Android上读取CAN总线数据,通常有两种方式:
- 通过SocketCAN —— Linux内核原生支持,Android Automotive也继承了这一点。
- 通过JNI调用底层库 —— 如果车机厂商提供了私有接口。
我个人更推荐SocketCAN,因为它标准、稳定,而且不需要厂商额外支持。来看看怎么用:
// 打开CAN socket
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
struct sockaddr_can addr;
struct ifreq ifr;
// 假设CAN接口是can0
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
// 设置过滤器,只接收OBD-II相关的CAN ID
struct can_filter rfilter[2];
rfilter[0].can_id = 0x7E8; // 发动机ECU响应
rfilter[0].can_mask = CAN_SFF_MASK;
rfilter[1].can_id = 0x7E9; // 变速箱ECU响应
rfilter[1].can_mask = CAN_SFF_MASK;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
// 循环读取数据
struct can_frame frame;
while (1) {
int nbytes = read(s, &frame, sizeof(struct can_frame));
if (nbytes > 0) {
// 解析CAN帧
Log.d("CAN", "ID: 0x%X, DLC: %d, Data: %02X %02X ...",
frame.can_id, frame.can_dlc,
frame.data[0], frame.data[1]);
}
}
Vehicle HAL 或者厂商提供的诊断服务来间接访问CAN总线。我曾经在一个项目里因为权限问题折腾了两天,最后发现厂商在 sepolicy 里把CAN socket给禁用了……
13.4 故障码解析:从原始数据到可读信息
故障码,也就是DTC(Diagnostic Trouble Code),是OBD-II诊断的核心输出。一个标准的DTC由5个字符组成,比如 P0301:
- 第一位:系统类型(P=动力总成,B=车身,C=底盘,U=网络)
- 第二位:代码类型(0=通用,1=制造商自定义)
- 第三位:子系统(1=燃油/空气,2=燃油系统,3=点火系统……)
- 第四五位:具体故障编号
举个例子,P0301 表示「动力总成-通用-点火系统-第1缸失火」。你想想看,如果没有这个标准,每个厂商自己定义一套编码,那维修师傅得背多少本手册?
在代码层面,解析故障码其实不复杂。我们通过OBD-II服务 03(读取已确认故障码)来获取:
// 请求:03 00 00 00 00 00 00 00
// 响应:43 01 03 01 00 00 00 00
// 解析:43是响应服务ID,01是故障码数量,03 01是第一个故障码
byte[] response = sendObdRequest(new byte[]{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
int dtcCount = response[1];
List<String> dtcList = new ArrayList<>();
for (int i = 0; i < dtcCount; i++) {
int highByte = response[2 + i * 2] & 0xFF;
int lowByte = response[3 + i * 2] & 0xFF;
String dtc = decodeDTC(highByte, lowByte);
dtcList.add(dtc);
}
private String decodeDTC(int high, int low) {
char[] prefix = {'P', 'C', 'B', 'U'};
char firstChar = prefix[(high >> 6) & 0x03];
int secondDigit = (high >> 4) & 0x03;
int thirdDigit = high & 0x0F;
int fourthDigit = (low >> 4) & 0x0F;
int fifthDigit = low & 0x0F;
return String.format("%c%d%d%d%d", firstChar, secondDigit, thirdDigit, fourthDigit, fifthDigit);
}
P0000。查了半天,发现是CAN总线的终端电阻没接好,导致信号反射,数据全乱了。所以,如果你发现读到的数据不对劲,先检查物理层,别急着怀疑代码。
13.5 知识体系总览
下面这张图,是我自己总结的车载诊断知识体系。你把它存下来,以后做项目时对照着看,思路会清晰很多。
从这张图你能看到,我们今天的课程其实覆盖了从物理层到应用层的完整链路。以太网和CAN是物理层和传输层的选择,OBD-II是协议层的标准,而EthernetManager和故障码解析则是应用层的实现。每一层都有它的坑,但只要你理解了整体架构,遇到问题就能快速定位。
好了,关于车载以太网和诊断的内容就讲到这里。记住,诊断不是「读个码就完事」——它涉及到硬件、协议、操作系统权限、数据解析等多个层面。下次你在车上读到 P0301 时,希望你能想起今天讲的CAN帧结构、OBD-II服务ID,以及那个让我折腾了两天的sepolicy权限问题。
公众号:蓝海资料掘金营,微信deep3321