SPI 总线介绍与使用指南
一、SPI 总线概述
SPI(Serial Peripheral Interface,串行外设接口)是一种由摩托罗拉公司开发的同步串行通信协议,主要用于短距离、高速率的芯片间通信。SPI 采用主从(Master-Slave)架构,支持全双工通信,广泛应用于 MCU 与外设(如传感器、Flash、显示屏、ADC/DAC 等)之间的数据交互。
1.1 核心特性
同步通信:依赖时钟信号同步数据传输,无起始 / 停止位,传输效率高;
全双工:主从设备可同时收发数据;
多从机支持:通过片选(CS/SS)信号实现多个从机挂载;
灵活的时钟配置:支持时钟极性(CPOL)和时钟相位(CPHA)的组合配置,适配不同外设;
无地址机制:通过片选信号指定通信从机,协议简单。
1.2 硬件引脚定义
SPI 总线核心引脚(4 线制,部分场景可简化为 3 线):
| 引脚名称 | 英文全称 | 功能描述 |
|---|---|---|
| SCK | Serial Clock | 时钟信号,由主设备产生,控制通信时序 |
| MOSI | Master Out Slave In | 主设备发送、从设备接收数据 |
| MISO | Master In Slave Out | 主设备接收、从设备发送数据 |
| CS/SS | Chip Select/Slave Select | 片选信号,主设备拉低选中对应从机,低电平有效(可配置) |
二、SPI 通信原理
2.1 时钟极性与相位(CPOL/CPHA)
SPI 的通信时序由 CPOL 和 CPHA 两个参数决定,共 4 种组合模式,需与外设手册匹配:
CPOL(时钟极性):定义 SCK 空闲时的电平
CPOL=0:空闲时 SCK 为低电平;
CPOL=1:空闲时 SCK 为高电平。
CPHA(时钟相位):定义数据采样的时机
CPHA=0:在 SCK 第一个跳变沿(上升 / 下降)采样数据;
CPHA=1:在 SCK 第二个跳变沿(上升 / 下降)采样数据。
2.2 通信流程
- 主设备拉低目标从机的 CS 引脚,选中该从机;
- 主设备产生 SCK 时钟信号,同步发送 / 接收数据:主设备通过 MOSI 逐位发送数据;从设备通过 MISO 逐位返回数据;
- 通信完成后,主设备拉高 CS 引脚,释放从机。
三、工程中 SPI 的使用(以 STM32 为例)
3.1 SPI 初始化配置(STM32 HAL 库)
1. 引脚初始化(gpio.c)
#include "gpio.h"
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能 SPI1 引脚时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* SPI1 SCK: PA5, MOSI: PA7, MISO: PA6 */
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7 | GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出(SCK/MOSI)
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* CS 引脚: PA4(手动控制) */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 初始拉高 CS,未选中从机 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
2. SPI 外设初始化(spi.c)
#include "spi.h"
SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void)
{
/* SPI 基本配置 */
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // 主设备模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES;// 全双工(2 线)
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8 位数据宽度
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0(空闲低电平)
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0(第一个边沿采样)
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制 NSS(CS)
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 时钟分频(APB2=72MHz → 4.5MHz)
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler(); // 初始化失败处理
}
}
/* SPI 底层硬件初始化(MSP) */
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
if(spiHandle->Instance==SPI1)
{
__HAL_RCC_SPI1_CLK_ENABLE(); // 使能 SPI1 时钟
}
}
3.2 SPI 读写函数封装(spi.c)
#include "spi.h"
#include "gpio.h"
/**
* @brief SPI 写字节到外设
* @param data: 要发送的字节
* @retval 无
*/
void SPI_WriteByte(uint8_t data)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低 CS,选中从机
HAL_SPI_Transmit(&hspi1, &data, 1, 100); // 发送 1 字节,超时 100ms
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 拉高 CS,释放从机
}
/**
* @brief SPI 从外设读取字节
* @retval 读取到的字节
*/
uint8_t SPI_ReadByte(void)
{
uint8_t data = 0;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低 CS
HAL_SPI_Receive(&hspi1, &data, 1, 100); // 接收 1 字节
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 拉高 CS
return data;
}
/**
* @brief SPI 读写数据(全双工)
* @param tx_buf: 发送缓冲区
* @param rx_buf: 接收缓冲区
* @param len: 数据长度
* @retval HAL 状态码
*/
HAL_StatusTypeDef SPI_ReadWrite(uint8_t* tx_buf, uint8_t* rx_buf, uint16_t len)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, len, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
return status;
}
3.3 主函数调用示例(main.c)
#include "main.h"
#include "spi.h"
#include "gpio.h"
int main(void)
{
/* 初始化硬件层 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
uint8_t tx_data = 0x5A;
uint8_t rx_data = 0;
while (1)
{
/* 写数据到 SPI 外设 */
SPI_WriteByte(tx_data);
/* 从 SPI 外设读数据 */
rx_data = SPI_ReadByte();
/* 全双工读写示例 */
uint8_t tx_buf[4] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx_buf[4] = {0};
SPI_ReadWrite(tx_buf, rx_buf, 4);
HAL_Delay(1000); // 延时 1s
}
}
四、SPI 使用注意事项
- 时钟速率匹配:主设备 SCK 速率需低于外设支持的最大速率(通常外设手册会标注);
- CPOL/CPHA 配置:必须与外设的 SPI 时序要求一致,否则数据读写错误;
- CS 信号控制:每次通信前拉低 CS,结束后拉高;多从机场景需确保同一时刻仅一个从机的 CS 被拉低;
- 数据位宽:根据外设要求配置 8 位 / 16 位数据宽度;
- 总线冲突:SPI 无总线仲裁机制,多主设备场景需额外设计冲突避免逻辑;
- 硬件兼容性:部分外设仅支持半双工,需配置
SPI_DIRECTION_1LINE模式; - 中断 / DMA 模式:高频 / 大数据量传输建议使用中断或 DMA 模式,避免阻塞主循环(HAL 库接口:
HAL_SPI_Transmit_IT/HAL_SPI_Transmit_DMA)。