基于MCP2515的Linux CAN总线驱动程序设计(三)

转自http://blog.csdn.net/leesheen/article/details/8775736

1. 前言

        上篇文章介绍了使用SPI子系统设计的基于MCP2515的Linux CAN总线驱动程序,这篇文章主要介绍MCP2515的字符设备驱动功能函数的实现。

2. 硬件设计

        MCP2515与S3C2416的硬件连接图如图3所示。如硬件原理图可知MCP2515芯片连接在S3C2416芯片的SPI0上,中断接在GPF1上;MCP2515输出连接SN65HVD230 CAN总线收发器,SN65HVD230是德州仪器公司生产的3.3V CAN收发器。为了节省功耗,缩小电路体积,MCP2515 CAN总线控制器的逻辑电平采用LVTTL,SN65HVD230就是与其配套的收发器。


图1 MCP2515硬件连接图
 

3. MCP2515功能函数的实现


3.1 MCP2515设备结构体

        首先需要定义MCP2515的设备结构体,包含MCP2515驱动所需要的相关属性。

[cpp]   view plain  copy
  1. struct mcp2515_chip {  
  2.     canid_t own_id;                  // CAN ID  
  3.     canid_t broadcast_id;            // Broadcase ID  
  4.     CanBandRate bandrate;            // 波特率  
  5.   
  6.     struct cdev cdev;                 // 字符设备结构体  
  7.     struct spi_device *spi;          // SPI 设备结构体  
  8.     struct class *class;             // Class类  
  9.   
  10.     struct work_struct irq_work;    // 工作队列  
  11.   
  12.     uint32_t count;                 // CAN 设备计数  
  13.   
  14.     uint8_t *spi_transfer_buf;      // SPI传输缓冲区  
  15.   
  16.       /* SPI输入输出缓冲区 */  
  17.     struct can_frame spi_tx_buf[MCP2515_BUF_LEN];  
  18.     struct can_frame spi_rx_buf[MCP2515_BUF_LEN];  
  19.   
  20.     uint32_t rxbin;                 // 接收报文计数  
  21.     uint32_t rxbout;                // 读报文计数  
  22.     uint32_t txbin;                 // 发送报文计数  
  23.     uint32_t txbout;                // 发送报文计数  
  24.   
  25.     wait_queue_head_t rwq;          // 读报文等待队列头  
  26. };  

        在整个驱动中我们都需要使用这个设备结构体,所以在驱动注册成功后在probe函数中要创建这个设备结构体,并且对这个结构体相应的初始化。

