菏泽市网站建设_网站建设公司_页面权重_seo优化
2025/12/17 12:45:49 网站建设 项目流程

一、SPI协议

1、什么是SPI

SPI(Serial Peripheral Interface)是一种同步串行通信接口,用于短距离通信,主要用于嵌入式系统中连接微控制器和各种外设。

2、特点

全双工同步通信:同时收发数据

主从架构:1个主设备,多个从设备

四线制(标准情况):

  • MOSI:主设备输出,从设备输入

  • MISO:主设备输入,从设备输出

  • SCLK:时钟信号(由主设备产生)

  • CS:片选信号(选择从设备)

3、SPI的模式

SPI有4种工作模式,由时钟极性CPOL时钟相位CPHA决定:

CPOLCPHA模式含义
000SPICLK初始电平为低电平,在第一个时钟沿采样数据
011SPICLK初始电平为低电平,在第二个时钟沿采样数据
102SPICLK初始电平为高电平,在第一个时钟沿采样数据
113SPICLK初始电平为高电平,在第二个时钟沿采样数据

二、SPI整体框架

1、重要结构体

控制器驱动结构体:spi_master

struct spi_master { struct device dev; struct list_head list; /* other than negative (== assign one dynamically), bus_num is fully * board-specific. usually that simplifies to being SOC-specific. * example: one SOC has three SPI controllers, numbered 0..2, * and one board's schematics might show it using SPI-2. software * would normally use bus_num=2 for that controller. */ 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; #define SPI_BPW_MASK(bits) BIT((bits) - 1) #define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1)) #define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1)) /* limits on transfer speed */ u32 min_speed_hz; u32 max_speed_hz; /* other constraints relevant to this driver */ u16 flags; #define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */ #define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */ #define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */ #define SPI_MASTER_MUST_RX BIT(3) /* requires rx */ #define SPI_MASTER_MUST_TX BIT(4) /* requires tx */ /* * on some hardware transfer / message size may be constrained * the limit may depend on device transfer settings */ size_t (*max_transfer_size)(struct spi_device *spi); size_t (*max_message_size)(struct spi_device *spi); /* I/O mutex */ struct mutex io_mutex; /* 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; /* Setup mode and clock, etc (spi driver may call many times). * * IMPORTANT: this may be called when transfers to another * device are active. DO NOT UPDATE SHARED REGISTERS in ways * which could break those transfers. */ int (*setup)(struct spi_device *spi); /* bidirectional bulk transfers * * + The transfer() method may not sleep; its main role is * just to add the message to the queue. * + For now there's no remove-from-queue operation, or * any other request management * + To a given spi_device, message queueing is pure fifo * * + The master's main job is to process its message queue, * selecting a chip then transferring data * + If there are multiple spi_device children, the i/o queue * arbitration algorithm is unspecified (round robin, fifo, * priority, reservations, preemption, etc) * * + Chipselect stays active during the entire message * (unless modified by spi_transfer.cs_change != 0). * + The message transfers use clock and SPI mode parameters * previously established by setup() for this device */ 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); /* * Used to enable core support for DMA handling, if can_dma() * exists and returns true then the transfer will be mapped * prior to transfer_one() being called. The driver should * not modify or store xfer and dma_tx and dma_rx must be set * while the device is prepared. */ bool (*can_dma)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *xfer); /* * These hooks are for drivers that want to use the generic * master transfer queueing mechanism. If these are used, the * transfer() function above must NOT be specified by the driver. * Over time we expect SPI drivers to be phased over to this API. */ bool queued; struct kthread_worker kworker; struct task_struct *kworker_task; struct kthread_work pump_messages; spinlock_t queue_lock; struct list_head queue; struct spi_message *cur_msg; bool idling; bool busy; bool running; bool rt; bool auto_runtime_pm; bool cur_msg_prepared; bool cur_msg_mapped; struct completion xfer_completion; size_t max_dma_len; int (*prepare_transfer_hardware)(struct spi_master *master); int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_master *master); int (*prepare_message)(struct spi_master *master, struct spi_message *message); int (*unprepare_message)(struct spi_master *master, struct spi_message *message); int (*spi_flash_read)(struct spi_device *spi, struct spi_flash_read_message *msg); bool (*flash_read_supported)(struct spi_device *spi); /* * These hooks are for drivers that use a generic implementation * of transfer_one_message() provied by the core. */ void (*set_cs)(struct spi_device *spi, bool enable); int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer); void (*handle_err)(struct spi_master *master, struct spi_message *message); /* gpio chip select */ int *cs_gpios; /* statistics */ struct spi_statistics statistics; /* DMA channels for use with core dmaengine helpers */ struct dma_chan *dma_tx; struct dma_chan *dma_rx; /* dummy data for full duplex devices */ void *dummy_rx; void *dummy_tx; int (*fw_translate_cs)(struct spi_master *master, unsigned cs); };

设备驱动结构体:spi_driver

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_device

struct spi_device { struct device dev; struct spi_master *master; u32 max_speed_hz; u8 chip_select; u8 bits_per_word; u16 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 */ #define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ #define SPI_READY 0x80 /* slave pulls low to pause */ #define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ #define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ #define SPI_RX_DUAL 0x400 /* receive with 2 wires */ #define SPI_RX_QUAD 0x800 /* receive with 4 wires */ int irq; void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; int cs_gpio; /* chip select gpio */ /* the statistics */ struct spi_statistics statistics; /* * 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 * - ... */ };

代表一次传输的结构体:spi_transfer

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; };

管理多个传输结构体:spi_message

struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; /* REVISIT: we might want a flag affecting the behavior of the * last transfer ... allowing things like "read 16 bit length L" * immediately followed by "read L bytes". Basically imposing * a specific message scheduling algorithm. * * Some controller drivers (message-at-a-time queue processing) * could provide that as their default scheduling algorithm. But * others (with multi-message pipelines) could need a flag to * tell them about such special cases. */ /* 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; /* list of spi_res reources when the spi message is processed */ struct list_head resources; };

