CAN通信協(xié)議使用了有一段時(shí)間了,但都是基于軟件層面的使用,對于其波形不是很了解,正好這段時(shí)間比較閑,是時(shí)候補補硬知識。
開(kāi)始之前,先介紹一下設備:



用CCS9.0導入TI提供的CAN驅動(dòng)庫,每隔1秒鐘發(fā)送一個(gè)CAN信息:

1 int main(void) 2 { 3 tCANMsgObject sCANMessage; 4 unsigned char ucMsgData[4]; 5 6 // 7 // Set the clocking to run directly from the external crystal/oscillator. 8 // TODO: The SYSCTL_XTAL_ value must be changed to match the value of the 9 // crystal on your board. 10 // 11 SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | 12 SYSCTL_XTAL_16MHZ); 13 14 // 15 // Set up the serial console to use for displaying messages. This is 16 // just for this example program and is not needed for CAN operation. 17 // 18 InitConsole(); 19 20 // 21 // For this example CAN0 is used with RX and TX pins on port D0 and D1. 22 // The actual port and pins used may be different on your part, consult 23 // the data sheet for more information. 24 // GPIO port D needs to be enabled so these pins can be used. 25 // TODO: change this to whichever GPIO port you are using 26 // 27 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); 28 29 // 30 // Configure the GPIO pin muxing to select CAN0 functions for these pins. 31 // This step selects which alternate function is available for these pins. 32 // This is necessary if your part supports GPIO pin function muxing. 33 // Consult the data sheet to see which functions are allocated per pin. 34 // TODO: change this to select the port/pin you are using 35 // 36 GPIOPinConfigure(GPIO_PE4_CAN0RX); 37 GPIOPinConfigure(GPIO_PE5_CAN0TX); 38 39 // 40 // Enable the alternate function on the GPIO pins. The above step selects 41 // which alternate function is available. This step actually enables the 42 // alternate function instead of GPIO for these pins. 43 // TODO: change this to match the port/pin you are using 44 // 45 GPIOPinTypeCAN(GPIO_PORTE_BASE, GPIO_PIN_4 | GPIO_PIN_5); 46 47 // 48 // The GPIO port and pins have been set up for CAN. The CAN peripheral 49 // must be enabled. 50 // 51 SysCtlPeripheralEnable(SYSCTL_PERIPH_CAN0); 52 53 // 54 // Initialize the CAN controller 55 // 56 CANInit(CAN0_BASE); 57 58 // 59 // Set up the bit rate for the CAN bus. This function sets up the CAN 60 // bus timing for a nominal configuration. You can achieve more control 61 // over the CAN bus timing by using the function CANBitTimingSet() instead 62 // of this one, if needed. 63 // In this example, the CAN bus is set to 500 kHz. In the function below, 64 // the call to SysCtlClockGet() is used to determine the clock rate that 65 // is used for clocking the CAN peripheral. This can be replaced with a 66 // fixed value if you know the value of the system clock, saving the extra 67 // function call. For some parts, the CAN peripheral is clocked by a fixed 68 // 8 MHz regardless of the system clock in which case the call to 69 // SysCtlClockGet() should be replaced with 8000000. Consult the data 70 // sheet for more information about CAN peripheral clocking. 71 // 72 73 sysclk = SysCtlClockGet(); 74 CANBitRateSet(CAN0_BASE, sysclk, 500000); 75 76 // 77 // Enable interrupts on the CAN peripheral. This example uses static 78 // allocation of interrupt handlers which means the name of the handler 79 // is in the vector table of startup code. If you want to use dynamic 80 // allocation of the vector table, then you must also call CANIntRegister() 81 // here. 82 // 83 // CANIntRegister(CAN0_BASE, CANIntHandler); // if using dynamic vectors 84 // 85 CANIntEnable(CAN0_BASE, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS); 86 87 CANRetrySet(CAN0_BASE, false); 88 // 89 // Enable the CAN interrupt on the processor (NVIC). 90 // 91 IntEnable(INT_CAN0); 92 93 // 94 // Enable the CAN for operation. 95 // 96 CANEnable(CAN0_BASE); 97 98 // 99 // Initialize the message object that will be used for sending CAN100 // messages. The message will be 4 bytes that will contain an incrementing101 // value. Initially it will be set to 0.102 //103 *(unsigned long *)ucMsgData = 0;104 sCANMessage.ulMsgID = 0x220; // CAN message ID105 sCANMessage.ulMsgIDMask = 0; // no mask needed for TX106 sCANMessage.ulFlags = MSG_OBJ_TX_INT_ENABLE; // enable interrupt on TX107 sCANMessage.ulMsgLen = sizeof(ucMsgData); // size of message is 4108 sCANMessage.pucMsgData = ucMsgData; // ptr to message content109 110 ucMsgData[0] = 0x12;111 ucMsgData[1] = 0x34;112 ucMsgData[2] = 0x56;113 ucMsgData[3] = 0x78;114 //115 // Enter loop to send messages. A new message will be sent once per116 // second. The 4 bytes of message content will be treated as an unsigned117 // long and incremented by one each time.118 //119 for(;;)120 {121 //122 // Print a message to the console showing the message count and the123 // contents of the message being sent.124 //125 UARTprintf("Sending msg: 0x%02X %02X %02X %02X",126 ucMsgData[0], ucMsgData[1], ucMsgData[2], ucMsgData[3]);127 128 //129 // Send the CAN message using object number 1 (not the same thing as130 // CAN ID, which is also 1 in this example). This function will cause131 // the message to be transmitted right away.132 //133 CANMessageSet(CAN0_BASE, 1, &sCANMessage, MSG_OBJ_TYPE_TX);134 135 //136 // Now wait 1 second before continuing137 //138 SimpleDelay();139 140 //141 // Check the error flag to see if errors occurred142 //143 if(g_bErrFlag)144 {145 UARTprintf(" error - cable connected?\n");146 }147 else148 {149 //150 // If no errors then print the count of message sent151 //152 UARTprintf(" total count = %u\n", g_ulMsgCount);153 }154 155 //156 // Increment the value in the message data.157 //158 //(*(unsigned long *)ucMsgData)++;159 }160 161 //162 // Return no errors163 //164 return(0);165 }

編譯,通過(guò)板載調試器下載代碼,復位運行代碼。
示波器探頭CH1連接TJA1050的CANH引腳,探頭CH2連接CANL引腳,地跟開(kāi)發(fā)板的GND連接,使用邊沿觸發(fā)模式捕獲波形:

為了方便分析,將波形保存成CSV格式。該CSV文件記錄了波形信息和數據,從第17行開(kāi)始,就是波形的數據,如下圖:

使用Matplotlib導入CSV,繪制折線(xiàn)圖,代碼如下:

1 import csv 2 import matplotlib 3 import matplotlib.pyplot as plt 4 import matplotlib.collections as collections 5 from matplotlib.ticker import MultipleLocator 6 import numpy as np 7 import pandas as pd 8 9 ax = plt.subplot()10 #將x主刻度標簽設置為125的倍數11 xmajorLocator = MultipleLocator(125) 12 ax.xaxis.set_major_locator(xmajorLocator)13 #y軸數據14 raw_canh = pd.read_csv("canh.csv")15 raw_canl = pd.read_csv("canl.csv")16 #x軸數據17 t = np.arange(130, 12000, 1)18 ax.plot(t, raw_canh[130:12000], raw_canl[130:12000])19 ax.xaxis.grid(True)20 21 plt.show()

運行,效果如下,

局部放大波形圖,

接下來(lái)的工作就是PS了,參照CAN2.0B的Spec,找到每一位的定義。首先是整個(gè)數據幀(Data Frame)的定義,

進(jìn)一步細化每個(gè)字段(Field):

將差分信號轉換為實(shí)際的二進(jìn)制值,十六進(jìn)制值。這里需要補充一點(diǎn)知識,CAN信號電壓與實(shí)際邏輯的關(guān)系,很好記憶,波形像口張開(kāi)的(O),表示邏輯0(顯示);另外一種則表示邏輯1(隱性)。如下圖:

根據上面的信息,我們可以進(jìn)一步得到以下數據,

如果你很細心的看上面圖,就會(huì )發(fā)現一個(gè)問(wèn)題,有些十六進(jìn)制為什么是有9位?因為有一位是填充位(Bit Stuffing),CAN2.0的協(xié)議規定,連續5個(gè)顯性/隱性電平后,要填充一位隱性/顯性電平。如上圖中的仲裁字段(Arbitration Field),連續5個(gè)'0'后,填充一個(gè)'1'。
分析到這里接近尾聲了,還有一個(gè)疑問(wèn),這個(gè)CRC校驗是怎么算出來(lái)的呢?從CAN2.0的Spec了解到,CRC的計算的值從SOF開(kāi)始,到數據字段(Data Field),多項式:
P(x) = x15+ x14+ x10+ x8+ x7+ x4+ x3+ 1
通過(guò)在線(xiàn)CRC計算網(wǎng)站,輸入我們的數據,計算CRC的值:

如我們所料,計算的CRC值是正確的!
-----------------------------------------------------------------------------------END
[參考資料]
聯(lián)系客服