[cpp]   view plain  copy
  1. static int __devinit mcp2515_probe(struct spi_device *spi)  
  2. {  
  3.     struct mcp2515_chip *chip;  
  4.     int ret;  
  5.     /* 为设备结构体申请空间 */  
  6.     chip = kmalloc(sizeof(struct mcp2515_chip), GFP_KERNEL);  
  7.     if (!chip) {  
  8.         ret = -ENOMEM;  
  9.         goto error_alloc;  
  10.     }  
  11.       
  12.       /* 初始化设备结构体 */  
  13.     dev_set_drvdata(&spi->dev, chip);  
  14.     ……  
  15.     /* 初始化工作队列 */  
  16.     INIT_WORK(&chip->irq_work, mcp2515_irq_handler);  
  17.       /* 申请中断 */  
  18.     ret = request_irq(IRQ_EINT(1), mcp2515_irq,   
  19.                       IRQF_DISABLED | IRQF_TRIGGER_FALLING, DEVICE_NAME, spi);  
  20.     if (ret < 0) {  
  21.         printk("MCP2515: Request_irq() Error!\n");  
  22.         goto error_irq;  
  23.     }  
  24.       /* 初始化等待队列头 */  
  25.     init_waitqueue_head(&chip->rwq);  
  26.     /* 注册设备 */  
  27.     ……  
  28.    printk ("MCP2515: MCP2515 Can Device Driver.\n");  
  29. }  

        使用dev_set_drvdata函数把spi与设备结构体chip关联起来,在只有spi参数传入的情况下,可以使用dev_get_drvdata获取设备结构体chip。

 3.2 MCP2515中断函数

        根据芯片手册可知,MCP2515有8个中断源,当中断发生时,INT引脚将被MCP2515拉低为低电平,并且保持低电平状态直至MCU清除中断。

        由原理图可知MCP2515芯片外部中断接到S3C2416的外部中断1口上,所以在probe时使用request_irq申请外部中断1的中断。我们可以通过判断中断标志位的方式来接收数据。然而我们使用request_irq函数注册的中断实际是中断上半部,在Linux中把中断分为两个部分,在上半部不能有中断发生,尽可能使上半部处理少的工作。在MCP2515中断函数中,我们要判断中断标志,和接收数据,所以需要下半部机制。下面引自《Linux设备驱动开发详解》第10章第2节,详细介绍了中断的半部机制。

        设备中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务尽可能的短小精悍。但是,在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。
        在Linux内核中,为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux将中断处理程序分为两个部分:上半部(top half)和下半部(bottom half)。中断处理程序的上半部在接收到一个中断时就立即执行,但只做比较紧急的工作,这些工作都是在所有中断被禁止的情况下完成的,所以要快,否则其它的中断就得不到及时的处理。那些耗时又不紧急的工作被推迟到下半部去。中断处理程序的下半部分(如果有的话)几乎做了中断处理程序所有的事情。它们最大的不同是上半部分不可中断,而下半部分可中断。在理想的情况下,最好是中断处理程序上半部分将所有工作都交给下半部分执行,这样的话在中断处理程序上半部分中完成的工作就很少,也就能尽可能快地返回。但是,中断处理程序上半部分一定要完成一些工作,例如,通过操作硬件对中断的到达进行确认,还有一些从硬件拷贝数据等对时间比较敏感的工作。剩下的其他工作都可由下半部分执行。
        对于上半部分和下半部分之间的划分没有严格的规则,靠驱动程序开发人员自己的编程习惯来划分,不过还是有一些习惯供参考:

     * 如果该任务对时间比较敏感,将其放在上半部中执行。
     * 如果该任务和硬件相关,一般放在上半部中执行。
     * 如果该任务要保证不被其他中断打断,放在上半部中执行(因为这是系统关中断)。
     * 其他不太紧急的任务, 一般考虑在下半部执行。
        下半部分并不需要指明一个确切时间,只要把这些任务推迟一点,让它们在系统不太忙并且中断恢复后执行就可以了。通常下半部分在中断处理程序一返回就会马上运行。内核中实现下半部的手段不断演化,目前已经从最原始的BH(bottom half)衍生出BH(在2.5中去除)、软中断(softirq在2.3引入)、tasklet(在2.3引入)、工作队列(work queue在2.5引入)。稍后笔者将介绍后两种方式。
        尽管上半部和下半部的结合能够改善系统的响应能力,但是,Linux设备驱动中的中断处理并不一定要分成两个半部。如果中断要处理的工作本身就很少,则完全可以直接在上半部全部完成。

        所以我们只需要在中断函数中把当前任务加入到工作队列中,使用下半部机制来完成中断判断和数据的读取。

[cpp]   view plain  copy
  1. static irqreturn_t mcp2515_irq(int irq, void *dev_id)  
  2. {  
  3.    struct spi_device *spi = dev_id;  
  4.    struct mcp2515_chip *chip = dev_get_drvdata(&spi->dev);  
  5.   
  6.    schedule_work(&chip->irq_work);  
  7.   
  8.    return IRQ_HANDLED;  
  9. }  

3.3 MCP2515中断下半部

        要使用下半部机制,首先要建立一个工作队列,这个工作队列在设备结构体中已经创建,然后就可以在probe函数中初始化这个工作队列。

[cpp]   view plain  copy
  1. INIT_WORK(&chip->irq_work, mcp2515_irq_handler);  
        这样,我们就可以在下半部中来判断中断类型,做出相应的处理。
