十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android车机CAN通信全链路实战:从SocketCAN到UDS诊断

Android车机CAN通信全链路实战:从SocketCAN到UDS诊断 1. 项目概述这不是“在Android上跑个CAN收发”而是一整套车载诊断与通信能力的工程落地你看到这个标题的第一反应可能是“Android也能接CAN不是汽车ECU才玩这个吗”——这恰恰是绝大多数刚接触车载Android开发的人最真实的困惑。我第一次接到“在车机上实现UDS诊断”需求时也以为只是调个JNI接口、读几帧报文的事。结果花了整整三周才让第一帧ISO-TP分段响应从CAN FD总线上稳定回传到Java层中间踩的坑包括内核SocketCAN驱动未启用CAN FD模式、DBC信号解析时字节序误判导致电池SOC显示为负数、UDS 0x22服务读取PID时因NRC 0x31Request Out of Range反复失败却查不出协议栈哪一层丢帧……这些都不是文档里写清楚的“配置步骤”而是必须亲手拧开硬件、抓波形、翻内核日志、比对DBC定义才能定位的真实战场。这个项目标题里的每一个词都不是孤立的技术点而是一条贯穿硬件驱动、内核协议栈、用户态通信、数据建模与应用逻辑的完整链路CAN是物理层和数据链路层的基石SocketCAN是Linux内核为CAN设备提供的标准化用户态访问接口CAN FD是带宽升级的演进形态DBC是整车信号语义的“字典”ISO-TP是承载UDS等高层协议的传输层封装UDS则是整个车载诊断体系的业务语言。它们共同构成了一套在Android车机上实现“与整车ECU对话”的最小可行系统。适合谁不是只懂Java的App开发者也不是只调示波器的硬件工程师而是需要横跨Android系统层、车载通信协议、整车电子架构三界的复合型车载软件工程师——尤其是正在参与智能座舱、OTA升级、远程诊断、BMS监控等项目的同学。你不需要从零造轮子但必须理解每个环节的输入输出、边界条件与失效模式。接下来我会把这条链路上所有关键节点拆开告诉你为什么这么设计、参数怎么算、命令怎么敲、日志怎么看、Bug怎么揪。2. 核心技术链路拆解从物理总线到诊断服务的七层穿透2.1 CAN总线不只是“高低电平”而是确定性实时通信的物理契约很多人把CAN简单理解为“两根线传数据”这就像说“TCP就是发几个字节”。CAN真正的价值在于它用硬件机制解决了分布式系统中最棘手的三个问题多主竞争、错误隔离、时间确定性。我们拆开看仲裁机制不是“抢带宽”而是“抢话语权”CAN帧ID越小优先级越高。这不是软件调度而是由总线上的显性位逻辑0和隐性位逻辑1电平决定的——当两个节点同时发送一个发0一个发1总线呈现0发1的节点立刻检测到不一致自动退出发送。这个过程在微秒级完成且无需中央控制器。我在调试某车型的网关模块时发现仪表盘刷新帧ID0x180和ADAS摄像头校准帧ID0x2A5冲突后者因ID更大被强制延迟但延迟时间严格受总线波特率约束不会影响下一周期的同步。错误处理不是“重传”而是“自愈隔离”每个CAN节点内置错误计数器TEC/REC。当节点持续发送错误帧如位错误、CRC错误其错误计数超过127就会进入“被动错误”状态——只能发被动错误帧不影响总线直到计数低于127才恢复。若计数超255则彻底关闭输出变成“总线脱离”状态。这保证了单个故障节点不会拖垮整条总线。实测中我们曾故意短接某ECU的CAN_H线其错误计数在3秒内飙升至255总线其他节点通信完全不受影响。波特率设置是“采样点精度”的数学游戏CAN波特率 晶振频率 / (BRP × (1 TSEG1 TSEG2))。其中TSEG1传播段相位缓冲段1和TSEG2相位缓冲段2共同决定采样点位置。标准CAN采样点通常设在87.5%即TSEG1:TSEG26:1而CAN FD要求更高精度——比如热词里提到的“6501”实际是采样点65%对应TSEG16,TSEG21。为什么因为CAN FD速率翻倍后信号边沿抖动更敏感采样点必须落在眼图最开阔处。我用示波器抓过同一总线在500kbps和2Mbps下的眼图后者有效采样窗口缩窄40%此时若还用87.5%采样点误码率会飙升。所以CAN FD的BS1/BS2配置绝不是照搬CAN必须根据实际线缆长度、终端电阻、驱动芯片手册重新计算。提示不要盲目相信芯片厂商给的“推荐值”。我们某项目用NXP S32K144手册推荐TSEG16,TSEG21但实测在10米线束下误码率高最终调整为TSEG17,TSEG22采样点移至68%误码率归零。验证方法很简单用CANoe发连续帧Android端用candump -L统计错误帧数。2.2 SocketCANLinux内核的CAN协议栈不是“驱动”而是“标准API”很多Android开发者一上来就想找“CAN驱动源码”这是方向性错误。SocketCAN不是某个具体芯片的驱动而是Linux内核为所有CAN控制器SJA1000、MCP251x、NXP FlexCAN等抽象出的统一网络接口。它的核心价值在于让你用操作socket的方式操作CAN总线屏蔽底层硬件差异。这意味着什么设备即网络接口加载can-dev.ko和具体控制器驱动如flexcan.ko后系统会出现can0、can1等网络接口。你可以用ip link set can0 up type can bitrate 500000启动它用ifconfig can0查看状态——这和配置eth0毫无区别。我们某车机项目用瑞萨R-Car H3其CAN控制器驱动已集成在Android kernel中只需在BoardConfig.mk里开启CONFIG_CAN_FLEXCANy编译后就能看到can0。报文即socket数据包发送CAN帧用sendto()接收用recvfrom()数据结构是标准的struct can_framestruct can_frame { canid_t can_id; // 11位标准ID或29位扩展ID含RTR/IDE标志 __u8 can_dlc; // 数据长度0-8字节 __u8 data[8]; // 实际数据 };注意can_id不是纯IDbit 31是EFF_FLAG扩展帧bit 30是RTR_FLAG远程帧。解析时必须用CAN_EFF_MASK、CAN_RTR_FLAG宏提取否则ID会错乱。我见过太多人直接printf(%d, frame.can_id)结果打印出2147483648这种诡异数字——其实是0x80000000被当成有符号int了。过滤器是“硬件级白名单”SocketCAN支持在驱动层配置ID过滤避免用户态接收无关报文。例如只接收ID为0x123和0x456的帧ip link set can0 down ip link set can0 type can bitrate 500000 ip link set can0 up # 添加两个过滤规则 echo 123 /sys/class/net/can0/device/can_filter echo 456 /sys/class/net/can0/device/can_filter这比在Java层用if判断高效百倍尤其在1000帧/秒的ADAS总线上能显著降低CPU占用。注意Android默认禁用SocketCAN模块。需在kernel config中开启CONFIG_CANy, CONFIG_CAN_RAWy, CONFIG_CAN_BCMy并确保init.rc中执行insmod /system/lib/modules/can-dev.ko。我们某项目因忘记加载can-dev.koip link show始终看不到can0排查了两天才发现是模块依赖问题。2.3 CAN FD带宽翻倍的代价——协议栈、硬件、工具链的全面升级CAN FD不是CAN的“补丁”而是一次重构。热词里反复出现的“CAN FD”、“采样点6501”背后是三个维度的硬性要求物理层升级CAN FD需要支持双速率切换的收发器如TJA1057G传统CAN收发器TJA1040无法工作。我们曾用TJA1040接CAN FD总线结果只能收到标准CAN帧FD帧全部丢失——因为其无法识别FD的BRSBit Rate Switch标志位。控制器能力并非所有CAN控制器都支持FD。NXP S32K144支持但老款S32K116不支持瑞萨R-Car M3支持H3需确认具体版本。验证方法cat /proc/net/can应显示fd_on字段为1或用candump -D can0看是否支持-fFD模式参数。协议栈适配Linux 4.0内核原生支持CAN FD但Android BSP往往滞后。我们某项目基于Android 10内核4.14需手动打补丁修改drivers/net/can/flexcan.c添加FD相关寄存器配置CTRL2[ESRGM], CBT寄存器等并确保struct canfd_frame结构体被正确导出。否则AF_CANsocket创建时会返回EPROTONOSUPPORT。CAN FD的核心参数是数据段波特率它独立于仲裁段。典型配置仲裁段500kbps保证兼容性数据段2Mbps。此时采样点计算公式变为采样点 (TSEG1 1) / (TSEG1 TSEG2 3)热词“6501”即TSEG16, TSEG21 → 7/9 ≈ 77.8%但实际工程中常取65%TSEG16,TSEG22 → 7/1070%或70%TSEG17,TSEG22 → 8/11≈72.7%。选择依据是示波器实测的眼图宽度——必须保证在BRS切换后的高速段采样点仍落在信号稳定的平台区。实操心得不要在未验证硬件支持时就写CAN FD代码。先用cansend can0 123#DEADBEEF发标准帧再用cansend -f can0 123##1DEADBEEF发FD帧-f表示FD##1表示数据长度为16字节。如果后者失败且dmesg | grep flexcan报invalid bit timing说明内核或硬件不支持FD。2.4 DBC文件车载通信的“数据库Schema”不是文本而是信号语义地图DBCData Dictionary for CAN文件常被误解为“报文格式说明书”其实它是整车信号的元数据描述包含ID、信号名、起始位、长度、字节序、缩放因子、偏移量、单位、物理值范围等。没有DBCCAN报文就是一堆无意义的01串。热词中“DBC文件制作”、“DBC复用信号”直指痛点。信号布局是“位域”而非“字节流”CAN帧数据域8字节64位信号可跨越字节边界。例如某车速信号起始位12长度16位则占据第1字节bit4~bit7 第2字节全部8位 第3字节bit0~bit3。DBC中用start_bit和length定义解析时必须按位操作不能简单memcpy。我们某项目用Java解析DBC因用ByteBuffer.getShort()直接读2字节导致车速在127km/h时跳变——因为信号跨越字节高位在低地址字节而Java默认大端实际是小端布局。字节序Endianness是生死线DBC中BYTE_ORDER字段标定信号存储顺序。Motorola格式Intel低位字节在前小端IEEE格式高位字节在前大端。国内车企多用Motorola。验证方法用CANoe发ID0x100数据0x00010000若DBC定义信号起始位0长度16那么解析值应为0x00011Motorola还是0x0100256IEEE实测为准。复用信号Multiplexed Signal是“一帧多用”的钥匙通过一个“多路复用器信号”MUX控制同一ID下不同信号组的激活。例如ID0x200MUX信号值0时解析信号A/B/CMUX1时解析信号X/Y/Z。DBC中用CM_ Multiplex注释定义。我们做BMS监控时同一ID用于上报单体电压MUX0和温度MUX1若解析时忽略MUX值会把温度值当成电压显示误差达100℃。提示DBC不是静态文档。整车厂每轮标定都会更新DBC必须建立版本管理流程。我们用Git管理DBC每次刷写ECU固件时同步更新车机端DBC文件并在App启动时校验MD5。曾因DBC版本不匹配导致空调面板显示“-40℃”实际是温度信号被解析为电压信号。2.5 ISO-TPUDS的“运输层”不是协议而是分段重组引擎UDSUnified Diagnostic Services协议本身不定义如何在CAN上传输它依赖ISO-15765-2ISO-TP作为传输层。热词“ISO-TP”常与“UDS”并列但二者职责分明ISO-TP负责把大于8字节的UDS请求/响应拆成多帧CAN报文并可靠重组UDS定义具体服务如0x22读数据、0x31刷写和响应码NRC。ISO-TP有四种帧类型Single Frame (SF)数据≤7字节1帧搞定首字节高4位0。First Frame (FF)数据7字节首帧首字节高4位1后12位表示总长度。Consecutive Frame (CF)后续帧首字节高4位2低4位为序列号0-15循环。Flow Control (FC)接收方发给发送方的控制帧告知能否继续发、发多少帧、间隔多久。关键参数是STminSeparation Time minimum即CF帧间的最小间隔。热词没提但它是调试UDS的高频故障点。STmin0x00表示“尽快发”但ECU可能来不及处理STmin0x7F表示127ms间隔。我们某项目ECU要求STmin0x2032ms但Android端默认用0x00导致ECU响应NRC 0x72Busy Repeat Request。SocketCAN提供can_isotp模块实现ISO-TP但Android需手动加载# 加载ISO-TP模块 insmod /system/lib/modules/can-isotp.ko # 绑定can0到isotp0接口设置STmin0x20 ip link add dev isotp0 type isotp txcan can0 rxcan can0 txdlc 8 rxdlc 8 stmin 0x20 ip link set isotp0 up之后即可像操作普通socket一样收发ISO-TP帧。注意txdlc/rxdlc必须设为8CAN标准帧最大DLCCAN FD下需设为64。注意ISO-TP不是“透明通道”。它会丢弃非法帧如CF序列号错乱、超时重传默认1000ms、流量控制。若ECU响应慢需调大rxtimeout参数否则recvfrom()直接超时返回。我们某项目因未设rxtimeoutUDS 0x31刷写时ECU擦除Flash耗时2秒Android端1秒超时反复重发导致刷写失败。2.6 UDS车载诊断的“业务协议”不是命令而是状态机交互UDSISO-14229是面向服务的诊断协议核心是会话控制Session Control和安全访问Security Access的状态机。热词“UDS 19服务”、“UDS 31服务”、“UDS NRC”正是其骨架。会话控制是“开门砖”默认在Default Session0x01功能有限。要执行刷写0x31或读取DTC0x19必须先切到Extended Session0x03或Programming Session0x02。命令格式0x10 0x03服务ID子功能。ECU响应0x50 0x03表示成功。我们某项目因未切Session所有0x22读取均返回NRC 0x7FService Not Supported。安全访问是“防盗锁”刷写ECU前必须解锁。典型流程请求种子0x27 0x01→ ECU返回6字节随机种子Seed计算密钥用厂商算法如XOR、AES对Seed加密发送密钥0x27 0x02 6字节Key→ ECU校验通过则解锁热词“uds nrc”中NRC 0x33Security Access Denied即密钥错误。我们曾因算法实现中字节序颠倒导致密钥永远错误。19服务ReadDTCInformation是“故障字典”支持多种子功能如0x02Report DTCByStatusMask读当前故障0x0AReport Supported DTC读所有支持的DTC。响应数据包含DTC码3字节、状态1字节。DTC码按ISO-15031定义如P01010x00 0x10 0x01P动力系统01燃油/空气01传感器性能。解析时需查表转换。31服务RoutineControl是“执行指令”如0x31 0x01 0x01擦除Flash0x31 0x01 0x02校验Flash。响应中0x71表示Routine开始0x7F表示失败0x51表示完成。进度通过0x31 0x03Request RoutineResults查询。实操心得UDS不是“发请求等响应”的简单IO。它要求严格的状态同步。我们某项目在OTA刷写时因网络波动导致CF帧丢失ISO-TP层重传但ECU已超时关闭会话导致后续所有请求返回NRC 0x7F。解决方案在Java层维护会话状态每次UDS请求前检查Session是否有效无效则重发0x10 0x03。3. Android端工程实现从内核驱动到Java SDK的全链路打通3.1 系统层准备Android BSP定制与内核配置Android车机不是通用手机其BSPBoard Support Package必须为CAN定制。这不是App层能解决的必须介入系统构建。内核配置是前提在kernel/arch/arm64/configs/xxx_defconfig中确保CONFIG_CANy CONFIG_CAN_RAWy CONFIG_CAN_BCMy CONFIG_CAN_DEVy CONFIG_CAN_FLEXCANy # 或对应控制器驱动 CONFIG_CAN_ISOTPy # ISO-TP必需 CONFIG_CAN_FDy # 若需CAN FD CONFIG_CAN_XLy # 若需CAN XL下一代缺失任一选项/proc/net/can将为空ip link看不到can0。设备树DTS是硬件绑定在arch/arm64/boot/dts/xxx/xxx.dtsi中添加CAN控制器节点flexcan1 { pinctrl-names default; pinctrl-0 pinctrl_flexcan1; clocks clks IMX_CLK_FLEXCAN1, clks IMX_CLK_FLEXCAN1_IPG; clock-names ipg, per; status okay; /* CAN FD必需 */ can-fd-mode; /* 设置波特率 */ bus-speed 500000; ># 加载CAN模块 on early-init insmod /system/lib/modules/can-dev.ko insmod /system/lib/modules/flexcan.ko insmod /system/lib/modules/can-isotp.ko # 配置CAN接口 on property:sys.boot_completed1 exec_start configure_can service configure_can /system/bin/sh -c ip link set can0 up type can bitrate 500000 class main user root group root注意sys.boot_completed1时机很重要。太早如on init模块未加载完太晚如on property:dev.bootcomplete1App可能已启动并尝试访问can0。提示Android 12引入seccomp-bpf限制某些系统调用如socket(AF_CAN, ...)被禁止。需在sepolicy中添加规则# device/xxx/sepolicy/private/can.te allow system_server can_device:chr_file { read write ioctl open getattr }; allow system_server self:capability net_admin;3.2 JNI层封装绕过Binder直通SocketCAN的高性能通道Android App层用Java但SocketCAN是Linux socket必须通过JNI桥接。关键原则JNI不是“翻译器”而是“零拷贝通道”。避免Java层频繁创建/销毁socket每个new CanSocket()都触发socket()系统调用开销大。我们在JNI层用单例管理socket fd// native-lib.cpp static int g_can_fd -1; extern C JNIEXPORT jint JNICALL Java_com_example_can_CanManager_nativeOpen(JNIEnv *env, jobject thiz, jstring ifname) { if (g_can_fd ! -1) return g_can_fd; // 已打开 const char *iface env-GetStringUTFChars(ifname, nullptr); struct sockaddr_can addr; struct ifreq ifr; g_can_fd socket(PF_CAN, SOCK_RAW, CAN_RAW); strcpy(ifr.ifr_name, iface); ioctl(g_can_fd, SIOCGIFINDEX, ifr); addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_index; bind(g_can_fd, (struct sockaddr *)addr, sizeof(addr)); env-ReleaseStringUTFChars(ifname, iface); return g_can_fd; }Java层调用一次CanManager.open(can0)后续所有读写复用同一fd。数据传递用Direct ByteBuffer杜绝拷贝Java层申请ByteBuffer.allocateDirect(1024)JNI层用GetDirectBufferAddress()获取指针直接read(fd, buf, len)写入。对比jbyteArray方式吞吐量提升3倍以上。我们某ADAS项目需处理1000帧/秒用jbyteArrayCPU占用率达80%改用DirectBuffer后降至25%。异步事件用epoll不阻塞主线程CAN接收不能用recvfrom()阻塞否则UI卡死。JNI层创建epoll fd监听can_fd可读事件通过JavaVM-AttachCurrentThread()回调Java层onCanFrameReceived(byte[] data)。关键代码// 启动epoll线程 pthread_create(epoll_thread, nullptr, epoll_loop, nullptr); void* epoll_loop(void*) { int epfd epoll_create1(0); struct epoll_event ev, events[10]; ev.events EPOLLIN; ev.data.fd g_can_fd; epoll_ctl(epfd, EPOLL_CTL_ADD, g_can_fd, ev); while (1) { int n epoll_wait(epfd, events, 10, 100); // 100ms超时 if (n 0 events[0].data.fd g_can_fd) { ssize_t len recvfrom(g_can_fd, buf, sizeof(buf), 0, nullptr, nullptr); // 回调Java env-CallVoidMethod(java_obj, method_id, java_buffer); } } }注意JNI层必须处理errnoEAGAIN非阻塞socket无数据和errnoEINTR系统调用被信号中断否则线程可能异常退出。我们某项目因未处理EINTR车辆颠簸时GPS中断信号导致epoll线程崩溃。3.3 Java SDK设计面向诊断场景的领域模型封装JNI提供原子能力Java SDK需封装为业务语义。我们摒弃“CAN帧收发器”思路直接构建UDS诊断SDK。会话管理器SessionManager封装会话状态机public class SessionManager { private int currentSession SESSION_DEFAULT; // 0x01 public void enterExtendedSession() { sendUdsRequest(new byte[]{0x10, 0x03}); // 解析响应更新currentSession } public boolean isInProgrammingSession() { return currentSession SESSION_PROGRAMMING; // 0x02 } }安全访问器SecurityAccess集成厂商算法public class SecurityAccess { private byte[] seed; public byte[] requestSeed() { return sendUdsRequest(new byte[]{0x27, 0x01}); // 返回seed } public boolean unlockWithKey(byte[] key) { byte[] req new byte[8]; req[0] (byte) 0x27; req[1] (byte) 0x02; System.arraycopy(key, 0, req, 2, 6); byte[] resp sendUdsRequest(req); return resp.length 2 resp[1] (byte) 0x02; // 正确响应 } }DBC解析器DbcParser动态加载DBC生成信号映射public class DbcParser { private MapInteger, ListSignal idToSignals new HashMap(); public void loadDbc(InputStream dbcStream) { // 解析DBC文本提取SG_行构建Signal对象 // Signal包含name, startBit, length, isSigned, factor, offset, unit } public double parseSignal(byte[] canData, String signalName) { // 根据signal配置从canData中提取位域应用factor/offset } }UDS服务代理UdsService提供高层APIpublic class UdsService { public Response readDataByIdentifier(int did) { byte[] req new byte[]{0x22, (byte)(did 8), (byte)did}; byte[] resp sendUdsRequest(req); return new Response(resp); // 封装NRC、数据等 } public void flashEcu(File binFile) { sessionManager.enterProgrammingSession(); securityAccess.unlock(); // 安全访问 routineControl.eraseFlash(); // 31 01 01 transferData(binFile); // 分块发送23服务 } }实操心得不要在Java层做位运算。我们初期用Integer.bitCount()等方法解析信号性能极差。改为JNI层用C位操作((data[i] 8) | data[i1]) shiftJava层只调用parseSignal(int id, String name)速度提升10倍。3.4 调试与验证从示波器到Logcat的全栈可观测性车载开发最怕“黑盒”。必须建立从物理层到应用层的全链路可观测性。物理层示波器抓波形关键点CAN_H/CAN_L差分电压隐性2.5V显性3.5V、上升/下降时间500ns、眼图张开度。我们用Rigol DS1054Z触发BRS标志位验证FD高速段波形质量。链路层candump实时监控# 抓取所有帧含错误帧 candump -L can0 # 过滤特定ID candump can0,123:7FF # CAN FD模式 candump -f can0日志中ERR表示错误帧RTR表示远程帧X表示扩展帧。传输层isotpdump看ISO-TP流# 监控isotp0接口 isotpdump -s isotp0 # 显示SF/FF/CF/FC帧类型和序列号若看到CF: seq0后无CF: seq1说明CF丢失若FC: BS0说明接收方缓冲区满。应用层Logcat结构化日志在Java SDK中打关键日志Log.d(UDS, String.format(REQ [%02X %02X %02X] - %s, req[0], req[1], req[2], sessionId)); Log.w(UDS, String.format(NRC %02X from ECU, nrc));用adb logcat -s UDS:*过滤配合--pid按进程过滤。提示建立“诊断日志包”机制。当UDS失败时自动打包candump最后1000帧、isotpdump最后100帧、Logcat最后500行压缩为zip供后台分析。我们某项目因此快速定位到ECU固件bug在特定内存压力下ECU的ISO-TP接收缓冲区溢出丢弃CF帧。4. 常见问题与实战排障那些文档里不会写的血泪教训4.1 “CAN设备不存在”从dmesg到硬件连接的逐层排查现象ip link show无can0dmesg | grep can无输出。Step 1查内核日志dmesg | grep -i flexcan\|can看是否有flexcan 400d0000.can: failed to get clock——缺少时钟配置。Step 2查设备树cat /proc/device-tree/soc0/400d0000.can/status若输出disabled说明DTS中status disabled未改为okay。Step 3查硬件连接用万用表测CAN_H/CAN_L对地电压正常应为2.5V左右。若CAN_H3.3V、CAN_L0V说明终端电阻缺失或收发器损坏。我们某项目因车机底板CAN终端电阻虚焊导致总线完全静默。Step 4查模块依赖lsmod | grep can若无can_dev则insmod失败。用depmod -a重建模块依赖再insmod。排查口诀日志无输出→查内核配置设备无节点→查DTS电压不对→查硬件模块加载失败→查依赖。4.2 “UDS响应NRC 0x7F”服务不支持的10种可能NRC 0x7FService Not Supported是最常见的UDS错误但原因千差万别。现象可能原因验证方法解决方案所有服务都返回0x7F未进入Extended Session发0x10 0x03看是否返回0x50 0x03调用sessionManager.enterExtendedSession()仅0x22服务返回0x7FDID不被ECU支持查ECU DTC文档确认DID0xF190是否存在
返回列表