sja1000 驱动实现

更新时间:
2024-01-10
下载文档

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 设备库,此时若发生错误说明接收缓冲区满,需要调用设置总线状态回调函数设置总线状态为接收缓冲区溢出。

在中断中若硬件控制器发生接收缓冲区溢出,则需要设置总线状态为接收溢出,若硬件控制器报总线错误,则需要设置总线状态为总线关闭。

文档内容是否对您有所帮助?
有帮助
没帮助