2、驱动框架图(重要!!!)

三、设备树描述

1、SPI主控制器节点(Master)

必须属性

#address-cells = <1>; // 子节点reg属性用1个cell表示片选 #size-cells = <0>; // 必须设为0 compatible = "厂商,型号"; // 匹配驱动程序

可选属性

cs-gpios = <&gpio 16 1>; // 片选GPIO列表 num-cs = <1>; // 片选引脚总数

2、SPI设备节点(Device)

必须属性

compatible = "厂商,型号"; // 匹配设备驱动 reg = <0>; // 片选编号(0,1,2...) spi-max-frequency = <1000000>; // 最大SPI时钟

可选属性

spi-cpol; // 空属性,表示CPOL=1 spi-cpha; // 空属性,表示CPHA=1 spi-cs-high; // 空属性,片选高有效 spi-3wire; // 空属性,三线模式 spi-lsb-first; // 空属性,LSB优先 spi-tx-bus-width = <2>; // MOSI线数(默认1) spi-rx-bus-width = <2>; // MISO线数(默认1) spi-rx-delay-us = <10>; // 读后延时(微秒) spi-tx-delay-us = <10>; // 写后延时(微秒)

四、SPI设备驱动解析:spidev(内核自带)

1、spidev是什么?

spidev是一个通用的SPI用户态设备驱动程序,允许在用户空间直接访问SPI设备,无需编写内核驱动。

核心特点:

  • 用户态API:通过设备文件 /dev/spidevB.D 访问

  • 无需内核驱动:可直接操作SPI硬件

  • 简单易用:提供标准的文件操作接口

2、设备树配置

匹配条件:

设备树节点compatible属性为以下值之一时,自动匹配spidev驱动:

1、"rohm,dh2228fv"

2、"lineartechnology,ltc2488"

3、"spidev"

设备树示例:

spidev0: spidev@0 { compatible = "spidev"; // 匹配spidev驱动 reg = <0>; // 片选0 spi-max-frequency = <50000000>; // 最大50MHz };

3、设备文件命名规则

/dev/spidevB.D
  • B:SPI总线编号

  • D:该SPI主控制器下的设备序号(片选号)

示例:/dev/spidev1.0 表示总线1上的片选0设备

4、spidev驱动程序流程图

5、spidev的缺点

1、使用read、write函数时,只能读、写,这是半双工方式。使用ioctl可以达到全双工的读写。

2、不支持中断:只能使用轮询方式

3、仅支持同步操作:所有I/O操作都会阻塞直到完成

4、性能较低:用户态-内核态切换有开销

5、功能有限:不支持DMA等高级功能

五、SPI设备驱动框架(自己编写)

1、SPI进行数据传输的重要API

1.1、简易API

a、同步读写函数

/** * SPI同步写 * @spi: 写哪个设备 * @buf: 数据buffer * @len: 长度 * 这个函数可以休眠 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_write(struct spi_device *spi, const void *buf, size_t len); /** * SPI同步读 * @spi: 读哪个设备 * @buf: 数据buffer * @len: 长度 * 这个函数可以休眠 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_read(struct spi_device *spi, void *buf, size_t len); /** * spi_write_then_read : 先写再读, 这是一个同步函数 * @spi: 读写哪个设备 * @txbuf: 发送buffer * @n_tx: 发送多少字节 * @rxbuf: 接收buffer * @n_rx: 接收多少字节 * 这个函数可以休眠 * * 这个函数执行的是半双工的操作: 先发送txbuf中的数据,在读数据,读到的数据存入rxbuf * * 这个函数用来传输少量数据(建议不要操作32字节), 它的效率不高 * 如果想进行高效的SPI传输,请使用spi_{async,sync}(这些函数使用DMA buffer) * * 返回值: 0-成功, 负数-失败码 */ extern int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);

b、便捷读写函数

/** * spi_w8r8 - 同步函数,先写8位数据,再读8位数据 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * * 返回值: 成功的话返回一个8位数据(unsigned), 负数表示失败码 */ static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd); /** * spi_w8r16 - 同步函数,先写8位数据,再读16位数据 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * 读到的16位数据: * 低地址对应读到的第1个字节(MSB),高地址对应读到的第2个字节(LSB) * 这是一个big-endian的数据 * * 返回值: 成功的话返回一个16位数据(unsigned), 负数表示失败码 */ static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd); /** * spi_w8r16be - 同步函数,先写8位数据,再读16位数据, * 读到的16位数据被当做big-endian,然后转换为CPU使用的字节序 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * 这个函数跟spi_w8r16类似,差别在于它读到16位数据后,会把它转换为"native endianness" * * 返回值: 成功的话返回一个16位数据(unsigned, 被转换为本地字节序), 负数表示失败码 */ static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);