[cpp]   view plain  copy
  1. static void mcp2515_irq_handler(struct work_struct *work)  
  2. {  
  3.     struct mcp2515_chip *chip = container_of(work, struct mcp2515_chip, irq_work);  
  4.     struct spi_device *spi = chip->spi;  
  5.     int intf;  
  6.   
  7.     while (1) {  
  8.   
  9.         intf = mcp2515_read_reg(spi, CANINTF);  
  10.         if (!intf)  
  11.             break;  
  12.   
  13.         //if (intf & CANINTF_WAKIF)  
  14.         if (intf & CANINTF_MERRF)  
  15.             mcp2515_hw_reset(spi);  
  16.         if (intf & CANINTF_ERRIF)  
  17.             mcp2515_write_reg(spi, EFLG, 0x00);  
  18.         if (intf & CANINTF_TX2IF)  
  19.             mcp2515_tx(spi, 2);  
  20.         if (intf & CANINTF_TX1IF)  
  21.             mcp2515_tx(spi, 1);  
  22.         if (intf & CANINTF_TX0IF)  
  23.             mcp2515_tx(spi, 0);  
  24.         if (intf & CANINTF_RX1IF)  
  25.             mcp2515_rx(spi, 1);  
  26.         if (intf & CANINTF_RX0IF)  
  27.             mcp2515_rx(spi, 0);  
  28.   
  29.         mcp2515_write_bits(spi, CANINTF, intf, 0x00);  
  30.   
  31.         if (chip->rxbin != chip->rxbout)  
  32.             wake_up_interruptible(&chip->rwq);  
  33.     }  
  34. }  

        在下半部中,需要根据不同的中断类型来做出不同的处理,这里对除了唤醒中断之外的所有中断进行了相应的处理。后面会结合发送和接收函数详细介绍上面的代码。

3.4 MCP2515设置

        根据芯片手册,MCP2515支持CAN2.0B技术规范所定义的标准数据帧、扩展数据帧和远程帧,详细帧格式如图2、图3和图4所示。


图2 CAN标准数据帧


图3 CAN扩展数据帧


图4 CAN远程帧

        三种不同的帧格式分别有不同的工作场合,但扩展帧因为有更多的地址位,所以应用场合比较广泛,下面详细介绍了如何设置MCP2515 ID的程序。

[cpp]   view plain  copy
  1. static long mcp2515_unlocked_ioctl (struct file *filp,   
  2.                                     unsigned int cmd, unsigned long arg)  
  3. {  
  4.     struct mcp2515_chip *chip = filp->private_data;  
  5.     struct spi_device *spi = chip->spi;  
  6.     int ret = 0;  
  7.   
  8.     switch(cmd) {  
  9.     case CAN_Set_Own_ID:  
  10.         mcp2515_set_id(spi, (canid_t)arg, Own_ID);  
  11.         break;  
  12.     case CAN_Set_Broadcast_ID:  
  13.         mcp2515_set_id(spi, (canid_t)arg, Broadcast_ID);  
  14.         break;  
  15.     case CAN_Set_Bandrate:  
  16.         ret = mcp2515_set_bandrate(spi, arg);  
  17.         break;  
  18.         
  19.     ......  
  20.         
  21.     default:  
  22.         break;  
  23.     }  
  24.     return ret;  
  25. }  

        MCP2515可以设置滤波器,以便有选择的接受总线上的数据,来减少MCU的工作工作负荷。根据芯片手册可以查到MCP2515相关的滤波器,本驱动在设置本地ID后会自动装载到屏蔽滤波器。

[cpp]   view plain  copy
  1. static void mcp2515_set_id(struct spi_device *spi, canid_t id, int8_t flag)  
  2. {  
  3.     struct mcp2515_chip *chip = dev_get_drvdata(&spi->dev);  
  4.   
  5.     if (flag == Own_ID) {  
  6.         mcp2515_write_can_id(spi, (uint32_t)&chip->own_id, id, 1, 1);  
  7.         mcp2515_write_can_id(spi, RXFSIDH(0), id, 1, 0);  
  8.         mcp2515_write_can_id(spi, RXFSIDH(3), id, 1, 0);  
  9.         mcp2515_write_can_id(spi, RXFSIDH(4), id, 1, 0);  
  10.         mcp2515_write_can_id(spi, RXFSIDH(5), id, 1, 0);  
  11.     }  
  12.   
  13.     if (flag == Broadcast_ID) {  
  14.         mcp2515_write_can_id(spi, (uint32_t)&chip->broadcast_id, id, 1, 1);  
  15.         mcp2515_write_can_id(spi, RXFSIDH(1), id, 1, 0);  
  16.         mcp2515_write_can_id(spi, RXFSIDH(2), id, 1, 0);  
  17.     }  
  18. }  

