0


根据hex文件制作UDS统一诊断服务CAN多帧报文-python

1. 背景

  1. 统一诊断服务 (Unified diagnostic services UDS) ISO-14229系列标准定义。
  2. 诊断通信的过程从用户角度来看非常容易理解,诊断仪发送诊断请求(request),ECU给出诊断响应(response),而UDS就是为不同的诊断功能的requestresponse定义了统一的内容和格式。
  3. UDS的通信种,CAN报文的传输方式根据内容长短分为 单帧 和多帧传输。
  4. 单帧的组包简单而多帧的组包比较复杂,本博客主要讨论多帧。
  5. CAN报文的帧分为标准帧,扩展帧,远程帧等。大多数情况下使用标准帧。
  6. 标准帧每帧报文是8个字节。
  7. 如果一个UDS报文在一个帧里发不完,那么就需要以多帧的格式组包,然后依次发送。
  8. 对于0x36服务(文件传输)而言,因为文件大多比较长,基本都要以多帧的形式发送,这是一个需要多帧组包的常见场景。
  9. 如果你对UDS的服务还不了解的话,可以查看这篇博客

统一诊断服务(UDS)_晓翔仔的博客-CSDN博客_统一诊断服务

2. CAN报文多帧的格式特点

  1. 0X36服务一个文件可以拆解为多个block,每个block会根据0x36服务之前发送的0x34请求的响应拆分为多个fluid(流)。
  2. 一个完整的fluid(流)的结构可以分为三个部分,如下图:

fluid的第一行

  1. 1个字节的第一个1表示这是一个多帧。
  2. 1字节后一个1+第2个字节(红色)表示fluid的长度。
  3. 3个字节(橙色)表示服务类型,36是文件传输服务。
  4. 4个字节(绿色)表示fluid序号,从0X01开始递增(注意:0xff后面接着0x00)。
  5. 此后都是数据内容(图中d表示数据)。很容易知道 fluid的长度(红色)等于 “数据总长度+2

fluid的第二部分

  1. 每行第1个字节都是序列号,从212f,所以fluid的第二部分最多有15行。
  2. 每行的第28个字节是数据。

fluid的第三部分

  1. 每行第1个字节都是序列号,从202f2f后面接着20,循环往复,可以有很长很长。
  2. 每行的第28个字节是数据。

