Linux驱动开发 - spi子系统(5) 应用层spi的读写操作

目录

前言

设备树

SPI 应用层读写操作

内核中如何实现


前言

上一篇介绍了Linux内核层spi的读写操作,本文主要介绍Linux应用层spi的读写操作。

设备树

&spi0 {
	status = "okay";
	#address-cells=<1>;
	#size-cells=<0>;
	pinctrl-names = "default";
	pinctrl-0 = <&spi0_pins_0>;

	spidev0:spi0@0 {
		compatible = "spi-mt65xx-dev";//与驱动文件spidev.c中的compatible匹配
		reg = <0>;
		spi-max-frequency = <10000000>;
	};

	smi230:spi0@1 {
		compatible = "SMI230GYRO";
		reg = <1>;                   //每个子节点的reg值不能一样
		spi-max-frequency = <10000000>;
	};
};


&spi1 {
	status = "okay";
	#address-cells=<1>;
	#size-cells=<0>;
	pinctrl-names = "default";
	pinctrl-0 = <&spi1_pins_1>;

	spidev1:spi1@0 {
		compatible = "spi-mt65xx-dev";//与驱动文件spidev.c中的compatible匹配
		reg = <0>;
		spi-max-frequency = <10000000>;
	};
};

&spi2 {
	status = "okay";
	#address-cells=<1>;
	#size-cells=<0>;
	pinctrl-names = "default";
	pinctrl-0 = <&spi2_pins_2>;

	spidev2:spi2@0 {
		compatible = "spi-mt65xx-dev";//与驱动文件spidev.c中的compatible匹配
		reg = <0>;
		spi-max-frequency = <10000000>;
	};
};

 spi0增加了子节点spidev0,spi1增加了子节点spidev1,spi2增加了子节点spidev2。设备树中为什么是compatible = "spi-mt65xx-dev"呢,因为在文件drivers/spi/spidev.c中,也添加了scompatible = "spi-mt65xx-dev"(如下代码段),所以设备树和spidev.c中的compatible的值匹配,然后进入probe函数,最终在应用层生成设备文件(/dev/spidev0.0、/dev/spidev1.0、/dev/spidev2.0)。

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "rohm,dh2228fv" },
	{ .compatible = "lineartechnology,ltc2488" },
	{ .compatible = "ge,achc" },
	{ .compatible = "semtech,sx1301" },
	{ .compatible = "lwn,bk4" },
	{ .compatible = "dh,dhcom-board" },
	{ .compatible = "menlo,m53cpld" },
	{ .compatible = "spi-mt65xx-dev" },//与设备树中的compatible匹配
	{},
};

SPI 应用层读写操作

spi0、spi1、spi2在应用层对应的设备文件分别为/dev/spidev0.0、/dev/spidev1.0、/dev/spidev2.0,然后我们就可以通过open、write、read、ioctl、close操作这些设备文件,从而实现spi的读写操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/spi/spidev.h>

typedef enum
{
	SPIMODE0  =   SPI_MODE_0,
	SPIMODE1  =   SPI_MODE_1,
	SPIMODE2  =   SPI_MODE_2,
	SPIMODE3  =   SPI_MODE_3,
}SPI_MODE;

typedef enum
{
	S_6_75M  	=   6750000,
	S_13_5M  	=   13500000,
	S_27M  		=   27000000,
}SPI_SPEED;

int fd_spi = -1;

static int spi_init(void)
{
    SPI_MODE mode;
    char spi_bits;
    SPI_SPEED spi_speed;
    int ret;

    if(fd_spi > 0)
    {
        printf("spi is already init");
        return -1;
    }

    fd_spi = open("/dev/spidev0.0", O_RDWR);  //open spi设备/dev/spidev0.0
    if (fd_spi < 0)
    {
        printf("can't open spi dev %s, fd_spi = %d\n", dev_name, fd_spi);
        return -1;  
    }

    /*
    * spi mode
    */
    mode = SPIMODE3;
    ret = ioctl(fd_spi, SPI_IOC_WR_MODE32, &mode);//设置spi的模式
    if (ret == -1)
    {
        perror("can't set spi mode");
        close(fd_spi);
        return ret;
    }
    /*
     * bits per word
     */
    spi_bits = 8;
    ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);//设置spi的字长
    if (ret == -1)
    {
        perror("can't set bits per word");
        close(fd_spi);
        return ret;
    }

    /*
    * speed
    */
    spi_speed = (uint32_t)S_13_5M;
    ret = ioctl(fd_spi, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);//设置spi的最大传输速率
    if (ret == -1)
    {
        perror("can't set max speed hz");
        close(fd_spi);
        return ret;
    }
    printf("spi mode:0x%x\n", mode);
    printf("bits per word: %d\n", spi_bits);
    printf("max speed    : %d Hz (%d KHz)\n", spi_speed, spi_speed/1000);
    printf("Successfully to init i2c %s, fd_i2c = %d\n", dev_name, fd_spi);
    return fd_spi;
}

static int spi_uninit(void)
{
    int ret;

    ret = close(fd_spi);//关闭spi设备
    if (0 != ret)
    {
        printf("Failed to uninit i2c, ret = %d\n", ret);
    }      
    else
    {
        fd_spi = -1;
        printf("Successfully to uninit i2c, ret = %d\n", ret); 
    }
        
    return 0;
}


static void spi_read(void)
{
    int ret;
	int i;
    int len = 1024;
	char  readbuf[1024] = {0};

    ret = read(fd_spi, readbuf, len);//spi读操作
    if (ret < 0)
    {
        perror("can't read spi message");
        return ret;
    }

    for (i = 0; i<1024; i++) {
		if (!(i % 32))
		puts("");
		printf("%.2X ", readbuf[i]);
	}
	puts("");

    return 0;
}


