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
int main(void){ tCANMsgObject sCANMessage; unsigned char ucMsgData[4]; // 初始化系统时钟 SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // 配置CAN Tx和Rx引脚 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); GPIOPinConfigure(GPIO_PE4_CAN0RX); GPIOPinConfigure(GPIO_PE5_CAN0TX); GPIOPinTypeCAN(GPIO_PORTE_BASE, GPIO_PIN_4 | GPIO_PIN_5); // 使能CAN模块时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_CAN0); // 初始化CAN模块 CANInit(CAN0_BASE); // 设置CAN波特率 CANBitRateSet(CAN0_BASE, SysCtlClockGet(), 500000); // 使能CAN中断 CANIntEnable(CAN0_BASE, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS); CANRetrySet(CAN0_BASE, false); // 使能CAN中断 IntEnable(INT_CAN0); // 使能CAN模块 CANEnable(CAN0_BASE); // 初始化CAN报文 *(unsigned long *)ucMsgData = 0; sCANMessage.ulMsgID = 0x220; // CAN message ID sCANMessage.ulMsgIDMask = 0; // no mask needed for TX sCANMessage.ulFlags = MSG_OBJ_TX_INT_ENABLE; // enable interrupt on TX sCANMessage.ulMsgLen = sizeof(ucMsgData); // size of message is 4 sCANMessage.pucMsgData = ucMsgData; // ptr to message content ucMsgData[0] = 0x12; ucMsgData[1] = 0x34; ucMsgData[2] = 0x56; ucMsgData[3] = 0x78; for(;;) { // 发送CAN报文 CANMessageSet(CAN0_BASE, 1, &sCANMessage, MSG_OBJ_TYPE_TX); // 延时1秒钟 SimpleDelay(); } return(0);}
编译,通过板载调试器下载代码,复位运行代码。
硬件准备
示波器探头CH1连接TJA1050的CANH引脚,探头CH2连接CANL引脚,地跟开发板的GND连接,使用边沿触发模式捕获波形:
分析
为了方便分析,将波形保存成CSV格式。该CSV文件记录了波形信息和数据,从第17行开始,就是波形的数据,如下图:
使用Matplotlib导入CSV,绘制折线图,代码如下:
import csvimport matplotlibimport matplotlib.pyplot as pltimport matplotlib.collections as collectionsfrom matplotlib.ticker import MultipleLocatorimport numpy as npimport pandas as pdax = plt.subplot()#将x主刻度标签设置为125的倍数xmajorLocator = MultipleLocator(125) ax.xaxis.set_major_locator(xmajorLocator)#y轴数据raw_canh = pd.read_csv("canh.csv")raw_canl = pd.read_csv("canl.csv")#x轴数据t = np.arange(130, 12000, 1)ax.plot(t, raw_canh[130:12000], raw_canl[130:12000])ax.xaxis.grid(True)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
[参考资料]
- http://www.ti.com/tool/EK-TM4C123GXL
- https://elearning.vector.com/