单片机
返回首页

【IMX6ULL学习笔记】二十一、SPI驱动和设备

2025-02-28 来源:cnblogs

一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 内核使用 spi_master 表示 SPI 主机驱动,spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中,内容如下(有缩减):


struct spi_master {

    struct device dev;


    struct list_head list;

......

    s16 bus_num;


    /* chipselects will be integral to many controllers; some others

     * might use board-specific GPIOs.

    */

    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;


    /* bitmask of supported bits_per_word for transfers */

    u32 bits_per_word_mask;

......

    /* limits on transfer speed */

    u32 min_speed_hz;

    u32 max_speed_hz;


    /* 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;

......

    int (*setup)(struct spi_device *spi);


......

    int (*transfer)(struct spi_device *spi,

                    struct spi_message *mesg);

......


    int (*transfer_one_message)(struct spi_master *master,

                                struct spi_message *mesg);

......

};

第 41 行:transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。

第 45 行:transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。


和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的,SOC 的使用者不用操心。


SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master。


1、spi_master 申请与释放

spi_alloc_master 函数用于申请 spi_master,函数原型如下:


struct spi_master *spi_alloc_master(struct device  *dev,

                                    unsigned       size)

函数参数和返回值含义如下:


dev:设备,一般是 platform_device 中的 dev 成员变量。

size:私有数据大小,可通过 spi_master_get_devdata 函数获取这些私有数据。

返回值:申请到的 spi_master。

spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:


void spi_master_put(struct spi_master *master)

函数参数和返回值含义如下:


master:要释放的 spi_master。

返回值:无。

2、spi_master 的注册与注销

注册:

当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为spi_register_master,函数原型如下:


int spi_register_master(struct spi_master *master)

函数参数和返回值含义如下:


master:要注册的 spi_master。

返回值:0,成功;负值,失败。

I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册,spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。


注销:

如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:


void spi_unregister_master(struct spi_master *master)

函数参数和返回值含义如下:


master:要注销的 spi_master。

返回值:无。

如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉 spi_master。


2、I.MX6U SPI 主机驱动分析

和 I2C 的适配器驱动一样,SPI 主机驱动一般都由 SOC 厂商编写好了,打开 imx6ull.dtsi 文件,找到如下所示内容:


ecspi3: ecspi@02010000 {

    #address-cells = <1>;

    #size-cells = <0>;

    compatible = 'fsl,imx6ul-ecspi', 'fsl,imx51-ecspi';

    reg = <0x02010000 0x4000>;

    interrupts = ;

    clocks = <&clks IMX6UL_CLK_ECSPI3>,

    <&clks IMX6UL_CLK_ECSPI3>;

    clock-names = 'ipg', 'per';

    dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;

    dma-names = 'rx', 'tx';

    status = 'disabled';

};

第 4 行:compatible 属性值,compatible 属性有两个值“fsl,imx6ul-ecspi”和“fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)主机驱动。


I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c,在此文件中找到如下内容:


static struct platform_device_id spi_imx_devtype[] = {

    {

        .name = 'imx1-cspi',

        .driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,

    }, {

        .name = 'imx21-cspi',

        .driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,

        ......

    }, {

        .name = 'imx6ul-ecspi',

        .driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,

    }, {

        /* sentinel */

    }

};


static const struct of_device_id spi_imx_dt_ids[] = {

    { .compatible = 'fsl,imx1-cspi', .data =

                        &imx1_cspi_devtype_data, },

......

    { .compatible = 'fsl,imx6ul-ecspi', .data =

                        &imx6ul_ecspi_devtype_data, },

    { /* sentinel */ }

};

MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);

......

......

......

static struct platform_driver spi_imx_driver = {

    .driver = {

        .name = DRIVER_NAME,

        .of_match_table = spi_imx_dt_ids,

        .pm = IMX_SPI_PM,

    },

    .id_table = spi_imx_devtype,

    .probe = spi_imx_probe,

    .remove = spi_imx_remove,

};

module_platform_driver(spi_imx_driver);

