Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1357343
  • 博文数量: 281
  • 博客积分: 8800
  • 博客等级: 中将
  • 技术积分: 3346
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-17 22:31
文章分类

全部博文(281)

文章存档

2013年(1)

2012年(18)

2011年(16)

2010年(44)

2009年(86)

2008年(41)

2007年(10)

2006年(65)

我的朋友

分类: LINUX

2009-02-07 17:12:53

Linux 2.6下SPI设备模型
 
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200. SPI总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。外围设置FLASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。SPI总线系统可直接与各个厂家生产的多种标准外围器件直接接口,该接口一般使用4条线:串行时钟线(SCK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOST和低电平有效的从机选择线SS(有的 SPI接口芯片带有中断信号线INT或INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI).
 

SPI总线协议

SPISerial Peripheral Interface),顾名思义,就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI总线是一种高速,全双工,同步的通信总线,只占用四根线就可完成基本的数据通信,既节约了芯片的管脚,也给PCB的布局布线提供了方便。正是出于这种简单易用的特点,越来越多的芯片集成了这种通信协议。

SPI的通信原理很简单,以主从方式工作,一个主设备可以控制一个或多个从设备。主从设备之间的通信至少需要4根线,包括MISO(主输入从输出数据传输线),MOSI(主输出从输入数据传输线),SCK(时钟),CS(片选)。

1)      MISO -主设备数据输入端/从设备数据输出端

2)      MOSI -主设备数据输出端/从设备数据输入端

3)      SCK  -时钟信号,由主设备提供给从设备

4)      CS   -从设备使能信号,由主设备控制

当主设备与多个从设备连接时,主设备通过寻址选通当前要访问的从设备。被选中的从设备的使能信号CS被置为有效状态,其他从设备的使能信号被置为无效状态,从而实现一点对多点的通信。

因为SPI是串行通信协议,也就是说数据是一位一位的传输,这就需要SCK时钟信号提供时钟脉冲,数据传输线基于此脉冲完成数据的传输。输出数据在时钟的上升沿(或下降沿)改变,在紧接着的下降沿(或上升沿)被读取,完成一位数据的输出,输入同理。这样,要完成8位数据的传输则至少需要SCK8个时钟周期。

要注意的是,SCK信号线只由主设备控制,从设备不能控制此信号线。因此,在一个基于SPI的设备中,至少有一个主设备。当没有时钟跳变时,从设备不采集或传送数据,这样,主设备就可以在数据传输期间通过对SCK时钟信号的控制一位一位的传输数据甚至暂停数据的传输。

SPI协议举例

SPI通信的过程实际上就是主从设备各自的移位寄存器之间的数据交换。如图所示。

    假设主设备的8位移位寄存器中放的是待发送的数据01010101,上升沿发送,下降沿接收,高位先发送,(从设备的8位移位寄存器中放的数是不确定的,假设为10111010)。那么第一个上升沿到来时,主设备将移位寄存器中的最高位数据发送出去,主设备数据输出端MOSI=0;同时从设备也将其移位寄存器的最高位数据发送出去,从设备数据输出端MISO=1,这时主设备移位寄存器中的数据变为1010101x。下降沿到来时,MOSI上的数据被锁存到从设备的寄存器中,这时从设备的移位寄存器=01110100,同时MISO上的数据被锁存到主设备的寄存器中。也就是说,当主设备向从设备发送一位数据的同时,从设备也会从移位寄存器中回送一位数据。这样在8个脉冲以后,两个寄存器的内容互换一次,从而完成了一段SPI传输。表1详细描述了主设备在向从设备发送8位数据的过程中移位寄存器中数据的变化。

1

脉冲

主设备

移位寄存器

从设备

移位寄存器

主设备

待发送数据

从设备

回送数据

0

01010101

10111010

0

1

1(上升沿)

1010101x

0111010x

0

1

(下降沿)

10101011

01110100

2(上升沿)

0101011x

1110100x

1

0

(下降沿)

01010110

11101001

3(上升沿)

1010110x

1101001x

0

1

(下降沿)

10101101

11010010

4(上升沿)

0101101x

1010010x

1

1

(下降沿)

01011011

10100101

5(上升沿)

1011011x

0100101x

0

1

(下降沿)

10110111

01001010

6(上升沿)

0110111x

1001010x

1

0

(下降沿)

01101110

10010101

7(上升沿)

1101110x

0010101x

0

1

(下降沿)

11011101

00101010

8(上升沿)

1011101x

0101010x

1

0

(下降沿)

10111010

01010101

 

