[CAN波形分析] 一次CAN波形分析之旅
阅读原文时间:2023年07月09日阅读:2

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) = x15+ x14+ x10+ x8+ x7+ x4+ x3+ 1

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

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

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

[参考资料]

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章