第 10 行:spi_imx_devtype 为 SPI 无设备树匹配表。

第 17 行:spi_imx_dt_ids 为 SPI 设备树匹配表。

第 21 行:“fsl,imx6ul-ecspi”匹配项,因此可知 I.MX6U 的 ECSPI 驱动就是 spi-imx.c 这个文件。

第 29~38 行:platform_driver 驱动框架,和 I2C 的适配器驱动一样,SPI 主机驱动器采用了 platfom 驱动框架。当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行。


spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,定义在 drives/spi/spi-imx.c 中,内容如下:


static int spi_imx_probe(struct platform_device *pdev)

{

struct device_node *np = pdev->dev.of_node;

const struct of_device_id *of_id =

of_match_device(spi_imx_dt_ids, &pdev->dev);

struct spi_imx_master *mxc_platform_info =

dev_get_platdata(&pdev->dev);

struct spi_master *master;

struct spi_imx_data *spi_imx;

struct resource *res;

int i, ret, num_cs, irq;


if (!np && !mxc_platform_info) {

dev_err(&pdev->dev, 'can't get the platform datan');

return -EINVAL;

}


ret = of_property_read_u32(np, 'fsl,spi-num-chipselects', &num_cs);

if (ret < 0) {

if (mxc_platform_info)

num_cs = mxc_platform_info->num_chipselect;

else

return ret;

}


master = spi_alloc_master(&pdev->dev,

sizeof(struct spi_imx_data) + sizeof(int) * num_cs);

if (!master)

return -ENOMEM;


platform_set_drvdata(pdev, master);


master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);

master->bus_num = pdev->id;

master->num_chipselect = num_cs;


spi_imx = spi_master_get_devdata(master);

spi_imx->bitbang.master = master;


for (i = 0; i < master->num_chipselect; i++) {

int cs_gpio = of_get_named_gpio(np, 'cs-gpios', i);

if (!gpio_is_valid(cs_gpio) && mxc_platform_info)

cs_gpio = mxc_platform_info->chipselect[i];


spi_imx->chipselect[i] = cs_gpio;

if (!gpio_is_valid(cs_gpio))

continue;


ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],

DRIVER_NAME);

if (ret) {

dev_err(&pdev->dev, 'can't get cs gpiosn');

goto out_master_put;

}

}


spi_imx->bitbang.chipselect = spi_imx_chipselect;

spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;

spi_imx->bitbang.txrx_bufs = spi_imx_transfer;

spi_imx->bitbang.master->setup = spi_imx_setup;

spi_imx->bitbang.master->cleanup = spi_imx_cleanup;

spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;

spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;

spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;


init_completion(&spi_imx->xfer_done);


spi_imx->devtype_data = of_id ? of_id->data :

(struct spi_imx_devtype_data *) pdev->id_entry->driver_data;


res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

spi_imx->base = devm_ioremap_resource(&pdev->dev, res);

if (IS_ERR(spi_imx->base)) {

ret = PTR_ERR(spi_imx->base);

goto out_master_put;

}


irq = platform_get_irq(pdev, 0);

if (irq < 0) {

ret = irq;

goto out_master_put;

}


ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0,

       dev_name(&pdev->dev), spi_imx);

if (ret) {

dev_err(&pdev->dev, 'can't get irq%d: %dn', irq, ret);

goto out_master_put;

}


spi_imx->clk_ipg = devm_clk_get(&pdev->dev, 'ipg');

if (IS_ERR(spi_imx->clk_ipg)) {

ret = PTR_ERR(spi_imx->clk_ipg);

goto out_master_put;

}


spi_imx->clk_per = devm_clk_get(&pdev->dev, 'per');

if (IS_ERR(spi_imx->clk_per)) {

ret = PTR_ERR(spi_imx->clk_per);

goto out_master_put;

}


ret = clk_prepare_enable(spi_imx->clk_per);

if (ret)

goto out_master_put;


ret = clk_prepare_enable(spi_imx->clk_ipg);

if (ret)

goto out_put_per;


spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);

