Python实现CAN报文转换工具教程
作者:rectsuly 发布时间:2022-06-13 02:34:06
一、CAN报文简介
CAN是控制器局域网络(Controller Area Network, CAN)的简称,是由以研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO 11898),是国际上应用最广泛的现场总线之一。 在北美和西欧,CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。
CAN总线以报文为单位进行数据传送。CAN报文按照帧格式可分为标准帧和扩展帧,标准帧是具有11位标识符的CAN帧,扩展帧是具有29位标识符的CAN帧。按照帧类型可分为:1.从发送节点向其它节点发送数据;2.远程帧:向其它节点请求发送具有同一识别符的数据帧;3.错误帧:指明已检测到总线错误;4.过载帧:过载帧用以在数据帧(或远程帧)之间提供一附加的延时。共有两种编码格式:Intel格式和Motorola格式,在编码优缺点上,Motorola格式与Intel格式并没有孰优孰劣之分,只不过根据设计者的习惯,由用户自主选择罢了。当然,对于使用者来讲,在进行解析之前,就必须要知道编码的格式是哪一种,否则,就不能保证正确地解析信号的含义。以下就以8位字节编码方式的CAN总线信号为例,详细分析一下两者之间的区别。
Intel编码格式
当一个信号的数据长度不超过1个字节(8位)并且信号在一个字节内实现(即该信号没有跨字节实现):该信号的高位(S_msb)将被放在该字节的高位,信号的低位(S_lsb)将被放在该字节的低位。
当一个信号的数据长度超过1个字节(8位)或者数据长度不超过一个字节但是采用跨字节方式实现时:该信号的高位(S_msb)将被放在高字节(MSB)的高位,信号的低位(S_lsb)将被放在低字节(LSB)的低位。
Motorola编码格式
当一个信号的数据长度不超过1个字节(8位)并且信号在一个字节内实现(即该信号没有跨字节实现):该信号的高位(S_msb)将被放在该字节的高位,信号的低位(S_lsb)将被放在该字节的低位。
当一个信号的数据长度超过1个字节(8位)或者数据长度不超过一个字节但是采用跨字节方式实现时:该信号的高位(S_msb)将被放在低字节(MSB)的高位,信号的低位(S_lsb)将被放在高字节(LSB)的低位。
可以看出,当一个信号的数据长度不超过1Byte时,Intel与Motorola两种格式的编码结果没有什么不同,完全一样。当信号的数据长度超过1Byte时,两者的编码结果出现了明显的不同。
二、CAN报文转换工具需求分析
1、 支持标准帧的CAN报文的转换,扩展帧暂不支持
2、 CAN报文支持Intel、motorola两种编码,先支持motorola格式,后期追加Intel格式
3、 工具具有一定的容错处理能力、报告生成能力
4、 制定统一格式,方便使用者修改测试脚本
5、增加交互模式,键盘输入,控制台输出;例如:
提示语:startBit:length:minValue:maxValue:setValue
输入:35:1:0:1:1
或:35:1:::1
控制台输出:00 00 00 00 08 00 00 00
Intel和Motorola编码举例:
三、交互模式
代码如下:
import sys
print("----------------欢迎使用CAN报文转换工具交互模式----------------")
print("请输入CAN信号,格式为:startBit:length:minValue:maxValue:setValue")
print("例如:32:1:0:1:1")
print("或者省略minValue和maxValue:35:1:::1")
print("信号输入结束请再按一次回车")
#十进制转换成二进制list
def octToBin(octNum, bit):
while(octNum != 0):
bit.append(octNum%2)
octNum = int(octNum/2)
for i in range(64-len(bit)):
bit.append(0)
sig = []
startBit = []
length = []
setValue = []
#输入CAN信号
while True:
input_str = input()
if not len(input_str):
break
if(input_str.count(":")<4):
print("输入格式错误,参数缺少setValue,请重新输入!")
continue
if(input_str.split(":")[4]==""):
print("setValue参数不能为空,请重新输入!")
continue
sig.append(input_str)
#解析CAN信号
for i in range(len(sig)):
startBit.append(int(sig[i].split(":")[0]))
length.append(int(sig[i].split(":")[1]))
setValue.append(int(sig[i].split(":")[4]))
#CAN数组存放CAN报文值
CAN = []
for i in range(64):
CAN.append(-1)
for i in range(len(startBit)):
#长度超过1Byte的情况,暂不支持
if(length[i]>16):
print("CAN信号长度超过2Byte,暂不支持!!!")
sys.stdin.readline()
sys.exit()
#长度未超过1Byte的情况且未跨字节的信号
if((startBit[i]%8 + length[i])<=8):
for j in range(length[i]):
bit = []
#setValue的二进制值按字节位从低到高填
octToBin(setValue[i],bit)
#填满字节长度值
if(CAN[startBit[i]+j]==-1):
CAN[startBit[i]+j] = bit[j]
#字节存在冲突
else:
print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
sys.stdin.readline()
sys.exit()
#跨字节的信号
else:
#高位位数和低位位数
highLen = 8 - startBit[i]%8
lowLen = length[i] - highLen
bit = []
#setValue的二进制值按字节位从低到高填
octToBin(setValue[i],bit)
#先填进信号的高位
for j1 in range(highLen):
if(CAN[startBit[i]+j1]==-1):
CAN[startBit[i]+j1] = bit[j1]
#字节存在冲突
else:
print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
sys.stdin.readline()
sys.exit()
#再填进信号的低位
for j2 in range(lowLen):
if(CAN[(int(startBit[i]/8)-1)*8+j2]==-1):
CAN[(int(startBit[i]/8)-1)*8+j2] = bit[highLen+j2]
#字节存在冲突
else:
print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
sys.stdin.readline()
sys.exit()
#剩余位默认值设为0
for i in range(64):
if(CAN[i]==-1):
CAN[i] = 0
#----------------将二进制list每隔8位转换成十六进制输出----------------
#其中,map()将list中的数字转成字符串,按照Motorola格式每隔8位采用了逆序
# ''.join()将二进制list转换成二进制字符串,int()将二进制字符串转换成十进制
#hex()再将十进制转换成十六进制,upper()转换成大写,两个lstrip()将"0X"删除,
#zfill()填充两位,输出不换行,以空格分隔
print(hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
运行截图:
错误提示:
四、配置项模式
配置文件如下:
##注释
::start
#编码格式:0=Intel;1=Motorola
encodeType=1
#帧格式:0=标准帧;1=扩展帧;
canMode=0
#帧类型:0=数据帧;...
canType=0
#默认初始值(0~1)
defaultValue=0
#MSG定义
msgName=BCM_FrP01
msgID=0x2CD
#长度(BYTE)
msgLength=8
#signal定义
#sigName=name:startBit:length:minValue:maxValue:setValue
#sigName=ReverseSw:25:6:0:1:13
#sigName=Trunk_BackDoor_Sts:33:2:0:1:2
#sigName=DRVUnlockState:37:2:0:1:3
#sigName=HeadLampLowBeam:40:8:0:1:60
#sigName=HoodStatus:51:1:0:1:0
#sigName=HeadLampHighBeam:52:1:0:1:0
#sigName=RLDoorStatus:59:1:0:1:0
#sigName=RRDoorStatus:58:1:0:1:0
#sigName=PsgDoorStatus:57:2:0:1:0
sigName=One:0:8:0:255:165
sigName=Two:24:12:0:4095:1701
sigName=Three:54:5:0:31:25
::end
::start
#编码格式:0=Intel;1=Motorola
encodeType=1
#帧格式:0=标准帧;1=扩展帧;
canMode=0
#帧类型:0=数据帧;...
canType=0
#默认初始值(0~1)
defaultValue=0
#MSG定义
msgName=BCM_FrP
msgID=0x2CD
#长度(BYTE)
msgLength=8
#signal定义
#sigName=name:startBit:length:minValue:maxValue:setValue
#sigName=ReverseSw:25:6:0:1:13
#sigName=Trunk_BackDoor_Sts:33:2:0:1:2
#sigName=DRVUnlockState:37:2:0:1:3
#sigName=HeadLampLowBeam:40:8:0:1:60
#sigName=HoodStatus:51:1:0:1:0
#sigName=HeadLampHighBeam:52:1:0:1:0
#sigName=RLDoorStatus:59:1:0:1:0
#sigName=RRDoorStatus:58:1:0:1:0
#sigName=PsgDoorStatus:57:2:0:1:0
sigName=One:35:1:0:1:1
::end
代码如下:
#!/usr/bin/python
defaultValue = 0
sigName = []
startBit = []
length = []
minValue = []
maxValue = []
setValue = []
#CAN数组存放CAN报文值
CAN = []
logFile = open("log.txt","w")
def parseConfig():
config = open("Config.txt","r")
count = 0
isError = False
for line in config:
line = line.strip()
#注释
if(line.find("#")>=0):
continue
#开始标记
elif(line.find("::start")>=0):
count = count + 1
isError = False
if(count>1):
sigName.clear()
startBit.clear()
length.clear()
setValue.clear()
continue
else:
continue
elif(isError == True):
continue
#编码格式
elif(line.find("encodeType")>=0):
encodeType = line.split("=")[1]
if(encodeType != "1"):
isError = True
print(str(count) + ". CAN报文生成失败!!!目前仅支持Motorola编码格式,暂不支持Intel编码格式!")
logFile.write("%d. CAN报文生成失败!!!目前仅支持Motorola编码格式,暂不支持Intel编码格式!\n" % count)
continue
#帧格式
elif(line.find("canMode")>=0):
canMode = line.split("=")[1]
if(canMode != "0"):
isError = True
print(str(count) + ". CAN报文生成失败!!!目前仅支持标准帧,暂不支持扩展帧!")
logFile.write("%d. CAN报文生成失败!!!目前仅支持标准帧,暂不支持扩展帧!\n" % count)
continue
#帧类型
elif(line.find("canType")>=0):
canType = line.split("=")[1]
if(canType != "0"):
isError = True
print(str(count) + ". CAN报文生成失败!!!目前仅支持数据帧,暂不支持其他帧!")
logFile.write("%d. CAN报文生成失败!!!目前仅支持数据帧,暂不支持其他帧!\n" % count)
continue
#默认初始值
elif(line.find("defaultValue")>=0):
global defaultValue
defaultValue = int(line.split("=")[1])
#MSG名称
elif(line.find("msgName")>=0):
msgName = line.split("=")[1]
#MSGID
elif(line.find("msgID")>=0):
msgID = line.split("=")[1]
#MSG长度
elif(line.find("msgLength")>=0):
msgLength = line.split("=")[1]
#signal定义
elif(line.find("sigName")>=0):
sigName.append(line.split(":")[0].split("=")[1])
startBit.append(int(line.split(":")[1]))
length.append(int(line.split(":")[2]))
#minValue.append(int(line.split(":")[3]))
#maxValue.append(int(line.split(":")[4]))
setValue.append(int(line.split(":")[5]))
elif(line.find("::end")>=0):
rV,errMsg = getCANMessage()
if(rV == "-1"):
isError = True
print(str(count) + ". CAN报文生成失败!!!" + errMsg)
logFile.write("%d. CAN报文生成失败!!!%s\n" % (count,errMsg))
continue
print(str(count) + ". CAN报文生成成功!!!")
logFile.write("%d. CAN报文生成成功!!!\n" % count)
#----------------------------输出标题信息----------------------------
print("msgName\t\tmsgID\t\tmsgLen\t\tmsgData")
logFile.write("msgName\t\tmsgID\t\tmsgLen\t\tmsgData\n")
if(len(msgName)<8):
print(msgName + "\t\t",end="")
logFile.write("%s\t\t" % msgName)
else:
print(msgName + "\t",end="")
logFile.write("%s\t" % msgName)
print(msgID + "\t\t",end="")
logFile.write("%s\t\t" % msgID)
print(msgLength + "\t\t",end="")
logFile.write("%s\t\t" % msgLength)
#----------------将二进制list每隔8位转换成十六进制输出----------------
#其中,map()将list中的数字转成字符串,按照Motorola格式每隔8位采用了逆序
# ''.join()将二进制list转换成二进制字符串,int()将二进制字符串转换成十进制
#hex()再将十进制转换成十六进制,upper()转换成大写,两个lstrip()将"0X"删除,
#zfill()填充两位,输出不换行,以空格分隔
print(hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s " % hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
logFile.write("%s\n" % hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
config.close()
#十进制转换成二进制list
def octToBin(octNum, bit):
while(octNum != 0):
bit.append(octNum%2)
octNum = int(octNum/2)
for i in range(64-len(bit)):
bit.append(0)
#获取CAN报文值
def getCANMessage():
CAN.clear()
for i in range(64):
CAN.append(-1)
for i in range(len(startBit)):
#长度超过1Byte的情况,暂不支持
if(length[i]>16):
errMsg = " CAN信号长度超过2Byte,暂不支持!!!"
#print(sigName[i] + errMsg)
return "-1",errMsg
#长度未超过1Byte的情况且未跨字节的信号
if((startBit[i]%8 + length[i])<=8):
for j in range(length[i]):
bit = []
#setValue的二进制值按字节位从低到高填
octToBin(setValue[i],bit)
#填满字节长度值
if(CAN[startBit[i]+j]==-1):
CAN[startBit[i]+j] = bit[j]
#字节存在冲突
else:
errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
#print(sigName[i] + errMsg)
return "-1",errMsg
#跨字节的信号
else:
#高位位数和低位位数
highLen = 8 - startBit[i]%8
lowLen = length[i] - highLen
bit = []
#setValue的二进制值按字节位从低到高填
octToBin(setValue[i],bit)
#先填进信号的高位
for j1 in range(highLen):
if(CAN[startBit[i]+j1]==-1):
CAN[startBit[i]+j1] = bit[j1]
#字节存在冲突
else:
errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
#print(sigName[i] + errMsg)
return "-1",errMsg
#再填进信号的低位
for j2 in range(lowLen):
if(CAN[(int(startBit[i]/8)-1)*8+j2]==-1):
CAN[(int(startBit[i]/8)-1)*8+j2] = bit[highLen+j2]
#字节存在冲突
else:
errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
#print(sigName[i] + errMsg)
return "-1",errMsg
#剩余位设为默认值
for i in range(64):
if(CAN[i]==-1):
CAN[i] = defaultValue
#若无错误则返回正确值
return "0","success!"
if __name__ == "__main__":
#调用parseConfig()函数开始执行程序
parseConfig()
运行结果:
1. CAN报文生成成功!!!
msgNamemsgIDmsgLenmsgData
BCM_FrP010x2CD8A5 00 06 A5 00 06 40 00
2. CAN报文生成成功!!!
msgNamemsgIDmsgLenmsgData
BCM_FrP0x2CD800 00 00 00 08 00 00 00
来源:https://blog.csdn.net/rectsuly/article/details/76126269
猜你喜欢
- 使用vscode的过程中 自己或者push代码以后 代码的格式可能会出现错乱,作为一个成熟的开发 , 当然应该遵守一些代码规范, 首先代码整
- 前言网上很多例子都说cv2.minAreaRect函数的输出的角度范围在[-90,0],但是实测输出范围在[0,90]。再进行调研,确定为o
- asp数字分页涵数参数说明:SQL: 查询语句,PageSizeN: 每页显示多少新闻记录classid: 栏目ID,PageCountS:
- 1、安装pecl及创建快捷键(若安装php时已带可忽略这步安装步骤)# cd /usr/local/php/bin/ //可查看
- 前面讲解了使用纯numpy实现数值微分和误差反向传播法的手写数字识别,这两种网络都是使用全连接层的结构。全连接层存在什么问题呢?那就是数据的
- PHPMailer是一个封装好的PHP邮件发送类,支持发送HTML内容的电子邮件,以及可以添加附件发送,并不像PHP本身mail()函数需要
- 目录一、索引下推优化的原理二、索引下推的具体实践1、没有使用ICP2、使用ICP三、索引下推使用条件索引下推(Index Condition
- 求字符串中最大的递增子序列数据库环境:SQL SERVER 2005如题,求字符串“abcbklmnodfghijkmer”中最大的递增子序
- #! /usr/bin/python''' File&n
- 随着ES6规范的到来,Js中定义变量的方法已经由单一的 var 方式发展到了 var、let、const 三种之多。var 众所周知,可那俩
- 一、使用python内置commands模块执行shellcommands对Python的os.popen()进行了封装,使用SHELL命令
- 上一篇介绍了 HTML5 中 Canvas 的路径,这篇将要介绍一下 Canvas&nbs
- 代码和代码运行的结果:代码:import itertools as itswords="rot123"a=its.pro
- 这篇文章主要介绍了Python命令行click参数用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 1 简介孤立森林(isolation Forest)是一种高效的异常检测算法,它和随机森林类似,但每次选择划分属性和划分点(值)时都是随机的
- 前言最近助教改作业导出的成绩表格跟老师给的名单顺序不一致,脑壳一亮就用pandas写了个脚本自动吧原始导出的成绩誊写到老师给的名单中了哈哈哈
- 比如说在1-3000之内生成随机永不重复数,点击运行代码的时候请注意,此代码比较占用资源,如果硬件配置比较菜请把count改小。俺的电脑配置
- 在如今的Web设计中,图片的应用是必不可少的,为了更好地设计网站效果,大体积的图片被越来越多地应用到Web设计中来,所以,更好地优化图片文件
- 首先先发一下我的项目路径1. 首先要下载 sass-resources-loadernpm install sass
- 前言在AI领域,来快速实现一个idea:前后端开发+部署+展现,如果走传统的前后端分离开发+服务器docker部署等方式,会很重且入门成本很