1、第1章 CAN接口应用1.1 概述CAN,全称为“Controller Area Network”,即控制器局域网,是一种用于连接电子控制单元(ECU)的多主站共用型串行总线标准,并成为国际上应用最广泛的现场总线之一。CAN特别适用于电磁干扰和其它电子噪声强的环境,它可以使用像RS-485这样的平衡差分线或者更稳定可靠的双绞线。最初,CAN被设计作为汽车环境中的微控制器通讯,在车载各电子控制装置ECU之间交换信息,形成汽车电子控制网络。后来也使用在许多嵌入式控制应用中(比如:工业和医疗)。当总线长度小于40米时位速率可高达1Mbps。位速率会随着节点之间距离的增加而降低(例如:总线长度为500
2、米时位速率为125 Kbps)。Luminary公司的LM3S2000/5000/8000 系列ARM Cortex-M3处理器内建13( 详细配置请参考芯片手册) 路CAN控制器,可同时支持多路CAN总线的操作,使器件可用作网关、开关、工业或汽车应用中多个CAN总线的路由器。1.2 特性Stellaris CAN 模块具有以下特性: 支持CAN2.0 A/B协议; 位速率可编程(高达1 Mbps); 具有32个报文对象; 每个报文对象都具有自己的标识符屏蔽码(过滤器使用后方能使用); 包含可屏蔽中断; 在时间触发的CAN(TTCAN)应用中禁止自动重发送模式; 自测试操作具有可编程的回环模式
3、; 具有可编程的FIFO模式; 数据长度从0到8字节; 通过 CAN0Tx 和 CAN0Rx 管脚与外部CAN PHY无缝连接。1.3 外设驱动库函数说明Stellaris Peripheral Driver Library为用户提供了完整可靠的CAN通信底层API函数,用户通过调用API 函数即可完成CAN控制器配置、报文对象配置及 CAN中断管理等CAN模块开发工作。使用Stellaris Peripheral Driver Library提供的API函数开发CAN模块应用,必须了解相关的数据结构及枚举类型,下面就先介绍CAN模块API 函数所涉及的数据结构及枚举类型。注:在can.h 和
4、hw_can.h中我们一直要应用,所有的结构体都是在can.h中已经定义1.3.1 数据结构 1 tCANBitClkParms tCANBitClkParms是CAN位时钟设置参数的结构类型,其原型定义如程序清单1.1所示。注:后文中将讲到对CAN波特率真设置时,就要对CAN的位时间寄存器进行操作,虽然在驱动库里面已经对其进行了封装,但是我们还要了解它的工作原理.程序清单1.1 tCANBitClkParms结构原型typedef struct unsigned int uSyncPropPhase1Seg; / 这个成员用于保存位时间中的传输段及相位缓冲段1 / 的和,取值范围216 un
5、signed int uPhase2Seg; / 这个成员用于保存位时间中的相位缓冲段2的值, / 取值范围为18 unsigned int uSJW; / 这个成员用于保存位时间中的同步跳转宽度,取值范围14 unsigned int uQuantumPrescaler; / CAN波特率预分频值,取值范围为11023 tCANBitClkParms;2 tCANMsgObject tCANMsgObject结构用于组织配置报文对象的所有参数,其原型定义如程序清单 1.2所示。程序清单1.2 tCANMsgObject结构原型typedef struct unsigned long ulMs
6、gID; / 11或29位的CAN报文标识符 unsigned long ulMsgIDMask; / 报文滤波器使能后的标识符掩码 ,如果不使能报文滤波器的话,可以不用这个数据unsigned long ulFlags; / 由tCANObjFlags列举的配置参数 unsigned long ulMsgLen; / 报文数据域长度 unsigned char *pucMsgData; / 指向配置报文对象数据域数据的指针tCANMsgObject;1.3.2 枚举类型 1 tCANObjFlags 枚举类型tCANObjFlags中定义的常量将在调用 CANMessageSet( )和CA
7、NMessageGet( )函数时的tCANMsgObject型变量中用到,tCANObjFlags的原型定义如程序清单1.3所示。程序清单1.3 tCANObjFlags枚举类型typedef enum MSG_OBJ_TX_INT_ENABLE = 0x00000001, / 表示将使能或已使能发送中断 MSG_OBJ_RX_INT_ENABLE = 0x00000002, / 表示将使能或已使能接收中断 MSG_OBJ_EXTENDED_ID = 0x00000004, / 表示报文对象将使用或已使用扩展标识符 MSG_OBJ_USE_ID_FILTER = 0x00000008, /
8、表示将使用或已使用报文标识符滤波 MSG_OBJ_NEW_DATA = 0x00000080, / 表示报文对象中有可用的新数据 MSG_OBJ_DATA_LOST = 0x00000100, / 表示自上次读取数据后报文对象丢失了数据 MSG_OBJ_USE_DIR_FILTER = (0x00000010 | MSG_OBJ_USE_ID_FILTER),/ 表示报文对象将使用或已使用传输方向滤波, 如果使用方向滤波,则必须同时使用报文标识符滤波 MSG_OBJ_USE_EXT_FILTER = (0x00000020 | MSG_OBJ_USE_ID_FILTER), / 表示报文对象将
9、使用或已使用扩展标识符滤波, 如果使用扩展标识符滤波,则必须同时使用报文标识符滤波 MSG_OBJ_REMOTE_FRAME = 0x00000040, / 表示这个报文对象是一个远程帧 MSG_OBJ_NO_FLAGS = 0x00000000 / 表示这个报文对象不设置任何标志位tCANObjFlags;2 tCANIntStsReg tCANIntStsReg所列举的类型在调用函数CANIntStatus( )时用到,tCANIntStsReg的原型定义如程序清单1.4所示。程序清单1.4 tCANIntStsReg枚举类型typedef enum CAN_INT_STS_CAUSE,
10、/ 读取CAN中断寄存器 CAN_INT_STS_OBJECT / 读取CAN报文中断挂起标志. tCANIntStsReg;3 tCANStsReg tCANStsReg所列举的类型在调用函数CANStatusGet( )时用到,tCANStsReg 的原型定义如程序清单1.5所示。程序清单1.5 tCANStsReg枚举类型typedef enum CAN_STS_CONTROL, / 读取CAN控制器状态 CAN_STS_TXREQUEST, / 读取32个报文对象的发送请求位 CAN_STS_NEWDAT, / 读取32个报文对象的NewDat位 CAN_STS_MSGVAL / 读取
11、32个报文对象的MsgVal位tCANStsReg;4 tCANIntFlags tCANIntFlags所列举的类型在调用函数CANIntEnable( )和CANIntDisable( )时用到,tCANIntFlags的原型定义如程序清单1.6所示。程序清单1.6 tCANIntFlags枚举类型typedef enum CAN_INT_ERROR = 0x00000008, / 表示CAN控制器允许产生错误中断 CAN_INT_STATUS = 0x00000004, / 表示CAB控制器允许产生状态中断 CAN_INT_MASTER = 0x00000002 / 表示允许产生任何CA
12、N 中断,如果这位没设置, / 则不会产生任何中断tCANIntFlags;5 tMsgObjType tMsgObjType所列举的类型在调用API 函数CANMessageSet( )时用到,用于确定报文对象将被配置的类型,tMsgObjType的原型定义如程序清单1.7 所示。程序清单1.7 tMsgObjType枚举类型typedef enum MSG_OBJ_TYPE_TX, / 发送报文对象 MSG_OBJ_TYPE_TX_REMOTE, / 发送远程帧报文对象 MSG_OBJ_TYPE_RX, / 接收数据帧报文对象 MSG_OBJ_TYPE_RX_REMOTE, / 接收远程帧
13、报文对象 MSG_OBJ_TYPE_RXTX_REMOTE / 自动应答远程帧报文对象tMsgObjType;6 tCANStatusCtrl tCANStatusCtrl所列举的类型为调用函数CANStatusGet( )时的返回值的可能情况,包含所有的错误类型及总线状态,tCANStatusCtrl的原型定义如程序清单 1.8所示。程序清单1.8 tCANStatusCtrl枚举类型typedef enum CAN_STATUS_BUS_OFF = 0x00000080, / 脱离总线状态 CAN_STATUS_EWARN = 0x00000040, / 错误计数器已达到警告值 CAN_S
14、TATUS_EPASS = 0x00000020, / 错误计数器已达到被动错误值 CAN_STATUS_RXOK = 0x00000010, / 自上次读此状态以来,CAN控制器成功收到一帧数据 CAN_STATUS_TXOK = 0x00000008, / 自上次读此状态以来,CAN控制器成功发送一帧数据 CAN_STATUS_LEC_MSK = 0x00000007, / This is the mask for the last error code field. CAN_STATUS_LEC_NONE = 0x00000000, / 没有任何错误 CAN_STATUS_LEC_STU
15、FF= 0x00000001, / 位填充错误 CAN_STATUS_LEC_FORM = 0x00000002, / 格式错误 CAN_STATUS_LEC_ACK = 0x00000003, / 应答错误 CAN_STATUS_LEC_BIT1 = 0x00000004, / 总线1错误 CAN_STATUS_LEC_BIT0 = 0x00000005, / 总线0错误 CAN_STATUS_LEC_CRC = 0x00000006, / CRC效验错误 CAN_STATUS_LEC_MASK = 0x00000007 / This is the mask for the CAN Last
16、 Error Code (LEC). tCANStatusCtrl;注:以上为 can.h 中所有的结构体类型和枚举型类型的 CAN 有关的数据位类型1.3.3 接口函数 CAN控制器的初始化非常简单,直接调用CANInit( )即可,如表1.1所示。使用 CANBitTimingSet( )设置 CAN 通信波特率及位时钟等参数,如表 1.2 所示。注:后文中将专门介绍关于 can 通信波特率及位时钟等参数的设置情况.调用 CANBitTimingGet( )函数可以得到当前的位时钟配置信息,如表 1.3 所示。表 1.3 CANBitTimingGet( )函数配置好通信波特率后,可以调用
17、 CANEnable( )函数,使能 CAN 控制器,调用 CANEnable( )函数后,CAN 控制器自动接入总线并开始处理报文,如发送挂起的报文、从总线上接收报文等。CANEnable( )函数如表 1.4 所示。调用 CANDisable( )函数,将使 CAN 控制器停止报文处理,但对应报文对象中的配置信息及状态信息将不会因此改变,CANDisable( )函数如表 1.5 所示。通过调用 CANErrCntrGet( )函数可以读取 CAN 控制器当前的发送错误计数器值及接收错误计数器值,CANErrCntrGet( )函数如表 1.6 所示.通过调用 CANIntClear( )
18、函数则可以清除相应的中断标志,如表 1.9 所示。CAN 中断服务函数的设置也相当简单,通过调用 CANIntRegister( )函数可以将普通的 C 函数注册为 CAN 的中断服务函数,而不用去理会 ROM 中断向量表的配置 a,CANIntRegister( )函数如表 1.10 所示。注a:使用RAM中的向量表时须将VTABLE进行向量表对齐,否 则无法进入中断,解决方法可以采用以下任一种: 1.在链接配置文件(LM3S.icf )中“place in SRAM readwrite, block HEAP ;”的上一行添加“place at start of SRAM readwrit
19、e section VTABLE ;”。需要注意的是,当不需要将中断向量表重映射至RAM中时,若添加了上面那句“段定位”语句将会产生链接错误,所以上面那句“段定位”语 句只在有需要的工程中适用! 2.向量表数组定义的地方使用“编译器关键字”进行向量表对齐;具体方法为:将驱动库源程序文件 interrupt.c文件中的定义语句,如程序清单 1.9 所示,替换成如程序清单 1.10 所示的语句,然后重新编译生成新的驱动库文件,并替换工程中原来的驱动库文件即可。表 1.16 CANRetrySet( )函数调用 CANStatusGet( )函数可以读取 CAN 控制器的状态信息,CANStatus
20、Get( )函数如表 1.18 所示。1.4 CAN模块应用流程基于Stellaris Peripheral Driver Library的开发模式,可以减少用户在零阶段的投入,缩短研发周期,群星系列CAN模块应用的基本流程如图1.1所示。1 CAN 应用初始化CAN应用的初始化初始化工作包括CAN 引脚时钟使能、CAN模块时钟使能、CAN通信引脚配置、CAN控制器初始化、CAN通信波特率设置及CAN控制器使能等,具体的操作如程序清单1.11所示。程序清单1.11 CAN应用初始化 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); /* 使能GPIOD系
21、统外设*/ SysCtlPeripheralEnable(SYSCTL_PERIPH_CAN0); /* 使能CAN控制器系统外设*/ GPIOPinTypeCAN(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1); CANInit(CAN0_BASE); /* 初始化CAN节点*/ CANSetBitTiming(CAN0_BASE, /* 设置节点波特率 */ CANEnable(CAN0_BASE); /* 启动 CAN 节点*/2 设置接收报文对象 Stellaris CAN模块具有32个报文对象,每个报文对象都有自己的标识符屏蔽码;既可配置为单一的报
22、文对象,也可配置为属于FIFO缓冲器,应用相当灵活。下面简单介绍如何对单个报文对象进行配置。一个报文对象包含的主要信息有:报文(帧)ID 、帧ID屏蔽码、报文对象控制参数、报文数据长度、报文数据等,共涉及十多个接口寄存器。程序清单1.12向用户展示了“基于驱动库的编程”无需深入了解每个寄存器的功能,便可轻松完成报文对象的配置工作。程序清单1.12 配置接收报文对象tCANMsgObject tMsgObj; unsigned char ucBufferIn8 = 0; tMsgObj.ulFlags = (MSG_OBJ_RX_INT_ENABLE | MSG_OBJ_EXTENDED_ID
23、| MSG_OBJ_USE_EXT_FILTER |MSG_OBJ_USE_DIR_FILTER); /* 允许接收中断,扩展帧,报文方向滤波 */ tMsgObj.ulMsgID = 0x123; /* 报文滤波ID */ tMsgObj.ulMsgIDMask =0xFFFF; /* 报文ID掩码*/ tMsgObj.pucMsgData = ucBufferIn; /* 指向数据存储空间 */ tMsgObj.ulMsgLen = 8; /* 设置数据域长度 */ CANMessageSet(CAN0_BASE, 1, /* 配置数据帧“接收报文对象“ */3 使能 CAN 中断在收发数
24、据之前,还必须使能CAN中断,并设置好CAN 中断服务函数,设置CAN 中断服务函数有方法:一是直接将启动文件(startup.c)中的中断向量表(_vector_table) 中对应的位置换上中断服务函数名;二是在程序中调用CANIntRegister( )函数注册CAN中断服务函数。使能CAN中断的操作如程序清单1.13所示。程序清单1.13 使能CAN中断 unsigned long ulIntNum; CANIntEnable(CAN0_BASE, CAN_INT_MASTER | CAN_INT_ERROR); /* 使能CAN控制器中断源*/ ulIntNum= CANIntNum
25、berGet(CAN0_BASE); /* 获取CAN0的中断号*/ IntEnable(ulIntNum); /* 使能CAN控制器中断(to CPU) */ IntMasterEnable(); /* 使能中断总开关 */4 配置发送数据报文对象及发送数据 将要发送报文的帧类型、报文标志字符、数据长度及数据内容写入报文对象,再将报文对象配置为“发送数据帧”报文对象,则这个报文对象将进入发送队列,由CAN控制器将数据发送至总线上。如果使能了发送中断,当数据被成功发送后这个报文对象将会产生挂起中断。如果CAN模块丢失了仲裁或者在发送期间发生错误,那么一旦 CAN总线再次空闲就会重新发送报文。程
26、序清单1.14展示了 CAN模块如何实现发送数据。程序清单1.14 发送数据 tCANMsgObject MsgObjectTx; unsigned char ucBufferIn8= 0,1,2,3,4,5,6,7; /* 要发送的测试数据*/ MsgObjectTx.ulFlags = MSG_OBJ_EXTENDED_ID; /* 扩展帧*/ MsgObjectTx.ulMsgID = 0x123; /* 取得报文标识符*/ MsgObjectTx.ulMsgLen = 8; /* 标记数据域长度 */ MsgObjectTx.pucMsgData = ucBufferIn; /* 传递
27、数据存放指针 */ MsgObjectTx.ulFlags |= MSG_OBJ_TX_INT_ENABLE; /* 标记发送中断使能*/ CANRetrySet(CAN0_BASE, 31); /* 启动发送失败重发 */ CANMessageSet(CAN0_BASE, 31, /* 配置 31 号报文对象为发送对象 */5 接收数据 报文处理器将来自CAN模块接收移位寄存器的报文存储到报文RAM 中相应的报文对象中,在CAN 中断服务函数(接收报文对象的挂起中断)中调用 CANMessageGet( )函数即可从报文对象中的读取到接收的数据,如程序清单1.15所示。程序清单1.15 接收
28、数据 注:CANFRAME是在my_can.h中重新定义的一个结构体数据类型CANFRAME *ptCanFrame; CANFRAME tCanFrame; /* 定义接收缓存 */ tCANMsgObject MsgObjectRe; ptCanFrame = /* 取得缓存地址 */ MsgObjectRe.pucMsgData = ptCanFrame-ucDatBuf; /* 传递帧数据缓存地址*/ CANMessageGet(GpCanNodeInfo-ulBaseAddr, 1, /* 读取CAN报文并清除中断*/ /* 数据处理等 */1.5 即时解决方案从1.4我们可以了解到
29、,基于Stellaris Peripheral Driver Library开发CAN 模块应用,虽然不需要直接操作寄存器,但也都涉及了十几个底层API函数,不便于移植和维护,因此我们引入my_can模块(my_can.c和my_can.h),对CAN模块的API函数进行了进一步的封装,使得CAN模块的应用接口更加容易维护和移植。在封装过程中使用了比较多的结构类型,下面主要介绍my_can模块中所定义的数据结构及API 函数。1 数据结构 CANNODEINFO CANCIRBUF CANFRAME 在实际应用中可能要用到多路CAN模块,所以在my_can 模块中将每路CAN控制器抽象为一个软
30、节点来管理,软件节点的结构如程序清单1.16所示。程序清单1.16 CAN节点信息结构CANNODEINFO typedef struct unsigned long ulBaseAddr; /* CAN控制器基址*/ unsigned char ucBaudRateIndex; /* 波特率参数索引 */ unsigned long ulDataReObjMask; /* 接收数据帧的报文对象,按位选通 */ unsigned long ulRemoteReObjMask; /* 接收远程帧的报文对象,按位选通 */ unsigned long ulTxMsgObjNr; /* 发送报文对象
31、编号 */ CANNODEINFO;在 CAN 通信实际应用中常常出现接收数据累积(来不及处理)的情况,my_can 模块采用循环队列缓冲区来存储 CAN 通信所接收到的数据,其示意图如图 1.2 所示。当读写指针不同或满标志置位时,表示缓冲区有可用数据;当读指针赶上写指针时则表示缓冲区为空(没有可用数据) ,当写指针赶上读指针时则表示缓冲区满, (此时置位缓冲区满标志) 。环形缓冲器的数据结构定义如程序清单 1.17 所示。图 1.2 软件循环缓冲器程序清单1.17 环形缓冲器结构CANCIRBUF typedef struct INT32U ulWriteIndex; /* 缓存ptCan
32、FramBuf 写下标*/ INT32U ulReadIndex; /* 缓存ptCanFramBuf 读下标*/ INT16U ulLength; /* 记录缓存深度 */ BOOLEAN bIsFull; /* 缓冲区满标志 */ CANFRAME *ptCanFramBuf; /* 指向帧缓冲区首地址 */ CANCIRBUF;当CAN控制器从总线上接收到一帧数据并通过验收滤波后,CAN控制器将置位相应报文对象的NewDat位并产生一个挂起中断,以通知CPU 收到新数据。CPU 则可在CAN 中断服务程序中将接收到的帧保存至缓存中,帧数据的存储结构如程序清单1.18所示。程序清单1.18
33、 报文数据结构CANFRAME typedef struct unsigned char ucTtypeFormat; /* 帧类型 */ unsigned char ucDLC; /* 数据场长度 */ unsigned long ulID; /* CAN报文ID */ unsigned char ucDatBuf8; /* 报文数据场 */ CANFRAME;2 宏定义 my_can模块的宏定义如程序清单1.19所示。程序清单1.19 my_can模块的宏定义/* 函数返回类型*/ #define FAIL 0 #define SUCCESS 1 #define BUSY 2 #defin
34、e FULL 3 #define EMPTY 4 #define NOT_FULL 5 #define NOT_EMPTY 6 / 选择CAN 模块时钟#define FCAN8M 8000000UL #define FCAN2M 2000000UL #define FCAN50M 50000000UL #define FCAN FCAN8M /* 定义CAN模块时钟*/ / 宏定义接收、发送报文对象#define RE_DATA_SN 0x00FFFFFFUL /* 按位选择接收数据帧的报文对象*/ #define RE_RMRQS_SN 0x7F000000UL /* 按位选择接收远程帧的
35、报文对象*/ #define TX_MSG_SN 32 /* 发送报文对象编号 */ #define TX_OBJ_MASK (1UL ulBaseAddr = ulChannelNr; /* CAN控制器基地址*/ pCanNodeInfo-ucBaudRateIndex = ulBaud; /* CAN控制器波特率索引*/ pCanNodeInfo-ulDataReObjMask = ulDataReObjMask; /* 接收数据帧报文对象选择掩码 */ pCanNodeInfo-ulRemoteReObjMask = ulRemoteReObjMask;/* 接收远程帧报文对象选择掩码
36、 */ pCanNodeInfo-ulTxMsgObjNr = ulTxMsgObj; /* 发送报文对象*/ 调用canApplyInit( )函数可以将软件CAN节点的信息配置到实际的 CAN模块中,并初始化CAN控制器及CAN模块工作的软件环境(如接收和发送缓冲区),然后启动CAN 节点,使CAN控制器同步于外部CAN总线。 canApplyInit( )函数如程序清单1.21所示。程序清单1.21 canApplyInit( )函数void canApplyInit(void *pCAN, unsigned long ulFrameID, unsigned long ulFrameID
37、Mask, unsigned char ucFramType) CANNODEINFO *pCanNodeInfo; pCanNodeInfo = (CANNODEINFO *)pCAN; / 软环境初始化 canCirBufInit( /* 初始化接收循环队列缓冲区 */ canCirBufInit( /* 初始化发送循环队列缓冲区 */ GulMsgObjReMask = RE_DATA_SN; /* 初始化接收报文对象掩码 */ GptCanCirBuf = 0; /* 用于传递需要发送的缓冲区 */ GbCanCirBufSend = false; /* 用于标记GptCanCirBu
38、f所指否应 */ /* 被发送 */ / 硬件初始化 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); /* 使能GPIOD系统外设 */ SysCtlPeripheralEnable(SYSCTL_PERIPH_CAN0); /* 使能CAN控制器系统外设*/ GPIOPinTypeCAN(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1); /* 配置引脚连接 */ CANInit(pCanNodeInfo-ulBaseAddr); /* 初始化CAN控制器*/ CANSetBitTiming(pCanNodeInfo
39、-ulBaseAddr, /* 设定节点波特率 */ CANEnable(pCanNodeInfo-ulBaseAddr); /* 退出初始化模式,启动CAN节点 */ canAcceptFilterSet(pCanNodeInfo, ulFrameID, ulFrameIDMask, ucFramType); /* 验收滤波设置 */ canIntEnable(pCanNodeInfo); /* 使能CAN中断*/ CAN模块应用的软件环境初始化主要包括模块内全局变量及接收和发送缓冲区的初始化工作。调用canCirBufInit( )函数可以对指定的循环队列进行初始化,canCirBufIn
40、it( )函数如所示。 程序清单1.22 canCirBufInit( )函数void canCirBufInit(void *ptBuf, CANFRAME *ptCanFrameBuf, INT8U ucLength) unsigned int i,j; CANCIRBUF *ptCanCirBuf; ptCanCirBuf = (CANCIRBUF *)ptBuf; /* 取得循环队列地址 */ ptCanCirBuf-bIsFull = false; ptCanCirBuf-ulWriteIndex = 0; /* Buffer写下标清零*/ ptCanCirBuf-ulReadInd
41、ex = 0; /* Buffer读下标清零*/ ptCanCirBuf-ulLength = ucLength; /* 记录长度,此值不允再许更改 */ ptCanCirBuf-ptCanFramBuf = ptCanFrameBuf; /* 指向数据缓存区首地址*/ for (i = 0; i ptCanFramBufi.ucTtypeFormat = 0; /* 0 标准帧;1 扩展帧*/ ptCanCirBuf-ptCanFramBufi.ucDLC = 0; /* 数据场长度 */ ptCanCirBuf-ptCanFramBufi.ulID = 0; /* CAN报文ID */ f
42、or (j = 0; j ptCanFramBufi.ucDatBufj = 0;/* 报文数据场 */ 调用canCirBufMalloc( )函数,则可以从指定的循环队列申请一帧报文的存储空间,若循环队列不满,则调用此函数后将自动调整环形缓冲器的写指针,并判断“缓冲区满”条件,当满足条件时置位满标志。canCirBufMalloc( ) 函数如程序清单1.23 所示。程序清单1.23 canCirBufMalloc( )函数static CANFRAME *canCirBufMalloc(void *ptBuf) CANCIRBUF *ptCanCirBuf; CANFRAME *ptCa
43、nFrame; int i; ptCanCirBuf = (CANCIRBUF *)ptBuf; if (ptCanCirBuf-bIsFull = true) /* 接循环队列已满 */ return (CANFRAME *)0; /* 返回0 */ ptCanFrame = /* 取得需要返回的地址 */ if (ptCanCirBuf-ulWriteIndex = ptCanCirBuf-ulLength) ptCanCirBuf-ulWriteIndex = 0; /* 构成环形队列 */ if (ptCanCirBuf-ulWriteIndex = ptCanCirBuf-ulRea
44、dIndex) /* 写指针赶上读指针缓冲区满 */ ptCanCirBuf-bIsFull = true; ptCanFrame-ucTtypeFormat = STD_DATA; /* 初始化申请的存储空间 */ ptCanFrame-ucDLC= 0; ptCanFrame-ulID = 0; for (i = 0; i ucDatBufi = 0; return ptCanFrame; /* 返回所申请的存储空间地址 */ 调用canCirBufRead( )函数,则可以从指定的循环队列中读取数据,若循环队列不空,则调用此函数后自动调整环形缓冲器的读指针,并清除缓冲区满标志。在例程中通
45、过循环调用此函数来判断是否收到数据,canCirBufRead( )函数如程序清单1.24所示。程序清单1.24 canCirBufRead( )函数/* * 输入: * ptCanBuf 指向循环队列的空指针* 输出: *ptFrame 数据输出指针,指向报文结构变量的空指针* 返回: EMPTY 循环队列为空,即缓冲区中无可用数据* NOT_EMPTY 循环队列非空 */ INT8U canCirBufRead(void *ptCanBuf, void *ptFrame) CANCIRBUF *ptCanCirBuf; CANFRAME *ptCanFrame; INT8U i; ptCa
46、nCirBuf = (CANCIRBUF *)ptCanBuf; ptCanFrame = (CANFRAME *)ptFrame; if (ptCanCirBuf-ulReadIndex != ptCanCirBuf-ulWriteIndex) | (ptCanCirBuf-bIsFull = true) ptCanFrame-ucTtypeFormat = ptCanCirBuf-ptCanFramBufptCanCirBuf-ulReadIndex.ucTtypeFormat; ptCanFrame-ucDLC = ptCanCirBuf-ptCanFramBufptCanCirBuf-
47、ulReadIndex.ucDLC; ptCanFrame-ulID = ptCanCirBuf-ptCanFramBufptCanCirBuf-ulReadIndex.ulID; for (i= 0; i ucDatBufi = ptCanCirBuf-ptCanFramBufptCanCirBuf-ulReadIndex.ucDatBufi; ptCanCirBuf-ulReadIndex += 1; /* 读指针加1 */ if (ptCanCirBuf-ulReadIndex = ptCanCirBuf-ulLength) ptCanCirBuf-ulReadIndex = 0; /* 形成环形循环队列 */ ptCanCirBuf-bIsFull = false; /* 标志缓冲区不满 */ return NOT_EMPTY; else /* 无可用数据 */ return EMPTY; 调用canCirBufWrite ( )函数,则可以往指定的循环队列缓冲区中写一帧数据,若循环队列不满,则调用此函数后将自动调整环形缓冲器的写指针,并判断“缓冲区满”条件,当满足条件时置位满标志。canCirBufWrite( ) 函数如程序清单1.25所示。程序清单1.25 canCirBufWrite( )函数/*