/*

* Only validated on i.mx6 now, can remove the constrain if validated on

* other chips.

*/

if ((spi_imx->devtype_data == &imx51_ecspi_devtype_data

    || spi_imx->devtype_data == &imx6ul_ecspi_devtype_data)

    && spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))

dev_err(&pdev->dev, 'dma setup error,use pio insteadn');


spi_imx->devtype_data->reset(spi_imx);


spi_imx->devtype_data->intctrl(spi_imx, 0);


master->dev.of_node = pdev->dev.of_node;

ret = spi_bitbang_start(&spi_imx->bitbang);

if (ret) {

dev_err(&pdev->dev, 'bitbang start failed with %dn', ret);

goto out_clk_put;

}


dev_info(&pdev->dev, 'probedn');


clk_disable_unprepare(spi_imx->clk_ipg);

clk_disable_unprepare(spi_imx->clk_per);

return ret;


out_clk_put:

clk_disable_unprepare(spi_imx->clk_ipg);

out_put_per:

clk_disable_unprepare(spi_imx->clk_per);

out_master_put:

spi_master_put(master);


return ret;

}

第 126 行:调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册 spi_master。


对于 I.MX6U 来讲,SPI 主机的最终数据收发函数为 spi_imx_transfer:


static int spi_imx_transfer(struct spi_device *spi,

struct spi_transfer *transfer)

{

int ret;

struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);


if (spi_imx->bitbang.master->can_dma &&

    spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {

spi_imx->usedma = true;

ret = spi_imx_dma_transfer(spi_imx, transfer);

spi_imx->usedma = false; /* clear the dma flag */

if (ret != -EAGAIN)

return ret;

}

spi_imx->usedma = false;


return spi_imx_pio_transfer(spi, transfer);

}

此函数通过如下层层调用最终实现 SPI 数据发送:


spi_imx_transfer

    -> spi_imx_pio_transfer

        -> spi_imx_push

            -> spi_imx->tx

spi_imx 是个 spi_imx_data 类型的结构指针变量,其中 tx 和 rx 这两个成员变量分别为 SPI 数据发送和接收函数。I.MX6U SPI 主机驱动会维护一个 spi_imx_data 类型的变量 spi_imx,并且使用 spi_imx_setupxfer 函数来设置 spi_imx 的 tx 和 rx 函数。根据要发送的数据数据位宽的不同,分别有 8 位 、16 位和 32 位的发送函数,如下所示:


spi_imx_buf_tx_u8()

spi_imx_buf_tx_u16()

spi_imx_buf_tx_u32()

同理,也有 8 位、16 位和 32 位的数据接收函数,如下所示:


spi_imx_buf_rx_u8()

spi_imx_buf_rx_u16()

spi_imx_buf_rx_u32()

以 spi_imx_buf_tx_u8 这个函数为例,看看一个数据发送是怎么完成的,在 spi-imx.c 文件中找到如下所示内容:


#define MXC_SPI_BUF_TX(type)                     

static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx)

{                                                 

    type val = 0;                                 

                                                 

    if (spi_imx->tx_buf) {                       

        val = *(type *)spi_imx->tx_buf;           

        spi_imx->tx_buf += sizeof(type);         

    }                                             

                                                 

    spi_imx->count -= sizeof(type);             

                                                 

    writel(val, spi_imx->base + MXC_CSPITXDATA);

}


MXC_SPI_BUF_RX(u8)

MXC_SPI_BUF_TX(u8)

从示例代码可以看出,spi_imx_buf_tx_u8 函数是通过 MXC_SPI_BUF_TX 宏来实现的。

第 13 行:将要发送的数据值写入到 ECSPI 的 TXDATA 寄存器里面去,这和我们 SPI 裸机实验的方法一样。

第 17 行:MXC_SPI_BUF_TX(u8)展开就是 spi_imx_buf_tx_u8 函数。

其他的 tx 和 rx 函数都是这样实现的,这里就不做介绍了。I.MX6U 的 SPI 主机驱动程序就讲解到这里,基本套路和 I2C 的适配器驱动程序类似。


