SPI原理超详细讲解

SPI 原理超详细中文讲解(2025 版,保姆级教程)

SPI(Serial Peripheral Interface,串行外设接口)是由摩托罗拉(Motorola)开发的一种高速、同步、全双工的串行通信协议,广泛应用于嵌入式系统中,用于微控制器(MCU)与外设(如传感器、闪存、显示屏等)之间的短距离通信。本教程从基础概念到高级应用,深入讲解 SPI 的原理、硬件连接、时序、优缺点及实际场景,结合代码示例,适合嵌入式开发初学者到进阶工程师。内容基于 2025 年 10 月的最新嵌入式开发实践,参考 SPI 协议规范、STM32 参考手册及社区资源(如 CSDN、Electronics Stack Exchange)。


第一部分:SPI 基础概念(初学者级别)

1.1 什么是 SPI?
  • 定义:SPI 是一种主从式同步全双工的串行通信接口,用于设备间的高速数据传输,通常在电路板内(短距离,几厘米到 1 米)。
  • 核心特性
  • 主从模式:主设备(如 MCU)控制通信,从设备(如传感器)响应。
  • 同步通信:主设备提供时钟信号(SCLK),确保数据传输同步。
  • 全双工:主设备和从设备可同时发送和接收数据(通过 MOSI 和 MISO 线)。
  • 高速度:传输速率可达 MHz 级(常见 1-50 MHz,视硬件而定)。
  • 典型应用
  • 存储:SPI 闪存(如 W25Q64,用于存储固件)。
  • 传感器:如 MPU-6050 陀螺仪、加速度计。
  • 显示:OLED 或 TFT 显示屏(如 SSD1306)。
  • 通信模块:Wi-Fi/蓝牙模块(如 ESP8266)。
  • 类比:SPI 像一场“电话通话”,主设备拨号(发送时钟),双方同时对话(数据交换),但只有主设备能决定何时开始。
1.2 SPI 的组成
  • 角色
  • 主设备(Master):控制通信,生成时钟信号,决定数据传输。
  • 从设备(Slave):响应主设备,通过片选信号(SS/CS)激活。
  • 信号线(4 条核心线):
  • SCLK(Serial Clock,串行时钟):主设备生成,控制数据传输节奏。
  • MOSI(Master Out Slave In,主出从入):主设备发送数据到从设备。
  • MISO(Master In Slave Out,主入从出):从设备发送数据到主设备。
  • SS/CS(Slave Select/Chip Select,片选):主设备拉低(低电平)激活特定从设备。
  • 协议特点
  • 无固定帧格式:数据长度灵活(常见 8 位或 16 位)。
  • 无地址字段:通过 SS 选择目标设备。
  • 简单高效:无内置错误校验,需上层协议补充(如 CRC)。
  • 硬件要求
  • 主设备:MCU(如 STM32、Arduino、ESP32)。
  • 从设备:支持 SPI 的外设(如闪存、传感器)。
1.3 SPI 与其他协议对比
协议全双工速度线数复杂度典型场景
SPI高(1-50 MHz)4+(每从设备 1 条 SS)板内高速通信
I2C中(~400 kHz)2多设备低速通信
UART低(~115200 bps)2异步点对点通信

优点

  • 高速,适合实时数据传输。
  • 全双工,数据收发同时进行。
  • 硬件实现简单,MCU 通常内置 SPI 外设。
    缺点
  • 线数多(每增加从设备需额外 SS 线)。
  • 无内置错误检测机制。
  • 仅限短距离(受信号衰减限制)。

第二部分:SPI 工作原理(中级)

2.1 SPI 硬件连接
  • 单主单从连接
  主设备 (Master)         从设备 (Slave)
  [SCLK] ------> [SCLK]
  [MOSI] ------> [MOSI]
  [MISO] <------ [MISO]
  [SS]   ------> [SS]
  • 主设备输出 SCLK 和 MOSI,输入 MISO。
  • 从设备通过 SS(低电平)激活,响应主设备。
  • 单主多从连接
  • 多个从设备共享 SCLK、MOSI、MISO,每设备独占一条 SS 线。
  主设备                从设备1       从设备2
  [SCLK] ------> [SCLK]        [SCLK]
  [MOSI] ------> [MOSI]        [MOSI]
  [MISO] <------ [MISO]        [MISO]
  [SS1]  ------> [SS]          [ ]
  [SS2]  ------> [ ]           [SS]
  • 主设备通过拉低 SS1 或 SS2 选择从设备。
  • 注意:MISO 需支持三态输出(未选中从设备时为高阻态)。
2.2 SPI 时钟与数据时序