static void spi_write(void)
{
    int ret;
	int i;
    int len = 1024;
	char  writebuf[1024];

    for(i = 0 ;i < 1024;i++)
    {
        writebuf[i] = i%256;
    }
    
    ret = write(fd_spi, writebuf, len);//spi写操作
    if (ret < 0)
    {
        perror("can't write spi message");
        return ret;
    }

    return 0;
}

static void spi_write_read(void)//spi边写边读,全双工通信
{
    int ret;
	int i;
    int len = 1024;
	char  writebuf[1024];
	char  readbuf[1024] = {0};

    for(i = 0 ;i < 1024;i++)
    {
        writebuf[i] = i%256;
    }
    
    struct spi_ioc_transfer tr = {      //构建结构体变量
    .tx_buf = (unsigned long)writebuf,  //发送缓存
    .rx_buf = (unsigned long)readbuf,   //接收缓存
    .len = len,                         //读写长度
    .delay_usecs = 0,
    .speed_hz = 10000000,              //spi最大传输速率
    .bits_per_word = 8,                //spi字长
    };
    ret = ioctl(fd_spi, SPI_IOC_MESSAGE(1), &tr);//结构体变量传到内核,实现spi的边读边写
    if (ret < 0)
    {
        perror("can't send spi message");
        return ret;
    }

    for (i = 0; i<1024; i++) {
		if (!(i % 32))
		puts("");
		printf("%.2X ", readbuf[i]);  //打印读到的spi数据
	}
	puts("");


    return 0;
}

应用层操作SPI是不是很简单,通过调用open、write、read、ioctl、close就实现了。我们继续深入到内核,看是怎么实现的。

内核中如何实现

这里只简单分析一下函数调用关系,后续专门介绍drivers/spi/spidev.c

spi_write()   //应用层

         -->write()   //应用层

                 -->spidev_write()   //内核

                        -->spidev_sync_write()  //内核

spidev_sync_write(struct spidev_data *spidev, size_t len)

{

    struct spi_transfer t = {

            .tx_buf     = spidev->tx_buffer,

            .len        = len,

            .speed_hz   = spidev->speed_hz,

        };

    struct spi_message  m;

    spi_message_init(&m);

    spi_message_add_tail(&t, &m);

    return spidev_sync(spidev, &m);

}

spi_read()  //应用层

        -->read()   //应用层

                -->spidev_read()   //内核

                        -->spidev_sync_read()   //内核

spidev_sync_read(struct spidev_data *spidev, size_t len)

{

    struct spi_transfer t = {

            .rx_buf     = spidev->rx_buffer,

            .len        = len,

            .speed_hz   = spidev->speed_hz,

        };

    struct spi_message  m;

    spi_message_init(&m);

    spi_message_add_tail(&t, &m);

    return spidev_sync(spidev, &m);

}

 

spi_write_read()    //应用层

        -->iioctl(fd_spi, SPI_IOC_MESSAGE(1), &tr)  //应用层

                -->spidev_ioctl()   //内核

                        -->spidev_message()   //内核

static int spidev_message(struct spidev_data *spidev, struct spi_ioc_transfer *u_xfers, unsigned n_xfers)

{

        ......

    spi_message_init(&msg);

        ......

    spi_message_add_tail(k_tmp, &msg);

        ......

    status = spidev_sync(spidev, &msg);

}

可以看出,应用层的read、write、ioctl实现的spi读写操作,最终在内核中都是通过调用spidev_sync实现的,而spidev_sync又是通过调用spi_sync实现的。分析到这里,是不是和上一篇spi设备驱动介绍的一样了呢。

总结

应用层操作spi的读写比较简单,具体怎么实现spi的读写操作,还得看深入内核研究,做到知其然,并知其所以然,否则遇到问题的时候不知如何定位问题。

  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核版本2.6是一个较早的版本,对于SPI总线的支持相对较为简单。SPI(Serial Peripheral Interface)是一种同步的全双工串行通信总线,可用于连接微控制器与外围设备,例如传感器、存储器等。 在Linux 2.6版本中,对于SPI总线的驱动代码主要涉及以下几个方面: 1. 初始化SPI硬件:SPI总线的初始化包括选择SPI设备、配置SPI寄存器、设置传输速率、设置SPI模式等。这通常在驱动模块的probe函数中完成。 2. 定义SPI设备:在驱动代码中,需要定义SPI设备的一些基本信息,如设备名称、ID、SPI模式、位宽等。这些信息在驱动模块中进行定义,并通过spi_register_driver函数进行注册。 3. 数据传输:SPI驱动代码需要实现与SPI设备的数据传输功能,包括发送数据和接收数据。对于发送数据,驱动代码通过spi_write函数将数据发送给SPI设备;对于接收数据,驱动代码通过spi_read函数从SPI设备接收数据。 4. 中断处理:SPI驱动代码可以通过中断处理函数来处理SPI设备发生的中断事件,以便实时响应设备的数据传输情况。中断处理函数通常与硬件平台相关,需要根据具体的硬件平台进行编写。 5. 设备节点的创建和注册:驱动代码需要创建设备节点,并通过sysfs接口将设备节点注册到/sys/class/spi目录下,以提供给用户空间进行访问和控制。 需要注意的是,以上是对于Linux 2.6版本中SPI驱动代码大致的介绍,实际的驱动代码会根据具体的硬件平台和需求进行适配和实现。对于较新的Linux内核版本,SPI驱动代码的实现可能会有所不同,更加灵活和强大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值