3、SPI 设备驱动

spi 设备驱动也和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,在编写 SPI 设备驱动的时候需要实现 spi_driver 。spi_driver 结构体定义在include/linux/spi/spi.h 文件中,内容如下:


struct spi_driver {

    const struct spi_device_id *id_table;

    int (*probe)(struct spi_device *spi);

    int (*remove)(struct spi_device *spi);

    void (*shutdown)(struct spi_device *spi);

    struct device_driver driver;

};

可以看出,spi_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功以后 probe 函数就会执行。


注册驱动:

同样的,spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为 spi_register_driver,函数原型如下:


int spi_register_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:


sdrv:要注册的 spi_driver。

返回值:0,注册成功;赋值,注册失败。

注销驱动:

注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销,函数原型如下:


void spi_unregister_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:


sdrv:要注销的 spi_driver。

返回值:无。

spi_driver 注册示例程序如下:


/* probe 函数 */

static int xxx_probe(struct spi_device *spi)

{

    /* 具体函数内容 */

    return 0;

}


/* remove 函数 */

static int xxx_remove(struct spi_device *spi)

{

    /* 具体函数内容 */

    return 0;

}


/* 传统匹配方式 ID 列表 */

static const struct spi_device_id xxx_id[] = {

    {'xxx', 0},

    {}

};


/* 设备树匹配列表 */

static const struct of_device_id xxx_of_match[] = {

    { .compatible = 'xxx' },

    { /* Sentinel */ }

};


/* SPI 驱动结构体 */

static struct spi_driver xxx_driver = {

    .probe = xxx_probe,

    .remove = xxx_remove,

    .driver = {

        .owner = THIS_MODULE,

        .name = 'xxx',

        .of_match_table = xxx_of_match,

    },

    .id_table = xxx_id,

};


/* 驱动入口函数 */

static int __init xxx_init(void)

{

    return spi_register_driver(&xxx_driver);

}


/* 驱动出口函数 */

static void __exit xxx_exit(void)

{

    spi_unregister_driver(&xxx_driver);

}


module_init(xxx_init);

module_exit(xxx_exit);

第 1~37 行:spi_driver 结构体,需要 SPI 设备驱动人员编写,包括匹配表、probe 函数等。

和 i2c_driver、platform_driver 一样,就不详细讲解了。

第 40~43 行:在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。

第 46~49 行:在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。


4、SPI 设备驱动编写流程

1.SPI 设备信息描述

①、IO 的 pinctrl 子节点创建与修改

首先是根据所使用的 IO 来创建或修改 pinctrl 子节点,要注意的就是检查相应的 IO 有没有被其他的设备所使用,如果有的话需要将其删除掉!


②、SPI 设备节点的创建与修改

采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:


&ecspi1 {

    fsl,spi-num-chipselects = <1>;

    cs-gpios = <&gpio4 9 0>;

    pinctrl-names = 'default';

    pinctrl-0 = <&pinctrl_ecspi1>;

    status = 'okay';


    flash: m25p80@0 {

        #address-cells = <1>;

        #size-cells = <1>;

        compatible = 'st,m25p32';

        spi-max-frequency = <20000000>;

        reg = <0>;

    };

};

示例代码是 I.MX6Q 的一款板子上的一个 SPI 设备节点,在这个板子的 ECSPI 接

口上接了一个 m25p80,这是一个 SPI 接口的设备。

第 2 行:设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备。

第 3 行:设置“cs-gpios”属性,也就是片选信号为 GPIO4_IO09。

第 4 行:设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。

第 5 行:设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。

第 6 行:将 ecspi1 节点的“status”属性改为“okay”。

第 8~15 行:ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述其设备信息。第 8 行的“m25p80@0”后面的“0”表示 m25p80 接到了 ECSPI 的通道 0 上。这个要根据自己的具体硬件来设置。第 11 行是设备的 compatible 属性值,用于匹配设备驱动。第 12 行“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的 SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz。第 13 行 reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道,和“m25p80@0”后面的“0”一样。


2.SPI 设备数据收发处理流程

