CAN数据传输线的结构?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAN数据传输线的结构?相关的知识,希望对你有一定的参考价值。
CAN总线采用两条线缠绕在一起,两条线上的电位相反,若一条线的电压为5V,另一条线则为0V,两条线的电压和总等于常值。通过此办法,CAN总线将免受外界电磁场干扰,同时CAN总线向外辐射也保持中性,即无辐射。 参考技术A CAN总线采用两条线缠绕在一起,两条线上的电位相反,若一条线的电压为5V,另一条线则为0V,两条线的电压和总等于常值。通过此办法,CAN总线将免受外界电磁场干扰,同时CAN总线向外辐射也保持中性,即无辐射。 参考技术B “近期遇到CAN总线上错误帧的问题,花了些时间来了解CANbus数据帧的结构,利用工具也抓到了CANbus电压波形,在此来和大家分享一下。”CAN的全称是Controller Area Network,是ISO国际标准化的串行通信协议。因其高性能及可靠性,已被广泛应用于船舶、工业、医疗等领域。
下图为一张完整的CAN frame,其中绿线表示CAN_H,棕线表示CAN_L,蓝线表示电位差。
高速CAN中,CAN_H/CAN_L电压及显性/隐形的定义如下:
下面就结合理论及实例展示来介绍帧结构中的各部分含义:
一、帧起始、帧结束
帧起始及帧结束在标准帧及扩展帧中都存在,在帧的首尾,用于界定一个数据帧。
实例:帧起始
实例:帧结束
二、仲裁段
当总线上多个节点同时发送数据,应该先发送谁,后发送谁?由仲裁段来判决。
可以看出,帧ID值越小,优先级越高。
实例:仲裁段
三、控制段
控制段一共有6位,由扩展帧标志位IDE、保留位r、数据长度代码DLC组成,解释如下:
实例:控制段
四、数据段
一个数据帧传输的数据为0-8个字节,这种短帧结构使得CANbus的数据量小,发送和接收时间短,实时性高;同时被干扰的概率小,抗干扰能力强。
实例:数据段
五、CRC段
CRC校验用于CANbus的数据检错,CRC校验值存放于CRC段,CRC校验段由15位CRC值和1位CRC界定符构成。
实例:CRC段
六、ACK段
当一个节点接收到的帧起始到CRC段之间的内容没有错误时,将在ACK段发送一个显性电平。
实例:ACK段
文中从帧起始、仲裁段、控制段、数据段、CRC段、ACK段、帧结束来展示了CANbus数据帧的结构,为了方便理解,插入了很多实例图片,也借用了很多网络的资料,希望有助于理解。
下一篇将会介绍,CAN通信帧的分类及CANbus上的错误类型。
SylixOS SylixOS CAN总线驱动之三
SylixOS CAN报文传送流程
CAN报文传送流程框图
在SylixOS中CAN报文的传输框图如图 11所示。
图 11 SylixOS CAN报文传输框图
(注:此文档承接之前的文档编写,之前文档中详细介绍过的报文,传输结构体在此文档中不做详细介绍。)
SylixOS CAN报文缓存机制
在SylixOS中CAN报文的传输不是底层和上层应用的直接传输。而是在底层和应用层中间加了一层系统缓存队列。所有收发的CAN报文都要先经过一个系统缓存机制传输到真正调用到它的地方。
系统CAN发送报文缓存
SylixOS中CAN报文是以消息队列的方式进行缓存的,程序清单 21是向消息队列中写入一帧CAN报文的具体实现。
程序清单 21从缓存中读取一帧CAN报文
/********************************************************************************************************* ** 函数名称: __canITx ** 功能描述: 从发送缓冲区中读出一个数据 ** 输 入 : ** pcanDev CAN 设备 ** pcanframe 指向待读出的数据 ** 输 出 : ERROR_NONE or PX_ERROR ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __canITx (__CAN_DEV *pcanDev, PCAN_FRAME pcanframe) { INTREG iregInterLevel; INT iTemp = 0; if (!pcanDev || !pcanframe) { return (PX_ERROR); } iTemp = __canReadQueue(pcanDev, pcanDev->CAN_pcanqSendQueue, pcanframe, 1); /* 从发送队列中读取一帧数据 */ LW_SPIN_LOCK_QUICK(&pcanDev->CAN_slLock, &iregInterLevel); if (iTemp <= 0) { pcanDev->CAN_canstatWriteState.CANSTAT_bBufEmpty = LW_TRUE; /* 发送队列空 */ } LW_SPIN_UNLOCK_QUICK(&pcanDev->CAN_slLock, iregInterLevel); API_SemaphoreBPost(pcanDev->CAN_ulSendSemB); /* 释放信号量 */ SEL_WAKE_UP_ALL(&pcanDev->CAN_selwulList, SELWRITE); /* 释放所有等待写的线程 */ return ((iTemp) ? (ERROR_NONE) : (PX_ERROR)); }
系统CAN接收报文缓存
上层应用向底层传输一帧CAN报文的时候也是通过系统缓存,向系统缓存中写入一帧CAN报文的具体实现如程序清单 22所示。
程序清单 22向缓存中写入一帧CAN报文
/********************************************************************************************************* ** 函数名称: __canIRx ** 功能描述: 向接收缓冲区中写入一个数据 ** 输 入 : ** pcanDev CAN 设备 ** pcanframe 指向待写入的数据 ** 输 出 : ERROR_NONE or PX_ERROR ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __canIRx (__CAN_DEV *pcanDev, PCAN_FRAME pcanframe) { INT iTemp = 0; if (!pcanDev || !pcanframe) { return (PX_ERROR); } iTemp = __canWriteQueue(pcanDev, pcanDev->CAN_pcanqRecvQueue, pcanframe, 1); /* 往接收队列中写入一帧数据 */ API_SemaphoreBPost(pcanDev->CAN_ulRcvSemB); /* 释放信号量 */ SEL_WAKE_UP_ALL(&pcanDev->CAN_selwulList, SELREAD); /* select() 激活 */ return ((iTemp) ? (ERROR_NONE) : (PX_ERROR)); }
具体调用实现
应用层传输到驱动层具体实现
第一步:如程序清单 31所示,在应用层创建一个线程,打开一个CAN设备。
程序清单 31打开CAN设备
iFd = open(devname, O_RDWR, 0666); if (iFd < 0) { printf("failed to open %s!\n", devname); return (LW_NULL); }
第二步:如程序清单 32所示,填充一个CAN报文结构体。
程序清单 32填充CAN报文
CAN_FRAME canframe; canframe.CAN_bExtId = LW_FALSE; canframe.CAN_bRtr = LW_FALSE; canframe.CAN_ucLen = CAN_MAX_DATA; lib_memcpy((CHAR *)canframe.CAN_ucData, "01234567", CAN_MAX_DATA); canframe.CAN_uiId = 0;
第三步:如程序清单 33所示,调用write函数向系统TX缓存队列中写入一帧CAN报文,再调用ioctl函数实现底层传输。
程序清单 33填充发送缓存
stLen = write(iFd, &canframe, sizeof(CAN_FRAME)); ioctl(iFd, CAN_DEV_STARTUP, 0); case CAN_DEV_STARTUP: __flexcanStartup(pCanchan); break;
第四步:如程序清单 34所示,最终调用到底层传输函数。从系统队列中读取一帧CAN报文后对设备寄存器进行相关操作将消息传输到总线上。
程序清单 34底层starup函数
/********************************************************************************************************* ** 函数名称: __flexcanStartup ** 功能描述: 启动数据发送 ** 输 入 : pCanchan 通道对象 ** 输 出 : NONE ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __flexcanStartup (CAN_CHAN *pCanchan) { FLEXCAN_CHAN *pChannel = container_of(pCanchan, FLEXCAN_CHAN, CANCH_canchan); CAN_FRAME canFrame; INT iCount; if (!pChannel->CANCH_pcbGetTx) { return (PX_ERROR); } while (pChannel->CANCH_pcbGetTx(pChannel->CANCH_pvGetTxArg, /* 从发送缓冲区中读取数据发送 */ &canFrame) == ERROR_NONE) { __flexcanSend(pChannel, &canFrame); } return (ERROR_NONE); }
注:如果想要发送多帧CAN报文,在写入操作结束后需要加等待,以确保所有的CAN报文都成功写入系统缓存队列中。驱动层传输到应用层
第一步:底层如果接收到CAN报文以后,会触发一次中断,在中断服务函数中所做的事就是判断状态标志位置,如果是接收中断,就把接收到的CAN报文通过回调函数写入,系统缓存队列中,具体实现如程序清单 35所示。
程序清单 35 CAN底层中断服务函数
/********************************************************************************************************* ** 函数名称: __flexcanIrq ** 功能描述: can 中断服务程序 ** 输 入 : pChannel 通道对象 ** ulVector 中断向量号 ** 输 出 : ERROR CODE ** 全局变量: ** 调用模块: *********************************************************************************************************/ static irqreturn_t __flexcanIrq (PVOID pvArg, ULONG ulVector) { FLEXCAN_CHAN *pChannel; UINT32 uiIflag1, uiEsr, uiValue; CAN_FRAME canframe; can_debug("[CAN]irq\r\n"); pChannel = (FLEXCAN_CHAN *)pvArg; uiIflag1 = CAN_READ(FLEXCAN_IFLAG1); uiEsr = CAN_READ(FLEXCAN_ESR1); uiValue = CAN_READ(FLEXCAN_CTRL1); uiValue &= ~(FLEXCAN_CTRL1_ERR_ALL); CAN_WRITE(FLEXCAN_CTRL1, uiValue); CAN_WRITE(FLEXCAN_IMASK1, 0); if (uiEsr & FLEXCAN_ESR1_ERR_ALL) { can_debug("There is something wrong!\n"); CAN_READ(FLEXCAN_ESR1); CAN_WRITE(FLEXCAN_ESR1, FLEXCAN_ESR1_ERR_ALL); } if (uiEsr & FLEXCAN_ESR1_RX) { if (uiIflag1 & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE) { CAN_WRITE(FLEXCAN_IMASK1, FLEXCAN_IFLAG_DEFAULT & ~FLEXCAN_IFLAG_RX_FIFO_AVAILABLE); memset(&canframe, 0, sizeof(CAN_FRAME)); __flexcanRecv(pChannel, &canframe); if (pChannel->CANCH_pcbPutRcv(pChannel->CANCH_pvPutRcvArg, &canframe) != ERROR_NONE) { pChannel->CANCH_pcbSetBusState(pChannel->CANCH_pvSetBusStateArg, CAN_DEV_BUS_RXBUFF_OVERRUN); } } } if (uiIflag1 & FLEXCAN_IFLAG_RX_FIFO_OVERFLOW) { can_debug("FIFO overflow\n"); CAN_WRITE(FLEXCAN_IFLAG1, FLEXCAN_IFLAG_RX_FIFO_OVERFLOW); } CAN_WRITE(FLEXCAN_IMASK1, FLEXCAN_IFLAG_DEFAULT); return (LW_IRQ_HANDLED); }
第二步:在应用程序中创建一个线程,在线程中所做的事情就是不间断得读取系统缓存的消息队列。如果缓存不为空,就读取里面的CAN报文,并打印,具体操作如程序清单 36所示。
程序清单 36 CAN应用层读取缓存
while (1) { stLen = read(iFd, &canframe, sizeof(STR_CANMSG_T)); stFrameNum = stLen / sizeof(STR_CANMSG_T); if (stFrameNum != 1) { printf("failed to recv can frame, abort recving!\n"); break; } else { sprintf(cFramInfo, "id=%d, len=%d, data=%02x %02x %02x %02x %02x %02x %02x %02x\n", canframe.Id, (INT)canframe.DLC, canframe.Data[0], canframe.Data[1], canframe.Data[2], canframe.Data[3], canframe.Data[4], canframe.Data[5], canframe.Data[6], canframe.Data[7]); printf(cFramInfo); } }
CAN报文传输流程,到此结束。免责声明
内部交流文档,若发现相关错误或者建议,请及时联系文档创建者进行修订和更新。
本文出自 “12562800” 博客,请务必保留此出处http://12572800.blog.51cto.com/12562800/1917270
以上是关于CAN数据传输线的结构?的主要内容,如果未能解决你的问题,请参考以下文章