1. SPI 总线基础:协议概述与四线制信号

大家好,我是你们的讲师。咱们今天正式开讲 SPI 总线。说实话,做了这么多年嵌入式,SPI 是我用得最多的串行接口之一。它简单、高效,但坑也不少。这一章,我们先打好地基。

1.1 SPI 协议到底是什么?

SPI,全称 Serial Peripheral Interface,串行外设接口。它是由摩托罗拉公司推出的一种同步串行通信协议。说白了,就是让主设备(Master)和从设备(Slave)之间交换数据的一种方式。

我个人的习惯是,把 SPI 理解成「一个时钟,两条数据线,再加一条片选」。为什么这么讲?因为它的核心就是靠时钟来同步,数据线负责来回传数据,片选则决定了跟谁说话。

核心特点:

  • 同步通信:所有数据传输都由主设备产生的时钟信号驱动
  • 全双工:发送和接收可以同时进行
  • 主从模式:一个主设备控制总线,一个或多个从设备响应
  • 高速率:典型速率从几 MHz 到几十 MHz,甚至更高

你想想看,I2C 也是串行,UART 也是串行,为什么还要用 SPI?因为 SPI 快,而且全双工。我在一个数据采集项目里,需要同时读写 ADC 的配置寄存器和采样数据,用 SPI 一条指令就搞定了,换成 I2C 还得等应答。

1.2 四线制信号定义

标准的 SPI 接口有四根线。咱们一个一个说清楚。

信号名称 全称 方向 作用
SCLK Serial Clock 主→从 时钟信号,由主设备产生,同步数据传输
MOSI Master Out Slave In 主→从 主设备发送数据给从设备
MISO Master In Slave Out 从→主 从设备发送数据给主设备
CS Chip Select 主→从 片选信号,低电平有效,选中目标从设备

嗯,这里要注意:CS 是低电平有效。我见过不少新手,一开始把 CS 搞反了,结果怎么调都调不通。还有一点,MOSI 和 MISO 的名字千万别搞混——MOSI 是主出从入,MISO 是主入从出。记不住的话,你就想:主设备说「我输出给你(Slave)」,那就是 MOSI。

小技巧: 在原理图或 PCB 上,我习惯把 MOSI 标成蓝色,MISO 标成绿色,SCLK 标成黄色,CS 标成红色。这样一眼就能看出信号流向,调试时特别方便。

1.3 全双工通信原理

全双工,意思是发送和接收可以同时进行。SPI 是怎么做到的呢?

其实原理很简单:每个时钟周期,主设备通过 MOSI 发送一位数据,同时从设备通过 MISO 返回一位数据。也就是说,一次时钟跳变,数据就交换了一次。

我画个图你就明白了。

SPI 全双工通信原理图 主设备 (Master) 从设备 (Slave) SCLK MOSI (主→从) MISO (从→主) CS (低电平有效) 每个时钟周期:主设备发送 1 bit + 从设备返回 1 bit 8 个时钟周期完成一个字节的交换

看到没?SCLK 是公共的时钟线,MOSI 和 MISO 是两条独立的数据线。主设备往 MOSI 上放数据的同时,从设备往 MISO 上放数据。这样一来,一个时钟周期就完成了双向传输。

我曾经在一个 FPGA 项目里,用 SPI 跟 ADC 通信。ADC 每来一个时钟,就通过 MISO 返回一个采样值,同时我通过 MOSI 发送下一个通道的配置。这样流水线操作,效率非常高。

1.4 主从模式架构

SPI 总线是典型的主从架构。一个总线上只能有一个主设备(通常是 MCU 或 FPGA),但可以有多个从设备。

多从设备怎么连接?有两种方式:

  • 独立片选:每个从设备有自己的 CS 线,主设备通过拉低某个 CS 来选中对应的从设备
  • 菊花链(Daisy Chain):从设备串联,数据逐级传递,适合从设备数量多但引脚有限的场景

咱们这门课后面会专门讲菊花链的调试,这里先记住基本概念。

注意: 多从设备共用 MOSI/MISO/SCLK 时,必须保证未被选中的从设备的 MISO 引脚处于高阻态(Hi-Z),否则会造成总线冲突。我遇到过一块板子,就是因为从设备的 MISO 没有三态控制,导致整个 SPI 总线瘫痪。

1.5 一个简单的 SPI 读写示例

光说不练假把式。咱们看一段伪代码,模拟 SPI 读写一个字节的过程。

// SPI 写一个字节(同时读一个字节)
uint8_t spi_transfer_byte(uint8_t tx_data) {
    uint8_t rx_data = 0;
    for (int i = 7; i >= 0; i--) {
        // 在 SCLK 上升沿之前,设置 MOSI 数据位
        if (tx_data & (1 << i))
            MOSI_HIGH();
        else
            MOSI_LOW();

        // 产生 SCLK 上升沿
        SCLK_HIGH();
        delay();  // 等待半个时钟周期

        // 在 SCLK 下降沿采样 MISO
        if (MISO_READ())
            rx_data |= (1 << i);

        // 产生 SCLK 下降沿
        SCLK_LOW();
        delay();  // 等待半个时钟周期
    }
    return rx_data;
}

这段代码虽然简单,但把 SPI 的核心时序都体现出来了:先准备数据,再打时钟,然后采样。实际项目中,我们通常用 MCU 的 SPI 外设或者 FPGA 的 IP 核来实现,但理解底层原理对调试至关重要。

1.6 避坑指南

最后,分享几个我踩过的坑:

  • 时钟极性(CPOL)和相位(CPHA)不匹配:主设备和从设备的 SPI 模式必须一致,否则数据会错位。这个后面会详细讲。
  • CS 信号毛刺:CS 在切换时如果出现毛刺,会导致从设备误触发。我建议在 CS 线上加一个 10kΩ 上拉电阻。
  • 信号完整性:高速 SPI 时,SCLK 的反射和串扰会影响数据。必要时加串联电阻或调整驱动强度。

我的习惯: 每次新板子回来,我都会先用示波器抓一下 SCLK、MOSI、MISO 和 CS 的波形。确认时序正确后,再开始写驱动代码。这一步能省下后面大量的调试时间。