1.2、高级API

a、异步传输

/** * spi_async - 异步SPI传输函数,简单地说就是这个函数即刻返回,它返回后SPI传输不一定已经完成 * @spi: 读写哪个设备 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback) * 上下文: 任意上下文都可以使用,中断中也可以使用 * * 这个函数不会休眠,它可以在中断上下文使用(无法休眠的上下文),也可以在任务上下文使用(可以休眠的上下文) * * 完成SPI传输后,回调函数被调用,它是在"无法休眠的上下文"中被调用的,所以回调函数里不能有休眠操作。 * 在回调函数被调用前message->statuss是未定义的值,没有意义。 * 当回调函数被调用时,就可以根据message->status判断结果: 0-成功,负数表示失败码 * 当回调函数执行完后,驱动程序要认为message等结构体已经被释放,不能再使用它们。 * * 在传输过程中一旦发生错误,整个message传输都会中止,对spi设备的片选被取消。 * * 返回值: 0-成功(只是表示启动的异步传输,并不表示已经传输成功), 负数-失败码 */ extern int spi_async(struct spi_device *spi, struct spi_message *message);

b、同步传输

/** * spi_sync - 同步的、阻塞的SPI传输函数,简单地说就是这个函数返回时,SPI传输要么成功要么失败 * @spi: 读写哪个设备 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback) * 上下文: 能休眠的上下文才可以使用这个函数 * * 这个函数的message参数中,使用的buffer是DMA buffer * * 返回值: 0-成功, 负数-失败码 */ extern int spi_sync(struct spi_device *spi, struct spi_message *message); /** * spi_sync_transfer - 同步的SPI传输函数 * @spi: 读写哪个设备 * @xfers: spi_transfers数组,用来描述传输 * @num_xfers: 数组项个数 * 上下文: 能休眠的上下文才可以使用这个函数 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);

2、重要概念

在SPI子系统中,用spi_transfer结构体描述一个传输,用spi_message管理多个传输

SPI传输时,发出N个字节,同时得到N个字节

  • 即使只想读N个字节,也必须发出N个字节:可以发出0xff

  • 即使只想发出N个字节,也会读到N个字节:可以忽略读到的数据。

3、驱动程序框架(通过ioctl进行数据传输)

/* 全局变量 */ static struct spi_device *spi_dev; // 保存SPI设备指针,用于后续通信 static int major; // 字符设备主设备号 static struct class *dev_class; // 设备类,用于自动创建设备节点 static long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int val; u8 tx_buf[4]; // 发送缓冲区 u8 rx_buf[4]; // 接收缓冲区 struct spi_message msg; struct spi_transfer xfer; /* 步骤1: 从用户空间获取数据 */ if (copy_from_user(&val, (void __user *)arg, sizeof(int))) { return -EFAULT; // 复制失败返回错误 } /* 步骤2: 数据格式转换(根据硬件协议要求)*/ // 例如: 将32位数据转为大端序格式(高字节存放在低地址) tx_buf[0] = (val >> 24) & 0xFF; tx_buf[1] = (val >> 16) & 0xFF; tx_buf[2] = (val >> 8) & 0xFF; tx_buf[3] = val & 0xFF; /* 步骤3: 构造SPI传输 */ memset(&xfer, 0, sizeof(xfer)); xfer.tx_buf = tx_buf; // 指向发送数据 xfer.rx_buf = rx_buf; // 指向接收缓冲区 xfer.len = sizeof(tx_buf); // 传输字节数 /* 初始化消息并添加传输 */ spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); /* 执行同步传输 */ if (spi_sync(spi_dev, &msg) < 0) { return -EIO; // 传输失败 } /* 步骤4: 处理接收数据 */ val = (rx_buf[0] << 24) | (rx_buf[1] << 16) | (rx_buf[2] << 8) | rx_buf[3]; /* 将结果返回用户空间 */ if (copy_to_user((void __user *)arg, &val, sizeof(int))) { return -EFAULT; } return 0; } static const struct file_operations xxx_fops = { .owner = THIS_MODULE, .unlocked_ioctl = xxx_ioctl, // 用户空间控制接口 }; static int xxx_probe(struct spi_device *spi) { /* 保存spi_device指针供后续使用 */ spi_dev = spi; /* 配置SPI参数(可选)*/ // spi->mode = SPI_MODE_0; // spi->bits_per_word = 8; // spi->max_speed_hz = 1000000; // spi_setup(spi); /* 注册字符设备 */ major = register_chrdev(0, "spi_dev", &xxx_fops); // 0表示动态分配主设备号 if (major < 0) { pr_err("Failed to register chrdev\n"); return major; } /* 创建设备类 */ dev_class = class_create(THIS_MODULE, "spi_class"); if (IS_ERR(dev_class)) { unregister_chrdev(major, "spi_dev"); return PTR_ERR(dev_class); } /* 创建设备节点 /dev/spi_dev */ device_create(dev_class, NULL, MKDEV(major, 0), NULL, "spi_dev"); pr_info("SPI device probed successfully\n"); return 0; } /* 设备树匹配表 - 用于将驱动与设备树节点绑定 */ static const struct of_device_id xxx_dt_ids[] = { { .compatible = "vendor,device-name" }, // 与设备树中compatible属性匹配 {}, // 必须以空项结束 }; static int xxx_remove(struct spi_device *spi) { /* 按注册相反顺序清理资源 */ device_destroy(dev_class, MKDEV(major, 0)); // 删除设备节点 class_destroy(dev_class); // 删除设备类 unregister_chrdev(major, "spi_dev"); // 注销字符设备 pr_info("SPI device removed\n"); return 0; } static struct spi_driver xxx_spi_driver = { .driver = { .name = "xxx_spi", // 驱动名称 .of_match_table = of_match_ptr(xxx_dt_ids), // 设备树匹配表 }, .probe = xxx_probe, // 设备匹配时调用 .remove = xxx_remove, // 设备移除时调用 }; /* 模块加载函数 */ static int __init xxx_init(void) { /* 注册SPI驱动 */ return spi_register_driver(&xxx_spi_driver); } module_init(xxx_init); /* 模块卸载函数 */ static void __exit xxx_exit(void) { /* 注销SPI驱动 */ spi_unregister_driver(&xxx_spi_driver); } module_exit(xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Generic SPI device driver");

