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总线的连接¶
通用的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完整通信过程¶
主设备写操作¶
写操作流程:
主设备拉低目标从设备的CS信号,激活从设备。
主设备产生SCLK时钟信号。
主设备通过MOSI线发送写命令字节。
主设备通过MOSI线发送目标地址字节。
主设备通过MOSI线发送要写入的数据字节。
从设备通过MISO线返回状态或数据(可选)。
主设备拉高CS信号,结束通信。
主设备读操作¶
读操作流程:
主设备拉低目标从设备的CS信号,激活从设备。
主设备产生SCLK时钟信号。
主设备通过MOSI线发送读命令字节。
主设备通过MOSI线发送目标地址字节。
主设备通过MOSI线发送空字节(用于产生时钟,触发从设备返回数据)。
主设备通过MISO线接收从设备返回的数据字节。
主设备拉高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的场景¶
高速数据传输:需要快速传输大量数据的应用。
全双工通信:需要同时收发数据的实时系统。
短距离板内通信:芯片间或板内模块通信。
简单的主从架构:一主多从的简单系统。
高速外设接口:Flash存储器、显示屏、ADC/DAC等。
实时性要求高:需要微秒级响应的控制系统。
硬件资源充足:引脚资源相对丰富的系统。
工业环境应用:需要电气隔离的工业产品。
不适合选择SPI的场景¶
引脚资源紧张:只有少量GPIO可用的系统。
大量设备连接:需要连接10个以上设备的系统。
长距离通信:超过1米的板间通信。
多主设备系统:需要多个主设备共享总线。
复杂错误处理:需要高级错误检测和恢复机制。
低成本要求:对硬件成本非常敏感的应用。
简单的低速通信:仅需要简单的低速数据传输。
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通道,配置传输模式和时钟频率。函数原型
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 |
mode |
输入 |
qosa_spi_transmit_mode_e |
SPI传输模式;详见 qosa_spi_transmit_mode_e |
clk |
输入 |
qosa_spi_clk_e |
SPI时钟频率;详见 qosa_spi_clk_e |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
qosa_spi_deinit¶
功能描述
去初始化指定的SPI通道,释放相关资源。函数原型
qosa_spi_errcode_e qosa_spi_deinit(qosa_spi_port_e port)
参数说明
参数名 |
输入/输出 |
类型 |
说明 |
|---|---|---|---|
port |
输入 |
qosa_spi_port_e |
需要去初始化的SPI通道;详见 qosa_spi_port_e |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
qosa_spi_write¶
功能描述
通过指定的SPI通道发送数据。函数原型
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 |
txData |
输入 |
void * |
待发送数据的缓冲区指针 |
length |
输入 |
qosa_uint32_t |
要发送的数据长度;单位:字节 |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
qosa_spi_read¶
功能描述
通过指定的SPI通道接收数据。函数原型
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 |
rxData |
输出 |
void * |
存放接收数据的缓冲区指针 |
length |
输入 |
qosa_uint32_t |
要接收的数据长度;单位:字节 |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
qosa_spi_write_read¶
功能描述
执行SPI全双工读写操作,同时发送和接收数据。函数原型
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 |
rxData |
输出 |
void * |
存放接收数据的缓冲区指针 |
txData |
输入 |
void * |
待发送数据的缓冲区指针 |
length |
输入 |
qosa_uint32_t |
收发数据的长度;单位:字节 |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
qosa_spi_ioctl¶
功能描述
动态配置SPI参数。函数原型
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 |
cmd |
输入 |
qosa_spi_ioctl_cmd_e |
参数配置命令;详见 qosa_spi_ioctl_cmd_e |
arg |
输入 |
void * |
命令参数指针,指向的数据类型由cmd决定;详见 qosa_spi_ioctl_cmd_e |
返回值说明
QOSA_SPI_SUCCESS:函数执行成功
其他值(详见 qosa_spi_errcode_e):函数执行失败
枚举定义¶
qosa_spi_errcode_e¶
SPI操作错误码枚举定义如下:
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时钟频率配置枚举定义如下:
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传输模式枚举定义如下:
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通道选择枚举定义如下:
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主从模式枚举定义如下:
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时钟极性和相位配置枚举定义如下:
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)信号控制模式枚举定义如下:
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数据传输位顺序枚举定义如下:
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数据传输位宽度枚举定义如下:
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参数配置命令枚举定义如下:
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 |
应用逻辑流程图¶
示例代码¶
完整示例代码请查看 https://github.com/UniRTOS/UniRTOS-Doc-Examples/blob/main/peripheral/spi/spi_demo.c