当前位置:首页>Linux>Linux内核 SPI驱动详解

Linux内核 SPI驱动详解

  • 2026-02-05 00:56:35
Linux内核 SPI驱动详解
在嵌入式系统里,SPI(Serial Peripheral Interface)协议宛如一座桥梁,连接着各种设备,实现高效的数据交互。它作为一种高速的同步串行通信总线,凭借着简单易用、传输速率快等优势,在嵌入式设备中广泛应用,连接着传感器、Flash、显示屏等众多外设 ,是嵌入式系统中不可或缺的一部分。

而Linux系统下的SPI驱动开发,更是嵌入式工程师的核心必备技能——掌握它,才能充分释放Linux系统在嵌入式领域的优势,实现各类复杂外设的功能落地。从智能家居的传感器联动,到工业控制的设备通信,再到物联网终端的数据传输,SPI驱动都在背后提供支撑。

一、SPI 基础

想做好SPI驱动开发,先把协议基础打牢——SPI的核心逻辑并不复杂,重点掌握总线结构、工作模式和传输特性,就能避开大部分基础坑。

SPI 协议作为 SPI 驱动开发的基石,其原理和特性对于理解驱动的工作机制至关重要。

1.1 SPI 总线结构与信号定义

SPI采用主从架构,由1个主设备(如MCU)和1个或多个从设备(如SPI Flash)组成,主设备主导通信节奏,负责生成时钟信号、选择通信的从设备,从设备被动配合主设备完成数据收发。两者通过4根核心信号线实现通信,每根线的作用都很明确:

SCK(Serial Clock):时钟线,由主设备产生,是整个通信的 “节拍器”。它的频率决定了数据传输的速率,如同音乐的节奏一样,频率越高,数据传输就越快。在通信过程中,主设备和从设备依据 SCK 的脉冲信号来同步数据的发送和接收,确保数据的准确性和一致性。例如,在一个 SPI 通信系统中,主设备按照预先设定好的 SCK 频率,不断地发出脉冲信号,从设备则紧紧跟随这个节奏,在每个脉冲的特定时刻进行数据的处理,就像乐队成员按照指挥的节拍演奏一样,有条不紊。
MOSI(Master Output Slave Input):主发从收线,是主设备向从设备发送数据的通道。主设备将需要传输的数据,按照 SCK 的时钟节拍,一位一位地通过 MOSI 线传送给从设备。就好比一条信息传递的 “高速公路”,主设备是信息的 “发送者”,将数据源源不断地发送到这条 “高速公路” 上,让从设备能够顺利接收。
MISO(Master Input Slave Output):主收从发线,,与 MOSI 相反,是从设备向主设备发送数据的通道。当从设备有数据需要反馈给主设备时,便会通过 MISO 线,在 SCK 的同步下,将数据一位一位地传输给主设备。它就像是信息反馈的 “回程路”,确保主设备能够及时获取从设备的状态和数据。
CS(Chip Select):片选线,也称为 SS(Slave Select)从设备选择线,是主设备用来选择特定从设备的控制信号。在一个 SPI 系统中,可能存在多个从设备,而 CS 线就像一把 “钥匙”,主设备通过拉低某个从设备对应的 CS 线,来 “解锁” 并选中该从设备,使其能够与主设备进行通信。需要注意的是,CS 引脚通常为低电平有效,即当 CS 线为低电平时,对应的从设备被选中;当 CS 线为高电平时,从设备处于未被选中的高阻态,不会参与通信。在一些特殊的 SPI 芯片中,还存在 MOSI 与 MISO 复用的情况,这就需要我们在硬件设计和驱动开发时格外留意,根据芯片的特性进行合理的配置和处理。

1.2 SPI 四种工作模式

SPI的四种工作模式,本质是时钟极性(CPOL)和时钟相位(CPHA)的四种组合——这两个参数直接决定数据采样的时序,主从设备必须配置相同的工作模式,否则会出现数据传输错乱。

先明确两个核心参数的定义,不用死记硬背,理解逻辑即可:

CPOL(时钟极性):决定时钟线空闲时的电平。CPOL=0时,SCK空闲为低电平;CPOL=1时,SCK空闲为高电平。

CPHA(时钟相位):决定数据采样的时钟边沿。CPHA=0时,在时钟的第一个边沿(上升沿或下降沿)采样;CPHA=1时,在时钟的第二个边沿采样。

四种工作模式的特点:

  • Mode 0:CPOL = 0,CPHA = 0。在这种模式下,空闲时 SCK 为低电平,数据在第一个边沿(上升沿)采样。例如,当主设备向从设备发送数据时,主设备在 SCK 的上升沿将数据发送到 MOSI 线上,从设备则在同一上升沿从 MOSI 线上采样接收数据;从设备向主设备发送数据时,也是在 SCK 的上升沿将数据发送到 MISO 线上,主设备在该上升沿采样接收数据。这种模式就像是一场有条不紊的接力赛,选手们在固定的时刻(上升沿)进行交接,保证了数据传输的准确性。

  • Mode 1:CPOL = 0,CPHA = 1。空闲时 SCK 为低电平,数据在第二个边沿(下降沿)采样。主设备发送数据时,在 SCK 的下降沿将数据发送到 MOSI 线,从设备在该下降沿采样接收;从设备向主设备发送数据时同理。与 Mode 0 相比,数据采样的边沿发生了变化,就像是接力赛的交接时刻从上升沿变成了下降沿,但整个数据传输的流程依然紧密有序。

  • Mode 2:CPOL = 1,CPHA = 0。空闲时 SCK 为高电平,数据在第一个边沿(下降沿)采样。主设备和从设备在 SCK 的下降沿进行数据的发送和采样接收,与 Mode 0 和 Mode 1 的空闲电平及采样边沿都有所不同,它就像是另一种节奏的接力赛,选手们在高电平的空闲状态下,等待下降沿的到来进行交接。

  • Mode 3:CPOL = 1,CPHA = 1。空闲时 SCK 为高电平,数据在第二个边沿(上升沿)采样。这种模式下的数据采样和发送边沿与 Mode 1 相反,空闲电平与 Mode 2 相同,是一种独特的时序组合,适用于特定的外设通信需求。

