1. 背景
统一诊断服务 (Unified diagnostic services , UDS) 由ISO-14229系列标准定义。
诊断通信的过程从用户角度来看非常容易理解,诊断仪发送诊断请求(request),ECU给出诊断响应(response),而UDS就是为不同的诊断功能的request和response定义了统一的内容和格式。
在UDS的通信种,CAN报文的传输方式根据内容长短分为 单帧 和多帧传输。
单帧的组包简单而多帧的组包比较复杂,本博客主要讨论多帧。
CAN报文的帧分为标准帧,扩展帧,远程帧等。大多数情况下使用标准帧。
标准帧每帧报文是8个字节。
如果一个UDS报文在一个帧里发不完,那么就需要以多帧的格式组包,然后依次发送。
对于0x36服务(文件传输)而言,因为文件大多比较长,基本都要以多帧的形式发送,这是一个需要多帧组包的常见场景。
如果你对UDS的服务还不了解的话,可以查看这篇博客
统一诊断服务(UDS)_晓翔仔的博客-CSDN博客_统一诊断服务
2. CAN报文多帧的格式特点
0X36服务一个文件可以拆解为多个block,每个block会根据0x36服务之前发送的0x34请求的响应拆分为多个fluid(流)。
一个完整的fluid(流)的结构可以分为三个部分,如下图:
fluid的第一行
第1个字节的第一个1表示这是一个多帧。
第1字节后一个1+第2个字节(红色)表示fluid的长度。
第3个字节(橙色)表示服务类型,36是文件传输服务。
第4个字节(绿色)表示fluid序号,从0X01开始递增(注意:0xff后面接着0x00)。
此后都是数据内容(图中d表示数据)。很容易知道 fluid的长度(红色)等于 “数据总长度+2”
fluid的第二部分
每行第1个字节都是序列号,从21到2f,所以fluid的第二部分最多有15行。
每行的第2到8个字节是数据。
fluid的第三部分
每行第1个字节都是序列号,从20到2f,2f后面接着20,循环往复,可以有很长很长。
每行的第2到8个字节是数据。
3. 代码实现
有一个".hex"结尾的文件,已知它不用拆分block,每个fluid的长度要求是254。
现在诊断仪需要将整个文件以多帧的形式发送到指定ECU,那么应该以什么形式的报文发送过去呢?
我这里用python写了组包的代码。代码如下:
import math
import string
import time
import zlib
def list_to_hex_string(list_data):
list_str = '[ '
for x in list_data:
list_str += '0x{:02X},'.format(x)
list_str += ' ]'
return list_str
def printlist(list):
print(list_to_hex_string(list))
# print(list)
# time.sleep(0.001) # 为了保证打印的时序,可以每打印一条睡眠一会儿
def main():
# 要注意 如果hex文件首行或者末行不是数据,请将非数据行删除后再填写filename
filename = "001-APP.hex"
fluidLengthMax = 254 # 每个fluidCount长度上限 可能是1098 254等,重要节点109 221
if fluidLengthMax <= 4:
print("fluidLengthMax too short!")
return
# 获取所有报文
f = open(filename, 'r')
file_data = f.readlines()
block = []
for content in file_data:
content = content.replace("\n", "")
if content[0:7] == ':020000': # 这是非数据行,不能要
continue
block.append(content[9:-2]) # 前8个是地址,后2个是校验码
# packageData是总的所有报文内容
packageData = "".join(block)
BlockLength = len(packageData) // 2
print('数据内容的总长度是:', BlockLength)
if BlockLength <= 4:
print("data length too short!")
return
# packageData是总的所有报文内容,报文字符两两一组,以16进制字符串的形式写入dataList列表
dataList = []
for i in range(0, BlockLength):
dataList.append(packageData[2 * i] + packageData[2 * i + 1])
for i in range(0, BlockLength):
dataList[i] = int(dataList[i], 16)
# print(dataList)
# Compute a CRC-32 checksum of data
dataListCRC32 = zlib.crc32(bytes(dataList))
print("crc32 of dataList is:%#x" % dataListCRC32)
# 计算packageData可以被分解为fluidCount个1098长度的块,注意 一个字节长度用16进制表示是2位
fluidCount = math.floor(len(dataList) / fluidLengthMax)
print('fluidCount is ', fluidCount)
lastFluidLength = BlockLength - fluidCount * fluidLengthMax
print('the length of last fluid is ', lastFluidLength)
# return
list_data_out = [] # list_data_out用于计算CRC
# 每个 fluid长度是 fluidLengthMax, 但是最后一个fluid的长度是 lastFluidLength
for m in range(0, fluidCount + 1): # 遍历每一个fluid
if m == fluidCount:
# print("这是最后一块fluid")
fluidLength = lastFluidLength # 最后一块的fluid的长度由计算而来
if fluidLength == 0: # 最后一块的fluid长度是0的话,那就直接退出
break
else:
fluidLength = fluidLengthMax # 不是最后一个fluid,长度是固定值,
# 将所有报文以一个字节长度为一组放到列表dataList里,之后打印内容从dataList取
dataListfluid = []
for i in range(m * fluidLengthMax, m * fluidLengthMax + fluidLength):
dataListfluid.append(dataList[i])
# print("*****************Next print is the %dth fluid" % m)
# 打印第一个帧 (格式如 164c3601********)
list0 = [0x14, 0x4c, 0x36, 0x01, 0x55, 0x55, 0x55, 0x55] # 初始化,这里的值只是一个格式的范例
# 第0位赋值 左起4个bit一定是1(表示多帧的首诊),左起5-8个bit连同下一位的8个bit是长度 长度包括了0x36和fluidCount,所以需要+2
list0[0] = (fluidLength + 2) // 0x100 + 0x10
# 第1位赋值
list0[1] = (fluidLength + 2) % 0x100
# 第3位赋值 这一位对应fluidCount+1,每个fluid依次递增,注意若增长到0xff,下一个是0x00
list0[3] = (m + 1) % 0x100
# 第4位到第7位赋值
if fluidLength >= 4: # 这一帧可以填满
for j in range(4, 8):
list0[j] = dataListfluid[j - 4]
list_data_out.append(list0[j])
printlist(list=list0)
else: # 这一帧不够填满
for j in range(4, 4 + fluidLength):
list0[j] = dataListfluid[j - 4]
list_data_out.append(list0[j])
printlist(list=list0)
if m == fluidCount: # 如果是最后一个fluid,并且剩余长度还不足以填满一帧,意味着所有数据处理完成
print('all input data print over')
break
if fluidLength == 4: # 这一帧填满时,fluid已到结尾,只有最后一个fluid才可能出现这样情况
break
# 打印多帧21-2F
list1 = []
dataList_position = 4 # 若执行到这一步,那么数据列表章的位置只可能是4
for i in range(0x21, 0x30, 1):
list1.append(i)
if dataList_position > fluidLength - 7: # 判断是否是多帧的最后一帧,如果是,则将最后一阵填充结束后退出循环
# print('the last line ,position is:', dataList_position, ' fluidLength is:', fluidLength)
for k in range(dataList_position, fluidLength):
list1.append(dataListfluid[k]) # 最后一帧第一部分报文数据填写
list_data_out.append(dataListfluid[k])
for k in range(7 - fluidLength + dataList_position):
list1.append(0x55) # 最后一帧第二部分填充符填写,填充0x55
dataList_position = fluidLength
printlist(list=list1)
break
for j in range(dataList_position, dataList_position + 7): # 不是最后一帧,全部填充为数据
list1.append(dataListfluid[j])
list_data_out.append(dataListfluid[j]) # 用于计算CRC
printlist(list=list1)
# 每打印一行,便要清空list1,并且 BlockLength加7
list1 = []
dataList_position = dataList_position + 7
if dataList_position == fluidLength: # 长度正好结束,那么直接退出
break
if dataList_position == fluidLength:
if m == fluidCount: # 判断成立说明最后一帧已经处理结束
print('all input data print over')
break
else: # 本fluid结束,进入下一个fluid计算
continue
list2 = []
# 打印多帧20-2F
for i in range(1, 1000000): # 这里循环次数需要设很大,中途某次循环检测到打印的是最后一行,会自动退出的
for j in range(0x20, 0x30, 1):
list2.append(j)
if dataList_position > fluidLength - 7: # 每行打印前要判断是否是多帧的最后一帧,如果是,则将最后一阵填充结束后退出循环
# print('the last line ,position is:', dataList_position, ' fluidLength is:', fluidLength)
for k in range(dataList_position, fluidLength):
list2.append(dataListfluid[k]) # 最后一帧打印,填写数据部分
list_data_out.append(dataListfluid[k])
for k in range(7 - fluidLength + dataList_position):
list2.append(0x55) # 最后一帧打印,填写填充符部分
dataList_position = fluidLength
printlist(list=list2)
list2 = []
break
for k in range(dataList_position, dataList_position + 7): # 不是最后一帧,全部填充为数据
list2.append(dataListfluid[k])
list_data_out.append(dataListfluid[k]) # 用于计算CRC
printlist(list=list2)
# 每打印一行,便要清空list2,并且 BlockLength加7
list2 = []
dataList_position = dataList_position + 7
if dataList_position == fluidLength: # 长度正好结束,那么直接退出
break
if dataList_position == fluidLength: # 判断成立说明最后一帧已经处理结束
break
data_out_CRC32 = zlib.crc32(bytes(list_data_out))
print("crc32 of printed data is:%#x" % data_out_CRC32)
if data_out_CRC32 == dataListCRC32:
print("crc32 check pass!")
return 'true'
else:
print("crc32 check fail!")
return 'false'
if __name__ == '__main__':
main()
4.测试验证
测试前,将一个名为“*.hex“的文件放在代码同级目录下,获取每个fluidCount长度上限赋给 fluidLengthMax变量。代码执行结果如下:
数据内容的总长度是: 97031
crc32 of dataList is:0x80399f9d
fluidCount is 382
the length of last fluid is 3
[ 0x11,0x00,0x36,0x01,0x4B,0x4F,0x53,0x54, ]
......
[ 0x10,0x05,0x36,0x7F,0x6B,0x23,0x00,0x55, ]
all input data print over
crc32 of printed data is:0x80399f9d
crc32 check pass!
经过物理平台测试,本博客python编写的构造多帧报文的代码是正确的。
本文转载自: https://blog.csdn.net/qq_33163046/article/details/127341134
版权归原作者 晓翔仔 所有, 如有侵权,请联系我们删除。
版权归原作者 晓翔仔 所有, 如有侵权,请联系我们删除。