跳转至

SPI通信

概述

SPI(Serial Peripheral Interface,串行外设接口)是一种高速全双工同步串行通信协议,由Motorola公司开发,广泛应用于Flash存储器、传感器、显示屏等外设的通信。

SPI特点

  1. 四线制:MOSI、MISO、SCK、CS
  2. 全双工:同时发送和接收数据
  3. 高速:可达数十MHz
  4. 主从模式:一个主设备,多个从设备
  5. 简单高效:无地址机制,协议简单

SPI信号线

信号 方向 说明
MOSI 主→从 主出从入数据线
MISO 从→主 主入从出数据线
SCK 主→从 时钟信号
CS/SS 主→从 片选信号(低电平有效)

SPI工作模式

SPI有4种工作模式,由CPOL和CPHA决定:

模式 CPOL CPHA 说明
Mode 0 0 0 空闲低电平,第一边沿采样
Mode 1 0 1 空闲低电平,第二边沿采样
Mode 2 1 0 空闲高电平,第一边沿采样
Mode 3 1 1 空闲高电平,第二边沿采样
Text Only
Mode 0 (CPOL=0, CPHA=0):
    ____    ____    ____    ____
SCL     |__|    |__|    |__|    |__
        ^   ^    ^   ^    ^   ^
      采样  采样  采样  采样

Mode 1 (CPOL=0, CPHA=1):
    ____    ____    ____    ____
SCL     |__|    |__|    |__|    |
          ^   ^    ^   ^    ^   ^
        采样  采样  采样  采样

Linux SPI编程

用户态编程(spidev)

C
#include <linux/spi/spidev.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int fd = open("/dev/spidev0.0", O_RDWR);

// 配置SPI模式
uint8_t mode = SPI_MODE_0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

// 配置位/字
uint8_t bits = 8;
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);

// 配置最大速度
uint32_t speed = 1000000;
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

// 传输数据
uint8_t tx[4] = {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 = speed,
    .bits_per_word = bits,
};

ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

close(fd);

多次传输

C
struct spi_ioc_transfer tr[2];

// 第一次传输:发送命令
tr[0].tx_buf = (unsigned long)&cmd;
tr[0].rx_buf = 0;
tr[0].len = 1;
tr[0].cs_change = 1;  // 保持CS选中

// 第二次传输:接收数据
tr[1].tx_buf = 0;
tr[1].rx_buf = (unsigned long)rx_data;
tr[1].len = len;

ioctl(fd, SPI_IOC_MESSAGE(2), tr);

STM32 SPI编程

HAL库方式

C
#include "stm32f4xx_hal.h"

SPI_HandleTypeDef hspi1;

void SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    HAL_SPI_Init(&hspi1);
}

// 片选控制
#define CS_LOW()  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)

// 读写数据
uint8_t spi_read_write(uint8_t data)
{
    uint8_t rx;
    CS_LOW();
    HAL_SPI_TransmitReceive(&hspi1, &data, &rx, 1, 100);
    CS_HIGH();
    return rx;
}

DMA方式

C
uint8_t tx_buf[256];
uint8_t rx_buf[256];

CS_LOW();
HAL_SPI_TransmitReceive_DMA(&hspi1, tx_buf, rx_buf, 256);

// DMA传输完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    CS_HIGH();
}

ESP32 SPI编程

C
#include "driver/spi_master.h"

#define SPI_HOST    SPI2_HOST
#define PIN_MOSI    23
#define PIN_MISO    19
#define PIN_SCLK    18
#define PIN_CS      5

spi_device_handle_t spi;

void spi_init(void)
{
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_MISO,
        .mosi_io_num = PIN_MOSI,
        .sclk_io_num = PIN_SCLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096
    };
    
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1000000,
        .mode = 0,
        .spics_io_num = PIN_CS,
        .queue_size = 7,
    };
    
    spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
    spi_bus_add_device(SPI_HOST, &devcfg, &spi);
}

// 传输数据
spi_transaction_t t = {
    .length = 8 * 4,
    .tx_buffer = tx_data,
    .rx_buffer = rx_data,
};
spi_device_transmit(spi, &t);

树莓派SPI编程

spidev(Python)

Python
import spidev

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
spi.mode = 0

# 传输数据
rx = spi.xfer2([0x03, 0x00, 0x00, 0x00])

# 读写字节
rx = spi.xfer([0xAA])

spi.close()

设备树SPI配置

Devicetree
&spi0 {
    status = "okay";
    
    spidev@0 {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
    
    flash@1 {
        compatible = "jedec,spi-nor";
        reg = <1>;
        spi-max-frequency = <40000000>;
        spi-cpol;
        spi-cpha;
    };
};

常用SPI设备

SPI Flash(W25Q系列)

C
#define W25Q_CMD_READ        0x03
#define W25Q_CMD_WRITE_EN    0x06
#define W25Q_CMD_PAGE_PROG   0x02
#define W25Q_CMD_ERASE_SECT  0x20

// 读数据
void w25q_read(uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[4] = {
        W25Q_CMD_READ,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Receive(&hspi1, buf, len, 100);
    CS_HIGH();
}

// 写使能
void w25q_write_enable(void)
{
    uint8_t cmd = W25Q_CMD_WRITE_EN;
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
    CS_HIGH();
}

// 页编程
void w25q_page_program(uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[4] = {
        W25Q_CMD_PAGE_PROG,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    w25q_write_enable();
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Transmit(&hspi1, buf, len, 100);
    CS_HIGH();
}

TFT显示屏(ILI9341)

C
#define ILI9341_CMD  0
#define ILI9341_DATA 1

void ili9341_write(uint8_t type, uint8_t data)
{
    HAL_GPIO_WritePin(DC_PORT, DC_PIN, type);  // DC引脚
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, &data, 1, 100);
    CS_HIGH();
}

// 初始化
void ili9341_init(void)
{
    ili9341_write(ILI9341_CMD, 0x01);  // Software Reset
    HAL_Delay(100);
    ili9341_write(ILI9341_CMD, 0x28);  // Display OFF
    ili9341_write(ILI9341_CMD, 0xCF);
    ili9341_write(ILI9341_DATA, 0x00);
    // ...
}

ADC芯片(MCP3008)

C
uint16_t mcp3008_read(uint8_t channel)
{
    uint8_t tx[3] = {0x01, (0x80 | (channel << 4)), 0x00};
    uint8_t rx[3] = {0};
    
    CS_LOW();
    HAL_SPI_TransmitReceive(&hspi1, tx, rx, 3, 100);
    CS_HIGH();
    
    return ((rx[1] & 0x03) << 8) | rx[2];
}

SPI调试

波形分析

使用逻辑分析仪观察: - 时钟频率是否正确 - CPOL/CPHA是否匹配设备要求 - 数据是否正确传输 - CS信号是否正确控制

常见问题

  1. 数据全为0xFF或0x00
  2. 检查MISO连接
  3. 检查设备是否正常工作

  4. 通信不稳定

  5. 降低时钟频率
  6. 检查线路长度和阻抗匹配
  7. 添加适当的延时

  8. 数据错误

  9. 检查SPI模式是否匹配设备
  10. 检查位顺序(MSB/LSB)
  11. 检查字节顺序

SPI vs I2C

特性 SPI I2C
信号线 4线 2线
速度 高(可达数十MHz) 低(100k/400k/3.4M)
全双工
寻址 片选 地址机制
应用 高速外设 低速外设

参考资料