3. 代码实现

  1. 有一个".hex"结尾的文件,已知它不用拆分block,每个fluid的长度要求是254
  2. 现在诊断仪需要将整个文件以多帧的形式发送到指定ECU,那么应该以什么形式的报文发送过去呢?
  3. 我这里用python写了组包的代码。代码如下:
  1. import math
  2. import string
  3. import time
  4. import zlib
  5. def list_to_hex_string(list_data):
  6. list_str = '[ '
  7. for x in list_data:
  8. list_str += '0x{:02X},'.format(x)
  9. list_str += ' ]'
  10. return list_str
  11. def printlist(list):
  12. print(list_to_hex_string(list))
  13. # print(list)
  14. # time.sleep(0.001) # 为了保证打印的时序,可以每打印一条睡眠一会儿
  15. def main():
  16. # 要注意 如果hex文件首行或者末行不是数据,请将非数据行删除后再填写filename
  17. filename = "001-APP.hex"
  18. fluidLengthMax = 254 # 每个fluidCount长度上限 可能是1098 254等,重要节点109 221
  19. if fluidLengthMax <= 4:
  20. print("fluidLengthMax too short!")
  21. return
  22. # 获取所有报文
  23. f = open(filename, 'r')
  24. file_data = f.readlines()
  25. block = []
  26. for content in file_data:
  27. content = content.replace("\n", "")
  28. if content[0:7] == ':020000': # 这是非数据行,不能要
  29. continue
  30. block.append(content[9:-2]) # 前8个是地址,后2个是校验码
  31. # packageData是总的所有报文内容
  32. packageData = "".join(block)
  33. BlockLength = len(packageData) // 2
  34. print('数据内容的总长度是:', BlockLength)
  35. if BlockLength <= 4:
  36. print("data length too short!")
  37. return
  38. # packageData是总的所有报文内容,报文字符两两一组,以16进制字符串的形式写入dataList列表
  39. dataList = []
  40. for i in range(0, BlockLength):
  41. dataList.append(packageData[2 * i] + packageData[2 * i + 1])
  42. for i in range(0, BlockLength):
  43. dataList[i] = int(dataList[i], 16)
  44. # print(dataList)
  45. # Compute a CRC-32 checksum of data
  46. dataListCRC32 = zlib.crc32(bytes(dataList))
  47. print("crc32 of dataList is:%#x" % dataListCRC32)
  48. # 计算packageData可以被分解为fluidCount个1098长度的块,注意 一个字节长度用16进制表示是2位
  49. fluidCount = math.floor(len(dataList) / fluidLengthMax)
  50. print('fluidCount is ', fluidCount)
  51. lastFluidLength = BlockLength - fluidCount * fluidLengthMax
  52. print('the length of last fluid is ', lastFluidLength)
  53. # return
  54. list_data_out = [] # list_data_out用于计算CRC
  55. # 每个 fluid长度是 fluidLengthMax, 但是最后一个fluid的长度是 lastFluidLength
  56. for m in range(0, fluidCount + 1): # 遍历每一个fluid
  57. if m == fluidCount:
  58. # print("这是最后一块fluid")
  59. fluidLength = lastFluidLength # 最后一块的fluid的长度由计算而来
  60. if fluidLength == 0: # 最后一块的fluid长度是0的话,那就直接退出
  61. break
  62. else:
  63. fluidLength = fluidLengthMax # 不是最后一个fluid,长度是固定值,
  64. # 将所有报文以一个字节长度为一组放到列表dataList里,之后打印内容从dataList取
  65. dataListfluid = []
  66. for i in range(m * fluidLengthMax, m * fluidLengthMax + fluidLength):
  67. dataListfluid.append(dataList[i])
  68. # print("*****************Next print is the %dth fluid" % m)
  69. # 打印第一个帧 (格式如 164c3601********)
  70. list0 = [0x14, 0x4c, 0x36, 0x01, 0x55, 0x55, 0x55, 0x55] # 初始化,这里的值只是一个格式的范例
  71. # 第0位赋值 左起4个bit一定是1(表示多帧的首诊),左起5-8个bit连同下一位的8个bit是长度 长度包括了0x36和fluidCount,所以需要+2
  72. list0[0] = (fluidLength + 2) // 0x100 + 0x10
  73. # 第1位赋值
  74. list0[1] = (fluidLength + 2) % 0x100
  75. # 第3位赋值 这一位对应fluidCount+1,每个fluid依次递增,注意若增长到0xff,下一个是0x00
  76. list0[3] = (m + 1) % 0x100
  77. # 第4位到第7位赋值
  78. if fluidLength >= 4: # 这一帧可以填满
  79. for j in range(4, 8):
  80. list0[j] = dataListfluid[j - 4]
  81. list_data_out.append(list0[j])
  82. printlist(list=list0)
  83. else: # 这一帧不够填满
  84. for j in range(4, 4 + fluidLength):
  85. list0[j] = dataListfluid[j - 4]
  86. list_data_out.append(list0[j])
  87. printlist(list=list0)
  88. if m == fluidCount: # 如果是最后一个fluid,并且剩余长度还不足以填满一帧,意味着所有数据处理完成
  89. print('all input data print over')
  90. break
  91. if fluidLength == 4: # 这一帧填满时,fluid已到结尾,只有最后一个fluid才可能出现这样情况
  92. break
  93. # 打印多帧21-2F
  94. list1 = []
  95. dataList_position = 4 # 若执行到这一步,那么数据列表章的位置只可能是4
  96. for i in range(0x21, 0x30, 1):
  97. list1.append(i)
  98. if dataList_position > fluidLength - 7: # 判断是否是多帧的最后一帧,如果是,则将最后一阵填充结束后退出循环
  99. # print('the last line ,position is:', dataList_position, ' fluidLength is:', fluidLength)
  100. for k in range(dataList_position, fluidLength):
  101. list1.append(dataListfluid[k]) # 最后一帧第一部分报文数据填写
  102. list_data_out.append(dataListfluid[k])
  103. for k in range(7 - fluidLength + dataList_position):
  104. list1.append(0x55) # 最后一帧第二部分填充符填写,填充0x55
  105. dataList_position = fluidLength
  106. printlist(list=list1)
  107. break
  108. for j in range(dataList_position, dataList_position + 7): # 不是最后一帧,全部填充为数据
  109. list1.append(dataListfluid[j])
  110. list_data_out.append(dataListfluid[j]) # 用于计算CRC
  111. printlist(list=list1)
  112. # 每打印一行,便要清空list1,并且 BlockLength加7
  113. list1 = []
  114. dataList_position = dataList_position + 7
  115. if dataList_position == fluidLength: # 长度正好结束,那么直接退出
  116. break
  117. if dataList_position == fluidLength:
  118. if m == fluidCount: # 判断成立说明最后一帧已经处理结束
  119. print('all input data print over')
  120. break
  121. else: # 本fluid结束,进入下一个fluid计算
  122. continue
  123. list2 = []
  124. # 打印多帧20-2F
  125. for i in range(1, 1000000): # 这里循环次数需要设很大,中途某次循环检测到打印的是最后一行,会自动退出的
  126. for j in range(0x20, 0x30, 1):
  127. list2.append(j)
  128. if dataList_position > fluidLength - 7: # 每行打印前要判断是否是多帧的最后一帧,如果是,则将最后一阵填充结束后退出循环
  129. # print('the last line ,position is:', dataList_position, ' fluidLength is:', fluidLength)
  130. for k in range(dataList_position, fluidLength):
  131. list2.append(dataListfluid[k]) # 最后一帧打印,填写数据部分
  132. list_data_out.append(dataListfluid[k])
  133. for k in range(7 - fluidLength + dataList_position):
  134. list2.append(0x55) # 最后一帧打印,填写填充符部分
  135. dataList_position = fluidLength
  136. printlist(list=list2)
  137. list2 = []
  138. break
  139. for k in range(dataList_position, dataList_position + 7): # 不是最后一帧,全部填充为数据
  140. list2.append(dataListfluid[k])
  141. list_data_out.append(dataListfluid[k]) # 用于计算CRC
  142. printlist(list=list2)
  143. # 每打印一行,便要清空list2,并且 BlockLength加7
  144. list2 = []
  145. dataList_position = dataList_position + 7
  146. if dataList_position == fluidLength: # 长度正好结束,那么直接退出
  147. break
  148. if dataList_position == fluidLength: # 判断成立说明最后一帧已经处理结束
  149. break
  150. data_out_CRC32 = zlib.crc32(bytes(list_data_out))
  151. print("crc32 of printed data is:%#x" % data_out_CRC32)
  152. if data_out_CRC32 == dataListCRC32:
  153. print("crc32 check pass!")
  154. return 'true'
  155. else:
  156. print("crc32 check fail!")
  157. return 'false'
  158. if __name__ == '__main__':
  159. main()

4.测试验证

  1. 测试前,将一个名为“*.hex“的文件放在代码同级目录下,获取每个fluidCount长度上限赋给 fluidLengthMax变量。代码执行结果如下:
  1. 数据内容的总长度是: 97031
  2. crc32 of dataList is:0x80399f9d
  3. fluidCount is 382
  4. the length of last fluid is 3
  5. [ 0x11,0x00,0x36,0x01,0x4B,0x4F,0x53,0x54, ]
  6. ......
  7. [ 0x10,0x05,0x36,0x7F,0x6B,0x23,0x00,0x55, ]
  8. all input data print over
  9. crc32 of printed data is:0x80399f9d
  10. crc32 check pass!

经过物理平台测试,本博客python编写的构造多帧报文的代码是正确的。

标签: UDS 多帧 python

本文转载自: https://blog.csdn.net/qq_33163046/article/details/127341134
版权归原作者 晓翔仔 所有, 如有侵权,请联系我们删除。

“根据hex文件制作UDS统一诊断服务CAN多帧报文-python”的评论:

还没有评论