自己DIY一个智能家居模型框架--spi驱动

Linux的spi驱动是这个项目一个关键点,就靠spi与stm32MCU进行数据传输来操作stm32产生相应动作。

这篇主要是介绍spi驱动的基本实现过程,并没有详细解释源码。

Linux SPI驱动总体架构
      在2.6的linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。
      Linux 中SPI驱动代码位于drivers/spi目录。
 
SPI核心层
      SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
Linux中,SPI核心层的代码位于driver/spi/ spi.c
SPI控制器驱动层
      SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
      在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述。
主设备对应SOC芯片中的SPI控制器,通常,一个SOC中可能存在多个SPI控制器,每个控制器下可以连接多个SPI从设备,每个从设备有各自独立的CS引脚。每个从设备共享另外3个信号引脚:SCK、MISO、MOSI。任何时刻,只有一个CS引脚处于有效状态,与该有效CS引脚连接的设备此时可以与主设备(SPI控制器)通信,其它的从设备处于等待状态,并且它们的3个引脚必须处于高阻状态。


因此SPI驱动分为两类:

控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。这部分通常会由特定的开发板提供商提供,不用自己写。

协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。这部分涉及具体的SPI从设备,通常需要自己编写。


分析一下注册spi驱动:

1、 Platform bus

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

2、Platform_device

SPI控制器对应platform_device的定义方式,参看arch/arm/mach-exynos/Dev-spi.c文件,需要在平台文件下注册spi设备信息,其中就会包含使用的是哪一路spi引脚。我的开发板spi都定义在三星写的平台文件中arch/arm/mach-exynos/mach-itop4412.c中

static struct spi_board_info spi2_board_info[] __initdata = {
#if CONFIG_SPI_SPIDEV
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>.modalias = "spidev",
<span style="white-space:pre">		</span>.platform_data = NULL,
<span style="white-space:pre">		</span>.max_speed_hz = 10*1000*1000,
<span style="white-space:pre">		</span>.bus_num = 2,
<span style="white-space:pre">		</span>.chip_select = 0,
<span style="white-space:pre">		</span>.mode = SPI_MODE_0,
<span style="white-space:pre">		</span>.controller_data = &spi2_csi[0],
<span style="white-space:pre">	</span>}
#endif
这里定义了基本信息:

<pre name="code" class="plain" style="widows: 1;">.controller_data = &spi2_csi[0],

 中spi2_csi如下 
 
static struct s3c64xx_spi_csinfo spi2_csi[] = {
<span style="white-space:pre">	</span>[0] = {
<span style="white-space:pre">		</span>.line       = EXYNOS4_GPC1(2),
<span style="white-space:pre">		</span>.set_level  = gpio_set_value,
<span style="white-space:pre">		</span>.fb_delay   = 0x1,
<span style="white-space:pre">	</span>},
};
可以看到SPI2片选引脚为GPC1_2


3、Platform_driver

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

最后这一项就完全是仿照Linux带的参考历程稍微修改就可以用了。就比如讯为将spi设备改名为RC522,然后就将Linux带的驱动中所有的spidev改名为RC522,就可以用了(如果想要源码评论发邮箱)。


最后附上我用的测试程序:

1,先将MISO和MOSI进行短接,自发自接来测试spi通信是否正常,因为ioctl操作Linux带的spi驱动是全双工通信,也就是说接受与发送数据同时进行。

2,源码(从网上找的,改下设备节点就能用

#include <stdint.h>  
#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <getopt.h>  
#include <fcntl.h>  
#include <sys/ioctl.h>  
#include <linux/types.h>  
#include <linux/spi/spidev.h>  
  
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))  
  
static void pabort(const char *s)  
{  
    perror(s);  
    abort();  
}  
  
static const char *device = "/dev/rc522";  
static uint8_t mode;  
static uint8_t bits = 8;  
static uint32_t speed = 700000;  
static uint16_t delay;  
  
static void transfer(int fd)  
{  
    int ret;  
    uint8_t tx[] = {    //要发送的数据数组  
        100,100,100,100,100,100
    };  
    uint8_t rx[ARRAY_SIZE(tx)] = {0, }; //接收的数据数据  
    struct spi_ioc_transfer tr = {  //声明并初始化spi_ioc_transfer结构体  
        .tx_buf = (unsigned long)tx,  
        .rx_buf = (unsigned long)rx,  
        .len = ARRAY_SIZE(tx),  
        .delay_usecs = delay,  
        .speed_hz = speed,  
        .bits_per_word = bits,  
    };  
    //SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量  
    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);   //ioctl默认操作,传输数据  
    if (ret < 1)  
        pabort("can't send spi message");  
  
    for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { //打印接收缓冲区  
        if (!(ret % 6))     //6个数据为一簇打印  
            puts("");  
        printf("%.2X ", rx[ret]);  
    }  
    puts("");  
}  
  