SPI 的时序由时钟极性(CPOL)时钟相位(CPHA)决定,组合形成 4 种模式。

  • 时钟极性(CPOL)
  • CPOL=0:空闲时 SCLK 为低电平(默认)。
  • CPOL=1:空闲时 SCLK 为高电平。
  • 时钟相位(CPHA)
  • CPHA=0:数据在时钟的第一个边沿采样(上升沿或下降沿)。
  • CPHA=1:数据在时钟的第二个边沿采样。
  • 四种模式: 模式 CPOL CPHA 空闲电平 采样边沿 常见性 Mode 0 0 0 低 上升沿 最常用 Mode 1 0 1 低 下降沿 较少 Mode 2 1 0 高 下降沿 较少 Mode 3 1 1 高 上升沿 较常见
  • 时序图(Mode 0 示例)
  SCLK:  _____/‾‾‾‾‾\_____/‾‾‾‾‾\_____
  MOSI:  -----[D7][D6][D5]...[D0]-----
  MISO:  -----[D7][D6][D5]...[D0]-----
  SS:    ‾‾‾‾‾|________________|‾‾‾‾‾
  • SS 拉低激活从设备。
  • SCLK 提供时钟,上升沿采样数据。
  • MOSI/MISO 同时传输 8 位(高位先行,MSB first)。
2.3 数据传输流程
  1. 初始化
  • 主设备配置 SPI 外设(模式、速率、数据位)。
  • 从设备等待 SS 信号。
  1. 片选
  • 主设备拉低 SS,激活从设备。
  1. 时钟驱动
  • 主设备生成 SCLK 脉冲。
  • 每时钟周期,主设备通过 MOSI 发送 1 位,从设备通过 MISO 返回 1 位。
  1. 数据交换
  • 主设备发送指令/地址/数据。
  • 从设备返回状态/数据。
  1. 结束
  • 主设备拉高 SS,释放从设备。

示例:读取 SPI 闪存(W25Q64):

  • 主设备发送指令 0x03(读取数据)+ 3 字节地址。
  • 从设备返回存储内容(如 1 字节数据)。

第三部分:SPI 编程实现(高级)

SPI 编程依赖硬件平台(如 STM32、Arduino、Raspberry Pi)。以下以 C/C++ 语言,结合常见平台,提供代码示例。

3.1 STM32 SPI 配置(基于 HAL 库)
  • 硬件:STM32F4 开发板 + W25Q64 闪存。
  • 初始化(STM32Cube HAL 伪代码):

include “stm32f4xx_hal.h”

SPI_HandleTypeDef hspi1;
void MX_SPI1_Init() {
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // 主模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
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; // 软件控制 SS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 速率
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
// 初始化失败处理
}
}

  • 发送/接收
    uint8_t tx_data = 0x03; // 读取指令 uint8_t rx_data; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // SS 拉低 HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // SS 拉高
  • 说明
  • 配置 Mode 0(CPOL=0, CPHA=0)。
  • 软件控制 SS(GPIOA Pin 4)。
  • 速率约为系统时钟的 1/8(如 16 MHz 主频,SPI 为 2 MHz)。
3.2 Arduino SPI 示例
  • 硬件:Arduino Uno + SPI 设备(如 SSD1306 OLED)。
  • 代码(读取设备状态):

include

void setup() {
Serial.begin(9600);
SPI.begin(); // 初始化 SPI
SPI.setDataMode(SPI_MODE0); // Mode 0
SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz
pinMode(10, OUTPUT); // SS 引脚
}
void loop() {
digitalWrite(10, LOW); // 激活从设备
uint8_t data = SPI.transfer(0x05); // 发送状态读取指令
digitalWrite(10, HIGH); // 释放
Serial.println(data, HEX); // 打印状态
delay(1000);
}

  • 说明
  • Arduino 默认使用 Pin 10 作为 SS。
  • SPI.transfer 同时发送和接收 1 字节。
3.3 Linux SPI(基于 spidev)
  • 硬件:Raspberry Pi + SPI 设备。
  • 代码(C++,读取 4 字节数据):

include

include

include

include

include

int main() {
int fd = open(“/dev/spidev0.0”, O_RDWR);
if (fd < 0) { std::cerr << “Open failed” << std::endl; return 1; } uint8_t mode = SPI_MODE_0; ioctl(fd, SPI_IOC_WR_MODE, &mode); uint8_t tx[] = {0x03, 0x00, 0x00, 0x00}; // 读取指令 + 地址 uint8_t rx[4] = {0}; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = 4, .speed_hz = 1000000, .bits_per_word = 8, }; ioctl(fd, SPI_IOC_MESSAGE(1), &tr); for (int i = 0; i < 4; i++) { std::cout << “Received: ” << (int)rx[i] << std::endl; } close(fd); return 0; }

  • 编译运行
  g++ -o rpi_spi rpi_spi.cpp
  sudo ./rpi_spi
  • 说明
  • /dev/spidev0.0 是 Raspberry Pi 的 SPI 设备节点。
  • 需要启用 SPI(raspi-config > Interface Options > SPI)。

第四部分:SPI 高级特性与优化

4.1 SPI 模式选择
  • 数据手册:从设备(如 W25Q64)指定支持的模式(常见 Mode 0 或 Mode 3)。
  • 动态切换:STM32 支持运行时修改 CPOL/CPHA(hspi1.Init)。
  • 调试:用逻辑分析仪(Saleae Logic 或 Sigrok)验证时序是否正确。
4.2 性能优化
  • DMA 传输:STM32 使用 DMA 传输大块数据,降低 CPU 负载。
  HAL_SPI_TransmitReceive_DMA(&hspi1, tx_buffer, rx_buffer, size);
  • 高时钟速率:根据从设备支持,调高 SCLK(如 50 MHz,需检查数据手册)。
  • 批量传输:一次发送多字节,减少 SS 切换开销。
  uint8_t tx_data[4] = {0x03, 0x00, 0x00, 0x00};
  HAL_SPI_Transmit(&hspi1, tx_data, 4, 1000);
4.3 多从设备管理
  • GPIO 控制 SS:为每个从设备分配独立 GPIO 引脚。
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 从设备 1
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);   // 从设备 2
  • 轮询/中断:主设备轮询从设备状态,或用中断响应数据就绪。