3.1、在xxx_ioctl中为什么需要进行数据格式转换

  • 默认 SPI协议:MSB first每个字节内高位先发)。

  • 假设传 int val = 123 时,发送的字节顺序取决于你如何把这个整数的内存传给 SPI 发送函数,一般是按照内存地址从低到高逐个字节发送

  • 如果希望整数的最高字节先出现在 SPI 线上,需要先转换成大端序(或调整字节顺序)再发送。

3.2、为什么没有提供 xxx_open 函数?

Linux内核的处理机制,当用户空间调用open("/dev/spi_dev", ...)时:

a、字符设备驱动的默认行为

对于字符设备驱动程序,如果没有提供open函数:

  • 内核的VFS层会自动处理open操作

  • 只要设备注册成功并且有对应的struct file_operations,open就会成功

  • 内核会简单地为进程创建一个struct file结构体,并将其与设备关联

b、什么情况下可以省略open函数?

这个驱动省略open函数是可行的,因为:

  • 没有设备特定的初始化需求:打开设备时不需要分配私有数据、初始化硬件等

  • 没有并发控制需求:这里假设设备可以同时被多个进程打开使用(虽然可能有并发问题)

  • 只需要ioctl功能:用户只需要通过 ioctl() 来与设备交互

六、SPI控制器驱动程序框架

1、SPI传输的数据组织方式

SPI传输的数据结构分为三个层次,从底层到高层依次是:spi_transfer(传输单元)、spi_message(消息)和spi_master(控制器队列)。

1.1、spi_transfer(传输单元)

  • 这是最小的传输单位,包含以下信息:

    • tx_buf:发送数据的缓冲区(指针)

    • rx_buf:接收数据的缓冲区(指针)

    • len:传输的数据长度(以字节为单位)

  • 一个spi_transfer代表一次连续的数据传输(片选信号保持有效)

1.2、spi_message(消息)

  • 一个spi_message管理多个spi_transfer,这些transfer属于同一个SPI设备。

  • spi_message将这些spi_transfer组织成一个链表。

  • 当需要连续传输多个spi_transfer(可能中间需要改变传输参数,如速度、字长等)时,将它们放入同一个spi_message中。

1.3、spi_master(控制器队列)

  • 一个SPI控制器(spi_master)可以同时管理多个spi_message(来自多个SPI设备)。

  • 这些spi_message被放入一个队列中,由SPI控制器驱动程序依次处理。

传输流程

当发起SPI传输时,流程如下:

1、将spi_transfer添加到spi_message中。

2、将spi_message添加到spi_master的队列中。

3、SPI控制器驱动程序从队列中取出spi_message,然后依次处理其中的每个spi_transfer。

2、SPI控制器的两种传输方法

在Linux内核中,SPI控制器驱动程序有两种实现传输的方式,分别对应spi_master结构体中的两个回调函数:transfertransfer_one_message

2.1、老方法:使用transfer回调函数

  • 驱动程序实现控制器的transfer函数,该函数接收一个spi_message指针作为参数。

  • transfer函数中,驱动程序需要遍历该message中的所有spi_transfer,并处理每个transfer的传输。

  • 这种方法下,驱动程序需要自己处理片选信号(CS)的切换、传输参数(如速度、传输模式)的切换等。

2.2、新方法:使用transfer_one_message回调函数

  • 驱动程序实现transfer_one_message函数,该函数也是接收一个spi_message指针作为参数。

  • 与老方法不同,新方法中,SPI核心层会为每个spi_message调用控制器的transfer_one_message,然后在该函数中,驱动程序可以调用控制器的transfer_one(或者类似的辅助函数)来处理每个spi_transfer。

  • 新方法允许SPI核心层更好地控制传输流程,例如在传输之间自动切换片选信号(如果配置了cs_change标志)等。

两种方法的对比

  • 老方法:驱动程序需要处理整个message中的所有transfer,包括transfer之间的衔接(如片选切换)。这种方式下,驱动程序拥有更多的控制权,但需要编写更多的代码来处理细节。

  • 新方法:驱动程序只需要实现一个处理单个message的函数,然后由SPI核心层来管理多个message的队列和每个message中的多个transfer。这种方式下,驱动程序可以更专注于硬件操作,而将流程控制交给核心层。

