Python實現(xiàn)CAN報文轉(zhuǎn)換工具教程
一、CAN報文簡介
CAN是控制器局域網(wǎng)絡(luò)(Controller Area Network, CAN)的簡稱,是由以研發(fā)和生產(chǎn)汽車電子產(chǎn)品著稱的德國BOSCH公司開發(fā)的,并最終成為國際標(biāo)準(zhǔn)(ISO 11898),是國際上應(yīng)用最廣泛的現(xiàn)場總線之一。 在北美和西歐,CAN總線協(xié)議已經(jīng)成為汽車計算機(jī)控制系統(tǒng)和嵌入式工業(yè)控制局域網(wǎng)的標(biāo)準(zhǔn)總線,并且擁有以CAN為底層協(xié)議專為大型貨車和重工機(jī)械車輛設(shè)計的J1939協(xié)議。
CAN總線以報文為單位進(jìn)行數(shù)據(jù)傳送。CAN報文按照幀格式可分為標(biāo)準(zhǔn)幀和擴(kuò)展幀,標(biāo)準(zhǔn)幀是具有11位標(biāo)識符的CAN幀,擴(kuò)展幀是具有29位標(biāo)識符的CAN幀。按照幀類型可分為:1.從發(fā)送節(jié)點向其它節(jié)點發(fā)送數(shù)據(jù);2.遠(yuǎn)程幀:向其它節(jié)點請求發(fā)送具有同一識別符的數(shù)據(jù)幀;3.錯誤幀:指明已檢測到總線錯誤;4.過載幀:過載幀用以在數(shù)據(jù)幀(或遠(yuǎn)程幀)之間提供一附加的延時。共有兩種編碼格式:Intel格式和Motorola格式,在編碼優(yōu)缺點上,Motorola格式與Intel格式并沒有孰優(yōu)孰劣之分,只不過根據(jù)設(shè)計者的習(xí)慣,由用戶自主選擇罷了。當(dāng)然,對于使用者來講,在進(jìn)行解析之前,就必須要知道編碼的格式是哪一種,否則,就不能保證正確地解析信號的含義。以下就以8位字節(jié)編碼方式的CAN總線信號為例,詳細(xì)分析一下兩者之間的區(qū)別。
Intel編碼格式
當(dāng)一個信號的數(shù)據(jù)長度不超過1個字節(jié)(8位)并且信號在一個字節(jié)內(nèi)實現(xiàn)(即該信號沒有跨字節(jié)實現(xiàn)):該信號的高位(S_msb)將被放在該字節(jié)的高位,信號的低位(S_lsb)將被放在該字節(jié)的低位。
當(dāng)一個信號的數(shù)據(jù)長度超過1個字節(jié)(8位)或者數(shù)據(jù)長度不超過一個字節(jié)但是采用跨字節(jié)方式實現(xiàn)時:該信號的高位(S_msb)將被放在高字節(jié)(MSB)的高位,信號的低位(S_lsb)將被放在低字節(jié)(LSB)的低位。
Motorola編碼格式
當(dāng)一個信號的數(shù)據(jù)長度不超過1個字節(jié)(8位)并且信號在一個字節(jié)內(nèi)實現(xiàn)(即該信號沒有跨字節(jié)實現(xiàn)):該信號的高位(S_msb)將被放在該字節(jié)的高位,信號的低位(S_lsb)將被放在該字節(jié)的低位。
當(dāng)一個信號的數(shù)據(jù)長度超過1個字節(jié)(8位)或者數(shù)據(jù)長度不超過一個字節(jié)但是采用跨字節(jié)方式實現(xiàn)時:該信號的高位(S_msb)將被放在低字節(jié)(MSB)的高位,信號的低位(S_lsb)將被放在高字節(jié)(LSB)的低位。
可以看出,當(dāng)一個信號的數(shù)據(jù)長度不超過1Byte時,Intel與Motorola兩種格式的編碼結(jié)果沒有什么不同,完全一樣。當(dāng)信號的數(shù)據(jù)長度超過1Byte時,兩者的編碼結(jié)果出現(xiàn)了明顯的不同。
二、CAN報文轉(zhuǎn)換工具需求分析
1、 支持標(biāo)準(zhǔn)幀的CAN報文的轉(zhuǎn)換,擴(kuò)展幀暫不支持
2、 CAN報文支持Intel、motorola兩種編碼,先支持motorola格式,后期追加Intel格式
3、 工具具有一定的容錯處理能力、報告生成能力
4、 制定統(tǒng)一格式,方便使用者修改測試腳本
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報文轉(zhuǎn)換工具交互模式----------------") print("請輸入CAN信號,格式為:startBit:length:minValue:maxValue:setValue") print("例如:32:1:0:1:1") print("或者省略minValue和maxValue:35:1:::1") print("信號輸入結(jié)束請再按一次回車") #十進(jìn)制轉(zhuǎn)換成二進(jìn)制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("輸入格式錯誤,參數(shù)缺少setValue,請重新輸入!") continue if(input_str.split(":")[4]==""): print("setValue參數(shù)不能為空,請重新輸入!") 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數(shù)組存放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的情況且未跨字節(jié)的信號 if((startBit[i]%8 + length[i])<=8): for j in range(length[i]): bit = [] #setValue的二進(jìn)制值按字節(jié)位從低到高填 octToBin(setValue[i],bit) #填滿字節(jié)長度值 if(CAN[startBit[i]+j]==-1): CAN[startBit[i]+j] = bit[j] #字節(jié)存在沖突 else: print(sig[i] + "字節(jié)位存在沖突,生成CAN報文失?。。。?) sys.stdin.readline() sys.exit() #跨字節(jié)的信號 else: #高位位數(shù)和低位位數(shù) highLen = 8 - startBit[i]%8 lowLen = length[i] - highLen bit = [] #setValue的二進(jìn)制值按字節(jié)位從低到高填 octToBin(setValue[i],bit) #先填進(jìn)信號的高位 for j1 in range(highLen): if(CAN[startBit[i]+j1]==-1): CAN[startBit[i]+j1] = bit[j1] #字節(jié)存在沖突 else: print(sig[i] + "字節(jié)位存在沖突,生成CAN報文失敗?。?!") sys.stdin.readline() sys.exit() #再填進(jìn)信號的低位 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] #字節(jié)存在沖突 else: print(sig[i] + "字節(jié)位存在沖突,生成CAN報文失?。。?!") sys.stdin.readline() sys.exit() #剩余位默認(rèn)值設(shè)為0 for i in range(64): if(CAN[i]==-1): CAN[i] = 0 #----------------將二進(jìn)制list每隔8位轉(zhuǎn)換成十六進(jìn)制輸出---------------- #其中,map()將list中的數(shù)字轉(zhuǎn)成字符串,按照Motorola格式每隔8位采用了逆序 # ''.join()將二進(jìn)制list轉(zhuǎn)換成二進(jìn)制字符串,int()將二進(jìn)制字符串轉(zhuǎn)換成十進(jìn)制 #hex()再將十進(jìn)制轉(zhuǎn)換成十六進(jìn)制,upper()轉(zhuǎn)換成大寫,兩個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=標(biāo)準(zhǔn)幀;1=擴(kuò)展幀; canMode=0 #幀類型:0=數(shù)據(jù)幀;... canType=0 #默認(rèn)初始值(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=標(biāo)準(zhǔn)幀;1=擴(kuò)展幀; canMode=0 #幀類型:0=數(shù)據(jù)幀;... canType=0 #默認(rèn)初始值(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數(shù)組存放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 #開始標(biāo)記 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報文生成失敗?。。∧壳皟H支持標(biāo)準(zhǔn)幀,暫不支持?jǐn)U展幀!") logFile.write("%d. CAN報文生成失?。。。∧壳皟H支持標(biāo)準(zhǔn)幀,暫不支持?jǐn)U展幀!\n" % count) continue #幀類型 elif(line.find("canType")>=0): canType = line.split("=")[1] if(canType != "0"): isError = True print(str(count) + ". CAN報文生成失?。。。∧壳皟H支持?jǐn)?shù)據(jù)幀,暫不支持其他幀!") logFile.write("%d. CAN報文生成失?。。?!目前僅支持?jǐn)?shù)據(jù)幀,暫不支持其他幀!\n" % count) continue #默認(rèn)初始值 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) #----------------------------輸出標(biāo)題信息---------------------------- 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) #----------------將二進(jìn)制list每隔8位轉(zhuǎn)換成十六進(jìn)制輸出---------------- #其中,map()將list中的數(shù)字轉(zhuǎn)成字符串,按照Motorola格式每隔8位采用了逆序 # ''.join()將二進(jìn)制list轉(zhuǎn)換成二進(jìn)制字符串,int()將二進(jìn)制字符串轉(zhuǎn)換成十進(jìn)制 #hex()再將十進(jìn)制轉(zhuǎn)換成十六進(jìn)制,upper()轉(zhuǎn)換成大寫,兩個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() #十進(jìn)制轉(zhuǎn)換成二進(jìn)制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的情況且未跨字節(jié)的信號 if((startBit[i]%8 + length[i])<=8): for j in range(length[i]): bit = [] #setValue的二進(jìn)制值按字節(jié)位從低到高填 octToBin(setValue[i],bit) #填滿字節(jié)長度值 if(CAN[startBit[i]+j]==-1): CAN[startBit[i]+j] = bit[j] #字節(jié)存在沖突 else: errMsg = " 字節(jié)位存在沖突,生成CAN報文失?。。?!" #print(sigName[i] + errMsg) return "-1",errMsg #跨字節(jié)的信號 else: #高位位數(shù)和低位位數(shù) highLen = 8 - startBit[i]%8 lowLen = length[i] - highLen bit = [] #setValue的二進(jìn)制值按字節(jié)位從低到高填 octToBin(setValue[i],bit) #先填進(jìn)信號的高位 for j1 in range(highLen): if(CAN[startBit[i]+j1]==-1): CAN[startBit[i]+j1] = bit[j1] #字節(jié)存在沖突 else: errMsg = " 字節(jié)位存在沖突,生成CAN報文失?。。?!" #print(sigName[i] + errMsg) return "-1",errMsg #再填進(jìn)信號的低位 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] #字節(jié)存在沖突 else: errMsg = " 字節(jié)位存在沖突,生成CAN報文失?。。。? #print(sigName[i] + errMsg) return "-1",errMsg #剩余位設(shè)為默認(rèn)值 for i in range(64): if(CAN[i]==-1): CAN[i] = defaultValue #若無錯誤則返回正確值 return "0","success!" if __name__ == "__main__": #調(diào)用parseConfig()函數(shù)開始執(zhí)行程序 parseConfig()
運行結(jié)果:
1. CAN報文生成成功?。?! msgName msgID msgLen msgData BCM_FrP01 0x2CD 8 A5 00 06 A5 00 06 40 00 2. CAN報文生成成功?。?! msgName msgID msgLen msgData BCM_FrP 0x2CD 8 00 00 00 00 08 00 00 00
以上這篇Python實現(xiàn)CAN報文轉(zhuǎn)換工具教程就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python求兩點之間的直線距離(2種實現(xiàn)方法)
今天小編就為大家分享一篇Python求兩點之間的直線距離(2種實現(xiàn)方法),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07python?sklearn?畫出決策樹并保存為PDF的實現(xiàn)過程
這篇文章主要介紹了python?sklearn?畫出決策樹并保存為PDF的實現(xiàn)過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07通俗的講解深度學(xué)習(xí)中CUDA,cudatookit,cudnn和pytorch的關(guān)系
有些剛?cè)胄械呐笥芽偸歉悴磺宄﨏UDA,cudatookit,cudnn和pytorch的關(guān)系,那么今天這篇文章用通俗易懂的話講解了他們之間的關(guān)系,需要的朋友可以參考下,相信會對你有所幫助2023-03-03Python3里的super()和__class__使用介紹
這篇文章主要介紹了Python3里的super()和__class__使用介紹,本文用實例講解了這兩個方法之間的關(guān)系,需要的朋友可以參考下2015-04-04python?subprocess.run()、subprocess.Popen()、subprocess.check
Python的subprocess模塊是用于創(chuàng)建和管理子進(jìn)程的模塊,本文主要介紹了python?subprocess.run()、subprocess.Popen()、subprocess.check_output(),具有一定的參考價值,感興趣的可以了解一下2024-02-02使用python接受tgam的腦波數(shù)據(jù)實例
這篇文章主要介紹了使用python接受tgam的腦波數(shù)據(jù)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04