做网站竞价怎么找客户wordpress后台功能添加
2026/1/9 23:38:20 网站建设 项目流程
做网站竞价怎么找客户,wordpress后台功能添加,搜狗seo快速排名公司,wordpress前面头部目录框一、SPI协议1、什么是SPISPI#xff08;Serial Peripheral Interface#xff09;是一种同步串行通信接口#xff0c;用于短距离通信#xff0c;主要用于嵌入式系统中连接微控制器和各种外设。2、特点全双工同步通信#xff1a;同时收发数据主从架构#xff1a;1个主设备Serial Peripheral Interface是一种同步串行通信接口用于短距离通信主要用于嵌入式系统中连接微控制器和各种外设。2、特点全双工同步通信同时收发数据主从架构1个主设备多个从设备四线制标准情况MOSI主设备输出从设备输入MISO主设备输入从设备输出SCLK时钟信号由主设备产生CS片选信号选择从设备3、SPI的模式SPI有4种工作模式由时钟极性CPOL和时钟相位CPHA决定CPOLCPHA模式含义000SPICLK初始电平为低电平在第一个时钟沿采样数据011SPICLK初始电平为低电平在第二个时钟沿采样数据102SPICLK初始电平为高电平在第一个时钟沿采样数据113SPICLK初始电平为高电平在第二个时钟沿采样数据二、SPI整体框架1、重要结构体控制器驱动结构体spi_masterstruct 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 boards schematics might show it using SPI-2. software * would normally use bus_num2 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) /* cant do full duplex */ #define SPI_MASTER_NO_RX BIT(1) /* cant do buffer read */ #define SPI_MASTER_NO_TX BIT(2) /* cant 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 theres no remove-from-queue operation, or * any other request management * To a given spi_device, message queueing is pure fifo * * The masters 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_driverstruct 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_devicestruct 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_transferstruct spi_transfer { /* its 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_messagestruct 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(), thats 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; // 空属性表示CPOL1 spi-cpha; // 空属性表示CPHA1 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,dh2228fv2、lineartechnology,ltc24883、spidev设备树示例spidev0: spidev0 { compatible spidev; // 匹配spidev驱动 reg 0; // 片选0 spi-max-frequency 50000000; // 最大50MHz };3、设备文件命名规则/dev/spidevB.DBSPI总线编号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进行数据传输的重要API1.1、简易APIa、同步读写函数/** * 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、高级APIa、异步传输/** * 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_operationsopen就会成功内核会简单地为进程创建一个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结构体中的两个回调函数transfer和transfer_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 函数。

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

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

立即咨询