3、SPI控制器驱动程序框架(老方法)

/* ============================================================ * 第一部分:全局变量和设备树匹配表 * ============================================================ */ /* SPI Master控制器结构体指针 */ static struct spi_master *g_spi_master; /* 工作队列,用于异步处理SPI传输 */ static struct work_struct g_spi_work; /* 设备树匹配表 - 用于设备和驱动的绑定 */ static const struct of_device_id spi_dt_match[] = { { .compatible = "100ask,virtual_spi_master", }, /* 需与dts中compatible属性一致 */ { /* sentinel - 哨兵,数组结束标志 */ } }; MODULE_DEVICE_TABLE(of, spi_dt_match); /* ============================================================ * 第二部分:工作队列处理函数 * 功能:在工作队列上下文中处理SPI消息队列 * ============================================================ */ static void spi_work_handler(struct work_struct *work) { struct spi_message *mesg; struct spi_transfer *xfer; /* 遍历处理消息队列中的所有消息 */ while (!list_empty(&g_spi_master->queue)) { /* 从队列头取出一个消息 */ mesg = list_entry(g_spi_master->queue.next, struct spi_message, queue); list_del_init(&mesg->queue); /* 重置实际传输长度 */ mesg->actual_length = 0; /* ===== 硬件操作区域 ===== */ /* 1. 配置SPI控制器(时钟频率、模式、位宽等) */ // TODO: 根据mesg->spi->max_speed_hz等参数配置硬件 /* 2. 拉低片选信号(CS) */ // TODO: gpio_set_value(cs_gpio, 0); /* 3. 遍历消息中的所有transfer */ list_for_each_entry(xfer, &mesg->transfers, transfer_list) { /* 发送数据 */ if (xfer->tx_buf) { // TODO: 将tx_buf中的数据写入SPI发送寄存器 // for (i = 0; i < xfer->len; i++) // writel(tx_buf[i], SPI_TX_REG); } /* 接收数据 */ if (xfer->rx_buf) { // TODO: 从SPI接收寄存器读取数据到rx_buf // for (i = 0; i < xfer->len; i++) // rx_buf[i] = readl(SPI_RX_REG); } /* 累加实际传输长度 */ mesg->actual_length += xfer->len; /* 传输间延时 */ if (xfer->delay_usecs) udelay(xfer->delay_usecs); } /* 4. 拉高片选信号(CS) */ // TODO: gpio_set_value(cs_gpio, 1); /* ===== 传输完成处理 ===== */ /* 设置传输状态为成功 */ mesg->status = 0; /* 调用完成回调函数,通知上层传输完成 */ if (mesg->complete) mesg->complete(mesg->context); } } /* ============================================================ * 第三部分:SPI传输函数(核心接口) * 功能:接收上层的SPI消息,启动传输流程 * 这是一个通用的SPI控制器框架,因此不涉及硬件操作!!! * ============================================================ */ static int spi_transfer_handler(struct spi_device *spi, struct spi_message *mesg) { /* ----- 方法一:直接在当前上下文完成传输 ----- */ #if 0 mesg->status = 0; /* 设置状态为成功 */ /* 调用完成回调 */ if (mesg->complete) mesg->complete(mesg->context); return 0; #else /* ----- 方法二:异步传输(推荐) ----- */ /* 使用工作队列异步处理,不阻塞调用者 */ /* 适用场景:复杂传输、中断上下文调用、提高系统响应性 */ /* 1. 初始化消息状态 */ mesg->actual_length = 0; /* 实际传输长度清零 */ mesg->status = -EINPROGRESS; /* 设置状态为进行中 */ /* 2. 将消息加入master的队列尾部 */ list_add_tail(&mesg->queue, &spi->master->queue); /* 3. 调度工作队列,异步处理传输 */ schedule_work(&g_spi_work); /* 4. 立即返回,传输在后台进行 */ return 0; #endif } /* ============================================================ * 第四部分:平台设备probe函数 * 功能:驱动加载时的初始化 * ============================================================ */ static int spi_driver_probe(struct platform_device *pdev) { struct spi_master *master; int ret; /* 1. 分配spi_master结构体 */ /* 参数:设备指针,私有数据大小(0表示无私有数据) */ master = spi_alloc_master(&pdev->dev, 0); if (!master) { dev_err(&pdev->dev, "Failed to allocate SPI master\n"); return -ENOMEM; } /* 保存master指针供其他函数使用 */ g_spi_master = master; /* 2. 配置master参数 */ master->transfer = spi_transfer_handler; /* 设置传输函数 */ master->num_chipselect = 2; /* 支持的片选数量(可选) */ master->mode_bits = SPI_CPOL | SPI_CPHA | /* 支持的SPI模式(可选) */ SPI_CS_HIGH | SPI_LSB_FIRST; master->bits_per_word_mask = SPI_BPW_MASK(8) | /* 支持的位宽(可选) */ SPI_BPW_MASK(16); /* 3. 初始化工作队列,绑定工作队列和工作函数 */ INIT_WORK(&g_spi_work, spi_work_handler); /* 4. 关联设备树节点 */ master->dev.of_node = pdev->dev.of_node; /* 5. 硬件初始化(根据实际硬件添加) */ // TODO: 申请IO内存、时钟、中断等资源 // base = devm_ioremap_resource(&pdev->dev, res); // clk = devm_clk_get(&pdev->dev, NULL); // clk_prepare_enable(clk); /* 6. 注册spi_master到内核 */ ret = spi_register_master(master); if (ret < 0) { dev_err(&pdev->dev, "Failed to register SPI master\n"); spi_master_put(master); /* 释放master */ return ret; } dev_info(&pdev->dev, "SPI master registered successfully\n"); return 0; } /* ============================================================ * 第五部分:平台设备remove函数 * 功能:驱动卸载时的清理工作 * ============================================================ */ static int spi_driver_remove(struct platform_device *pdev) { /* 1. 注销spi_master */ spi_unregister_master(g_spi_master); /* 2. 释放硬件资源(根据实际情况添加) */ // TODO: 释放中断、关闭时钟、释放IO内存等 // free_irq(irq, master); // clk_disable_unprepare(clk); return 0; } /* ============================================================ * 第六部分:平台驱动结构体 * ============================================================ */ static struct platform_driver spi_platform_driver = { .probe = spi_driver_probe, /* 设备匹配时调用 */ .remove = spi_driver_remove, /* 设备移除时调用 */ .driver = { .name = "generic_spi_master", .of_match_table = spi_dt_match, /* 设备树匹配表 */ }, }; /* ============================================================ * 第七部分:模块加载和卸载 * ============================================================ */ static int __init spi_module_init(void) { return platform_driver_register(&spi_platform_driver); } static void __exit spi_module_exit(void) { platform_driver_unregister(&spi_platform_driver); } module_init(spi_module_init); module_exit(spi_module_exit); /* ============================================================ * 模块信息 * ============================================================ */ MODULE_DESCRIPTION("Generic SPI Master Controller Driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name");

4、SPI控制器驱动程序框架(新方法:推荐使用)

/* ============================================================ * SPI控制器驱动通用框架 - 基于spi_bitbang * 功能:使用Linux内核提供的spi_bitbang辅助层简化SPI Master驱动开发 * 优势:bitbang层帮助管理消息队列和工作线程,驱动只需实现底层传输 * ============================================================ */ /* ============================================================ * 第一部分:全局变量定义 * ============================================================ */ /* SPI Master控制器结构体 - 内核SPI核心层使用 */ static struct spi_master *g_virtual_master; /* SPI Bitbang结构体 - bitbang辅助层使用,简化队列管理 */ static struct spi_bitbang *g_virtual_bitbang; /* 完成量 - 用于同步等待传输完成(配合中断使用) */ static struct completion g_xfer_done; /* ============================================================ * 第二部分:设备树匹配表 * 功能:定义驱动支持的设备类型,与DTS中compatible属性匹配 * ============================================================ */ static const struct of_device_id spi_virtual_dt_ids[] = { { .compatible = "100ask,virtual_spi_master", }, /* 需与设备树中compatible一致 */ { /* sentinel - 哨兵元素,表示数组结束 */ } }; MODULE_DEVICE_TABLE(of, spi_virtual_dt_ids); /* ============================================================ * 第三部分:中断服务函数(可选,根据硬件实现) * 功能:硬件传输完成时触发,唤醒等待的传输函数 * 注意:这里仅为示例框架,实际需要在probe中注册中断 * ============================================================ */ static irqreturn_t spi_virtual_isr(int irq, void *dev_id) { /* 1. 清除硬件中断标志 */ // TODO: 读取状态寄存器,清除中断位 // u32 status = readl(SPI_STATUS_REG); // writel(status, SPI_STATUS_REG); /* 写1清零 */ /* 2. 通知传输函数:传输已完成 */ complete(&g_xfer_done); return IRQ_HANDLED; } /* ============================================================ * 第四部分:数据传输核心函数 * 功能:执行一次SPI数据传输(发送+接收) * 参数: * - spi: SPI设备结构体(包含配置信息) * - transfer: 传输描述符(包含tx_buf、rx_buf、len等) * 返回:成功返回传输字节数,失败返回负数错误码 * ============================================================ */ static int spi_virtual_transfer(struct spi_device *spi, struct spi_transfer *transfer) { int timeout; int i; const u8 *tx_buf = transfer->tx_buf; /* 发送缓冲区 */ u8 *rx_buf = transfer->rx_buf; /* 接收缓冲区 */ /* ----- 方法一:使用完成量+中断(推荐用于实际硬件) ----- */ #if 1 /* 1. 重新初始化完成量(准备新的传输) */ reinit_completion(&g_xfer_done); /* 2. 启动硬件传输 */ /* TODO: 实际硬件操作示例 */ /* for (i = 0; i < transfer->len; i++) { // 发送一个字节 if (tx_buf) writel(tx_buf[i], SPI_TX_DATA_REG); else writel(0xFF, SPI_TX_DATA_REG); // 只读时发送dummy数据 // 等待发送完成(或在中断中处理) while (!(readl(SPI_STATUS_REG) & SPI_TX_READY)) cpu_relax(); // 读取接收的字节 if (rx_buf) rx_buf[i] = readl(SPI_RX_DATA_REG); } */ /* 这里为演示完成量用法,直接complete(实际应在ISR中调用) */ complete(&g_xfer_done); /* 3. 等待传输完成(带超时保护) */ timeout = wait_for_completion_timeout(&g_xfer_done, msecs_to_jiffies(100)); /* 100ms超时 */ if (!timeout) { dev_err(&spi->dev, "I/O Error in PIO: Transfer timeout\n"); return -ETIMEDOUT; } #else /* ----- 方法二:轮询方式(适用于无中断硬件) ----- */ /* 循环处理每个字节 */ for (i = 0; i < transfer->len; i++) { /* 发送数据 */ if (tx_buf) { // TODO: writel(tx_buf[i], SPI_TX_DATA_REG); } else { // TODO: writel(0xFF, SPI_TX_DATA_REG); /* dummy数据 */ } /* 轮询等待发送完成 */ // TODO: while (!(readl(SPI_STATUS_REG) & SPI_TX_COMPLETE)) // cpu_relax(); /* 接收数据 */ if (rx_buf) { // TODO: rx_buf[i] = readl(SPI_RX_DATA_REG); } } #endif /* 4. 返回实际传输的字节数 */ return transfer->len; } /* ============================================================ * 第五部分:片选控制函数 * 功能:控制SPI设备的片选信号(CS/SS) * 参数: * - spi: SPI设备结构体 * - is_on: BITBANG_CS_ACTIVE(片选有效) 或 BITBANG_CS_INACTIVE(片选无效) * ============================================================ */ static void spi_virtual_chipselect(struct spi_device *spi, int is_on) { /* TODO: 根据硬件实现CS控制 */ /* 方法一:GPIO控制(最常见) */ /* int cs_gpio = spi->cs_gpio; if (is_on == BITBANG_CS_ACTIVE) { // 拉低CS(假设低电平有效) gpio_set_value(cs_gpio, 0); } else { // 拉高CS gpio_set_value(cs_gpio, 1); } */ /* 方法二:寄存器控制(硬件自动CS) */ /* if (is_on == BITBANG_CS_ACTIVE) { // 使能片选 writel(BIT(spi->chip_select), SPI_CS_ENABLE_REG); } else { // 禁用片选 writel(0, SPI_CS_ENABLE_REG); } */ } /* ============================================================ * 第六部分:设置传输参数函数(可选) * 功能:在传输前配置SPI控制器(时钟、模式、位宽等) * 说明:如果硬件需要动态配置,可实现此函数 * ============================================================ */ #if 0 static int spi_virtual_setup_transfer(struct spi_device *spi, struct spi_transfer *t) { u32 speed_hz = t ? t->speed_hz : spi->max_speed_hz; u8 bits_per_word = t ? t->bits_per_word : spi->bits_per_word; /* 1. 配置时钟频率 */ // TODO: u32 divider = clk_get_rate(master_clk) / speed_hz; // writel(divider, SPI_CLK_DIV_REG); /* 2. 配置SPI模式(CPOL、CPHA) */ // TODO: u32 mode = 0; // if (spi->mode & SPI_CPOL) mode |= SPI_CPOL_BIT; // if (spi->mode & SPI_CPHA) mode |= SPI_CPHA_BIT; // writel(mode, SPI_MODE_REG); /* 3. 配置位宽 */ // TODO: writel(bits_per_word - 1, SPI_BIT_WIDTH_REG); return 0; } #endif /* ============================================================ * 第七部分:平台设备probe函数 * 功能:驱动加载时初始化SPI控制器 * ============================================================ */ static int spi_virtual_probe(struct platform_device *pdev) { struct spi_master *master; int ret; /* 1. 分配spi_master结构体 */ /* 参数:设备指针,私有数据大小(这里分配spi_bitbang大小) */ g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang)); if (master == NULL) { dev_err(&pdev->dev, "spi_alloc_master error.\n"); return -ENOMEM; } /* 2. 获取spi_bitbang结构体(存储在master的私有数据中) */ g_virtual_bitbang = spi_master_get_devdata(master); /* 3. 初始化完成量(用于中断同步) */ init_completion(&g_xfer_done); /* 4. 配置spi_bitbang结构体(核心步骤) */ g_virtual_bitbang->master = master; /* 关联master */ g_virtual_bitbang->txrx_bufs = spi_virtual_transfer; /* 数据传输函数 */ g_virtual_bitbang->chipselect = spi_virtual_chipselect;/* 片选控制函数 */ // g_virtual_bitbang->setup_transfer = spi_virtual_setup_transfer; /* 可选 */ /* 5. 配置spi_master参数 */ master->dev.of_node = pdev->dev.of_node; /* 关联设备树节点 */ master->num_chipselect = 2; /* 支持的片选数量(可选) */ master->mode_bits = SPI_CPOL | SPI_CPHA | /* 支持的模式(可选) */ SPI_CS_HIGH | SPI_LSB_FIRST; /* 6. 硬件资源初始化(根据实际硬件添加) */ /* TODO: 申请IO内存 */ // struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // void __iomem *base = devm_ioremap_resource(&pdev->dev, res); /* TODO: 申请时钟 */ // struct clk *clk = devm_clk_get(&pdev->dev, "spi"); // clk_prepare_enable(clk); /* TODO: 申请中断 */ // int irq = platform_get_irq(pdev, 0); // devm_request_irq(&pdev->dev, irq, spi_virtual_isr, 0, "spi", master); /* TODO: 初始化硬件寄存器 */ // writel(SPI_RESET, SPI_CTRL_REG); // writel(SPI_ENABLE, SPI_CTRL_REG); /* 7. 启动spi_bitbang(创建工作队列,注册master) */ /* 注意:使用bitbang方式时用spi_bitbang_start而不是spi_register_master */ /* 推荐:使用bitbang方式(bitbang层自动管理消息队列和工作线程) */ ret = spi_bitbang_start(g_virtual_bitbang); if (ret) { dev_err(&pdev->dev, "bitbang start failed with %d\n", ret); spi_master_put(master); return ret; } dev_info(&pdev->dev, "SPI master registered successfully\n"); return 0; } /* ============================================================ * 第八部分:平台设备remove函数 * 功能:驱动卸载时清理资源 * ============================================================ */ static int spi_virtual_remove(struct platform_device *pdev) { /* 1. 停止bitbang(停止工作队列,注销master) */ spi_bitbang_stop(g_virtual_bitbang); /* 2. 释放master结构体 */ spi_master_put(g_virtual_master); /* 3. 释放硬件资源(根据实际情况添加) */ /* TODO: 禁用时钟 */ // clk_disable_unprepare(clk); /* TODO: 释放中断(使用devm_xxx则自动释放) */ // free_irq(irq, master); dev_info(&pdev->dev, "SPI master removed\n"); return 0; } /* ============================================================ * 第九部分:平台驱动结构体 * ============================================================ */ static struct platform_driver spi_virtual_driver = { .probe = spi_virtual_probe, /* 设备匹配成功时调用 */ .remove = spi_virtual_remove, /* 设备移除或模块卸载时调用 */ .driver = { .name = "virtual_spi", .of_match_table = spi_virtual_dt_ids, /* 设备树匹配表 */ }, }; /* ============================================================ * 第十部分:模块加载和卸载入口 * ============================================================ */ static int virtual_master_init(void) { return platform_driver_register(&spi_virtual_driver); } static void virtual_master_exit(void) { platform_driver_unregister(&spi_virtual_driver); } module_init(virtual_master_init); module_exit(virtual_master_exit); /* ============================================================ * 模块信息 * ============================================================ */ MODULE_DESCRIPTION("Virtual SPI bus driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("www.100ask.net");

七、SPI 控制器的Master模式和Slave模式

1、SPI 主从模式核心差异

  • 主动 vs 被动:Master(主设备)主动发起通信,Slave(从设备)只能被动响应

  • 引脚方向:Master的SS(片选)、SCLK(时钟)引脚为输出;Slave的对应引脚为输入

  • 传输控制:Master控制传输时序,Slave遵循Master的时钟和片选信号

2、设备树配置

// SPI控制器节点添加slave属性 spi1: spi@... { compatible = "..."; reg = <...>; spi-slave; // 关键:声明此为Slave模式 // 可选的slave协议子节点 slave { compatible = "spi-slave-protocol"; // 协议相关配置 }; };
  • Master模式:设备树子节点对应真实物理设备(如Flash、传感器)

  • Slave模式:设备树子节点用于选择Slave Handler协议驱动(指定如何处理数据),不代表真实物理设备,只是模拟一个SPI从设备

3、数据传输流程对比

3.1、Master 模式流程

1. 初始化(设置引脚为输出) 2. 准备数据 → 填充TX FIFO 3. 主动发起传输 4. 等待传输完成中断 5. 中断中读取RX FIFO数据 6. 传输完成

3.1、Slave 模式流程

1. 初始化(设置引脚为输入) 2. 准备数据 → 填充TX FIFO 3. 使能接收中断(等待被选中) 4. 被Master选中后自动传输 5. 中断中读取RX FIFO数据 6. 传输完成(由Master控制结束)

4、SPI控制器在Slave模式下的工作流程(以i.MX6ULL为例)

步骤1:在设备树中声明SPI控制器为slave模式,并指定一个slave handler(设备驱动程序,模拟一个从设备)。

步骤2:内核启动时,SPI控制器驱动程序(如spi-imx.c)会初始化。在初始化过程中,它会读取设备树,发现该控制器被配置为slave模式,因此会设置相应的传输函数(slave模式专用的传输函数)和slave_abort函数。

步骤3:当SPI控制器被注册时,会调用 of_register_spi_devices 来创建spi_device。对于slave模式,这些spi_device并不对应实际的硬件设备,而是对应一个spi slave handler。每个spi_device的driver_override会被设置为指定的slave handler的名字,这样在匹配时就会绑定到这个handler。

步骤4:当远端的SPI master通过SS片选信号选中本设备(即本SPI控制器)时,开始传输数据。此时,本SPI控制器(作为slave)会接收到时钟和片选信号,并触发中断。

步骤5:在中断处理函数中,会调用slave模式下的传输函数(即 spi_imx_pio_transfer_slave)来处理数据传输。这个传输函数会从TX FIFO中发送数据,并将接收到的数据存入RX FIFO。

步骤6:同时,spi slave handler中注册的回调函数(如transfer)会被调用,以便用户程序可以处理接收到的数据和准备要发送的数据。

步骤7:传输完成后,中断处理函数会完成相应的清理工作,并等待下一次传输。

注意:在slave模式下,传输是由远端的master发起的,所以slave端只能被动地等待传输。因此,slave端的传输函数中使用的是wait_for_completion_interruptible 来等待传输完成,而不能设置超时时间。如果用户程序想要取消等待,可以调用 slave_abort 函数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询