# SPI ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- # 功能概述 ## 基础定义 SPI(Serial Peripheral Interface),即串行外设接口,是由摩托罗拉(现为NXP半导体)于20世纪80年代开发的一种高速、全双工、同步的串行通信协议,用于连接微控制器及其外围设备。SPI接口主要应用在EEPROM、Flash、实时时钟、A/D转换器、数字信号处理器和数字信号解码器之间。SPI采用主从架构:每次通信由一个主设备控制,可连接一个或多个从设备,主设备负责启动和结束通信会话。蜂窝模组SPI默认作为主设备,外部设备作为从设备。 ### SPI总线的连接 ```{image} images/image_LPLTbR761oJN1RxRG3bcWBgSnle.webp :width: 941px :height: 691px :align: center ``` 通用的SPI接口一般包含4根通讯线: - SCK:主设备向从设备传输时钟信号,控制数据交换的时机以及速率; - SS/CS:用于主设备片选从设备,使被选中的从设备能够被主设备访问; - SDO/MOSI:在主设备上也被称为Tx-Channel,作为数据的出口,主要用于SPI设备发送数据; - SDI/MISO:在主设备上也被称为Rx-Channel,作为数据的入口,主要用于SPI设备接收数据。 SPI通信是全双工的,这意味着数据可以在两个方向上同时进行传输。对于SPI,这是通过MOSI(主设备输出,从设备输入)和MISO(主设备输入,从设备输出)线路实现的。 ### SPI总线的特点 - 全双工通信:数据可以同时发送和接收。 - 同步传输:使用时钟信号同步数据传输。 - 高速传输:相比I2C等协议,SPI可以达到更高的传输速率。 - 主从架构:由一个主设备控制通信,一个或多个从设备响应。 - 硬件简单:接口信号线较少,硬件实现简单。 - 无寻址机制:通过片选信号选择从设备。 SPI总线的数据传输速率可以从几千bps到几百Mbps甚至更高,具体取决于主设备和从设备的规格和性能。 ## SPI通信时序介绍 SPI的时序逻辑由时钟极性(CPOL)和时钟相位(CPHA)共同定义,决定了数据在时钟信号(SCLK)的哪个边沿被采样和更新。 ### 时钟极性(CPOL) 定义SCLK在空闲状态(无数据传输时)的电平: - **CPOL=0**:SCLK空闲时为低电平 - **CPOL=1**:SCLK空闲时为高电平 ### 时钟相位(CPHA) 定义数据在时钟的哪个边沿被采样: - **CPHA=0**:数据在SCLK的第一个边沿(从空闲状态跳变的边沿)被采样 - **CPHA=1**:数据在SCLK的第二个边沿(与第一个边沿相反的边沿)被采样 ### 四种SPI模式 结合CPOL和CPHA的不同设置,SPI协议定义了四种工作模式: #### 模式0(CPOL=0,CPHA=0) - 空闲状态下SCLK为低电平 - 数据在SCLK的上升沿采样 - 数据变化通常发生在SCLK下降沿 - 在很多低速外设中广泛应用 #### 模式1(CPOL=0,CPHA=1) - 空闲状态下SCLK为低电平 - 数据在SCLK的下降沿采样 - 数据在第一个边沿发生变化,在第二个边沿采样 - 适用于对数据稳定性要求较高的场合 #### 模式2(CPOL=1,CPHA=0) - 空闲状态下SCLK为高电平 - 数据在SCLK的下降沿采样 - 数据通常在SCLK上升沿发生变化 - 常用于某些特定硬件设备要求高时钟电平的场合 #### 模式3(CPOL=1,CPHA=1) - 空闲状态下SCLK为高电平 - 数据在SCLK的上升沿采样 - 数据变化发生在SCLK下降沿 - 适用于对时序要求较严谨的系统 ### 数据传输时序 - **片选控制**:主设备通过拉低目标从设备的CS信号,通知从设备准备进入通信状态。 - **时钟同步**:主设备产生SCLK信号,作为所有数据传输的同步时钟。 - **数据发送与接收**:在每个时钟周期内,主设备在MOSI线上发送数据的同时,从设备在MISO线上传回数据。 - **数据位序**:通常情况下,每个数据帧为8位,数据从高位到低位传输(MSB优先)。 - **结束通信**:数据传输完成后,主设备将CS信号置为高电平,结束本次通信。 ### 基本帧结构 - **片选激活**:主设备拉低CS信号,启动通信。 - **命令字节**:通常是第一个传输的字节,用于指定操作类型。 - **地址字节**:用于指定要访问的寄存器或存储单元地址。 - **数据字节**:实际要传输的数据内容。 - **片选释放**:主设备拉高CS信号,结束通信。 ### 传输特性 - **数据位宽**:通常为8位,也有部分设备支持16位或更长的数据帧。 - **传输顺序**:高位优先(MSB)或低位优先(LSB),由硬件配置决定。 - **全双工特性**:在每个时钟周期内,数据在MOSI和MISO线上同时传输。 - **无应答机制**:SPI协议没有类似I2C那样的ACK机制,数据可靠性依赖严格的时序设计。 ## SPI完整通信过程 ### 主设备写操作 **写操作流程:** 1. 主设备拉低目标从设备的CS信号,激活从设备。 2. 主设备产生SCLK时钟信号。 3. 主设备通过MOSI线发送写命令字节。 4. 主设备通过MOSI线发送目标地址字节。 5. 主设备通过MOSI线发送要写入的数据字节。 6. 从设备通过MISO线返回状态或数据(可选)。 7. 主设备拉高CS信号,结束通信。 ### 主设备读操作 **读操作流程:** 1. 主设备拉低目标从设备的CS信号,激活从设备。 2. 主设备产生SCLK时钟信号。 3. 主设备通过MOSI线发送读命令字节。 4. 主设备通过MOSI线发送目标地址字节。 5. 主设备通过MOSI线发送空字节(用于产生时钟,触发从设备返回数据)。 6. 主设备通过MISO线接收从设备返回的数据字节。 7. 主设备拉高CS信号,结束通信。 **全双工特性:** SPI的独特之处在于其全双工通信能力。在每个时钟周期内: - 主设备在MOSI线上发送数据的同时,从设备在MISO线上返回数据。 - 这种同时收发的机制极大地提高了通信效率。 ## SPI的优缺点 ### 优点 - **高速传输**:可达数十Mbps甚至更高,适合高速数据传输。 - **全双工通信**:数据可以同时发送和接收,效率高。 - **简单灵活**:协议简单,硬件实现成本低。 - **多模式支持**:四种工作模式,适应不同设备需求。 - **信号隔离友好**:单向信号,易于电隔离。 - **无复杂仲裁**:相对健壮,没有复杂的总线仲裁机制。 - **硬件加速**:现代控制器支持DMA、FIFO等高级特性。 - **广泛支持**:几乎所有微控制器都集成了SPI接口。 ### 缺点 - **引脚占用多**:至少需要4根线,相比I2C更占引脚资源。 - **无寻址机制**:需要额外的片选信号选择从设备。 - **多从设备支持差**:每个从设备需独立CS线。 - **无错误检测**:没有内置的错误检测机制如CRC。 - **无应答机制**:无法确认数据是否被正确接收。 - **短距离限制**:主要用于板内短距离通信。 - **时序要求严格**:主从设备模式必须完全一致。 - **无正式标准**:事实上的标准,但没有统一的官方标准。 ## SPI与其他协议比较 | **特性** | **SPI** | **I2C** | **UART** | | --- | --- | --- | --- | | 通信方式 | 同步、全双工 | 同步、半双工 | 异步、全双工 | | 信号线数量 | 4根(SCLK、MOSI、MISO、CS) | 2根(SDA、SCL) | 2根(TX、RX) | | 寻址方式 | 硬件片选(CS) | 7/10位地址 | 无(点对点) | | 多设备支持 | 一主多从(需独立CS) | 多主多从 | 点对点 | | 速率 | 高(数十Mbps) | 中(标准模式100 kbps) | 低(通常低于1 Mbps) | | 应答机制 | 无 | 有(ACK/NACK) | 无 | | 硬件复杂度 | 中等 | 中等 | 低 | | 典型应用 | 高速外设通信 | 低速多设备通信 | 点对点通信 | ## SPI的选择建议 ### 适合选择SPI的场景 1. **高速数据传输**:需要快速传输大量数据的应用。 2. **全双工通信**:需要同时收发数据的实时系统。 3. **短距离板内通信**:芯片间或板内模块通信。 4. **简单的主从架构**:一主多从的简单系统。 5. **高速外设接口**:Flash存储器、显示屏、ADC/DAC等。 6. **实时性要求高**:需要微秒级响应的控制系统。 7. **硬件资源充足**:引脚资源相对丰富的系统。 8. **工业环境应用**:需要电气隔离的工业产品。 ### 不适合选择SPI的场景 1. **引脚资源紧张**:只有少量GPIO可用的系统。 2. **大量设备连接**:需要连接10个以上设备的系统。 3. **长距离通信**:超过1米的板间通信。 4. **多主设备系统**:需要多个主设备共享总线。 5. **复杂错误处理**:需要高级错误检测和恢复机制。 6. **低成本要求**:对硬件成本非常敏感的应用。 7. **简单的低速通信**:仅需要简单的低速数据传输。 # SPI API ## 头文件 *qosa_spi.h* ## 函数概览 | **函数** | **说明** | | --- | --- | | *qosa_spi_init()* | 初始化指定的SPI通道,配置传输模式和时钟频率 | | *qosa_spi_deinit()* | 去初始化指定的SPI通道,释放相关资源 | | *qosa_spi_write()* | 通过指定的SPI通道发送数据 | | *qosa_spi_read()* | 通过指定的SPI通道接收数据 | | *qosa_spi_write_read()* | 执行SPI全双工读写操作,同时发送和接收数据 | | *qosa_spi_ioctl()* | 动态配置SPI参数 | ## 函数详解 ### qosa_spi_init - **功能描述** 初始化指定的SPI通道,配置传输模式和时钟频率。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_init(qosa_spi_port_e port, qosa_spi_transmit_mode_e mode, qosa_spi_clk_e clk) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | | *mode* | 输入 | *qosa_spi_transmit_mode_e* | SPI传输模式;详见 [*qosa_spi_transmit_mode_e*](#qosaspitransmitmodee) | | *clk* | 输入 | *qosa_spi_clk_e* | SPI时钟频率;详见 [*qosa_spi_clk_e*](#qosaspiclk_e) | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ### qosa_spi_deinit - **功能描述** 去初始化指定的SPI通道,释放相关资源。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_deinit(qosa_spi_port_e port) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | 需要去初始化的SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ### qosa_spi_write - **功能描述** 通过指定的SPI通道发送数据。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_write(qosa_spi_port_e port, void *txData, qosa_uint32_t length) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | | *txData* | 输入 | void * | 待发送数据的缓冲区指针 | | *length* | 输入 | qosa_uint32_t | 要发送的数据长度;单位:字节 | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ### qosa_spi_read - **功能描述** 通过指定的SPI通道接收数据。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_read(qosa_spi_port_e port, void *rxData, qosa_uint32_t length) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | | *rxData* | 输出 | void * | 存放接收数据的缓冲区指针 | | *length* | 输入 | qosa_uint32_t | 要接收的数据长度;单位:字节 | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ### qosa_spi_write_read - **功能描述** 执行SPI全双工读写操作,同时发送和接收数据。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_write_read(qosa_spi_port_e port, void *rxData, void *txData, qosa_uint32_t length) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | | *rxData* | 输出 | void * | 存放接收数据的缓冲区指针 | | *txData* | 输入 | void * | 待发送数据的缓冲区指针 | | *length* | 输入 | qosa_uint32_t | 收发数据的长度;单位:字节 | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ### qosa_spi_ioctl - **功能描述** 动态配置SPI参数。 - **函数原型** ```c qosa_spi_errcode_e qosa_spi_ioctl(qosa_spi_port_e port, qosa_spi_ioctl_cmd_e cmd, void *arg) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *port* | 输入 | *qosa_spi_port_e* | SPI通道;详见 [*qosa_spi_port_e*](#qosaspiport_e) | | *cmd* | 输入 | *qosa_spi_ioctl_cmd_e* | 参数配置命令;详见 [*qosa_spi_ioctl_cmd_e*](#qosaspiioctlcmde) | | *arg* | 输入 | void * | 命令参数指针,指向的数据类型由cmd决定;详见 [*qosa_spi_ioctl_cmd_e*](#qosaspiioctlcmde) | - **返回值说明** *QOSA_SPI_SUCCESS*:函数执行成功 其他值(详见 [*qosa_spi_errcode_e*](#qosaspierrcode_e)):函数执行失败 ## 枚举定义 ### qosa_spi_errcode_e SPI操作错误码枚举定义如下: ```c typedef enum { QOSA_SPI_SUCCESS = 0, QOSA_SPI_EXECUTE_ERR = 1 | QOSA_SPI_ERRCODE_BASE, QOSA_SPI_MEM_ADDR_NULL_ERR, QOSA_SPI_INVALID_PARAM_ERR, QOSA_SPI_WRITE_READ_ERR, } qosa_spi_errcode_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_SUCCESS* | 函数执行成功 | | *QOSA_SPI_EXECUTE_ERR* | 执行错误 | | *QOSA_SPI_MEM_ADDR_NULL_ERR* | 内存地址为空 | | *QOSA_SPI_INVALID_PARAM_ERR* | 参数无效 | | *QOSA_SPI_WRITE_READ_ERR* | 读写失败 | ### qosa_spi_clk_e SPI时钟频率配置枚举定义如下: ```c typedef enum { QOSA_SPI_CLK_INVALID = -1, QOSA_SPI_CLK_812_5KHZ = 812500, QOSA_SPI_CLK_1_625MHZ = 1625000, QOSA_SPI_CLK_3_25MHZ = 3250000, QOSA_SPI_CLK_6_5MHZ = 6500000, QOSA_SPI_CLK_13MHZ = 13000000, QOSA_SPI_CLK_26MHZ = 26000000, QOSA_SPI_CLK_52MHZ = 52000000, } qosa_spi_clk_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_CLK_INVALID* | 无效时钟频率 | | *QOSA_SPI_CLK_812_5KHZ* | 时钟频率812.5 kHz | | *QOSA_SPI_CLK_1_625MHZ* | 时钟频率1.625 MHz | | *QOSA_SPI_CLK_3_25MHZ* | 时钟频率3.25 MHz | | *QOSA_SPI_CLK_6_5MHZ* | 时钟频率6.5 MHz | | *QOSA_SPI_CLK_13MHZ* | 时钟频率13 MHz | | *QOSA_SPI_CLK_26MHZ* | 时钟频率26 MHz | | *QOSA_SPI_CLK_52MHZ* | 时钟频率52 MHz | ### qosa_spi_transmit_mode_e SPI传输模式枚举定义如下: ```c typedef enum { QOSA_SPI_TRANSMIT_POLLING = 0, QOSA_SPI_TRANSMIT_DMA, } qosa_spi_transmit_mode_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_TRANSMIT_POLLING* | FIFO读写模式 | | *QOSA_SPI_TRANSMIT_DMA* | DMA读写模式 | ### qosa_spi_port_e SPI通道选择枚举定义如下: ```c typedef enum { QOSA_SPI_PORT_NONE = -1, QOSA_SPI_PORT0, QOSA_SPI_PORT1, QOSA_SPI_PORT2, QOSA_SPI_PORT_MAX, } qosa_spi_port_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_PORT_NONE* | 无效通道(占位值) | | *QOSA_SPI_PORT0* | SPI0 | | *QOSA_SPI_PORT1* | SPI1 | | *QOSA_SPI_PORT2* | SPI2 | | *QOSA_SPI_PORT_MAX* | 通道数量上限(无效通道) | ### qosa_spi_mode_e SPI主从模式枚举定义如下: ```c typedef enum { QOSA_SPI_MODE_MASTER = 0, QOSA_SPI_MODE_SLAVE, } qosa_spi_mode_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_MODE_MASTER* | 主模式(默认) | | *QOSA_SPI_MODE_SLAVE* | 从模式 | ### qosa_spi_frame_format_e SPI时钟极性和相位配置枚举定义如下: ```c typedef enum { QOSA_SPI_CLK_CPOL0_CPHA0 = 0, QOSA_SPI_CLK_CPOL0_CPHA1, QOSA_SPI_CLK_CPOL1_CPHA0, QOSA_SPI_CLK_CPOL1_CPHA1, } qosa_spi_frame_format_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_CLK_CPOL0_CPHA0* | 时钟极性0/时钟相位0(默认) | | *QOSA_SPI_CLK_CPOL0_CPHA1* | 时钟极性0/时钟相位1 | | *QOSA_SPI_CLK_CPOL1_CPHA0* | 时钟极性1/时钟相位0 | | *QOSA_SPI_CLK_CPOL1_CPHA1* | 时钟极性1/时钟相位1 | ### qosa_spi_nss_mode_e SPI片选(NSS)信号控制模式枚举定义如下: ```c typedef enum { QOSA_SPI_NSS_MASTER_HARDWARE = 0, QOSA_SPI_NSS_MASTER_SOFTWARE, QOSA_SPI_NSS_SLAVE_HARDWARE, } qosa_spi_nss_mode_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_NSS_MASTER_HARDWARE* | 主模式硬件控制CS引脚(默认) | | *QOSA_SPI_NSS_MASTER_SOFTWARE* | 主模式软件控制CS引脚 | | *QOSA_SPI_NSS_SLAVE_HARDWARE* | 从模式硬件控制CS引脚 | ### qosa_spi_bit_order_e SPI数据传输位顺序枚举定义如下: ```c typedef enum { QOSA_SPI_MSB_FIRST = 0, QOSA_SPI_LSB_FIRST, } qosa_spi_bit_order_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_MSB_FIRST* | 收发数据以MSB为起始位(默认) | | *QOSA_SPI_LSB_FIRST* | 收发数据以LSB为起始位 | ### qosa_spi_data_width_e SPI数据传输位宽度枚举定义如下: ```c typedef enum { QOSA_SPI_WIDTH_1_BYTES = 0, QOSA_SPI_WIDTH_2_BYTES, } qosa_spi_data_width_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_WIDTH_1_BYTES* | 数据位宽度为1字节(8位)(默认) | | *QOSA_SPI_WIDTH_2_BYTES* | 数据位宽度为2字节(16位) | ### qosa_spi_ioctl_cmd_e SPI参数配置命令枚举定义如下: ```c typedef enum { QOSA_SPI_IOCTL_NONE, QOSA_SPI_IOCTL_SET_MODE, QOSA_SPI_IOCTL_SET_CLK_POLARITY_PHASE, QOSA_SPI_IOCTL_SET_NSS_MODE, QOSA_SPI_IOCTL_SET_CLOCK_FREQUENCY, QOSA_SPI_IOCTL_SET_BIT_ORDER, QOSA_SPI_IOCTL_SET_DATA_WIDTH, } qosa_spi_ioctl_cmd_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_SPI_IOCTL_NONE* | 无操作(占位值) | | *QOSA_SPI_IOCTL_SET_MODE* | 设置SPI主从模式,该命令取值类型为 *qosa_spi_mode_e* | | *QOSA_SPI_IOCTL_SET_CLK_POLARITY_PHASE* | 设置SPI时钟极性和相位,该命令取值类型为 *qosa_spi_frame_format_e* | | *QOSA_SPI_IOCTL_SET_NSS_MODE* | 设置片选(NSS)信号控制模式,该命令取值类型为 *qosa_spi_nss_mode_e* | | *QOSA_SPI_IOCTL_SET_CLOCK_FREQUENCY* | 设置SPI时钟频率 | | *QOSA_SPI_IOCTL_SET_BIT_ORDER* | 设置数据传输位顺序,该命令取值类型为 *qosa_spi_bit_order_e* | | *QOSA_SPI_IOCTL_SET_DATA_WIDTH* | 设置数据传输位宽度,该命令取值类型为 *qosa_spi_data_width_e* | # 应用逻辑流程图 ```{figure} images/board_HKJpwhqnrhqULXbr3PYctm0DnTd.jpg :align: center :alt: image ``` # 示例代码 完整示例代码请查看 https://github.com/UniRTOS/UniRTOS-Doc-Examples/blob/main/peripheral/spi/spi_demo.c