zoukankan      html  css  js  c++  java
  • 基于MCP2515的Linux CAN总线驱动程序设计(二)

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

    作者:李老师,华清远见嵌入式学院讲师。

    1.前言

    CAN(Controller Area Network)总线,即控制器局域网总线,是一种有效支持分布式控制或实时控制的串行通信网络。由于其高性能、高可靠性、及独特的设计和适宜的价格而广泛应用于工业现场控制、智能楼宇、医疗器械、交通工具以及传感器等领域,并已被公认为几种最有前途的现场总线之一。CAN总线规范已经被国际标准化组织制订为国际标准ISO11898,并得到了众多半导体器件厂商的支持。

    本文使用华清远见FS2416平台。FS2416使用Socket网络设备驱动字符设备驱动两种方式向Linux内核提供MCP2515的驱动,上篇文章介绍了使用Socket方式设计的基于MCP2515的Linux CAN总线驱动程序,这篇文章主要介绍编写一个MCP2515的字符设备驱动。

    2.FS2416简介


    图1 FS2416开发板

    FS2416采用的是三星公司的ARM926EJ内核CPU S3C2416,无论从性能上,还是成本上, S3C2416都强于2440,是2440的最完美替代者。

    作为32/16 bit RISC指令集、低成本、低功耗、高性能的微处理器。S3C2416使用了65nm的制作工艺从而降低成本、功耗及提高性能,其使用的ARM926EJ的核心,集成了2D图形加速,添加了低功耗模式,支持内部ROM/RAM引导,支持moviNand启动和低功耗音频编解码。此外相对于其他ARM9芯片,它的外设也得到了升级,有更多的资源。


    图2 FS2416板级资源介绍

    3.MCP2515简介

    MCP2515是一种独立的CAN总线通信控制器,是Microchip公司首批独立CAN解决方案的升级器件,其传输能力较Microchip公司原有CAN控制器(MCP2510)高两倍,最高通信速率可达到1Mbps。MCP2515能够接收和发送标准数据帧和扩展数据帧以及远程帧,通过两个接收屏蔽寄存器和六个接收过滤寄存器滤除无关报文,从而减轻CPU负担。

    MCP2515主要功能参数及电气特性如下:
            (1)支持CAN技术规范2.0A/B, 最高传输速率达到1Mbps;
            (2)支持标准数据帧、扩展数据帧和远程帧,每帧数据域长度可为0~8个字节;
            (3)内含两个的接收缓冲器和三个发送缓冲器,并且可编程设定优先级;
            (4)内含六个29位(bit)的接收过滤寄存器和两个29位(bit)的接收屏蔽寄存器;
            (5)高速SPI接口,支持SPI 0,0和1,1模式;
            (6)一次性模式可确保报文被一次性传输;
            (7)具有可编程时钟脉冲输出引脚,可作为其他芯片时钟信号源;
            (8) 帧起始(SOF)信号输出功能可被用于在确定的系统中(如时间触发CAN-TTCAN)执行时隙功能,或在CAN总线诊断中决定早期总线出级;
            (9) 采用低功耗CMOS技术,工作电压:2.7V~5.5V, 工作电流:5mA(待机状态1μA);
            (10)工作温度范围:(I)-40℃到+85℃,(E)-40℃到+125℃。

    4.硬件设计

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


    图3 MCP2515硬件连接图

    5.MCP2515 CAN字符设备驱动的实现

    5.1 SPI子系统简介

    基于子系统去开发驱动程序已经是Linux内核中普遍的做法了。前面介绍使用Socket编写MCP2515 CAN总线的驱动也是基于SPI子系统开发的。在驱动开发前,需要先熟悉下SPI通讯协议中的几个关键的地方,后面在编写驱动时,需要考虑相关因素。

    SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。MCP2515与S3C2416的SPI连接如图3所示。SO脚为它的数据输入脚,SI为数据输出脚,SCK为时钟脚。

    SPI常用四种数据传输模式,主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果CPOL = 0,串行同步时钟的空闲状态为低电平;如果CPOL = 1,串行同步时钟的空闲状态为高电平。如果CPHA = 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。具体的时序如图4所示。

    这四种模式中究竟选择哪种模式取决于设备。如MCP2515芯片手册中说明它可以支持(0,0)(1,1)两种模式,即:CPOL = 0,CPHA = 0和CPOL = 1,CPHA = 1。



    图4 SPI数据传输时序

    5.2 Linux下SPI驱动的开发

    首先明确SPI驱动层次,如图5所示:


    图5 SPI驱动层次

    我们以上面的这个图为思路:

    ① platform bus
            platform bus对应的结构是platform_bus_type,这个内核开始就定义好的。我们不需要定义。

    ② platform_device
            SPI控制器对应platform_device的定义方式,同样以S3C2416中的SPI控制器为例,参看arch/arm/plat-s3c24xx/dev-spi.c文件。

    01 static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
            02
            03 struct platform_device s3c_device_spi0 = {
            04                 .name = "s3c2410-spi",        //名称,要和Platform_driver匹配
            05                .id= 0,         //第0个控制器,S5PC100中有3个控制器
            06                .num_resources= ARRAY_SIZE(s3c_spi0_resource),        //占用资源的种类
            07                .resource= s3c_spi0_resource,         //指向资源结构数组的指针
            08                .dev= {
            09                .dma_mask = &s3c_device_spi0_dmamask,        //dma寻址范围
            10                .coherent_dma_mask = 0xffffffffUL
            11         //可以通过关闭cache等措施保证一致性的dma寻址范围
            12                }
            13         };
            14
            15 EXPORT_SYMBOL(s3c_device_spi0);

    ③ platform_driver
            再看platform_driver,参看drivers/spi/spi_s3c24xx.c文件

    static struct platform_driver s3c24xx_spi_driver = {
            .driver = {
                    .name = "s3c24xx-spi", //名称,和platform_device对应
                    .owner = THIS_MODULE,
            },
            .remove = s3c24xx_spi_remove,
            .suspend = s3c24xx_spi_suspend,
            .resume = s3c24xx_spi_resume,
            };
            MODULE_ALIAS("platform:s3c24xx-spi");
            static int __init s3c24xx_spi_init(void)
            {
                    return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
            }

    和平台中注册的platform_device匹配后,调用s3c24xx_spi_probe。然后根据传入的platform_device参数,构建一个用于描述SPI控制器的结构体spi_master,并注册。spi_register_master(master)。后续注册的spi_device需要选定自己的spi_master,并利用spi_master提供的传输功能传输spi数据。

    和I2C类似,SPI也有一个描述控制器的对象叫spi_master。其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。

    01 struct spi_master {
            struct device dev;
            struct list_head list;
            /* 表示是SPI主机控制器的编号。由平台代码决定. */
            s16        bus_num;
            /* 控制器支持的片选数量,即能支持多少个spi设备 */
            u16        num_chipselect;
            /* some SPI controllers pose alignment requirements on DMAable
            * buffers; let protocol drivers know about these requirements. */
            u16        dma_alignment;
            /* spi_device.mode flags understood by this controller driver */
            u16        mode_bits;
            /* other constraints relevant to this driver */
            u16        flags;
            /* lock and mutex for SPI bus locking */
            spinlock_t        bus_lock_spinlock;
            struct mutex        bus_lock_mutex;
            /* flag indicating that the SPI bus is locked for exclusive use */
            bool        bus_lock_flag;
            /* 针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用 */
            int        (*setup)(struct spi_device *spi);
            /* bidirectional bulk transfers */
            int        (*transfer)(struct spi_device *spi, struct spi_message *mesg);
            /* called on release() to free memory provided by spi_master */
            void        (*cleanup)(struct spi_device *spi);
            };

    这样,我们就可以执行make menuconfig配置内核选项,添加SPI驱动到我们的内核了。

    Device drivers->
            [*]SPI support ->
            <*> Samsung S3C2416 series type SPI

    ④ spi bus
            Spi总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义

    struct bus_type spi_bus_type = {
            .name = "spi",
            .dev_attrs= spi_dev_attrs,
            .match= spi_match_device,
            .uevent= spi_uevent,
            .pm= &spi_pm,
            };
            EXPORT_SYMBOL_GPL(spi_bus_type);

    对应的匹配规则如下:

    static int spi_match_device(struct device *dev,struct device_driver *drv)
            {
            const struct spi_device*spi = to_spi_device(dev);
            const struct spi_driver*sdrv = to_spi_driver(drv);
            /* Attempt an OF style match */
            if (of_driver_match_device(dev, drv))
                    return 1;
            if (sdrv->id_table)
                    return !!spi_match_id(sdrv->id_table, spi);
            return strcmp(spi->modalias, drv->name) == 0;
            }

    ⑤ spi_device

    下面是spi_device的构建与注册。spi_device对应的含义是挂接在spi总线上的一个设备,所以描述它的时候应该明确它自身的设备特性、传输要求、及挂接在哪个总线上。参看arch/arm/mach-s3c2416/mach-smdk2416.c文件。

    根据硬件原理图图3所示,MCP2515挂在SPI0上,使用中断GPF1。

    #define SMDK2416_MCP2515 0
            static struct s3c24xx_spi_csinfo smdk_spi0_csi[] = {
            {
            .set_level = smdk_mmcspi_cs_set_level,
            .fb_delay = 0x3,
            },
            };
            static struct mcp251x_platform_data mcp251x_info = {
                    .oscillator_frequency = 8 * 1000 * 1000, //设置MCP2515外部晶振频率
            };
            static struct spi_board_info s3c_spi_devs[] __initdata = {
                    {
                    .modalias= "mcp2515",        //设备名,和spi_driver对应
                    .mode=SPI_MODE_0,        //CPOL=0, CPHA=0
                    .max_speed_hz=2 * 1000 * 1000,        //最大的spi时钟频率
                    .irq=IRQ_EINT(1),        //外部中断GPF1
                    .bus_num= 0,        //设备连接在spi控制器0上
                    .chip_select= 0,        //片选线号
                    .platform_data= &mcp251x_info,        //平台信息
                    .controller_data = &smdk_spi0_csi[SMDK2416_MCP2515],
                    },
            };

    事实上上文提到的spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_boardinfo扫描board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。

    spi_register_board_info(s3c_spi_devs, ARRAY_SIZE(s3c_spi_devs));

    ⑥ spi_driver

    下面就要介绍SPI驱动的编写了。

    首先构建一个spi_driver;

    static struct spi_driver mcp2515_driver = {
            .driver = {
                    .name = DEVICE_NAME,
                    .bus = &spi_bus_type,
                    .owner = THIS_MODULE,
                    },
            .probe= mcp2515_probe,
            .remove= __devexit_p(mcp2515_remove),
            .suspend= mcp2515_suspend,
            .resume= mcp2515_resume,
            };

    需要调用spi_register_driver函数注册这个spi_driver;

    static int __init mcp2515_can_init(void)
            {
                    return spi_register_driver(&mcp2515_driver);
            }

    在有匹配的spi device时,会调用mcp2515_probe函数;

    static int __devinit mcp2515_probe(struct spi_device *spi)
            {
                    struct mcp2515_chip *chip;
                    int ret;
                    /* 为设备结构体申请空间 */
                    chip = kmalloc(sizeof(struct mcp2515_chip), GFP_KERNEL);
                    if (!chip) {
                            ret = -ENOMEM;
                            goto error_alloc;
                    }
                    /* 初始化设备结构体 */
                    dev_set_drvdata(&spi->dev, chip);
                    ……
                    /* 初始化工作队列 */
                    INIT_WORK(&chip->irq_work, mcp2515_irq_handler);
                    /* 申请中断 */
                    ret = request_irq(IRQ_EINT(1), mcp2515_irq,IRQF_DISABLED | IRQF_TRIGGER_FALLING, DEVICE_NAME, spi);
                    if (ret < 0) {
                            printk("MCP2515: Request_irq() Error! ");
                            goto error_irq;
                    }
                    /* 初始化等待队列头 */
                    init_waitqueue_head(&chip->rwq);
                    /* 注册设备 */
                    ……
                    printk ("MCP2515: MCP2515 Can Device Driver. ");
            }

    根据传入的spi_device参数,可以找到对应的spi_master。当我们加载驱动后如下图显示即说明MCP2515驱动加载成功。


    图6 加载MCP2515驱动

    接下来,我们就可以利用SPI子系统为我们完成数据交互了。

    6.总结

    至此,使用SPI子系统注册MCP2515 CAN总线驱动设计的就介绍完了。设备注册成功之后,我们就可以对CAN总线通讯设计实现函数了。

    下一篇文章将会详细介绍MCP2515功能函数的实现。

  • 相关阅读:
    LeetCode153 Find Minimum in Rotated Sorted Array. LeetCode162 Find Peak Element
    LeetCode208 Implement Trie (Prefix Tree). LeetCode211 Add and Search Word
    LeetCode172 Factorial Trailing Zeroes. LeetCode258 Add Digits. LeetCode268 Missing Number
    LeetCode191 Number of 1 Bits. LeetCode231 Power of Two. LeetCode342 Power of Four
    LeetCode225 Implement Stack using Queues
    LeetCode150 Evaluate Reverse Polish Notation
    LeetCode125 Valid Palindrome
    LeetCode128 Longest Consecutive Sequence
    LeetCode124 Binary Tree Maximum Path Sum
    LeetCode123 Best Time to Buy and Sell Stock III
  • 原文地址:https://www.cnblogs.com/shulianghe/p/3724132.html
Copyright © 2011-2022 走看看