3.5 MCP2515发送报文

        MCP2515报文发送有相应的缓冲机制,它共有3个报文发送缓冲器,驱动接收到报文后首先要装载到发送缓冲器中,当检测到报文装载完毕后,驱动会执行报文发送指令,这时报文才会发送到总线上。下图是SPI装载缓冲器的时序。


图5 MCP2515装载缓存器SPI时序图

        所以我们首先要检测发送缓冲器的状态,当检测到发送缓冲器为空(没有准备发送的数据)时,才会将报文装载到相应的发送缓冲器中,否则会检测其他的缓冲器,如果所有缓冲器都为忙,则返回发送报文失败。

[cpp]   view plain  copy
  1. static ssize_t mcp2515_write (struct file *filp, const char __user *buf,  
  2.                               size_t count, loff_t *lof)  
  3. {  
  4.     struct mcp2515_chip *chip = filp->private_data;  
  5.     struct spi_device *spi = chip->spi;  
  6.     struct can_frame *frame;  
  7.     int txreq;  
  8.     int nbytes = 0;  
  9.     int ret;  
  10.       
  11.     if (count != sizeof(struct can_frame))  
  12.         return -EINVAL;  
  13.   
  14.     frame = &chip->spi_tx_buf[chip->txbin];  
  15.   
  16.     ret = copy_from_user(frame, buf, sizeof(struct can_frame));  
  17.     if (0 != ret)  
  18.         return -EFAULT;  
  19.   
  20.     chip->txbin++;  
  21.     if (chip->txbin >= MCP2515_BUF_LEN)  
  22.         chip->txbin = 0;  
  23.   
  24.     nbytes = frame->can_dlc & 0x0F;  
  25.           
  26.     txreq = mcp2515_read_state(spi, INSTRUCTION_CAN_STATE);  
  27.   
  28.     if (!(txreq & CAN_STATE_TX0REQ))  
  29.         mcp2515_tx(spi, 0);  
  30.   
  31.     if (!(txreq & CAN_STATE_TX1REQ))  
  32.         mcp2515_tx(spi, 1);  
  33.   
  34.     if (!(txreq & CAN_STATE_TX2REQ))  
  35.         mcp2515_tx(spi, 2);  
  36.   
  37.     return nbytes;  
  38. }  

        报文缓冲器准备完毕,就可以执行请求发送的SPI时序,相应的SPI时序如下图所示。


图6 MCP2515请求发送SPI时序图

3.6 MCP2515接收报文

        此驱动的报文接收使用的是阻塞的方式,报文接收实际上是在中断中完成的。当总线收到报文数据后,会把报文保存到接收缓冲器中。当我们在程序中调用read函数时,才会把报文从缓冲器中读出来,传到用户空间,如果缓冲器没有报文数据时,函数会阻塞,直到有缓冲器中有报文或者用户结束程序。

        当中断中接收到报文数据,装载到接受缓冲器后,会唤醒等待队列头,如果此时等待队列正在睡眠,则睡眠会被唤醒,来接收数据。

