stm32——SPI协议
stm32——SPI协议
STM32的SPI(Serial Peripheral Interface,串行外设接口)协议是一种高速、全双工、同步的串行通信协议,广泛评估微控制器与各种外设(如传感器、器件、显示器、模块等)之间的数据传输。STM32微控制器内置的SPI外设具有高度可配置性,支持多种操作模式和数据格式。
1. SPI协议概述
- 主从架构(Master-Slave Architecture):SPI通信采用主从模式。通常有一个主设备(Master)和多个从设备(Slave)。主设备负责发起通信、提供时钟信号,并选择通信的从设备。从设备在被选中时响应主设备的请求。
- 同步通信(Synchronous Communication):数据传输由一个共享的时钟信号(SCK)同步。主设备产生并控制SCK信号。
- 全双工(Full-Duplex):SPI支持同时进行数据发送和接收,即发送和接收通道是独立的。
2. SPI的四根基本信号线
典型的SPI通信使用四根信号线:
- SCK (Serial Clock):串行时钟线。由主设备生成,用于同步主设备和从设备之间的数据传输。
- MOSI (Master Out Slave In):主设备输出,从设备输入。主设备通过此线向从设备发送数据。
- MISO(Master In Slave Out):主设备输入,从设备输出。从设备通过此线向主设备发送数据。
- SS/ CS (Slave Select / Chip Select) :从设备选择/片选线。由主设备控制,用于选择通信的从设备。当SS/CS线为低电平(通常为活动低电平)时,对应的从设备被选中并准备好进行通信;当为高电平时,从设备在非选中状态,其MISO线通常会呈高阻态,允许从其他设备共享MISO线。
图1:SPI通信基本连接图(单主多从)
+---------------------+ +---------------------+| Master | | Slave 1 || | | |SCK <---|---------------------|------> SCK <---| |MOSI <---|---------------------|------> MOSI <---| |MISO <---|---------------------|<------ MISO <---| |CS1 <---|---------------------|------> SS/CS <---| |CS2 <---|---------------------+ +---------------------+| Master || | +---------------------+| | | Slave 2 || | | |SCK <---|---------------------|------> SCK <---| |MOSI <---|---------------------|------> MOSI <---| |MISO <---|---------------------|<------ MISO <---| |CS1 <---| | | |CS2 <---|---------------------|------> SS/CS <---| |+---------------------+ +---------------------+
说明:
SCK
、MOSI
、MISO
线路在多个从设备之间是共享的。- 每个从设备都需要布线独立的
SS/CS
线路,主设备通过将对应从设备的SS/CS
线路拉低来选择通信的从设备。
3. STM32 SPI工作模式(CPOL和CPHA)
SPI协议的灵活体现在其四种工作模式上,这由时钟时钟(CPOL)和时钟相位(CPHA)两个参数决定。
-
CPOL (Clock Polarity)
:时钟按钮。定义了SCK信号在空闲状态时的电平。
CPOL=0
:SCK闲置时间为低水平。CPOL=1
:SCK闲暇时间为高档次。
-
CPHA (Clock Phase)
:时钟相位。定义了数据在SCK的哪个边沿被采样。
CPHA=0
:数据在SCK的第一个时钟边沿(上升沿或下降沿,CPOL)进行采样。CPHA=1
:数据在SCK的第二个时钟边沿(上升沿或下降沿,CPOL)进行采样。
这四个组合组成了四种SPI模式:
- Mode 0 : CPOL=0, CPHA=0 (闲置低电平,第一个边沿采样)
- 模式1:CPOL=0,CPHA=1(闲置低水平,第二个边沿采样)
- 模式2:CPOL=1,CPHA=0(闲置高水平,第一个边沿采样)
- 模式3:CPOL=1,CPHA=1(闲置高水平,第二个边沿采样)
图2:SPI四种工作模式的交互图
SCK (CPOL=0, CPHA=0)
空闲低电平____ ____ ____
_____| |______| |______| |____^ ^| |采样点 移位点 (数据输出)SCK (CPOL=0, CPHA=1)
空闲低电平____ ____ ____
_____| |______| |______| |____^ ^| |移位点 采样点SCK (CPOL=1, CPHA=0)
空闲高电平
____ ____ ____|______| |______| |____^ ^| |采样点 移位点 (数据输出)SCK (CPOL=1, CPHA=1)
空闲高电平
____ ____ ____|______| |______| |____^ ^| |移位点 采样点
说明:
- 采样点(Sampling Edge):接收设备读取数据位的时钟边沿。
- 升降点(Shift Edge):发送设备将数据位输出到数据线的时钟边沿。
在通信过程中,主设备和从设备必须配置为相同的SPI模式,否则通信将无法正常进行。
4. STM32 SPI数据传输流程
SPI通信是基于移位寄存器的。主设备和从设备内部都有一个移位寄存器。当主设备启动通信时,它会拉低目标从设备的SS/CS线,然后开始生成SCK时钟信号。
在每个时钟周期:
- 主设备将要发送的数据位从其MOSI线上移出。
- 从设备将要发送的数据位从其MISO线路移出。
- 同时,主设备从MISO线接收数据位,从设备从MOSI线接收数据位。
这个过程就像两个升降台首尾相连,数据在主从设备之间“循环”移动。即使只需要单向传输数据,SPI也通常会进行双向数据交换(例如,主设备发送数据时,从设备会发送一些无效数据或初始数据)。
图3:SPI数据传输地图(全双工)
+-----------+ +-----------+| Master | | Slave || Shift | | Shift || Register | | Register |+-----------+ +-----------+| |MOSI <-----|-------------------------|-----> 数据从主设备发送到从设备(Master Out, Slave In)MISO <-----|-------------------------|<---- 数据从从设备发送到主设备(Master In, Slave Out)SCK <-----|-------------------------|-----> 时钟信号 (由主设备提供)SS/CS<-----|------------[选择从设备]------> 片选信号 (由主设备控制)
说明:
- 在每个时钟边沿,主设备和从设备同时进行数据的发送和接收。
- 数据通常为MSB ( Most Significant Bit) First(最高有效位优先)传输,但STM32也支持LSB First(最低有效位优先)传输。
5. STM32 SPI高级特性
STM32的SPI外设还支持以下高级功能,以提高通信效率和可靠性:
- 数据帧格式:支持Motorola和TI帧格式。
- 数据大小:可配置4位到16位数据帧。
- 预分频器(Prescaler):SPI时钟可以通过预分频器进行分频,以调整通信速度。SPI时钟速度不能超过内部中断频率的一半。
- DMA(Direct Memory Access):支持DMA传输,可以将数据直接从内存传输到SPI外设或从SPI外设传输到内存,从而减少CPU的干预,提高数据吞吐量。
- CRC (Cyclic Redundancy Check):支持硬件CRC校验,提高数据传输的可靠性。
- 中断(Interrupts):SPI外设置可以生成多种中断请求,如串口中断、串口非空中断、错误中断等,方便软件进行事件处理。
- 半双工和只收/只发送模式:除了全双工模式,STM32 SPI还支持半双工(消耗毛发数据线)以及只接收或只发送模式。
6. STM32 SPI配置流程(HAL库示例)
使用STM32Cube HAL库配置SPI通常包括以下步骤:
-
使能时钟:使能SPI外部设置和相关GPIO端口的时钟。
-
GPIO配置:配置SPI引脚(SCK、MOSI、MISO、SS/CS)为复用功能或通用推挽输出(用于软件SS)。
-
SPI参数初始化
:配置SPI实例,包括:
- 模式(主/从)
- 数据方向(全双工、半双工等)
- 数据大小(8位、16位)
- CPOL、CPHA
- 波特率预分频器
- 软件/硬件 NSS 管理
- 主要传输顺序(MSB/LSB)
-
使能SPI外设: 启用SPI模块。
-
数据传输:调用HAL库函数进行数据发送、接收或收发,可以通过阻塞模式(Polling)、中断模式(Interrupt)或DMA模式。
代码示例(STM32)
1.STM32 SPI操作的整体流程
- GPIO初始化:配置SPI相关的GPIO引脚(SCK, MISO, MOSI, 以及任选的SS/CS)作为对应的复用功能。
- SPI外设时钟使能:使能SPI外设时钟。
- SPI初始化:配置SPI的工作模式(主/从)、数据方向、数据帧大小、时钟相位、时钟相位、波特率预分频参数等。
- SS/CS 引脚控制(如果使用软件 SS):如果不使用硬件 SS,则需要手动控制一个 GPIO 引脚作为片选信号。
- 数据传输:调用HAL库提供的发送、接收或收发函数。
- 错误处理与中断/DMA回调(如果使用):在中断或DMA模式下,处理中断回调函数中的数据和错误。
2.具体函数操作顺序(使用STM32CubeIDE生成的HAL库为例)
假设我们使用STM32CubeIDE生成了项目,并且已经配置好SPI外设。以下是核心的代码片段和函数调用顺序:
2.1. 物品工作(通常由CubeIDE自动生成)
在main.c
文件中,你会看到以下结构:
// 定义一个SPI句柄
SPI_HandleTypeDef hspi1; // 假设使用SPI1// GPIO初始化函数声明
static void MX_GPIO_Init(void);
// SPI初始化函数声明
static void MX_SPI1_Init(void);int main(void)
{/* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init(); // 1. HAL库初始化/* Configure the system clock */SystemClock_Config(); // 2. 系统时钟配置/* Initialize all configured peripherals */MX_GPIO_Init(); // 3. GPIO初始化MX_SPI1_Init(); // 4. SPI外设初始化/* Infinite loop */while (1){/* Add user code here */}
}
2.2. GPIO 初始化函数 ( MX_GPIO_Init
)
该函数通常由CubeIDE根据你在GPIO配置页面的设置自动生成。它使能GPIO时钟,并配置各个引脚的模式、上下拉、速度等。
// Example: GPIO init for SPI1 (Master, Full-Duplex, Software NSS)
static void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOA_CLK_ENABLE(); // Enable GPIOA clock (assuming SPI1 uses PA5, PA6, PA7)__HAL_RCC_GPIOC_CLK_ENABLE(); // Enable GPIOC clock (assuming PC4 for SS/CS)/*Configure GPIO pin : PC4 (CS pin) */GPIO_InitStruct.Pin = GPIO_PIN_4;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Output Push-Pull for SS/CSGPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // Low speed for SS/CSHAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pins : PA5 (SCK), PA6 (MISO), PA7 (MOSI) */GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; // SCK, MISO, MOSIGPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate Function Push-PullGPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // High speed for SPI dataGPIO_InitStruct.Alternate = GPIO_AF5_SPI1; // Set alternate function to SPI1HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
2.3. SPI外设初始化函数 ( MX_SPI1_Init
)
该函数也是由CubeIDE自动生成,负责配置SPI的各种工作参数。
static void MX_SPI1_Init(void)
{/* SPI1 parameter configuration*/hspi1.Instance = SPI1; // 选择SPI1外设hspi1.Init.Mode = SPI_MODE_MASTER; // 配置为SPI主模式hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 双线全双工模式 (MOSI和MISO)hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 数据帧大小为8位hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性:空闲时SCK为低电平 (CPOL=0)hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位:数据在第一个时钟边沿采样 (CPHA=0)hspi1.Init.NSS = SPI_NSS_SOFT; // 软件片选管理 (如果使用硬件SS,改为SPI_NSS_HARD_INPUT/OUTPUT)hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 波特率预分频,例如PCLK / 16hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB优先传输hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 禁用TI模式hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用CRC校验hspi1.Init.CRCPolynomial = 10; // CRC多项式 (如果CRC开启)// 调用HAL库函数进行SPI初始化if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler(); // 初始化失败,调用错误处理函数}
}
函数操作顺序总结(初始化阶段):
-
HAL_Init()
:初始化HAL库,包括系统时钟、Systick等。 -
SystemClock_Config()
:配置系统时钟(通常由CubeIDE生成)。 -
MX_GPIO_Init()
__HAL_RCC_GPIOx_CLK_ENABLE()
:使能所有相关GPIO端口的时钟。HAL_GPIO_Init()
:配置每个SPI引脚的模式、速度、上下拉、复用功能等。
-
MX_SPI1_Init()
__HAL_RCC_SPIx_CLK_ENABLE()
:使能SPI外部设定的时钟。- 配置
hspi1
结构体成员(Instance
,Mode
,Direction
,DataSize
,CLKPolarity
,CLKPhase
,NSS
,BaudRatePrescaler
,FirstBit
,TIMode
,CRCCalculation
,CRCPolynomial
)。 HAL_SPI_Init(&hspi1)
: 调用HAL库函数执行SPI硬件初始化。
2.4. 数据传输操作
在main
函数的while(1)
循环中,可以执行数据传输操作。在每次通信之前,确保将目标设备的SS/CS引脚拉低,通信结束后再拉高。
使用软件NSS(推荐):
#define SLAVE_CS_PORT GPIOC
#define SLAVE_CS_PIN GPIO_PIN_4// 示例:发送数据
uint8_t txData[] = {0x01, 0x02, 0x03};
uint8_t rxData[3];
HAL_StatusTypeDef status;// 1. 拉低片选信号,选择从设备
HAL_GPIO_WritePin(SLAVE_CS_PORT, SLAVE_CS_PIN, GPIO_PIN_RESET); // CS low// 2. 发送数据 (阻塞模式)
status = HAL_SPI_Transmit(&hspi1, txData, sizeof(txData), HAL_MAX_DELAY); // 发送3个字节
if (status != HAL_OK)
{// Handle Error
}// 或者:接收数据 (阻塞模式)
status = HAL_SPI_Receive(&hspi1, rxData, sizeof(rxData), HAL_MAX_DELAY); // 接收3个字节
if (status != HAL_OK)
{// Handle Error
}// 或者:发送和接收数据 (阻塞模式)
status = HAL_SPI_TransmitReceive(&hspi1, txData, rxData, sizeof(txData), HAL_MAX_DELAY); // 同时发送和接收3个字节
if (status != HAL_OK)
{// Handle Error
}// 3. 拉高片选信号,释放从设备
HAL_GPIO_WritePin(SLAVE_CS_PORT, SLAVE_CS_PIN, GPIO_PIN_SET); // CS high// 4. 等待一段时间或执行其他操作
HAL_Delay(10); // 例如,等待10ms
函数操作顺序总结(数据传输阶段 - 支撑模式):
HAL_GPIO_WritePin(SLAVE_CS_PORT, SLAVE_CS_PIN, GPIO_PIN_RESET)
:拉低 SS/CS从设备中选中。- 选择传输函数
HAL_SPI_Transmit(&hspi1, pTxData, Size, Timeout)
: 发送数据。HAL_SPI_Receive(&hspi1, pRxData, Size, Timeout)
: 接收数据(发送空字节)。HAL_SPI_TransmitReceive(&hspi1, pTxData, pRxData, Size, Timeout)
:同时发送和接收数据。
HAL_GPIO_WritePin(SLAVE_CS_PORT, SLAVE_CS_PIN, GPIO_PIN_SET)
:拉高SS/CS以从设备释放。
2.5. 中断和DMA模式(高级应用)
如果使用中断或DMA模式,数据传输函数此时非阻塞的,并且需要处理回调函数:
中断模式:
-
使能SPI中断
MX_SPI1_Init()
之后,通常会包含以下代码(或在CubeIDE中勾选):
HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0); // 设置中断优先级 HAL_NVIC_EnableIRQ(SPI1_IRQn); // 使能SPI1中断
-
传输函数
HAL_SPI_Transmit_IT(&hspi1, pTxData, Size)
HAL_SPI_Receive_IT(&hspi1, pRxData, Size)
HAL_SPI_TransmitReceive_IT(&hspi1, pTxData, pRxData, Size)
-
中断回调函数
HAL_SPI_TxCpltCallback(&hspi1)
: 发送完成回调HAL_SPI_RxCpltCallback(&hspi1)
: 接收完成回调HAL_SPI_TxRxCpltCallback(&hspi1)
: 收发完成回调HAL_SPI_ErrorCallback(&hspi1)
: 错误回调
DMA模式:
- 配置DMA通道:在CubeIDE中配置DMA(Tx和Rx)。
- 传输函数
HAL_SPI_Transmit_DMA(&hspi1, pTxData, Size)
HAL_SPI_Receive_DMA(&hspi1, pRxData, Size)
HAL_SPI_TransmitReceive_DMA(&hspi1, pTxData, pRxData, Size)
- DMA中断回调函数:与中断模式类似,DMA传输完成后会触发对应的SPI回调函数。
函数操作顺序总结(中断/DMA模式):
- 初始化阶段
- 确定中断或DMA配置正确(使能中断/DMA通道,设置优先级)。
HAL_SPI_Init()
内部会处理中断使能(如果是中断模式)或DMA关联。
- 传输等级
HAL_GPIO_WritePin()
(SS/CS 低)- 调用
HAL_SPI_Transmit_IT/DMA()
或HAL_SPI_Receive_IT/DMA()
或HAL_SPI_TransmitReceive_IT/DMA()
。这些函数会立即返回。 - 在主程序中,可以执行其他任务。
- 等待或响应回调:在
HAL_SPI_TxCpltCallback
、HAL_SPI_RxCpltCallback
或HAL_SPI_TxRxCpltCallback
中处理数据完成的逻辑。 - 在这些回调函数中,通常会执行
HAL_GPIO_WritePin()
(SS/CS high)来从设备释放,或者触发下一个传输。 - 处理
HAL_SPI_ErrorCallback
中的错误。
3、注意事项
- 时钟匹配:确保STM32主设备和从设备的SPI模式(CPOL和CPHA)完全匹配。
- SS/CS管理:这是SPI通信的关键。一定要在每次通信前拉低SS/CS,结束通信后拉高,以确保从设备正确响应。如果是多从设备,每次只能选中一个从设备。
- 数据蜡烛图:确定发送和接收蜡烛图的大小足以容纳所有数据。
- 错误处理:经常检查HAL函数的返回值,并在出现
HAL_ERROR
或HAL_TIMEOUT
时进行适当的错误处理。 - 同步与异步:阻塞模式(轮询)简单但效率低;中断和DMA模式更复杂但能提高CPU利用率和数据吞吐量。
- 数据字节顺序:默认为MSB First,如果从设备为LSB First,需要在初始化时修改
FirstBit
成员。
以上是使用STM32 HAL库进行SPI通信的典型函数操作顺序。实际项目中,可能会根据具体的设备和应用需求进行参数。
总结:
STM32的SPI协议是一种强大而灵活的串行通信方式。了解其主从架构、四线连接、CPOL/CPHA定义的工作模式以及数据传输机制,对于在STM32项目中有效地应用SPI关键。STM32提供了丰富的硬件特性和灵活的软件配置选项,能够满足各种应用场景下的高速、可靠的通信需求。