Python實現CAN報文轉換工具教程
一、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])),CAN[23:15:-1])),CAN[31:23:-1])),CAN[39:31:-1])),CAN[47:39:-1])),CAN[55:47:-1])),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,end="") print(hex(int(''.join(map(str,2)).upper().lstrip("0").lstrip("X").zfill(2)) logFile.write("%s " % hex(int(''.join(map(str,2)).upper().lstrip("0").lstrip("X").zfill(2)) logFile.write("%s\n" % hex(int(''.join(map(str,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報文生成成功!!! 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實現CAN報文轉換工具教程就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。