在实际的 SPI 驱动开发中,必须确保主设备和从设备的工作模式一致,否则就会出现数据传输错误,就像两个选手按照不同的节奏进行接力,必然会导致交接失败。例如,在连接 SPI Flash 时,很多 SPI Flash 都工作在 Mode 3 模式下,我们在驱动开发时就需要将主设备也配置为 Mode 3 模式,以保证与 SPI Flash 的正常通信。

1.3 SPI 传输核心特性

SPI 协议具有全双工和同步传输两大显著优势。全双工特性使得主设备和从设备能够在同一时刻进行数据的发送和接收,就像两个人同时打电话,双方可以同时说话和倾听,大大提高了数据传输的效率。在 SPI 通信中,主设备通过 MOSI 线发送数据的同时,能够通过 MISO 线接收从设备返回的数据,实现了双向数据的实时交互,这在一些对数据传输速度要求较高的应用场景中,如高速数据采集、实时图像传输等,具有重要的意义。

同步传输则是 SPI 协议的另一个核心优势。SPI 通过 SCK 时钟信号来同步主设备和从设备的数据传输,确保双方在同一时刻进行数据的发送和接收,就像军队行军时,士兵们按照统一的口令和步伐前进,保证了数据传输的准确性和稳定性。这种同步机制使得 SPI 在数据传输过程中能够保持较高的可靠性,减少数据传输错误的发生。

当然,SPI 协议也并非十全十美,它存在一些局限性。SPI 协议没有应答机制,主设备无法确定从设备是否成功接收了数据,就像我们寄信时,无法得知对方是否已经收到信件,这在一些对数据可靠性要求极高的应用场景中,可能会带来一定的风险。SPI 也没有流控制机制,当主设备和从设备的处理速度不匹配时,可能会导致数据丢失。

为了提高 SPI 传输的效率,SPI 支持 FIFO(First In First Out,先进先出)和 DMA(Direct Memory Access,直接内存访问)两种传输方式。FIFO 方式通过在主设备和从设备中设置 FIFO 缓冲区,将数据先存储在缓冲区中,然后再进行传输,减少了数据传输的等待时间,提高了传输效率。而 DMA 方式则更为强大,它允许外设直接访问系统内存,无需 CPU 的干预,大大提高了数据传输的速度,减轻了 CPU 的负担。在一些对时序要求严格或大数据量传输的场景中,如红外设备的数据传输,DMA 模式就发挥着重要的作用,能够实现高效、稳定的数据传输。

二、Linux SPI 驱动架构

Linux对SPI驱动做了分层设计,核心是“解耦”——将硬件操作、核心逻辑、设备适配分开,提升驱动的可移植性和复用性。整个架构分为三层,各司其职、协同工作。

2.1 Linux SPI 驱动三层架构解析

三层架构从上到下依次为:SPI设备驱动层、SPI核心层、SPI控制器驱动层,每层的职责边界清晰,不用深入内核源码,理解其作用即可上手开发。

SPI 核心层处于整个架构的中间位置,它是连接 SPI 控制器驱动层和 SPI 设备驱动层的桥梁,起着承上启下的关键作用。SPI 核心层为整个 SPI 子系统提供了统一的核心数据结构定义,就像是制定了一套通用的语言和规范,使得不同的驱动层能够基于这些标准进行交互和通信。它还负责 SPI 控制器驱动和设备驱动的注册与注销管理,就像是一个严格的 “管理员”,对所有的驱动进行统一的登记和管理,确保驱动的有序运行。在 SPI 控制器驱动和设备驱动加载到系统中时,SPI 核心层会对它们进行注册,记录相关信息;当驱动不再需要时,SPI 核心层又会负责将其注销,释放资源。

SPI 核心层向上为 SPI 设备驱动层提供了统一的接口,使得设备驱动能够方便地通过总线控制器进行数据收发。这就好比为设备驱动提供了一把 “万能钥匙”,让它们能够轻松地开启与总线控制器通信的大门,无需关心底层总线控制器的具体差异和复杂实现。向下,SPI 核心层屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口。这就像是为不同的物理总线控制器穿上了一层 “统一的外衣”,让它们在 SPI 核心层的视角下变得一致,便于管理和操作 。在不同的硬件平台上,SPI 控制器的实现方式可能各不相同,但通过 SPI 核心层的抽象和屏蔽,设备驱动无需关注这些差异,只需按照统一的接口进行操作即可,大大提高了驱动的可移植性和通用性。

SPI 控制器驱动层直接与硬件 SPI 控制器对接,是实现 SPI 通信的底层关键环节。它负责控制具体的控制器硬件,包括初始化控制器、配置工作模式、设置时钟频率、管理 DMA 和中断操作等。这就像是一个 “硬件管家”,对 SPI 控制器的各种硬件资源进行精细的管理和控制,确保其能够正常工作 。在初始化 SPI 控制器时,需要设置其工作模式,根据具体需求选择合适的 SPI 模式(如 Mode 0、Mode 1、Mode 2、Mode 3);还需要配置时钟频率,以满足不同设备的数据传输速率要求。

由于多个上层的协议驱动可能会通过控制器请求数据传输操作,SPI 控制器驱动还需要负责对这些请求进行队列管理,保证先进先出的原则。这就像是一个 “交通警察”,在面对众多的数据传输请求时,合理地安排它们的顺序,确保数据传输的高效和有序。在一个复杂的 SPI 系统中,可能同时有多个设备需要通过 SPI 控制器进行数据传输,SPI 控制器驱动会将这些请求放入队列中,按照先进先出的顺序依次处理,避免数据传输的混乱和冲突 。

