sja1000 驱动实现
下面以 sja1000 控制器为例,一步步介绍如何编写一个 CAN 驱动程序,该驱动的源码在“/libsylixos/SylixOS/driver/can/”中作为一个可以使用的例程存在。
在进行设备创建之前,我们需要进行私有数据的初始化以及硬件初始化(含中断绑定等),sja1000 的私有数据结构体如下所示:
注意:
由于 sja1000 是一个通用 CAN 控制的驱动,因此有些函数以及私有结构体成员等是在 bsp 中赋值以及调用的,我们这里重点介绍驱动架构,不讨论 sja1000 控制器细节。
struct sja1000_chan {
CAN_DRV_FUNCS *pDrvFuncs;
LW_SPINLOCK_DEFINE (slock);
INT (*pcbSetBusState)(); /* bus status callback */
INT (*pcbGetTx)(); /* int callback */
INT (*pcbPutRcv)();
PVOID pvSetBusStateArg;
PVOID pvGetTxArg;
PVOID pvPutRcvArg;
unsigned long canmode; /* BAIS_CAN or PELI_CAN */
unsigned long baud;
SJA1000_FILTER filter;
/*
* user MUST set following members before calling this module api.
*/
unsigned long channel;
void (*setreg)(SJA1000_CHAN *, int reg, UINT8 val);
/* set register value */
UINT8 (*getreg)(SJA1000_CHAN *, int reg);
/* get register value */
/*
* you MUST clean cpu interrupt pending bit in reset()
*/
void (*reset)(SJA1000_CHAN *); /* hardware reset sja1000 */
void (*open)(SJA1000_CHAN *);
void (*close)(SJA1000_CHAN *);
void *priv; /* user can use this save some thing */
};
我们仅讨论标准架构相关的结构体,其中有六个成员是 CAN 设备库需要使用的,因此驱动必须提供,分别是 pcbSetBusState、pcbGetTx、pcbPutRcv、pvSetBusStateArg、pvGetTxArg 和 pvPutRcvArg。前三个成员是三个回调函数,它们的第一个参数分别对应后三个成员,至于其使用方法我们将在下文中做详细介绍。
为了注册到 CAN 设备库,我们需要提供一个持续存在的 CAN_CHAN 结构体,全局变量是一个很好的选择,且可以多次复用。正如所说的那样,sja1000 中的实现如下:
/***************************************************************************
sja1000 driver functions
***************************************************************************/
static CAN_DRV_FUNCS sja1000_drv_funcs = {
(INT (*)())sja1000Ioctl,
(INT (*)())sja1000TxStartup,
(INT (*)())sja1000CallbackInstall
};
sja1000 的驱动通过 sja1000Init 函数创建了一个 CAN_CHAN(SJA1000_CHAN),该通道包含了相应的驱动数据和驱动方法(可根据实际情况不包含这些数据而只包含一个 CAN_DRV_FUNCS 类型的结构),以下是创建 CAN_CHAN 的一种方法:
/***************************************************************************
** 函数名称: sja1000Init
** 功能描述: 初始化 SJA1000 驱动程序
***************************************************************************/
INT sja1000Init (SJA1000_CHAN *pcanchan)
{
if (pcanchan->channel == 0) {
_ErrorHandle(ENOSYS);
return (PX_ERROR);
}
LW_SPIN_INIT(&pcanchan->slock);
pcanchan->pDrvFuncs = &sja1000_drv_funcs;
pcanchan->channel = 0;
pcanchan->canmode = PELI_CAN;
pcanchan->baud = BTR_500K; /* default baudrate */
pcanchan->filter.acr_code = 0xFFFFFFFF;
pcanchan->filter.amr_code = 0xFFFFFFFF;
pcanchan->filter.mode = 1; /* single filter */
return (ERROR_NONE);
}
创建完成的 CAN_CHAN(SJA1000_CHAN)作为第二个参数传递给函数 API_CanDevCreate 即可完成注册,调用方法如下:
API_CanDevCreate("sja1000_can", (CAN_CHAN *)pcanchan, 256, 256);
下面通过实际的代码来分析一下 sja1000 驱动的实现关键点。
首先介绍回调安装函数即 sja1000CallbackInstall,该函数实现了上述三个回调函数及其参数的注册,在 sja1000 中详细实现如下:
/***************************************************************************
** 函数名称: sja1000CallbackInstall
** 功能描述: SJA1000 安装回调
***************************************************************************/
static INT sja1000CallbackInstall (SJA1000_CHAN *pcanchan,
INT callbackType,
INT (*callback)(),
VOID *callbackArg)
{
switch (callbackType) {
case CAN_CALLBACK_GET_TX_DATA:
pcanchan->pcbGetTx = callback;
pcanchan->pvGetTxArg = callbackArg;
return (ERROR_NONE);
case CAN_CALLBACK_PUT_RCV_DATA:
pcanchan->pcbPutRcv = callback;
pcanchan->pvPutRcvArg = callbackArg;
return (ERROR_NONE);
case CAN_CALLBACK_PUT_BUS_STATE:
pcanchan->pcbSetBusState = callback;
pcanchan->pvSetBusStateArg = callbackArg;
return (ERROR_NONE);
default:
_ErrorHandle(ENOSYS);
return (PX_ERROR);
}
}
实际上在其他 CAN 设备驱动中的实现也基本无改动,在这里该函数第一个参数为 sja1000 私有结构体的原因是其第一个成员即为 CAN_CHAN 。
然后我们来介绍下 sja1000 的设备驱动 I/O 控制函数 sja1000Ioctl,其实现如下:
/***************************************************************************
** 函数名称: sja1000Ioctl
** 功能描述: SJA1000 控制
***************************************************************************/
static INT sja1000Ioctl (SJA1000_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 800000:
pcanchan->baud = (ULONG)BTR_800K;
break;
case 700000:
pcanchan->baud = (ULONG)BTR_700K;
break;
case 600000:
pcanchan->baud = (ULONG)BTR_600K;
break;
case 666000:
pcanchan->baud = (ULONG)BTR_666K;
break;
case 500000:
pcanchan->baud = (ULONG)BTR_500K;
break;
case 400000:
pcanchan->baud = (ULONG)BTR_400K;
break;
case 250000:
pcanchan->baud = (ULONG)BTR_250K;
break;
case 200000:
pcanchan->baud = (ULONG)BTR_200K;
break;
case 125000:
pcanchan->baud = (ULONG)BTR_125K;
break;
case 100000:
pcanchan->baud = (ULONG)BTR_100K;
break;
case 80000:
pcanchan->baud = (ULONG)BTR_80K;
break;
case 50000:
pcanchan->baud = (ULONG)BTR_50K;
break;
case 40000:
pcanchan->baud = (ULONG)BTR_40K;
break;
case 30000:
pcanchan->baud = (ULONG)BTR_30K;
break;
case 20000:
pcanchan->baud = (ULONG)BTR_20K;
break;
case 10000:
pcanchan->baud = (ULONG)BTR_10K;
break;
case 5000:
pcanchan->baud = (ULONG)BTR_5K;
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:
case CAN_DEV_STARTUP:
pcanchan->reset(pcanchan);
pcanchan->pcbSetBusState(pcanchan->pvSetBusStateArg,
CAN_DEV_BUS_ERROR_NONE);
intreg = KN_INT_DISABLE();
sja1000InitChip(pcanchan);
sja1000SetMode(pcanchan, MOD_RM, 0); /* goto normal mode */
KN_INT_ENABLE(intreg);
/*
* if have data in send queue, start transmit
*/
sja1000TxStartup(pcanchan);
break;
default:
errno = ENOSYS;
return (PX_ERROR);
}
return (ERROR_NONE);
}
该函数已经在“CAN I/O 控制函数”章节介绍过,这里仍然提醒下需要注意 default 分支返回值,此外都是与控制器相关的操作,没有需要额外留意的地方。
我们再来说下启动发送函数,在 sja1000 中是 sja1000TxStartup 函数,其具体实现如下:
/***************************************************************************
** 函数名称: sja1000TxStartup
** 功能描述: SJA1000 芯片启动发送
***************************************************************************/
static INT sja1000TxStartup (SJA1000_CHAN *pcanchan)
{
INT i;
SJA1000_FRAME frame;
CAN_FRAME canframe;
if (pcanchan->pcbGetTx(pcanchan->pvGetTxArg, &canframe) == ERROR_NONE) {
frame.id = canframe.CAN_uiId;
frame.frame_info = canframe.CAN_bExtId << 7;
frame.frame_info |= (canframe.CAN_bRtr << 6);
frame.frame_info |= (canframe.CAN_ucLen & 0x0f);
if (!canframe.CAN_bRtr) {
for (i = 0; i < canframe.CAN_ucLen; i++) {
frame.data[i] = canframe.CAN_ucData[i];
}
}
sja1000Send(pcanchan, &frame);
}
return (ERROR_NONE);
}
在这个实现方法中,使用了在启动发送函数中直接发送的方法。在这里为了方便移植将 CAN_FRAME 结构体拷贝到了控制器私有结构体 SJA1000_FRAME 中,然后使用 sja1000 的发送函数立即发送。
最后我们来说下控制器 sja1000 的接收函数,它是在中断处理函数中实现的,该中断处理函数实现如下:
/***************************************************************************
** 函数名称: sja1000Isr
** 功能描述: SJA1000 中断处理
***************************************************************************/
VOID sja1000Isr (SJA1000_CHAN *pcanchan)
{
int i;
volatile UINT8 ir, temp;
SJA1000_FRAME frame;
CAN_FRAME canframe;
ir = GET_REG(pcanchan, IR);
if (ir & IR_BEI) { /* bus error int */
pcanchan->pcbSetBusState(pcanchan->pvSetBusStateArg, CAN_DEV_BUS_OFF);
return;
}
if (ir & IR_RI) { /* recv int */
while (1) {
temp = GET_REG(pcanchan, SR);
if (temp & 0x01) {
sja1000Recv(pcanchan, &frame);
if (frame.frame_info & 0x80) {
canframe.CAN_bExtId = LW_TRUE;
} else {
canframe.CAN_bExtId = LW_FALSE;
}
if (frame.frame_info & 0x40) {
canframe.CAN_bRtr = LW_TRUE;
} else {
canframe.CAN_bRtr = LW_FALSE;
}
canframe.CAN_ucLen = frame.frame_info & 0x0f;
canframe.CAN_uiId = frame.id;
if (!canframe.CAN_bRtr) {
for (i = 0; i < canframe.CAN_ucLen; i++) {
canframe.CAN_ucData[i] = frame.data[i];
}
}
if (pcanchan->pcbPutRcv(pcanchan->pvPutRcvArg, &canframe)) {
pcanchan->pcbSetBusState(pcanchan->pvSetBusStateArg,
CAN_DEV_BUS_RXBUFF_OVERRUN);
}
} else {
break;
}
}
}
if (ir & IR_TI) { /* send int */
if (pcanchan->pcbGetTx(pcanchan->pvGetTxArg, &canframe) == ERROR_NONE) {
frame.id = canframe.CAN_uiId;
frame.frame_info = canframe.CAN_bExtId << 7;
frame.frame_info |= (canframe.CAN_bRtr << 6);
frame.frame_info |= (canframe.CAN_ucLen & 0x0f);
if (!canframe.CAN_bRtr) {
for (i = 0; i < canframe.CAN_ucLen; i++) {
frame.data[i] = canframe.CAN_ucData[i];
}
}
sja1000Send(pcanchan, &frame);
}
}
if (ir & IR_DOI) { /* data overflow int */
COMMAND_SET(pcanchan, CMR_CDO);
pcanchan->pcbSetBusState(pcanchan->pvSetBusStateArg, CAN_DEV_BUS_OVERRUN);
}
}
发生中断后判断中断为接收中断,调用接收函数填充 sja1000 私有帧结构体,然后转化为 SylixOS 标准帧,调用接收回调函数将接收到的数据拷贝给 CAN 设备库,此时若发生错误说明接收缓冲区满,需要调用设置总线状态回调函数设置总线状态为接收缓冲区溢出。
在中断中若硬件控制器发生接收缓冲区溢出,则需要设置总线状态为接收溢出,若硬件控制器报总线错误,则需要设置总线状态为总线关闭。