SPI 设备驱动的核心是 spi_driver。当向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。


首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,内容如下:


struct spi_transfer {

    /* it's ok if tx_buf == rx_buf (right?)

     * for MicroWire, one buffer must be null

     * buffers must work with dma_*map_single() calls, unless

     * spi_message.is_dma_mapped reports a pre-existing mapping

     */

    const void *tx_buf;

    void *rx_buf;

    unsigned len;


    dma_addr_t tx_dma;

    dma_addr_t rx_dma;

    struct sg_table tx_sg;

    struct sg_table rx_sg;


    unsigned cs_change:1;

    unsigned tx_nbits:3;

    unsigned rx_nbits:3;

    #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */

    #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */

    #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */

    u8 bits_per_word;

    u16 delay_usecs;

    u32 speed_hz;


    struct list_head transfer_list;

};

第 7 行:tx_buf 保存着要发送的数据。

第 8 行:rx_buf 用于保存接收到的数据。

第 9 行:len 是要进行传输的数据长度,SPI 是全双工通信,在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。


spi_transfer 需要组织成 spi_message,spi_message 也是一个结构体,内容如下:


struct spi_message {

    struct list_head transfers;


    struct spi_device *spi;


    unsigned is_dma_mapped:1;

......

    /* completion is reported through a callback */

    void (*complete)(void *context);

    void *context;

    unsigned frame_length;

    unsigned actual_length;

    int status;


    /* for optional use by whatever driver currently owns the

     * spi_message ... between calls to spi_async and then later

     * complete(), that's the spi_master controller driver.

     */

    struct list_head queue;

    void *state;

};

在使用 spi_message 之前需要对其进行初始化,spi_message 初始化函数为spi_message_init,函数原型如下:


void spi_message_init(struct spi_message *m)

函数参数和返回值含义如下:


m:要初始化的 spi_message。

返回值:无。

spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用到 spi_message_add_tail 函数,此函数原型如下:


void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

函数参数和返回值含义如下:


t:要添加到队列中的 spi_transfer。

m:spi_transfer 要加入的 spi_message。

返回值:无。

spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:


int spi_sync(struct spi_device *spi, struct spi_message *message)

函数参数和返回值含义如下:


spi:要进行数据传输的 spi_device。

message:要传输的 spi_message。

返回值:无。

异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传输函数为 spi_async,函数原型如下:


int spi_async(struct spi_device *spi, struct spi_message *message)

函数参数和返回值含义如下:


spi:要进行数据传输的 spi_device。

message:要传输的 spi_message。

返回值:无。

本次实验中,采用同步传输方式来完成 SPI 数据的传输工作,也就是 spi_sync 函数。综上所述,SPI 数据传输步骤如下:

①、申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量,tx_buf 为要发送的数据。然后设置 rx_buf 成员变量,rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是要进行数据通信的长度。

②、使用 spi_message_init 函数初始化 spi_message。

③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。

④、使用 spi_sync 函数完成 SPI 数据同步传输。


通过 SPI 进行 n 个字节的数据发送和接收的示例代码如下所示:


/* SPI 多字节发送 */

static int spi_send(struct spi_device *spi, u8 *buf, int len)

{

    int ret;

    struct spi_message m;

    struct spi_transfer t = {

        .tx_buf = buf,

        .len = len,

    };

    spi_message_init(&m); /* 初始化 spi_message */

    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */

    ret = spi_sync(spi, &m); /* 同步传输 */

    return ret;

}


/* SPI 多字节接收 */

static int spi_receive(struct spi_device *spi, u8 *buf, int len)

{

    int ret;

    struct spi_message m;

    struct spi_transfer t = {

        .rx_buf = buf,

        .len = len,

    };

    spi_message_init(&m); /* 初始化 spi_message */

    spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */

    ret = spi_sync(spi, &m); /* 同步传输 */

    return ret;

}


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦线性调频增强器

  • 家用电器遥控器

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • DS1669数字电位器

  • HA1377 桥式放大器 BCL 电容 17W(汽车音频)

    相关电子头条文章