SPI总线工作方式

因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备实现方式不同,如图所示。

由图可知,SPI总线共有四种工作方式,主要是数据改变和采集的时间不同,在时钟上升沿和下降沿采集有不同定义。这里涉及到两个参数,串行同步时钟极性(CPOL)和时钟相位(CPHA)。如果CPOL=0,串行同步时钟(SCK)的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降沿)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降沿)数据被采样。时钟极性和相位配置正确后,数据才能够准确的发送和接收。例如从设备在时钟的上升沿接收数据,那么主设备的SPI时钟极性应该配置为下降沿有效。

SPI Flash存储器概述

SPI Flash(串行Flash)是通过SPI总线接口进行连续数据传输的小尺寸,低功耗的flash 存储器。串行flash SPI Flash)比并行flashPPI Flash)占用更少的线从系统中传送数据。对于引脚数目少的串行flash来讲,其优势是减少了系统板的空间,功耗和成本。

串行Flash的主要特征有:

1)      小尺寸

2)      操作电压:单个2.7-3.6V 或者 3.0-3.6V 读和写操作

3)      4线SPI串行接口结构

4)      连续读操作一个字节的特征

5)      低功耗

6)      灵活的擦除能力(Sector/Block/Chip-Erase能力)

7)      快速擦除( Sector-Erase或者Block-Erase: 18ms(典型)

8)      字节编程: 14us(典型)

9)      通过WP#引脚的硬件写保护

 

 

SPI的典型应用中,通信的双方一个是主(Master),一个是从(slave)。区别是由主设备提供通信时钟信号SCK给从设备,此外主设备还需要提供一个引脚来驱动Slave的片选信号CS。 主从设备的SO和SI是交叉连接的, 主的SO是数据输出口要接在从设备的SI上,反之依然。根据这样的设计, 能做SPI的设备往往是单片机, ARM芯片或者更强一些的CPU什么,而flash, 网络芯片或者声音A/D 和D/A转换芯片就扮演从设备的角色。主设备提供片选信号来选中从设备和连续时钟信号来驱动双方设备的读写过程。

由于从设备往往都是厂家设计好的, 主要的用户工作是如何在主设备上把从设备驱动起来。 这是把我搞的灰头土脸的地方。以我要驱动Flash为例, 根据看的文档,我直觉上知道应该把片选信号先拉低(选中flash),然后在SO上发出控制指令,最后等数据到来。结果我的程序是这样写的:
1、初始化SPI控制器,包括波特率设置
2、驱动CS为低电平选中flash
3、发送控制命令
4、接受数据

结果我收到的数据只有一个字节,内容为0. 忙了一个早上还是这个结果,搞的我极其郁闷,严重怀疑自己的RP,然后开始怀疑单片机是不是坏的,flash是不是坏的。一圈下来继续怀疑RP。 最后实在郁闷,就扛来示波器测波形。 因为这个玩也不熟悉,因此不敢轻易动,弄坏了把自己卖了才赔的起了(10G的哦)。结果发现430单片机的片选CS信号正常,在数据发送的时候SO口的确有波形输出,说明输出是对的。 但是。。。。为什么SCK没有连续时钟信号输出 ???? 我立刻理直气壮认为 单片机烧了,告诉师兄, 结果师兄暴汗.... :SPI主设备如果不连续输出数据,就不维护时钟了。顿时觉得自己长的好白阿。 正确的做法应该在主设备上送完指令后不停的送无用数据让spi控制器继续输出时钟并且读取发回来的数据。由于SPI控制器是同步读取数据的,因此我在发送的同时也读取数据,因此我送出去一个字节的数据,所以读回来一个数据,当然这个数据是无用的。

知道问题了,午饭后我把程序改成中断模式的, 所有的数据发送和接受全部采用中断。 发送寄存器一空 430就会发一个发送就绪中断,我在中断程序中把数组中的命令字发出去,等发完之后就一直发0x00,维护时钟,直到发送出去的字节数等于期望收到的数据量。另外一方面当数据收到后430就触发接收中断得到数据。 中断程序把数据读出来扔到接收数组里面。 等发送完后要检查是不是移位寄存器为空,防止还有数据没出去, 等空了就拉高片选信号断开flash. 完成这些后,检查接收数组,可以看到如果发送命令为n字节,则前面n字节的数据都是废的,所以要从n+1的位置来找收到的数据。 圆满大结局。(其实后面还碰到了波特率不对,结果丢数据的问题,但是很快解决了加上现在写的手酸,就略过拉)

 