4.4 错误处理
  • 超时:设置传输超时(如 HAL 的 Timeout 参数)。
  HAL_SPI_TransmitReceive(&hspi1, tx, rx, 1, 1000); // 1000ms 超时
  • 校验:在应用层添加 CRC 或校验和。
  • MISO 高阻态:确保未选中从设备时 MISO 为高阻态(硬件设计加拉电阻)。

第五部分:实际应用场景

5.1 场景 1:SPI 闪存读写
  • 任务:从 W25Q64 闪存读取数据。
  • 步骤
  • 发送指令 0x03(读取数据)+ 3 字节地址。
  • 通过 MISO 接收数据。
  • 代码(STM32):
  uint8_t tx_data[] = {0x03, 0x00, 0x00, 0x00}; // 指令 + 地址
  uint8_t rx_data[4];
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 4, 1000);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
  • 应用:固件存储、Bootloader。
5.2 场景 2:传感器通信
  • 任务:读取 MPU-6050 加速度计数据。
  • 步骤
  • 发送寄存器地址(如 0x3B,X 轴加速度)。
  • 接收 2 字节数据。
  • 应用:机器人、无人机。
5.3 场景 3:OLED 显示
  • 任务:驱动 SSD1306 OLED 显示屏。
  • 步骤
  • 发送初始化命令(如 0xAE 显示关闭)。
  • 传输像素数据(128×64 位图)。
  • 应用:嵌入式 UI、智能设备。

第六部分:常见问题与最佳实践

6.1 常见问题
问题原因解决方案
无响应SPI 模式错误检查从设备数据手册,确认 CPOL/CPHA。
数据损坏时钟过快降低 SCLK(如 1 MHz)。
MISO 无输出从设备未激活检查 SS 引脚是否拉低。
多设备冲突MISO 竞争确保仅一个从设备被选中。
6.2 最佳实践
  • 查阅数据手册:确认从设备支持的 SPI 模式和最大速率。
  • 拉电阻:MISO 和 SS 引脚加 10kΩ 上拉电阻,防止浮空。
  • 逻辑分析仪:用 Saleae Logic 或 Sigrok 验证时序。
  • 错误重试:实现超时和重试机制。
  • 电源稳定:确保 3.3V/5V 电源稳定,减少噪声。
6.3 调试工具
  • 逻辑分析仪:Saleae Logic、PulseView(分析 SCLK/MOSI/MISO)。
  • 示波器:检查波形完整性。
  • 串口监控:Arduino Serial 输出调试信息。

第七部分:资源与进阶学习

  • 学习路径
  • 1 天:理解 SPI 信号线和四种模式。
  • 2-3 天:实现 STM32/Arduino SPI 通信。
  • 1 周:掌握 DMA、多从设备和错误处理。
  • 资源
  • 官方文档:SPI 协议(https://www.analog.com/en/resources/technical-articles/understanding-spi-protocol.html)。
  • 英文教程:SparkFun SPI 指南(https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi)。
  • 中文资源:CSDN SPI 详解(https://blog.csdn.net/weixin_43883374/article/details/106926058)。
  • 书籍:《嵌入式系统与 ARM Cortex-M》(Joseph Yiu,中文版)。
  • 视频:Bilibili “SPI 协议详解”或 YouTube “SPI Protocol Explained” by GreatScott!。
  • 工具
  • MCU:STM32、ESP32、Arduino。
  • IDE:STM32CubeIDE、Arduino IDE、PlatformIO。
  • 硬件:逻辑分析仪、多用表。

总结:SPI 是一种高效、简单的串行通信协议,凭借全双工和高速度,广泛用于嵌入式设备通信。掌握其四种模式(CPOL/CPHA)、硬件连接和编程实现(如 STM32、Arduino),即可应对闪存、传感器和显示屏等场景。遇到调试问题(如时序错误),请提供硬件/代码细节,我可进一步指导!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注