static void print_usage(const char *prog)   //参数错误则打印帮助信息  
{  
    printf("Usage: %s [-DsbdlHOLC3]\n", prog);  
    puts("  -D --device   device to use (default /dev/spidev1.1)\n"  
         "  -s --speed    max speed (Hz)\n"  
         "  -d --delay    delay (usec)\n"  
         "  -b --bpw      bits per word \n"  
         "  -l --loop     loopback\n"  
         "  -H --cpha     clock phase\n"  
         "  -O --cpol     clock polarity\n"  
         "  -L --lsb      least significant bit first\n"  
         "  -C --cs-high  chip select active high\n"  
         "  -3 --3wire    SI/SO signals shared\n");  
    exit(1);  
}  
  
static void parse_opts(int argc, char *argv[])  
{  
    while (1) {  
        static const struct option lopts[] = {  //参数命令表  
            { "device",  1, 0, 'D' },  
            { "speed",   1, 0, 's' },  
            { "delay",   1, 0, 'd' },  
            { "bpw",     1, 0, 'b' },  
            { "loop",    0, 0, 'l' },  
            { "cpha",    0, 0, 'H' },  
            { "cpol",    0, 0, 'O' },  
            { "lsb",     0, 0, 'L' },  
            { "cs-high", 0, 0, 'C' },  
            { "3wire",   0, 0, '3' },  
            { "no-cs",   0, 0, 'N' },  
            { "ready",   0, 0, 'R' },  
            { NULL, 0, 0, 0 },  
        };  
        int c;  
  
        c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);  
  
        if (c == -1)  
            break;  
  
        switch (c) {  
        case 'D':   //设备名  
            device = optarg;  
            break;  
        case 's':   //速率  
            speed = atoi(optarg);  
            break;  
        case 'd':   //延时时间  
            delay = atoi(optarg);  
            break;  
        case 'b':   //每字含多少位  
            bits = atoi(optarg);  
            break;  
        case 'l':   //回送模式  
            mode |= SPI_LOOP;  
            break;  
        case 'H':   //时钟相位  
            mode |= SPI_CPHA;  
            break;  
        case 'O':   //时钟极性  
            mode |= SPI_CPOL;  
            break;  
        case 'L':   //lsb 最低有效位  
            mode |= SPI_LSB_FIRST;  
            break;  
        case 'C':   //片选高电平  
            mode |= SPI_CS_HIGH;  
            break;  
        case '3':   //3线传输模式  
            mode |= SPI_3WIRE;  
            break;  
        case 'N':   //没片选  
          //  mode |= SPI_NO_CS;  
            break;  
        case 'R':   //从机拉低电平停止数据传输  
         //   mode |= SPI_READY;  
            break;  
        default:    //错误的参数  
            print_usage(argv[0]);  
            break;  
        }  
    }  
}  
  
int main(int argc, char *argv[])  
{  
    int ret = 0;  
    int fd;  
  
    parse_opts(argc, argv); //解析传递进来的参数  
  
    fd = open(device, O_RDWR);  //打开设备文件  
    if (fd < 0)  
        pabort("can't open device");  
  
    /* 
     * spi mode //设置spi设备模式 
     */  
    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);    //写模式  
    if (ret == -1)  
        pabort("can't set spi mode");  
  
    ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);    //读模式  
    if (ret == -1)  
        pabort("can't get spi mode");  
  
    /* 
     * bits per word    //设置每个字含多少位 
     */  
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);   //写 每个字含多少位  
    if (ret == -1)  
        pabort("can't set bits per word");  
  
    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);   //读 每个字含多少位  
    if (ret == -1)  
        pabort("can't get bits per word");  
  
    /* 
     * max speed hz     //设置速率 
     */  
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);   //写速率  
    if (ret == -1)  
        pabort("can't set max speed hz");  
  
    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);   //读速率  
    if (ret == -1)  
        pabort("can't get max speed hz");  
    //打印模式,每字多少位和速率信息  
    printf("spi mode: %d\n", mode);  
    printf("bits per word: %d\n", bits);  
    printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);  
  
    transfer(fd);   //传输测试  
  
    close(fd);  //关闭设备  
  
    return ret;  
}  

3,通过Linux交叉编译链编译,传到开发板中运行。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值