[cpp]   view plain  copy
  1. static ssize_t mcp2515_read(struct file *filp,   
  2.                             char __user *buf, size_t count, loff_t * lof)  
  3. {  
  4.     struct mcp2515_chip *chip = filp->private_data;  
  5.     int nbytes = 0;  
  6.     int ret;  
  7.     struct can_frame *frame;  
  8.   
  9.     if(count != sizeof(struct can_frame))  
  10.         return -EINVAL;  
  11.   
  12.     while (chip->rxbin == chip->rxbout) {  
  13.   
  14.         if (filp->f_flags & O_NONBLOCK)  
  15.             return -EAGAIN;  
  16.         if (wait_event_interruptible(chip->rwq, (chip->rxbin != chip->rxbout)))  
  17.             return -ERESTARTSYS;  
  18.     }  
  19.   
  20.     frame = &chip->spi_rx_buf[chip->rxbout];  
  21.   
  22.     ret = copy_to_user(buf, frame, sizeof(struct can_frame));  
  23.    if (0 != ret)  
  24.         return -EFAULT;  
  25.   
  26.     chip->rxbout++;  
  27.     if (chip->rxbout >= MCP2515_BUF_LEN)  
  28.         chip->rxbout = 0;  
  29.   
  30.     nbytes = frame->can_dlc & 0x0F;  
  31.   
  32.     return nbytes;  
  33. }  

        read函数被唤醒后,要把报文从接收缓冲器中读出来,MCP2515提供了读缓冲器的SPI时序,如下图所示。



图7 MCP2515读缓冲区SPI时序图

4. 测试


4.1 硬件连接

        环境使用FS2416开发板,根据原理图可知“H”和“L”为CAN总线引脚,连接两个开发板的CAN总线。


图8 CAN总线测试连接图

4.2 插入模块驱动

        板子启动后,加载编译好的MCP2515驱动程序。

[plain]   view plain  copy
  1. $ insmod mcp2515.ko  


图9 模块插入成功

4.3 测试程序

① 接收端运行测试程序;

[plain]   view plain  copy
  1. $ ./CANtest -r  


图10 选择波特率

② 选择通讯的波特率,然后等待发送端数据;



图11 接收端等待数据

③ 发送端加载驱动,并且运行测试程序;

[plain]   view plain  copy
  1. $ insmod mcp2515.ko  
  2. $ ./CANtest –s  


图12 发送端选择波特率

④ 选择波特率(相互通讯收发端应该使用相同的波特率);



图13 准备发送数据

⑤ 发送端输入数据;



图14 发送端发送数据

⑥ 接收端接收数据;


图15 接收端接收到数据

5. 总结

        至此,使用SPI子系统注册MCP2515 CAN总线驱动详细设计就介绍完了。因为使用了SPI子系统的方式,由SPI子系统屏蔽了不同平台的差异,在拿到一个新平台后,我们只需要让其SPI正常工作,我们就可以把之前挂在SPI上的设备上设备的驱动拿到新平台上,相应的设备驱动在新平台上不需要改一行代码就可以正常工作了。这种做法充分体现了Linux分层的思想,这也是子系统设计的初衷。



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于CAN总线MCP2515和TJA1050温度测量系统是一种使用CAN总线通信的温度测量系统。MCP2515是一种控制器局部网络(CAN)控制器,能够处理CAN总线上的通信任务,而TJA1050是一种CAN收发器,用于接收和发送CAN总线上的信号。 在这个系统中,利用温度传感器测量温度,并将测得的温度数据传输到CAN总线上。MCP2515控制器接收到温度数据后,将其封装成CAN帧,并通过TJA1050收发器发送CAN总线上。其他设备或系统可以通过CAN总线接收到这些温度数据,进行进一步的处理和分析。 基于CAN总线MCP2515和TJA1050温度测量系统具有以下优点: 1. 可以实现多节点通信:CAN总线支持多节点连接,可以将多个温度传感器通过CAN总线连接起来,实现多点测量和通信。 2. 高可靠性:CAN总线具有高抗干扰性和高可靠性,能够在工业环境中稳定运行,保证温度数据的准确性和可靠性。 3. 实时性:CAN总线通信速率较快,可以实现高速的温度数据传输,保证数据的实时性和准确性。 4. 简化系统布线:CAN总线只需要两根数据线和两个端子,相比其他通信协议,系统布线更简单,减少了布线成本和工作量。 总而言之,基于CAN总线MCP2515和TJA1050温度测量系统能够实现多节点温度测量和通信,具有高可靠性和实时性,布线简单,适用于工业环境中的温度监测和控制应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值