Prepare

CAN通信协议使用了有一段时间了,但都是基于软件层面的使用,对于其波形不是很了解,正好这段时间比较闲,是时候补补硬知识。

开始之前,先介绍一下设备:

  • 咸鱼淘来的古董级别示波器GDS-2202。200MHz,数据记录长度是12500个点(每个点40ns,总记录长度是500us)
  • EK-LM4F120XL开发板。也就是现在的EK-TM4C123GXL,板载MCU是TM4C1233H6PM,对应原来的老型号LM4F120H5QR
  • CAN收发器,TJA1050模块

 Ongoing

软件准备

用CCS9.0导入TI提供的CAN驱动库,每隔1秒钟发送一个CAN信息:

  • 波特率:500 kb/s
  • ID(Normal): 0x220
  • 信息长度 :4 bytes
  • 数据:0x12, 0x34, 0x56, 0x78
  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 CAN
100     // messages.  The message will be 4 bytes that will contain an incrementing
101     // value.  Initially it will be set to 0.
102     //
103     *(unsigned long *)ucMsgData = 0;
104     sCANMessage.ulMsgID = 0x220;                    // CAN message ID
105     sCANMessage.ulMsgIDMask = 0;                    // no mask needed for TX
106     sCANMessage.ulFlags = MSG_OBJ_TX_INT_ENABLE;    // enable interrupt on TX
107     sCANMessage.ulMsgLen = sizeof(ucMsgData);       // size of message is 4
108     sCANMessage.pucMsgData = ucMsgData;             // ptr to message content
109
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 per
116     // second.  The 4 bytes of message content will be treated as an unsigned
117     // long and incremented by one each time.
118     //
119     for(;;)
120     {
121         //
122         // Print a message to the console showing the message count and the
123         // 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 as
130         // CAN ID, which is also 1 in this example).  This function will cause
131         // 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 continuing
137         //
138         SimpleDelay();
139
140         //
141         // Check the error flag to see if errors occurred
142         //
143         if(g_bErrFlag)
144         {
145             UARTprintf(" error - cable connected?\n");
146         }
147         else
148         {
149             //
150             // If no errors then print the count of message sent
151             //
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 errors
163     //
164     return(0);
165 }

编译,通过板载调试器下载代码,复位运行代码。

硬件准备

示波器探头CH1连接TJA1050的CANH引脚,探头CH2连接CANL引脚,地跟开发板的GND连接,使用边沿触发模式捕获波形:

分析

为了方便分析,将波形保存成CSV格式。该CSV文件记录了波形信息和数据,从第17行开始,就是波形的数据,如下图:

使用Matplotlib导入CSV,绘制折线图,代码如下:

 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()

运行,效果如下,

局部放大波形图,

 接下来的工作就是PS了,参照CAN2.0B的Spec,找到每一位的定义。首先是整个数据帧(Data Frame)的定义,

进一步细化每个字段(Field):

将差分信号转换为实际的二进制值,十六进制值。这里需要补充一点知识,CAN信号电压与实际逻辑的关系,很好记忆,波形像口张开的(O),表示逻辑0(显示);另外一种则表示逻辑1(隐性)。如下图:

根据上面的信息,我们可以进一步得到以下数据,

如果你很细心的看上面图,就会发现一个问题,有些十六进制为什么是有9位?因为有一位是填充位(Bit Stuffing),CAN2.0的协议规定,连续5个显性/隐性电平后,要填充一位隐性/显性电平。如上图中的仲裁字段(Arbitration Field),连续5个'0'后,填充一个'1'。

 Post

分析到这里接近尾声了,还有一个疑问,这个CRC校验是怎么算出来的呢?从CAN2.0的Spec了解到,CRC的计算的值从SOF开始,到数据字段(Data Field),多项式:

                                          P(x) = x+ x+ x+ x+ x+ x+ x+ 1

 通过在线CRC计算网站,输入我们的数据,计算CRC的值:

 如我们所料,计算的CRC值是正确的!

 -----------------------------------------------------------------------------------END

[参考资料]

01-15 21:12