UDS主要功能读取故障数据传输上传下载复位等借鉴参考了shnsxz大佬的博客借鉴参考了fish_study_csdn大佬的博客借鉴参考了小猫爪大佬的博客借鉴参考了心机之花大佬的专栏CANFD提供的参考手册强烈建议诊断请求15765第一字节为了解决数据过长即分包的问题15765-2总共定义了4种类型的帧结构表示四种帧的类型主要靠byte1的高四位单帧为0首帧为1连续为2流控为3在单帧的情况下(即)SF_DL代表后面有几个字节数据如果有没有使用的字节通常要用0x55或0xAA来填充。分包情况的工作流程如下图所示FF_DL代表后面有几个字节数据连续帧的SN是用于计数首先发送端通过FFFirstFrame启动通信其中前两个字节的高位4bit标识为0001代表FF低4bit与第二个字节共同表示总数据长度这个数据长度也包含回复FF的数据不包含连续帧的第一字节。接着接收端回应FCFlowControl首字节高4bit为0011低4bit的FlowStatus和第二个字节的BlockSize、第三个字节的SeperateTime共同指示接收速率。FlowStatus存在三种状态CTS0、WT1、OVFLW2。FlowStatus的状态FlowStatus为0时允许发送ConsecutiveFrame为1时要求暂停发送恢复时再发FlowStatus0通知若因资源限制无法接收数据则发送FlowStatus2的FlowControl。Stmin的状态由控制图可知Stmin的大小控制的是上一帧CF结束到开始下一帧CF中间的最小时间间隔BS的状态BS的大小是CF帧在没有流控帧的情况下能发多少帧例如BS为5那么CF能够发五帧然后就要看新的流控帧如何调配。CF就是承载FF无法完全承载的剩余数据了它第一个字节的高4位为0010低4bit用于标识CF的序列号序列号从1开始每发送一次CF增加1。当总数据达到FF中的数据长度后停止发送分段传输的诊断服务举例这是一个读取DTC的命令和应答。03 19 02 08 55 55 55 55 诊断仪发送的SingleFrame的request10 33 59 02 19 01 00 07 ECU以FirstFrame开始传输的response30 00 00 55 55 55 55 55 诊断仪发送的FC21 09 03 05 02 09 05 04 ECU发送的CF22 07 09 05 06 06 09 05 ECU发送的CF23 08 03 08 07 01 05 08 ECU发送的CF24 07 01 06 08 07 01 0C ECU发送的CF25 08 07 01 0D 08 07 03 ECU发送的CF26 07 09 08 01 01 09 09 ECU发送的CF27 01 07 09 AA AA AA AA ECU发送的CF此时传输结束BS和STmin等于0时表示接收端可以以最快的速度来接收数据发送端可以一次发送的CF数量不受限制。其余字节14229UDS报文有子功能1byteService ID(SID)服务标识符相当于CCP的CMD代表了这条诊断命令执行的什么功能。1byteSub-function当前服务标识符具体的操作代表当前诊断服务的具体操作其中Sub-function的8个bit最高位的1bit用于抑制正响应。当最高位为1时不会给出正响应为0给出正响应。xbyteParameter当前功能下的发送参数例如31 01 08 09为开启软件刷写检查31 02 08 09为关闭31为服务标识符01为子功能ID08 09为具体参数没有子功能1byteService ID服务标识符xbyteParameter当前功能下的发送参数通用介绍正响应的意思是执行成功后服务端返回报文报告执行成功负响应的意思是执行失败后服务端返回报文报告执行失败负响应返回的报文最高字节固定为7F第二字节为被拒绝的SID后续字节为被拒绝的原因发送报文27 05回复7F 27 13正响应返回的报文最高字节为SID基础上加上0x40剩余为发送数据与后续返回的数据如果没有子功能发送报文27 05回复67 05 xx xx xx诊断会话包含三个子功能01 Default默认会话02 Programming编程会话03 Extended扩展会话ECU上电后一般处于默认状态编程会话可以进行软件刷写等一系列操作扩展会话大部分诊断数据读写如图为UDS诊断协议图片进入01会话成功进入02会话失败进入03会话成功物理寻址和功能寻址物理寻址指的是请求端与单个响应端之间的通信而功能寻址是请求段与多个响应端之间的通信。即请求端通过物理寻址方式发送请求时只能有一个ECU可以回复响应如果通过功能寻址方式发送请求时同一网络中支持该功能寻址的所有ECU都需要回复响应。ECU设备有自己的物理地址和反馈地址。该地址会在项目初期就确定好。诊断仪向ECU A物理寻址则ECU A在自己的反馈地址发送数据给诊断。诊断仪功能寻址则ECU A\B\C都会在各自的反馈地址发送数据给诊断。常见的SID诊断服务10服务诊断会话10 01默认会话10 02编程会话10 03扩展会话11服务该服务请求ECU根据复位的内容有效地执行ECU复位11 01硬件复位11 03软件复位14服务使用此服务来清楚ECU内存中的故障内存的诊断行行行常见的请求命令为14FFFFFF19服务此服务读取ECU驻留诊断故障代码DTC信息的状态常见用法是19 02 09读取当前和历史故障22服务该服务允许通过DID向ECU请求读取数据值读取功能配置22 F1 01网络配置22 F1 10等27服务该服务在向ECU请求写入数据时需要进行的解锁服务请求种子2701发送钥匙270228服务用于“打开/关闭”ECU的某些消息的传输和/或接收以及消息的通讯类型常见的有28 01 只收不发2E服务该服务 允许通过DID在解锁条件下向ECU请求写入数据值常见的为写入车辆的网络配2E F1 10 xx2F服务使用此服务来替换输入信号、内部ECU功能和\或控制由服务器的数据接口引用的电子系统的输出执行器31服务客户端使用此服务来启动/停止例程并在服务器的内存中请求例程结果。通常刷写过程中会用到。参考小趴菜_自动驾驶搬砖人大佬其中22和2E读取和写入参数是依据DIDDID的功能值需要公司内部进行定义例如77 62我定义的是读取硬件版本22服务的例子// 0x23为诊断仪0x24为下位机// 03代表单帧有效值为三个字节// 22代表读取DID参数// 21 62为DID值在我的工程里命令为为读取硬件版本0x230322216200000000// 10中的1代表为连续帧的首帧013代表总数据长度即19个字节// 62 21 62为回复DID命令剩余字节为数据帧// 数据对应ASCII码35 32 33数据对应5 2 30x241013622162353233// 流控帧告诉下位机用什么速率发剩余报文// 30中的3代表流控帧0代表FS中的继续发送状态// 第一个00代表BS在没有流控帧的情况下能发多少帧第二个00代表stmin两帧发送的时间间隔// 即告诉下位机全速发送0x233000000000000000// 21中的2代表连续帧1代表是第一个连续帧后面的是数据0x242133333333323835// 21中的2代表连续帧2代表是第二个连续帧后面的是数据// 676 19所以的00不是数据0x242230302e313030002E服务的例子// 02表示单帧有效两个字节。10 03表示切换到扩展诊断0x230210030000000000// 06表示单帧有效字节为6个。50 03是固定的// 前两个字节代表P2Server_maxECU在接收到请求消息后需要在50ms时间内发出响应消息的性能要求。后两个字节代表P2EServer_max:当ECU发送否定响应码为0x78后到ECU再次发出响应消息最长5s时间。0x240650030032138800// 02同理27 01代表请求获取密钥种子0x230227010000000000// 06 67 01同理剩余的四个字节是种子值0x2406670166753a2578// 06同理27 02代表发送密钥值剩余的四个字节密钥值// 密钥算法两方确定好上位机接收下位机种子进行运算后发送给下位机CCP协议和这个差不多// 下位机与自己运算获得的密钥对比进行解锁0x230627028384858600// key正确发送正响应负响应的话35代表key错误0x240267020000000000// 10代表连续首帧14代表有20字节2e写入DID指定数据// 20 90代表软件版本0x2310142e2090353231// 流控帧全速发送0x243000000000000000// 连续帧0x2321343536353435330x232236343835313233// 传输完成给出正响应0x24036e209000000000DTC相关知识19服务读取故障信息常用的子功能有0102。如19 01 01为读取状态位为1的故障码数量19 02 01为读取状态位为1的故障码参考博客安娜_beier大佬的DTC相关内容梳理DTC编码的解析例如D14C51D1为High Byte、4C为Middle Byte、51为DTC Low ByteD1的意思为车辆集成厂商自定义燃油测量4C表示具体对象和类型51表示需要编程。和J1939类似属于解释方法可以完全不管PGN源地址等含义直接当成扩展ID用也可以。状态位即19 02 01中的01常用的有01代表当前故障08代表历史故障上位机相当于下发掩码VCU需要根据博客中的置位条件对DTC编码的状态进行置位所以一般是建立一个结构体放编码及状态等信息再根据上位机的掩码回传DTC编码及其他状态//DTC code ,故障检测函数指针故障检测周期故障有效次数存储地址故障等级InitAddDTC(0x056001,DTC_MonitorFun_uFuelLevel_Alarm,10,1,ADDR_001,LEVEL_C);后续根据周期遍历所有通过故障检测函数的返回值决定状态位的状态typedefunion{struct{uint8_tTestFailed:1;uint8_tTestFailedThisMonitoringCycle:1;uint8_tPendingDTC:1;uint8_tConfirmedDTC:1;uint8_tTestNotCompleteSinceLastClear:1;uint8_tTestFailedSinceLastClear:1;uint8_tTestNotCompleteThisMonitoringCycle:1;uint8_tWarningIndicatorRequested:1;}DTCbit;uint8_tDTCStatusByte;}DTCStatusType;下发190101报文回复不考虑计数09代表下位机可用掩码即01和08当前故障和历史故障0001代表符合的只有一个DTC59010900000100下发190201590209往后就是所有符合01掩码的DTC的编码与状态 例如DTC编码056001他的故障为历史故障即0805600108.....// 19 02 01读取当前故障0x23Rx d80319020100000000// 1连续帧 00B代表有11个有效字节59 02 09 代表正响应59子服务02 下位机可用掩码09 即只支持当前和历史故障// 04 10 0E 0904 10 0E代表当前故障的DTC可以用博客的方法进行分析我将其定义为1号传感器开路// 09从下一帧提上来的代表该故障的DTC状态09代表该故障是当前故障也是历史故障0x24Rx d8100B59020904100E// 全速发送将所有DTC和其状态一起发完0x23Rx d83000000000000000// 04 10 0F 09 同理 04100F为DTC码我将其定义为2号传感器开路09代表该故障是当前和历史故障0x24Rx d8210904100F090000// 19 02 08读取历史故障0x23Rx d80319020800000000// 10 0F 59 02 09同理有效字节与正响应回复// 04 10 02 08 同理 041002为DTC码08代表该故障是历史故障0x24Rx d8100F590209041002// 全速发送0x23Rx d83000000000000000// 04 10 0E 09 同理 DTC码为当前和历史故障0x24Rx d8210804100E090410// 04 10 0F 09 同理 DTC码为当前和历史故障0x24Rx d8220F090000000000// 清除所有故障0x23Rx d80414FF FF FF000000// 正响应0x24Rx d8015400000000000014服务清除故障信息参考博客跟我学UDS常用的有14 FF FF FF即清除所有故障信息代码刷写部分的例子借鉴参考了车小猿大佬的UDS十应用层 34/36/37博客删除具体报文在此仅体现刷写流程10 会话控制0385 诊断故障码0228 通信控制0310 会话控制0227 安全访问先 01后 0231 例程控制0134 请求下载36 数据传输37 退出传输31 例程控制0131 例程控制0111 重启01UDS底层移植我基于稀风大佬的代码进行的移植别的移植代码这个内容更详细UDSDemo注释完备结构清晰移植较为简单没有遇到什么坑使用暂未遇到问题推荐使用刷写的话可以在切换到编程会话时进行软复位进入BOOT或者在APP将接受到的数据存储到区域2复位时BOOT对其校验并将其写入到APP区使用Ringbuf时有几个宏定义报错在keil里增加支持gnu即可可能算问题的只有两个10 02切换到boot时等正相应回复完再切换否则错误boot里11服务重启之前先看看36服务的接收到的东西全写到FLASH了吗写完校验完再重启UDS上位机上位机完成刷写、写DID等操作使用pyqt结合各种UDS开源库较为简单可以使用udsoncanudsoncan也依赖于pythoncan和isotp所以对各种常见设备也有较好的支持如kvaserudsoncan官网官网上各种API讲的都很详细较为简单需要注意的点有config中“use_server_timing”: False否则36服务会报p2超时问题hex文件的解析可以用intelhex库在处理的时候分段的hex可以进行填充FF操作UDS分段不是不行但感觉要动脑子。如果自己平时填充建议填充方法1 瑞萨的CS中build tool-hex output options-Fill unused areas in the output ranges with the value可以选择填充FFIDE就帮hex填充了。填充方法2使用hexview手动填充操作简单。手动填充参考该博客填充方法3makefile中elf转hex过程中将空白处填充FF默认填充00。arm-none-eabi-objcopy -O ihex input.elf output.hex --gap-fill0xff刷写、读取写入DID可以封装为类在按下按键后初始化一个线程并实例化执行除此之外可以使能pyqtsingle抛出执行过程中的错误在界面显示贴出上位机我用到的网址client中是udsoncan的各种APIhttps://can-isotp.readthedocs.io/en/latest/https://python-can.readthedocs.io/en/stable/https://udsoncan.readthedocs.io/en/latest/index.htmlhttps://udsoncan.readthedocs.io/en/latest/udsoncan/client.html封装为exe后报错 No module named can.interfaces.kvaser问题是没有在你主文件引用这个库参考网址https://stackoverflow.com/questions/51312059/kvasers-can-library-has-been-loaded-but-program-executable-outputs-a-no-modul刷写可能遇到的问题一般工程都是通过ld或sct文件分成了几段但实际每段不可能占满。例如分了0x00和0x80但实际0x00这一段只用了0x40的大小剩下的部分是空着的UDS刷写时34服务只会发0x00的首地址和空间的大小在遇到空的地方就把0x80处的内容当成0x40后的内容进行写入就导致缺了一块内容传输失败所以就需要把0x40和0x80处的内容给填充填充为0XFF占位。方法一 一般使用srec_cat.exe即可。经常变化大小的程序段一般在最后面其他段的大小一般固定填充地址可以直接写死IDE调用bat脚本实现段的填充。srec_cat.exe aaa.hex-Intel-fill0xff0x400x80-o nnn.hex-Intel方法二用python实现也可以空白处的填充deffill_gaps_with_ff(input_hex_file,output_hex_fileNone):ihIntelHex(input_hex_file)start_addrih.minaddr()end_addrih.maxaddr()segmentsih.segments()filled_ihIntelHex()prev_endstart_addrforseg_start,seg_endinsegments:ifseg_startprev_end:foraddrinrange(prev_end,seg_start):filled_ih[addr]0xFFforaddrinrange(seg_start,seg_end):filled_ih[addr]ih[addr]prev_endseg_end fill_data_listlist(filled_ih.tobinarray())# filled_ih.write_hex_file(output_hex_file)returnfill_data_list方法三上位机用bin文件生成bin时makefile中的objcopy会自动在空白处填充只需要在上位机中写死刷写的起始地址即可。arm-none-eabi-objcopy -I ihex -O binary input.hex --gap-fill0xFF output.bin之前INCA可以分段刷写APP和标定区单独刷所以没遇到问题如果一块刷CCP刷写的时候SET_MTA会设置几次呢会不会遇到相同的问题又已经进行了测试上位机也可以进行如下操作获取每段的实际大小当刷完该段后主动发送37服务暂停刷写然后重新发送34服务定位到新地址继续刷写往后以此类推但请注意要确保你的控制器支持这样操作有些控制器这样操作就会卡死这种操作方法最简单不需要自己再次处理只需要控制器支持备忘录