SPI 设备驱动层则是针对具体的 SPI 外设进行驱动开发的层次,它负责实现设备的初始化、配置以及数据传输等功能。这就像是为每个 SPI 外设量身定制的 “专属管家”,根据设备的特点和需求,提供个性化的服务。在设备初始化阶段,需要根据设备的特性设置相关参数,如最大时钟频率、片选信号、工作模式、数据位宽等;还需要注册设备驱动,将设备与驱动关联起来,使得设备能够被系统识别和管理 。

在数据传输方面,SPI 设备驱动通过调用 SPI 核心层提供的接口,与 SPI 控制器驱动进行交互,实现与外设的数据交换。这就像是通过 SPI 核心层这座 “桥梁”,与底层的 SPI 控制器进行沟通,将数据顺利地传输到外设或从外设接收数据。当我们需要从 SPI Flash 中读取数据时,SPI 设备驱动会调用 SPI 核心层的接口,向 SPI 控制器驱动发送读取请求,SPI 控制器驱动则根据请求,控制 SPI 控制器从 SPI Flash 中读取数据,并将数据返回给 SPI 设备驱动 。

2.2 关键数据结构

Linux SPI驱动的核心,是三个关联紧密的数据结构:spi_master、spi_device、spi_driver,掌握它们的定义和关联,就能理清驱动的核心逻辑。

spi_master结构体用于描述 SPI 主控制器的属性和操作方法,它是 SPI 控制器驱动层的核心数据结构。在 Linux 内核中,spi_master结构体的定义如下:

struct spi_master {    struct device dev;    struct list_head list;    s16 bus_num;    u16 num_chipselect;    int (*setup)(struct spi_device *spi);    int (*transfer)(struct spi_device *spi, struct spi_message *mesg);    void (*cleanup)(struct spi_device *spi);    // 其他成员...};

其中,dev是一个struct device类型的成员,用于将 SPI 主控制器纳入 Linux 设备模型中进行管理,就像是给 SPI 主控制器颁发了一张 “设备身份证”,让它能够在 Linux 设备的大家庭中被识别和管理。list是一个链表头,用于将多个 SPI 主控制器连接成一个链表,方便系统对它们进行统一的管理和调度,就像是一条 “纽带”,将各个 SPI 主控制器串联在一起 。

bus_num表示 SPI 主控制器对应的总线号,用于标识不同的 SPI 总线,就像是给每一条 SPI 总线分配了一个独特的 “门牌号”,方便系统进行区分和定位。num_chipselect表示该控制器支持的片选信号数量,即能连接的从设备数量,它决定了这个 SPI 主控制器能够同时管理多少个从设备,就像是一个 “容量指标”,表明了 SPI 主控制器的 “管理能力” 。

setup函数指针用于设置 SPI 设备的工作模式、时钟频率等参数,在设备初始化或参数变更时被调用,就像是一个 “设备配置专家”,根据需求对 SPI 设备进行精准的配置。transfer函数指针则是实现 SPI 数据传输的核心函数,它负责将数据从主设备发送到从设备,或者从从设备接收数据到主设备,是数据传输的 “执行者”,在数据传输过程中起着关键的作用 。cleanup函数指针用于在设备注销时进行资源清理工作,释放设备占用的资源,就像是一个 “资源回收员”,在设备不再使用时,将其占用的资源归还给系统。

spi_device结构体用于描述具体的 SPI 外设,它记录了设备的各种通信参数和状态信息,是 SPI 设备驱动层与硬件设备进行交互的关键数据结构。其定义如下:

struct spi_device {    struct device dev;    struct spi_master *master;    u32 max_speed_hz;    u8 chip_select;    u8 mode;    u8 bits_per_word;    int irq;    void *controller_state;    void *controller_data;    char modalias[SPI_NAME_SIZE];    // 其他成员...};

dev同样是用于将 SPI 设备纳入 Linux 设备模型的成员,为 SPI 设备在系统中提供了一个 “身份标识”。master指针指向该设备所连接的 SPI 主控制器,建立了设备与控制器之间的关联,就像是一条 “连接纽带”,让 SPI 设备知道自己应该与哪个主控制器进行通信 。

max_speed_hz表示设备支持的最大时钟频率,它决定了设备的数据传输速率上限,就像是汽车的 “最高时速”,限制了数据传输的速度。chip_select表示片选信号,用于选择具体的从设备,在一个 SPI 总线上可能连接多个从设备,通过片选信号来确定与哪个从设备进行通信,就像是一把 “钥匙”,打开与特定从设备通信的通道 。

mode表示 SPI 设备的工作模式,取值可以是 SPI_MODE_0、SPI_MODE_1、SPI_MODE_2、SPI_MODE_3 等,不同的工作模式对应着不同的时钟极性和相位,决定了数据传输的时序,就像是不同的 “舞蹈节奏”,设备需要按照特定的模式进行数据传输 。bits_per_word表示每次传输的数据位宽,常见的有 8 位、16 位等,它决定了一次传输中能够携带的数据量,就像是一次运输的 “载货量” 。

irq表示设备的中断号,用于在设备需要中断服务时向系统发出请求,就像是一个 “紧急呼叫按钮”,当设备有紧急事情需要处理时,通过中断号向系统发出信号。controller_statecontroller_data分别用于保存控制器的运行状态和特定板子为控制器定义的数据,为设备与控制器之间的交互提供了必要的信息支持 。modalias是设备的别名,用于设备和驱动的匹配,在设备驱动注册时,通过比较modalias和驱动的名称或 ID 来确定是否匹配,就像是一把 “匹配钥匙”,找到与之对应的驱动 。

spi_driver结构体用于描述 SPI 设备驱动的相关信息和操作方法,它是 SPI 设备驱动层的核心数据结构,定义如下:

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);    int (*suspend)(struct spi_device *spi, pm_message_t mesg);    int (*resume)(struct spi_device *spi);    struct device_driver driver;};