基于AT91RM9200分析
       Atmel公司的ARM AT系列,其SPI驱动在kernel 2.6.23里已经包含。如果你打了at91-patch补丁的话,则在内核配置时要小心。在Device Drivers---- > Character devices ---- >取消选中SPI Driver(legacy) for at91rm9200 processor 。同时Device Drivers---- >SPI Support ---- > 选中SPI Support ,Atmel SPI Controler,同时选中 User mode SPI device driver support 。
SPI Driver(legacy) for at91rm9200 processor是保留选项,为了兼容以前版本。如果同时选中SPI Driver(legacy) for at91rm9200 processor,则在/sys里无法注册类spidev,也就无法将设备和驱动联系在一起。与现有atmel spi驱动发生冲突。

各选项对应的编译情况如下:
      
  • SPI support ---- Config_SPI  开启SPI功能
          
  • Debug support for SPI drivers ---- config SPI_DEBUG   开启SPI debug调试
           ----SPI Master Controller Drivers ---- depends on SPI_MASTER  生成spi.o
           Atmel SPI Controller ---- config SPI_ATMEL 生成atmel_spi.o
           Bitbanging SPI master ---- config SPI_BITBANG 生成spi_bitbang.o
           AT91RM9200 Bitbang SPI Master  ---- CONFIG_SPI_AT91  spi_at91_bitbang.o
           ---- SPI Protocol Masters ---- depends on SPI_MASTER
          SPI EEPROMs from most vendors ---- config SPI_AT25 生成at25.o
           User mode SPI device driver support ---- config SPI_SPIDEV 生成spidev.o
    总线
    注册SPI总线
    #spi.c
           struct bus_type spi_bus_type = {
           .name             = "spi",   // spi总线名称
           .dev_attrs       = spi_dev_attrs,
           .match           = spi_match_device,
           .uevent           = spi_uevent,
           .suspend  = spi_suspend,
           .resume          = spi_resume,
    };
    spi总线将在sysfs/bus下显示。
    其bus_type 结构表示总线,它的定义在中,如下
    struct bus_type {
           const char             * name;
           struct module         * owner;

           struct kset             subsys;
           struct kset             drivers;
           struct kset             devices;
           struct klist             klist_devices;
           struct klist             klist_drivers;

           struct blocking_notifier_head bus_notifier;

           struct bus_attribute * bus_attrs;
           struct device_attribute    * dev_attrs;
           struct driver_attribute    * drv_attrs;
           struct bus_attribute drivers_autoprobe_attr;
           struct bus_attribute drivers_probe_attr;

           int           (*match)(struct device * dev, struct device_driver * drv);
           int           (*uevent)(struct device *dev, char **envp,
                                  int num_envp, char *buffer, int buffer_size);
           int           (*probe)(struct device * dev);
           int           (*remove)(struct device * dev);
           void        (*shutdown)(struct device * dev);

           int (*suspend)(struct device * dev, pm_message_t state);
           int (*suspend_late)(struct device * dev, pm_message_t state);
           int (*resume_early)(struct device * dev);
           int (*resume)(struct device * dev);

           unsigned int drivers_autoprobe:1;
    };
    其中,当一个总线上的新设备或者新驱动被添加时,*match 函数会被调用。如果指定的驱动程序能够处理指定的设备,该函数返回非零值。
    对于spi总线,我们必须调用bus_register(&spi_bus_type)进行注册。调用如果成功,SPI总线子系统将被添加到系统中,在sysfs的/sys/bus目录下可以看到。然后,我们就可以向这个总线添加设备了。代码见下:
    static int __init spi_init(void)
    {
           int    status;

           buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
           if (!buf) {
                  status = -ENOMEM;
                  goto err0;
           }

           status = bus_register(&spi_bus_type);
           if (status
                  goto err1;

           status = class_register(&spi_master_class);
           if (status
                  goto err2;
           return 0;

    err2:
           bus_unregister(&spi_bus_type);
    err1:
           kfree(buf);
           buf = NULL;
    err0:
           return status;
    }

    设备
    spi设备的结构如下:
    #spi.h
    struct spi_device {
           struct device          dev;
           struct spi_master    *master;
           u32                max_speed_hz;
           u8                  chip_select;
           u8                  mode;
    #define    SPI_CPHA     0x01                     /* clock phase */
    #define    SPI_CPOL     0x02                     /* clock polarity */
    #define    SPI_MODE_0 (0|0)                     /* (original MicroWire) */
    #define    SPI_MODE_1 (0|SPI_CPHA)
    #define    SPI_MODE_2 (SPI_CPOL|0)
    #define    SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
    #define    SPI_CS_HIGH       0x04                     /* chipselect active high? */
    #define    SPI_LSB_FIRST    0x08                     /* per-word bits-on-wire */
    #define    SPI_3WIRE    0x10                     /* SI/SO signals shared */
    #define    SPI_LOOP     0x20                     /* loopback mode */
           u8                  bits_per_word;
           int                  irq;
           void               *controller_state;
           void               *controller_data;
           const char             *modalias;

           /*
            * likely need more hooks for more protocol options affecting how
            * the controller talks to each chip, like:
            *  - memory packing (12 bit samples into low bits, others zeroed)
            *  - priority
            *  - drop chipselect after each word
            *  - chipselect delays
            *  - ...
            */
    };
    device结构中包含了设备模型核心用来模拟系统的信息。spidev还有设备的其他信息,因此spi设备结构包含在spidev_data结构里。
    struct spidev_data {
           struct device          dev;
           struct spi_device    *spi;
           struct list_head       device_entry;

           struct mutex          buf_lock;
           unsigned         users;
           u8                  *buffer;
    };
    注册spi设备,
    #spidev.c
    static int spidev_probe(struct spi_device *spi)
    {
           …
           …
    status = device_register(&spidev->dev);


    }
    完成这个调用之后,我们就可以在sysfs中看到它了。

    SPI设备驱动程序
    spi驱动程序结构如下:
    struct spi_driver {
           int                  (*probe)(struct spi_device *spi);
           int                  (*remove)(struct spi_device *spi);
           void               (*shutdown)(struct spi_device *spi);
           int                  (*suspend)(struct spi_device *spi, pm_message_t mesg);
           int                  (*resume)(struct spi_device *spi);
           struct device_driver      driver;
    };
    spi驱动程序注册函数如下:
    int spi_register_driver(struct spi_driver *sdrv)
    {
           sdrv->driver.bus = &spi_bus_type;
           if (sdrv->probe)
                  sdrv->driver.probe = spi_drv_probe;
           if (sdrv->remove)
                  sdrv->driver.remove = spi_drv_remove;
           if (sdrv->shutdown)
                  sdrv->driver.shutdown = spi_drv_shutdown;
           return driver_register(&sdrv->driver);
    }
    spidev的驱动名如下:
    static struct spi_driver spidev_spi = {
           .driver = {
                  .name =          "spidev",
                  .owner = THIS_MODULE,
           },
           .probe =  spidev_probe,
           .remove =       __devexit_p(spidev_remove),
    };
    一个spi_register_driver调用将spidev添加到系统中。一旦初始化完成,就可以在sysfs中看到驱动程序信息。

    spidev类结构如下:
    static struct class spidev_class = {
           .name             = "spidev",
           .owner           = THIS_MODULE,
           .dev_release    = spidev_classdev_release,
    };

    AT91RM9200 SPIDEV初始化
    AT91RM9200的spi驱动,对于EK板,原先的SPI是用于dataflash的。其代码如下:
    static struct spi_board_info ek_spi_devices[] = {
           {     /* DataFlash chip */
                  .modalias = "mtd_dataflash",
                  .chip_select    = 0,
                  .max_speed_hz      = 15 * 1000 * 1000,
           },
    我们需要将.modalias改成我们自己的spi设备名
    在spi设备初始化代码中,class_register(&spidev_class)注册类,spi_register_driver(&spidev_spi)注册spidev驱动。
    #drivers/spi/spidev.c
    static int __init spidev_init(void)
    {
           int status;

           /* Claim our 256 reserved device numbers.  Then register a class
            * that will key udev/mdev to add/remove /dev nodes.  Last, register
            * the driver which manages those device numbers.
            */
           BUILD_BUG_ON(N_SPI_MINORS > 256);
           status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
           if (status
                  return status;

           status = class_register(&spidev_class);
           if (status
                  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
                  return status;
           }

           status = spi_register_driver(&spidev_spi);
           if (status
                  class_unregister(&spidev_class);
                  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
           }
           return status;
    }

    挂载/sys
    mount –t sysfs sysfs /sys
    可以看到有/sys/class/spidev/spidev0.0,表明设备已经挂载在总线上了,同时与驱动联系起来。
    使用mdev –s,可以在/dev下看到spidev0.0这个设备了。
    自此,spi设备驱动就可以工作了。

    测试程序:



    #include stdio.h>
    #include unistd.h>
    #include stdlib.h>
    #include fcntl.h>
    #include string.h>

    #include sys/ioctl.h>
    #include sys/types.h>
    #include sys/stat.h>

    #include linux/types.h>
    #include linux/spi/spidev.h>


    static int verbose;

    static void do_read(int fd, int len)
    {
           unsigned char buf[32], *bp;
           int status;

           /* read at least 2 bytes, no more than 32 */
           if (len  2)
                  len = 2;
           else if (len > sizeof(buf))
                  len = sizeof(buf);
           memset(buf, 0, sizeof buf);

           status = read(fd, buf, len);
           if (status  0) {
                  perror("read");
                  return;
           }
           if (status != len) {
                  fprintf(stderr, "short read\n");
                  return;
           }

           printf("read(%2d, %2d): %02x %02x,", len, status,
                  buf[0], buf[1]);
           status -= 2;
           bp = buf + 2;
           while (status-- > 0)
                  printf(" %02x", *bp++);
           printf("\n");
    }

    static void do_msg(int fd, int len)
    {
           struct spi_ioc_transfer xfer[2];
           unsigned char buf[32], *bp;
           int status;

           memset(xfer, 0, sizeof xfer);
           memset(buf, 0, sizeof buf);

           if (len > sizeof buf)
                  len = sizeof buf;

           buf[0] = 0xaa;
           xfer[0].tx_buf = (__u64) buf;
           xfer[0].len = 1;

           xfer[1].rx_buf = (__u64) buf;
           xfer[1].len = len;

           status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
           if (status  0) {
                  perror("SPI_IOC_MESSAGE");
                  return;
           }

           printf("response(%2d, %2d): ", len, status);
           for (bp = buf; len; len--)
                  printf(" %02x", *bp++);
           printf("\n");
    }

    static void dumpstat(const char *name, int fd)
    {
           __u8 mode, lsb, bits;
           __u32 speed;

           if (ioctl(fd, SPI_IOC_RD_MODE, &mode)  0) {
                  perror("SPI rd_mode");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb)  0) {
                  perror("SPI rd_lsb_fist");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits)  0) {
                  perror("SPI bits_per_word");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed)  0) {
                  perror("SPI max_speed_hz");
                  return;
           }

           printf("%s: spi mode %d, %d bits %sper word, %d Hz max\n",
                  name, mode, bits, lsb ? "(lsb first) " : "", speed);
    }

    int main(int argc, char **argv)
    {
           int c;
           int readcount = 0;
           int msglen = 0;
           int fd;
           const char *name;

           while ((c = getopt(argc, argv, "hm:r:v")) != EOF) {
                  switch (c) {
                  case 'm':
                         msglen = atoi(optarg);
                         if (msglen  0)
                                goto usage;
                         continue;
                  case 'r':
                         readcount = atoi(optarg);
                         if (readcount  0)
                                goto usage;
                         continue;
                  case 'v':
                         verbose++;
                         continue;
                  case 'h':
                  case '?':
    usage:
                         fprintf(stderr,
                                "usage: %s [-h] [-m N] [-r N] /dev/spidevB.D\n",
                                argv[0]);
                         return 1;
                  }
           }

           if ((optind + 1) != argc)
                  goto usage;
           name = argv[optind];

           fd = open(name, O_RDWR);
           if (fd  0) {
                  perror("open");
                  return 1;
           }

           dumpstat(name, fd);

           if (msglen)
                  do_msg(fd, msglen);

           if (readcount)
                  do_read(fd, readcount);

           close(fd);
           return 0;
    }


    备注:
    如果要设置模式,速率等,则可仿照以下语句:
    speed      =10*1000*1000; //10MHz
    if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)
                  perror("SPI max_speed_hz");
                  return;
           }
    默认spi_io_transfer时,每个字节之间有延时。在atmel_spi_setup.c文件里去掉该延时语句:
                  /* TODO: DLYBS and DLYBCT */
           //csr |= SPI_BF(DLYBS, 10);
           //csr |= SPI_BF(DLYBCT, 10);
    这样就可以达到无间隙快速传输批量数据。
    标准read(),write()两个函数仅适用于半双工传输,。在传输之间不激活片选。而SPI_IOC_MESSAGE(N)则是全双工传输,并且片选始终激活。
    SPI_IOC_MESSAGE传输长度有限制,默认是一页的长度,但是可以更改。
    spi_ioc_transfer结构的spi长度 是字节长度,16位传输的时候要注意。


    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/77715/showart_1154474.html
  • 阅读(2108) | 评论(0) | 转发(1) |
    给主人留下些什么吧!~~