CAN 驱动
CAN 驱动注册
CAN 设备库对 CAN 设备进行了封装,设备驱动仅需提供 I/O 控制函数、启动发送函数和回调安装函数即可,CAN 设备库位于“libsylixos/SylixOS/system/device/can”下。
在注册 CAN 设备驱动之前需要安装 CAN 设备库,其原型如下:
#include <SylixOS.h>
INT API_CanDrvInstall (void);
函数 API_CanDrvInstall 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
CAN 设备库封装了对IO子系统的注册,因此 CAN 设备驱动只需要调用设备库标准函数 API_CanDevCreate 即可绑定驱动并创建设备,其函数原型如下:
#include <SylixOS.h>
INT API_CanDevCreate (PCHAR pcName,
CAN_CHAN *pcanchan,
UINT uiRdFrameSize,
UINT uiWrtFrameSize);
函数 API_CanDevCreate 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 pcName :设备名称。
- 参数 pcanchan :CAN 驱动结构体指针,包含 CAN 设备驱动需要提供给 CAN 设备库的操作函数,上层会直接引用此结构体,因此需要持续有效。
- 参数 uiRdFrameSize :读取缓冲区大小,数值是 CAN 帧个数。
- 参数 uiWrtFrameSize :写入缓冲区大小,数值是 CAN 帧个数。
结构体 CAN_CHAN 详细描述如下:
typedef struct __can_drv_funcs CAN_DRV_FUNCS;
typedef struct __can_chan {
CAN_DRV_FUNCS *pDrvFuncs;
} CAN_CHAN; /* CAN 驱动结构体 */
struct __can_drv_funcs {
INT (*ioctl) (CAN_CHAN *pcanchan, INT cmd, PVOID arg);
INT (*txStartup) (CAN_CHAN *pcanchan);
INT (*callbackInstall) (CAN_CHAN *pcanchan,
INT callbackType,
CAN_CALLBACK callback,
PVOID callbackArg);
};
结构体 CAN_CHAN 中包含三个需要提供给 CAN 设备库的操作函数,其功能分别是提供 I/O 控制、启动设备发送和回调安装功能,其中这三个函数的第一个传入参数都是指向 CAN 驱动结构体的指针,即 API_CanDrvInstall 函数第二个参数的值。
CAN 回调函数
使用回调函数的目的是将 CAN 设备库中的结构体保存到驱动中以供缓冲区数据操作及总线状态操作使用,因此在驱动结构体中需要提供六个变量以保存数据,示例如下:
typedef struct {
...
INT (*pcbSetBusState)(); /* bus status callback */
INT (*pcbGetTx)(); /* int callback */
INT (*pcbPutRcv)();
PVOID pvSetBusStateArg;
PVOID pvGetTxArg;
PVOID pvPutRcvArg;
...
} CAN_CHAN;
以上三个回调函数的第一个参数是保存在驱动结构体中对应的 PVOID 类型参数,发送接收回调函数的第二个参数为 CAN 帧指针,总线状态设置回调函数的第二个参数为总线状态,详细取值如所示。
总线状态取值 | 含义 |
---|---|
CAN_DEV_BUS_ERROR_NONE | 正常状态 |
CAN_DEV_BUS_OVERRUN | 接收溢出 |
CAN_DEV_BUS_OFF | 总线关闭 |
CAN_DEV_BUS_LIMIT | 限定警告 |
CAN_DEV_BUS_PASSIVE | 错误被动 |
CAN_DEV_BUS_RXBUFF_OVERRUN | 接收缓冲溢出 |
CAN I/O 控制函数
CAN 设备库中的 I/O 控制函数主要实现了一些与控制器无关的功能,诸如冲刷缓冲区和获取 CAN 控制器状态等,而需要设备驱动实现的 I/O 控制函数则实现与控制器相关的功能,诸如复位 CAN 控制器和设置 CAN 控制器波特率等,详细功能如下表所示:
CAN 设备驱动 I/O 控制功能取值 | 含义 |
---|---|
CAN_DEV_OPEN | CAN 设备打开,主要实现中断使能等 |
CAN_DEV_CLOSE | CAN 设备关闭 |
CAN_DEV_SET_BAUD | 设置 CAN 设备波特率 |
CAN_DEV_SET_MODE | 设置 CAN 设备模式 0: BASIC CAN 1: PELI CAN |
CAN_DEV_REST_CONTROLLER | 复位 CAN 控制器 |
CAN_DEV_STARTUP | 启动 CAN 控制器 |
CAN_DEV_SET_FLITER | 设置 CAN 滤波器 (暂不支持) |
一个完整的驱动 I/O 控制函数示例如下:
static INT __Ioctl (CAN_CHAN *pcanchan, INT cmd, LONG arg)
{
INTREG intreg;
switch (cmd) {
case CAN_DEV_OPEN: /* 打开 CAN 设备 */
pcanchan->open(pcanchan);
break;
case CAN_DEV_CLOSE: /* 关闭 CAN 设备 */
pcanchan->close(pcanchan);
break;
case CAN_DEV_SET_BAUD: /* 设置波特率 */
switch (arg) {
case 1000000:
pcanchan->baud = (ULONG)BTR_1000K;
break;
case 900000:
pcanchan->baud = (ULONG)BTR_900K;
break;
case 666000:
pcanchan->baud = (ULONG)BTR_666K;
break;
...
default:
errno = ENOSYS;
return (PX_ERROR);
}
break;
case CAN_DEV_SET_MODE:
if (arg) {
pcanchan->canmode = PELI_CAN;
} else {
pcanchan->canmode = BAIS_CAN;
}
break;
case CAN_DEV_REST_CONTROLLER:
__startup(pcanchan);
break;
case CAN_DEV_STARTUP:
reset(pcanchan);
pcbSetBusState(pcanchan->pvSetBusStateArg, CAN_DEV_BUS_ERROR_NONE);
...
break;
default:
errno = ENOSYS;
return (PX_ERROR);
}
return (ERROR_NONE);
}
每个功能的具体实现与控制器相关,需要根据实际情况进行实现。在这里需要注意的是,如果传入 I/O 控制命令没有在 CAN 设备驱动 I/O 控制函数中实现,必须要如程序中 default 分支那样返回,否则 CAN 设备库会认为 I/O 控制命令已经成功,导致部分命令出错。
CAN 启动发送函数
这里称为启动发送函数的原因是每个控制器实现都有可能不同,比如 zynq7000 的 CAN 控制器驱动是设置 FIFO 空中断状态位,真正的发送函数则在 SylixOS 中断延迟处理队列中执行发送,而 sja1000 控制器则是在启动发送函数中立即发送。下面是一个立即发送的例子,以方便演示回调发送函数的使用方法:
static INT __TxStartup (CAN_CHAN *pcanchan)
{
CAN_FRAME canframe;
if (pcanchan->pcbGetTx(pcanchan->pvGetTxArg, &canframe) == ERROR_NONE) {
...
}
return (ERROR_NONE);
}
CAN 接收函数
我们不涉及 CAN 协议,因此只讨论 CAN 接收函数应该如何实现,下面是一个 CAN 接收函数的例子:
static INT __canRecv (__CAN_CHAN *pchannel)
{
UINT32 uiValue;
CAN_FRAME canframe;
lib_memset(&canframe, 0, sizeof(CAN_FRAME));
canframe.CAN_uiChannel = pchannel->CAHCH_uiChannel;
uiValue = CAN_READ(pchannel, RXFIFO_ID);
canframe.CAN_uiId = uiValue;
canframe.CAN_bExtId = LW_TRUE;
uiValue = CAN_READ(pchannel, RXFIFO_DLC);
canframe.CAN_ucLen = uiValue >> DLCR_DLC_OFFSET;
uiValue = CAN_READ(pchannel, RXFIFO_DATA1);
canframe.CAN_ucData[0] = uiValue >> 24;
canframe.CAN_ucData[1] = uiValue >> 16;
canframe.CAN_ucData[2] = uiValue >> 8;
canframe.CAN_ucData[3] = uiValue;
uiValue = CAN_READ(pchannel, RXFIFO_DATA2);
canframe.CAN_ucData[4] = uiValue >> 24;
canframe.CAN_ucData[5] = uiValue >> 16;
canframe.CAN_ucData[6] = uiValue >> 8;
canframe.CAN_ucData[7] = uiValue;
if (pchannel->CANCH_pcbPutRcv) {
if (pchannel->CANCH_pcbPutRcv(pchannel->CANCH_pvPutRcvArg, &canframe) != ERROR_NONE) {
pchannel->CANCH_pcbSetBusState(pchannel->CANCH_pvSetBusStateArg, CAN_DEV_BUS_RXBUFF_OVERRUN);
}
}
return (ERROR_NONE);
}
首先读取相关寄存器,填充 CAN_FRAME 结构体,然后调用接收回调函数将接收到的数据拷贝到 CAN 设备库,如果拷贝的时候发生错误说明缓冲区已满,这时候需要调用总线状态设置回调函数来设置总线状态为接收缓冲区溢出。
CAN 帧结构体
CAN 帧结构体封装了 CAN 通信的相关信息,发送和接收都需要使用此结构体,其详细描述如下:
typedef struct {
UINT32 CAN_uiId; /* 标识码 */
UINT32 CAN_uiChannel; /* 通道号 */
BOOL CAN_bExtId; /* 是否是扩展帧 */
BOOL CAN_bRtr; /* 是否是远程帧 */
UCHAR CAN_ucLen; /* 数据长度 */
UCHAR CAN_ucData[CAN_MAX_DATA]; /* 帧数据 */
} CAN_FRAME;
typedef CAN_FRAME *PCAN_FRAME; /* CAN帧指针类型 */
- CAN_uiId :该成员为 CAN 节点标示符,在一个 CAN 总线系统中,每个节点的标示符都是唯一的,大多数应用协议都会将 CAN_uiId 进行再定义,比如将一部分位用来表示设备的数据类型,一部分位表示设备的地址等。
- CAN_uiChannel :该成员不是 CAN 协议规定的,SylixOS 中用该数据表示系统中 CAN 设备的硬件通道编号,实际应用中通常不用处理。
- CAN_bExtId :如果该成员为 FALSE,这表示一个标准帧,CAN_uiId 的低 11 位有效,反之则表示为一个扩展帧,则 CAN_uiId 的低 29 位有效。
- CAN_bRtr :该成员表示是否为一个远程帧,远程帧的作用是让希望获取帧的节点主动向 CAN 系统中的节点请求与该远程帧标示符相同的帧。
- CAN_ucLen :该成员表示当前帧中数据的实际长度。
- CAN_ucData :一个 CAN 帧的最大帧数据长度为 8 字节,该成员存储了实际数据。