id_table是一个设备 ID 表,用于存储支持的设备 ID,在设备和驱动匹配时,通过比较设备的 ID 与id_table中的 ID 来确定是否匹配,就像是一个 “设备 ID 库”,驱动通过它来识别支持的设备 。

probe函数是设备驱动与设备匹配成功后调用的函数,用于完成设备的初始化工作,如分配资源、设置设备参数等,就像是设备的 “初始化向导”,在设备与驱动建立联系后,引导设备完成初始化过程 。remove函数在设备驱动被移除时调用,用于释放设备占用的资源,如注销中断、释放内存等,是设备资源的 “清理工”,在设备驱动不再使用时,将设备占用的资源清理干净 。

shutdown函数用于在系统关机或设备关闭时进行一些必要的操作,如停止设备运行、保存设备状态等,就像是设备的 “关机助手”,在系统或设备关闭时,协助设备做好收尾工作 。suspendresume函数分别用于设备的挂起和恢复操作,在系统进入低功耗模式或设备需要暂停工作时,调用suspend函数将设备挂起,节省功耗;当系统恢复正常或设备需要重新工作时,调用resume函数将设备恢复到正常状态,就像是设备的 “休眠与唤醒控制器”,控制设备的休眠和唤醒状态 。

driver是一个struct device_driver类型的成员,它包含了驱动的基本信息,如驱动名称、所属模块等,为驱动在 Linux 设备模型中提供了一个 “身份描述”,让系统能够识别和管理该驱动 。

这三个关键数据结构之间存在着紧密的关联关系。spi_device通过master指针与spi_master建立联系,表明设备所连接的主控制器;spi_driver通过probe函数与spi_device进行匹配和初始化,建立驱动与设备之间的对应关系。它们相互协作,共同完成了 SPI 设备的驱动开发和数据传输工作,是 Linux SPI 驱动开发中不可或缺的核心要素 。

三、Linux SPI 驱动开发实战

理论知识储备充足之后,进入实战环节,从设备树配置到 SPI 设备驱动编写,再到应用层测试,带大家掌握 Linux 下 SPI 驱动的开发技能。

3.1 设备树配置:SPI 设备的 “身份信息”

在基于 Linux 的嵌入式开发中,设备树(Device Tree)起着至关重要的作用,它就像是一份详细的设备 “档案”,记录了硬件设备的各种信息,包括 SPI 设备。通过设备树,我们可以清晰地描述 SPI 设备的硬件连接、属性配置等信息,使得内核能够准确地识别和管理 SPI 设备。接下来,我们将详细介绍如何在设备树中配置 SPI 设备。

在设备树中,首先要确保 SPI 控制器节点被正确启用。这就好比打开一扇门,让 SPI 设备能够与系统进行通信。以 ZYNQ/MPSOC 平台为例,在设备树文件中,SPI 控制器节点通常类似如下配置:

&spi0 {    status = "okay";    pinctrl-names = "default";    pinctrl-0 = <&spi0_pins>;};

在这段配置中,status = "okay"表示启用该 SPI 控制器节点,就像是给 SPI 控制器颁发了一张 “通行证”,让它能够正常工作。pinctrl-namespinctrl-0用于指定引脚控制信息,就像是给 SPI 控制器安排了合适的 “接线员”,确保引脚连接正确无误 。pinctrl-0所引用的&spi0_pins在设备树的其他部分会有具体的引脚配置定义,它会明确指定 SPI 控制器的各个引脚与硬件的连接关系,例如 SCK、MOSI、MISO、CS 等引脚分别连接到哪个 GPIO 引脚,确保硬件连接的准确性 。

接下来,在 SPI 控制器节点下添加 SPI 外设子节点,用于描述具体的 SPI 设备。以连接一个 SPI Flash 为例,子节点配置如下:

&spi0 {    status = "okay";    pinctrl-names = "default";    pinctrl-0 = <&spi0_pins>;    flash@0 {        compatible = "winbond,w25q128";        spi-max-frequency = <10000000>;        reg = <0>;        spi-mode = <3>;        #address-cells = <1>;        #size-cells = <1>;    };};

其中,compatible属性是设备树匹配驱动的关键,它就像是一把 “钥匙”,用于寻找与之对应的驱动程序。在这个例子中,compatible = "winbond,w25q128"表示该设备与winbond,w25q128兼容,内核会根据这个属性在驱动列表中查找匹配的驱动 。

spi-max-frequency属性用于设置 SPI 通信的最大频率,单位为 Hz。这里设置为10000000,即 10MHz,它决定了 SPI 设备的数据传输速率,就像是给 SPI 通信设置了一个 “速度上限”,不同的 SPI 设备可能支持不同的最大频率,需要根据设备手册进行合理设置 。

reg属性指定设备的片选号,这里设置为0,表示该 SPI Flash 使用 SPI 控制器的 0 号片选信号,就像是给 SPI Flash 分配了一个独特的 “身份标识”,让 SPI 控制器能够准确地选择它进行通信 。

spi-mode属性用于设置 SPI 的工作模式,取值范围为 0 - 3,对应前面介绍的四种工作模式。这里设置为3,即 CPOL = 1,CPHA = 1 的工作模式,需要根据 SPI 设备的要求进行设置,确保主设备和从设备的工作模式一致,才能保证数据传输的正确性 。

#address-cells#size-cells属性用于指定子节点中reg属性的地址和大小的表示方式,在这个例子中,它们都设置为1,表示reg属性用一个 32 位无符号整数来表示地址和大小,这是一种常见的设置方式,用于描述设备在总线上的地址和占用空间大小 。

在实际操作中,我们需要根据硬件原理图和 SPI 设备的手册,准确地填写这些属性值。如果硬件连接发生变化,或者更换了不同型号的 SPI 设备,相应的设备树配置也需要进行调整。例如,如果 SPI Flash 的型号变为gd25q128,则compatible属性需要修改为"gd,gd25q128";如果需要提高数据传输速率,可根据 SPI Flash 支持的最大频率,适当增大spi-max-frequency的值,但要注意不能超过设备的极限 。

3.2 SPI 设备驱动编写:从注册到数据传输

完成设备树配置后,接下来就是编写 SPI 设备驱动代码。这是实现 SPI 设备功能的核心部分,就像是为 SPI 设备编写一个专属的 “控制程序”,让它能够按照我们的需求进行工作。SPI 设备驱动的编写主要包括驱动注册、设备信息获取与配置以及数据传输等关键步骤。

首先,我们需要定义一个spi_driver结构体,并实现其中的关键函数,如proberemove等,然后通过spi_register_driver函数将驱动注册到内核中。以下是一个简单的 SPI 设备驱动框架示例:

#include<linux/module.h>#include<linux/spi/spi.h>static const struct spi_device_id spi_device_ids[] = {    {"winbond,w25q128"0},    {}};MODULE_DEVICE_TABLE(spi, spi_device_ids);static struct spi_driver spi_device_driver = {   .driver = {       .name = "spi_w25q128_driver",       .owner = THIS_MODULE,    },   .id_table = spi_device_ids,   .probe = spi_device_probe,   .remove = spi_device_remove,};staticint __init spi_device_driver_init(void){    return spi_register_driver(&spi_device_driver);}staticvoid __exit spi_device_driver_exit(void){    spi_unregister_driver(&spi_device_driver);}module_init(spi_device_driver_init);module_exit(spi_device_driver_exit);MODULE_LICENSE("GPL");

在这个示例中,spi_device_ids数组定义了该驱动支持的设备 ID,spi_device_driver结构体定义了 SPI 设备驱动的相关信息,包括驱动名称、设备 ID 表、probe函数和remove函数等。spi_device_driver_init函数在驱动模块加载时被调用,通过spi_register_driver函数将驱动注册到内核中;spi_device_driver_exit函数在驱动模块卸载时被调用,通过spi_unregister_driver函数将驱动从内核中注销 。

probe函数是驱动与设备匹配成功后调用的关键函数,用于完成设备的初始化工作,包括获取设备信息、配置通信模式与速率等。以下是probe函数的示例代码:

staticintspi_device_probe(struct spi_device *spi) {    int ret;    // 分配设备私有数据结构体    struct spi_device_private *priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);    if (!priv)        return -ENOMEM;    spi_set_drvdata(spi, priv);    // 获取设备信息    priv->max_speed_hz = spi->max_speed_hz;    priv->mode = spi->mode;    priv->bits_per_word = spi->bits_per_word;    // 配置通信模式与速率    spi->mode = SPI_MODE_3;    spi->max_speed_hz = 10000000;    ret = spi_setup(spi);    if (ret) {        dev_err(&spi->dev, "spi setup failed: %d\n", ret);        return ret;    }    dev_info(&spi->dev, "SPI device %s probed successfully\n", spi->modalias);    return 0;}

probe函数中,首先通过devm_kzalloc函数分配一个设备私有数据结构体priv,用于存储设备的一些私有信息,就像是为设备创建了一个专属的 “信息仓库” 。然后,将privspi设备关联起来,通过spi_set_drvdata函数将priv设置为spi设备的私有数据 。

接着,获取设备的一些信息,如最大时钟频率、工作模式、数据位宽等,并存储到priv结构体中。之后,根据实际需求配置 SPI 设备的通信模式和速率,这里将工作模式设置为 SPI_MODE_3,最大时钟频率设置为 10MHz,并通过spi_setup函数使配置生效 。如果spi_setup函数执行失败,会返回错误信息,并通过dev_err函数打印错误日志,提示用户配置失败;如果配置成功,则通过dev_info函数打印设备探测成功的信息 。

数据传输是 SPI 设备驱动的核心功能,Linux 提供了spi_transferspi_message结构体来实现 SPI 数据传输。spi_transfer结构体用于描述一次数据传输的具体信息,如发送缓冲区、接收缓冲区、传输长度等;spi_message结构体则用于管理一次完整的 SPI 数据传输操作,它可以包含多个spi_transfer结构体,就像是一个 “数据传输任务包”,将多个数据传输任务整合在一起 。

以下是一个简单的同步传输示例代码,实现从 SPI 设备读取数据:

staticssize_tspi_device_read(struct file *filp, char __user *buf, size_t count, loff_t *off){    struct spi_device *spi = filp->private_data;    struct spi_device_private *priv = spi_get_drvdata(spi);    // 构建SPI传输消息结构体    struct spi_transfer tr = {       .tx_buf = NULL,       .rx_buf = priv->rx_buffer,       .len = count,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&tr, &msg);    int ret = spi_sync(spi, &msg);    if (ret) {        dev_err(&spi->dev, "SPI read failed: %d\n", ret);        return ret;    }    // 将读取到的数据从内核缓冲区复制到用户空间    ret = copy_to_user(buf, priv->rx_buffer, count);    if (ret) {        dev_err(&spi->dev, "copy to user failed: %d\n", ret);        return -EFAULT;    }    return count;}

在这个示例中,spi_device_read函数是设备驱动提供的读取接口,当用户空间调用read函数时,会触发该函数的执行 。首先,获取spi设备和其私有数据结构体priv。然后,构建一个spi_transfer结构体tr,设置其接收缓冲区为priv->rx_buffer,传输长度为用户请求读取的数据长度count,这里tx_buf设置为NULL,表示只进行接收操作 。

接着,初始化一个spi_message结构体msg,并将tr添加到msg中,通过spi_message_add_tail函数将tr挂载到msg的传输链表中 。之后,调用spi_sync函数进行同步数据传输,该函数会阻塞当前线程,直到数据传输完成 。如果传输成功,ret的值为 0;如果传输失败,ret会返回相应的错误码 。

如果数据传输成功,再通过copy_to_user函数将读取到的数据从内核缓冲区priv->rx_buffer复制到用户空间的缓冲区buf中。如果复制过程中出现错误,会通过dev_err函数打印错误日志,并返回-EFAULT错误码;如果复制成功,则返回实际读取的数据长度count 。

除了同步传输,SPI 设备驱动还支持异步传输。异步传输适用于对数据传输实时性要求较高的场景,在数据传输过程中,不会阻塞当前线程,而是通过回调函数通知传输结果 。以下是一个简单的异步传输示例代码:

staticvoidspi_async_callback(struct spi_message *msg){    struct spi_transfer *transfer = list_entry(msg->transfers.next, struct spi_transfer, transfer_list);    struct spi_device *spi = msg->spi;    struct spi_device_private *priv = spi_get_drvdata(spi);    if (msg->status) {        dev_err(&spi->dev, "SPI async transfer failed: %d\n", msg->status);    } else {        // 处理传输成功的数据        dev_info(&spi->dev, "SPI async transfer success\n");    }}staticintspi_device_async_write(struct file *filp, constchar __user *buf, size_t count, loff_t *off){    struct spi_device *spi = filp->private_data;    struct spi_device_private *priv = spi_get_drvdata(spi);    // 构建SPI传输消息结构体    struct spi_transfer tr = {       .tx_buf = priv->tx_buffer,       .rx_buf = NULL,       .len = count,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&tr, &msg);    msg.complete = spi_async_callback;    return spi_async(spi, &msg);}

在这个示例中,spi_async_callback函数是异步传输的回调函数,当数据传输完成时,会被调用 。在回调函数中,首先获取传输结果,如果传输失败,会通过dev_err函数打印错误日志;如果传输成功,则可以在这里处理传输成功的数据 。

spi_device_async_write函数是设备驱动提供的异步写入接口,当用户空间调用异步写入函数时,会触发该函数的执行 。首先,构建spi_transferspi_message结构体,设置发送缓冲区为priv->tx_buffer,传输长度为用户请求写入的数据长度count,这里rx_buf设置为NULL,表示只进行发送操作 。

然后,将回调函数spi_async_callback赋值给msg.complete,通过spi_async函数发起异步数据传输 。spi_async函数会立即返回,不会阻塞当前线程,数据传输完成后,会自动调用回调函数spi_async_callback通知传输结果 。

在实际应用中,我们需要根据具体的需求选择合适的传输方式。同步传输适用于数据量较小、对实时性要求不高的场景,它的实现简单,易于理解和调试;异步传输则适用于数据量较大、对实时性要求较高的场景,能够充分利用系统资源,提高数据传输效率 。

在某些情况下,我们可能需要进行多次数据传输,并且希望将这些传输合并为一个原子操作,以避免数据丢失或混乱。例如,在与 SPI Flash 进行通信时,可能需要先发送一个擦除命令,然后再进行写入操作,如果这两个操作分开进行,可能会因为其他中断或任务的干扰,导致数据丢失或写入错误 。为了解决这个问题,我们可以将多个spi_transfer结构体合并到一个spi_message中,通过一次传输操作完成多个数据传输任务 。

以下是一个多transfer合并的示例代码:

staticintspi_device_multi_transfer(struct spi_device *spi, constvoid *tx_buf1, size_t len1, constvoid *tx_buf2, size_t len2){    struct spi_transfer tr1 = {       .tx_buf = tx_buf1,       .rx_buf = NULL,       .len = len1,    };    struct spi_transfer tr2 = {       .tx_buf = tx_buf2,       .rx_buf = NULL,       .len = len2,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&tr1, &msg);    spi_message_add_tail(&tr2, &msg);    return spi_sync(spi, &msg);}

在这个示例中,spi_device_multi_transfer函数实现了将两个spi_transfer结构体合并到一个spi_message中进行同步传输 。首先,构建两个spi_transfer结构体tr1tr2,分别设置它们的发送缓冲区和传输长度 。然后,初始化一个spi_message结构体msg,并将tr1tr2依次添加到msg中,通过spi_message_add_tail函数将它们挂载到msg的传输链表中 。最后,调用spi_sync函数进行同步数据传输,确保这两个数据传输操作作为一个原子操作完成,避免数据丢失或混乱 。

3.3 应用层测试

驱动加载完成后,需通过应用层代码验证功能,Linux提供spidev驱动,可直接在用户态操作SPI设备,无需重复开发应用层接口。

测试流程:打开SPI设备节点→配置通信参数→执行读写操作→关闭节点,示例代码如下(C语言):

#include<stdio.h>#include<fcntl.h>#include<linux/spi/spidev.h>#include<sys/ioctl.h>#define SPI_DEVICE "/dev/spidev0.0"// 设备节点(总线0,片选0)intmain(){    int fd, ret;    u8 tx_buf[] = {0x030x000x000x00};  // 读取指令+地址(W25Q128)    u8 rx_buf[10] = {0};                     // 接收缓冲区    // 1. 打开SPI设备节点    fd = open(SPI_DEVICE, O_RDWR);    if (fd < 0) {        perror("打开设备失败");        return -1;    }    // 2. 配置SPI参数(模式、位宽、频率)    u8 mode = SPI_MODE_3;    u8 bits = 8;    u32 speed = 10000000;    // 配置工作模式    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);    if (ret < 0) { perror("配置模式失败"); goto err; }    // 配置数据位宽    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);    if (ret < 0) { perror("配置位宽失败"); goto err; }    // 配置传输频率    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);    if (ret < 0) { perror("配置频率失败"); goto err; }    // 3. 执行读写传输    struct spi_ioc_transfer tr = {        .tx_buf = (unsigned long)tx_buf,        .rx_buf = (unsigned long)rx_buf,        .len = sizeof(tx_buf),        .speed_hz = speed,        .bits_per_word = bits,    };    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);    if (ret < 0) {        perror("传输失败");        goto err;    }    // 打印接收结果    printf("读取数据:");    for (int i = 0; i < sizeof(rx_buf); i++) {        printf("%02x ", rx_buf[i]);    }    printf("\n");err:    close(fd);    return ret;}

编译运行:将代码保存为spi_test.c,使用交叉编译器编译(适配目标平台),上传到开发板,运行后若能读取到SPI Flash的数据,说明驱动功能正常。

四、SPI 驱动调试排坑宝典

SPI驱动开发中,通信失败是常有的事,优先排查硬件,再优化软件,结合实战经验,整理了高频问题及解决方案,帮大家快速排坑。

4.1 硬件层面调试

硬件问题是通信失败的主要原因,示波器能直观看到信号波形,快速定位问题,重点检查4根信号线(SCK、MOSI、MISO、CS)

检查 SCK 时钟线的波形,测量其频率是否符合设定值。如果 SCK 频率与设备树或驱动中配置的频率不一致,可能会导致数据传输错误。在设备树中配置 SPI 时钟频率为 10MHz,但通过示波器测量发现 SCK 频率只有 5MHz,这时就需要检查时钟配置是否正确,是否存在硬件时钟分频等问题 。

查看 CS 片选线的波形,确认其是否在通信时被拉低。CS 引脚通常为低电平有效,在主设备与从设备通信时,CS 线应被拉低,以选中对应的从设备。如果 CS 线在通信时没有被拉低,可能是硬件连接错误,比如 CS 引脚与主设备或从设备的连接出现断路;也可能是驱动中片选信号的配置有误,需要仔细检查硬件连接和驱动配置 。

观察 MOSI 和 MISO 数据线的波形,判断数据是否正常传输。正常情况下,MOSI 线上的数据应按照 SCK 的时钟节拍,一位一位地传输到从设备;MISO 线上的数据应从从设备返回主设备,且与 SCK 时钟同步。如果 MOSI 或 MISO 波形出现异常,如数据丢失、波形不稳定等,可能是硬件连接问题,如 MOSI 或 MISO 引脚虚焊、短路;也可能是 SPI 设备的工作模式不匹配,需要检查硬件连接和工作模式配置 。

除了使用示波器测量信号波形,还需要排查其他硬件隐患。检查 SPI 设备的引脚连接是否正确,是否存在引脚短路、断路等问题。有些芯片的引脚功能可能会复用,需要确保 SPI 相关引脚被正确配置为 SPI 功能,而不是其他功能 。例如,在一些芯片中,MOSI 和 MISO 引脚可能会与其他功能引脚复用,如果配置错误,就会导致 SPI 通信异常 。

确保 SPI 设备的芯片供电正常,电压是否符合芯片的工作要求。如果芯片供电不足或电压不稳定,可能会导致芯片工作异常,影响 SPI 通信。在调试过程中,发现写寄存器的信号正常,也有应答信号,但读不了数据,最后检查发现是芯片的某一路电源没有供电,将这一路电源供电后,通信就恢复正常了 。

检查 SPI 总线上的上拉下拉电阻配置是否正确。上拉下拉电阻的作用是确保信号在空闲状态下保持稳定的电平,防止信号干扰。如果上拉下拉电阻配置不当,可能会导致信号电平不稳定,影响数据传输。在某些芯片中,需要将特定引脚通过 100k 到 220k 的电阻进行上拉或下拉,以保证芯片的正常工作 。

4.2 软件层面排坑

硬件无问题后,重点排查软件配置和代码逻辑,需要仔细核对设备树配置、驱动参数设置以及代码逻辑,确保软件层面的正确性 。

首先,要核对设备树配置与硬件实际连接是否一致。设备树是 SPI 设备在 Linux 系统中的 “身份信息”,如果设备树配置错误,会导致内核无法正确识别和管理 SPI 设备。在设备树中,需要检查 SPI 控制器节点是否正确启用,如status = "okay";检查 SPI 外设子节点的属性配置是否正确,如compatible属性是否与实际设备匹配、spi-max-frequency是否设置正确、reg是否指定了正确的片选号等 。在 Zynq 平台上,配置 SPI 设备时,需要确保设备树中的 SPI 控制器节点和外设子节点的配置与硬件原理图一致,否则会导致 SPI 通信失败 。

检查驱动中参数设置是否正确。在 SPI 设备驱动中,需要设置一些关键参数,如最大时钟频率、工作模式、数据位宽等。这些参数应与设备树中的配置以及硬件设备的要求一致。在probe函数中,会获取设备的一些信息并进行配置,如priv->max_speed_hz = spi->max_speed_hz;,需要确保spi->max_speed_hz的值符合硬件设备的要求,否则可能会导致数据传输错误 。

验证控制器驱动适配问题。在一些情况下,SPI 控制器驱动可能存在适配问题,导致数据传输异常。在进行多transfer传输时,可能会出现数据丢失的情况。这时,可以尝试将多个transfer合并为一个,看是否能解决问题。例如,在读取 SPI Flash 寄存器值时,原本使用三个spi_transfer进行传输,发现第二个和第三个spi_transfer的数据全部丢失,后来将其合并为一个spi_transfer,读回来的数据就正常了 。这可能是因为芯片厂商的 SPI 控制器驱动实现存在缺陷,没有正确处理多transfer传输,通过合并transfer可以绕过这个问题 。

4.3 典型问题解决方案

整理3个高频问题的解决方案:

当遇到读数据全零的问题时,可能是 MISO 引脚被设置为高阻态,导致无法读取数据。在调试过程中,通过示波器测量发现 MISO 信号为 0,查看芯片手册才知道该引脚被设置成高阻态了,需要拉高另一个引脚才能作为 MISO 功能 。这时,根据芯片手册的要求,拉高相应引脚,即可解决读数据全零的问题 。

写寄存器有应答信号,但读不到数据,可能是硬件电源问题。如前文所述,在检查写寄存器信号正常且有应答信号,但读不到数据时,通过测量芯片电源引脚,发现某一路电源没有供电,将这一路电源供电后,通信恢复正常 。这表明在遇到通信问题时,即使信号正常,也不能忽视硬件电源的检查 。

如果出现高低位传输顺序错误的情况,可能是驱动中高低位配置有误。在调试一个降噪模块时,发现发送的信号和接收的应答波形不一致,经检查发现是高位或低位先传输的配置错误。与 cpu 厂商工作人员联系后,调整了高低位传输配置,问题得到解决 。在 SPI 驱动开发中,需要注意数据传输顺序的配置,确保与硬件设备的要求一致 。

五、Linux SPI 驱动典型应用场景

5.1 嵌入式传感器数据采集

在嵌入式系统中,SPI 驱动在传感器数据采集中发挥着关键作用,尤其是在温度、压力、加速度等传感器的应用场景中。以温度传感器为例,许多高精度的温度传感器如 MAX6675,采用 SPI 接口与主控制器进行通信。通过 SPI 驱动程序,主设备能够向传感器发送指令,查询当前的温度数据;传感器则在接收到指令后,按照 SPI 协议的时序要求,将温度数据通过 MISO 线返回给主设备 。

在这个过程中,SPI 驱动的工作模式配置至关重要。由于不同的传感器对通信时序的要求不同,需要根据传感器的规格手册,匹配对应的 SPI 工作模式。例如,MAX6675 工作在 SPI Mode 0 模式下,主设备在配置 SPI 驱动时,就必须将工作模式设置为 Mode 0,确保 SCK 时钟信号的极性和相位与传感器的要求一致,这样才能保证数据的准确传输 。

在工业自动化领域,压力传感器用于监测管道内的压力变化,为生产过程提供重要的数据支持。通过 SPI 驱动实现压力传感器的数据采集,能够实时获取压力数据,并根据预设的阈值进行报警或控制操作,保障生产的安全和稳定 。在智能家居系统中,温湿度传感器通过 SPI 接口与主控芯片通信,SPI 驱动将采集到的温湿度数据传输给系统,用于智能调节室内环境,提升居住的舒适度 。

5.2 SPI 存储设备驱动开发

SPI 存储设备如 SPI Flash、EEPROM 等,在嵌入式系统中广泛应用于数据存储和程序存储。开发这些存储设备的驱动程序,是实现数据可靠存储和读取的关键 。

以 SPI Flash 为例,在驱动开发过程中,需要通过 SPI 总线实现对存储芯片的擦除、读写操作。在擦除操作时,驱动程序向 SPI Flash 发送擦除指令,按照 SPI 协议的时序要求,控制 SCK、MOSI 等信号,确保擦除指令准确无误地传输到芯片中 。在读取操作时,驱动程序先发送读取指令和地址信息,然后接收 SPI Flash 返回的数据;写入操作则相反,先发送写入指令和地址,再将需要写入的数据通过 MOSI 线传输到芯片中 。

为了提升大数据量读写的效率,SPI 存储设备驱动通常会结合 DMA 传输模式。DMA 允许存储设备直接访问系统内存,无需 CPU 的干预,大大提高了数据传输的速度。在对 SPI Flash 进行固件升级时,需要将大量的固件数据写入 Flash 中,使用 DMA 模式能够显著缩短升级时间,提高系统的稳定性和可靠性 。

5.3 SPI 屏幕驱动适配

在嵌入式显示领域,SPI 屏幕以其接口简单、成本低廉等优势,被广泛应用于各种小型显示设备中,如智能手表、工业控制屏等。以 RK3568、RK3588 等平台为例,适配 SPI 屏幕驱动需要进行一系列细致的工作 。

首先,需要通过设备树配置 SPI 屏幕的相关参数,包括 SPI 控制器节点的启用、屏幕子节点的属性设置等。在设备树中,要准确设置 SPI 屏幕的最大时钟频率、工作模式、数据位宽等参数,确保与屏幕的硬件规格一致 。

在驱动程序中,需要控制屏幕的显示时序,实现图像数据的传输与显示。这涉及到对 SPI 传输函数的调用,将显存中的图像数据按照 SPI 协议的要求,逐帧传输到 SPI 屏幕的驱动芯片中 。在传输过程中,要注意数据的格式和顺序,确保屏幕能够正确解析和显示图像 。

引脚功能复用配置也是 SPI 屏幕驱动适配的关键环节。在 RK3568、RK3588 等平台上,许多引脚具有复用功能,需要在设备树中正确配置引脚的功能,将其设置为 SPI 功能,以确保 SPI 屏幕能够正常工作 。如果引脚功能配置错误,可能会导致 SPI 通信异常,屏幕无法显示图像 。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-07 17:04:00 HTTP/2.0 GET : https://f.mffb.com.cn/a/472602.html
  2. 运行时间 : 0.205579s [ 吞吐率:4.86req/s ] 内存消耗:4,725.17kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=d22d760e4d14b58017e645db58ffe49b
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000844s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001774s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000728s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000677s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001555s ]
  6. SELECT * FROM `set` [ RunTime:0.000600s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001812s ]
  8. SELECT * FROM `article` WHERE `id` = 472602 LIMIT 1 [ RunTime:0.001653s ]
  9. UPDATE `article` SET `lasttime` = 1770455040 WHERE `id` = 472602 [ RunTime:0.054060s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.008072s ]
  11. SELECT * FROM `article` WHERE `id` < 472602 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001415s ]
  12. SELECT * FROM `article` WHERE `id` > 472602 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.007301s ]
  13. SELECT * FROM `article` WHERE `id` < 472602 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.003439s ]
  14. SELECT * FROM `article` WHERE `id` < 472602 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.026461s ]
  15. SELECT * FROM `article` WHERE `id` < 472602 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.008653s ]
0.209492s