从零写OS内核 | SPI 总线——高速串行通信

你有没有想过:一片 NOR Flash 芯片,是怎么和 CPU 交换数据的? 它没有地址线、没有复杂的主从协议,就靠 4 根线就能以几十 MHz 的速度传输数据。这就是 SPI(Serial Peripheral Interface)。

相比 I2C,SPI 更快、更简单、更适合高速场景。本文从协议原理讲到四种模式、Daisy Chain,最后给出 Linux 下用 spidev 调试的命令。


一、SPI 四种模式:CPOL 和 CPHA 的组合

SPI 有 4 种通信模式,由两个参数决定:

  • CPOL(Clock Polarity):空闲时时钟电平(0=低,1=高)
  • CPHA(Clock Phase):数据采样时机(0=第一个边沿,1=第二个边沿)

Bash
  Mode 0 (CPOL=0, CPHA=0): 空闲低时钟,第一个边沿采样
  ────────────────────────────────────────────────────
  SCLK    ───┐    ┌──────┐    ┌──────┐    ┌──
              ┌────┘      └────┘      └────┘
  SDA     ──[D7]──[D6]──[D5]──[D4]──[D3]──[D2]──...
  MOSI           ↑       ↑       ↑       ↑
            第一个边沿采样(CPHA=0)

  Mode 3 (CPOL=1, CPHA=1): 空闲高时钟,第二个边沿采样
  ────────────────────────────────────────────────────
  SCLK    ───┘    ┌──────┐    ┌──────┐    ┌─
              ┌────┘      └────┘      └────┘
  SDA     ──[D7]──[D6]──[D5]──[D4]──[D3]──[D2]──...
                    ↑       ↑       ↑
              第二个边沿采样(CPHA=1

四种模式速查:

模式 CPOL CPHA SCLK 空闲 采样边沿
0 0 0 上升沿
1 0 1 下降沿
2 1 0 下降沿
3 1 1 上升沿

常见芯片对应的模式:

  • NOR Flash(W25Q64):Mode 0 或 Mode 3
  • SD 卡:Mode 0
  • 传感器(陀螺仪 MPU6000):Mode 3

二、SPI 全双工和 Daisy Chain

SPI 是全双工总线,同时有 MOSI(Master Out Slave In)和 MISO(Master In Slave Out),主设备在发送一个字节的同时也在接收一个字节。

Bash
         Master                        Slave 1              Slave 2
           │                             │                     │
  MOSI ────────────────[D7..D0]─────────────────────────[D7..D0]──→ │
  MISO ←───────────────[D7..D0]─────────────────────────[D7..D0]── │ │
           │                             │                     │
  SCLK ─────────────────────────[Clock]─────────────────────────
  CS0  ───[L]──────────────────[H]──────────────────[H]
  CS1  ───[H]──────────────────[H]──────────────────[L]

Daisy Chain(菊花链)模式:
多个从设备串联,数据像流水线一样传递:

Bash
  Master → [Slave1] → [Slave2] → [Slave3] → Master
           移位寄存器    移位寄存器    移位寄存器

  发 3 字节:Master 发 byte1 → Slave1 收 byte1 并把 byte2 推到 Slave2...

Linux SPI Daisy Chain 例子(flashrom):

Bash
# 查看 SPI 控制器信息
$ lspci | grep SPI
00:00.2 SPI controller: Intel Corporation Device 9a13 (rev 01)

# Linux 下通过 spidev 测试
$ ls /sys/bus/spi/devices/
spi0.0  spi0.1  spi0.2
$ cat /sys/bus/spi/devices/spi0.0/modalias
spi:spi-max7315

三、SPI Flash:NOR 和 NAND 的区别

嵌入式系统最常用 SPI Flash 存储固件:

特性 SPI NOR Flash SPI NAND Flash
结构 透明或非透明 需要 ECC
读取 直接随机读取(XIP) 顺序页读取
写入 字节编程(Page Program) 页编程
擦除 扇区擦除(4K-64K) 块擦除(64K-128K)
价格 高(存储代码) 低(存储数据)
特点 可直接执行代码(XIP) 容量大

W25Q64(常见 NOR Flash,8MB)结构:

  • 128 个块(64KB 每块)
  • 每块 16 个扇区(4KB 每扇区)
  • 支持 Dual/Quad SPI(用 2 或 4 根数据线加速)

四、Linux spidev 实战

Bash
# 1. 确认有 SPI 控制器和 spidev 驱动
$ ls /dev/spidev*
/dev/spidev0.0  /dev/spidev0.1

# 2. 查看 SPI 设备信息
$ cat /sys/bus/spi/devices/spi0.0/modalias
spi:brainboxes

# 3. 用 spidev_test 发送一个字节(读 Flash ID)
$ cat /dev/spidev0.0
# spidev_test 需要构造十六进制命令字
$ ./spidev_test -D /dev/spidev0.0 -p "9F" -s 51200000 -b 8
# 发 9F(Read JEDEC ID),读 3 字节厂商+设备 ID

# 4. 读 Flash 芯片 ID(W25Q64)
$ ./spidev_test -D /dev/spidev0.0 -p "9F" -t
# 返回:EF 40 17 = Winbond 16MB Flash ✓

# 5. 读取 Flash 前 256 字节
$ ./spidev_test -D /dev/spidev0.0 -p "03 00 00 00" -r 256 -t
# 发 0x03(Read Data)+ 3 字节地址,读取 256 字节

spidev_test 源码片段(发送 Read JEDEC ID):

C
// spidev_test.c 片段
uint8_t tx[] = {0x9F};  // JEDEC ID 命令
uint8_t rx[3] = {0};

ioctl(fd, SPI_IOC_MESSAGE(1), &tr);  // 发送并接收
printf("JEDEC ID: %02X %02X %02Xn", rx[0], rx[1], rx[2]);
// EF 40 17 → Winbond 16MB

五、wandos 的 SPI 实现

wandos 目前在 arch/x86/core/spi.cpp 实现了基于 Port I/O 的 bitbang SPI:

Cpp
// wandos spi.cpp(简化)
void spi_transfer(uint8_t *tx, uint8_t *rx, int len) {
    for (int i = 0; i < len; i++) {
        uint8_t data = tx ? tx[i] : 0xFF;
        rx[i] = spi_shift_byte(data);  // 移位寄存器
    }
}

uint8_t spi_shift_byte(uint8_t byte) {
    uint8_t rx = 0;
    for (int i = 7; i >= 0; i--) {
        // MOSI:字节的最高位先发
        if (byte & (1 << i)) MOSI_HIGH; else MOSI_LOW;
        SCK_HIGH;  // 上升沿采样
        udelay(1);
        if (MISO_READ()) rx |= (1 << i);  // 读 MISO
        SCK_LOW;  // 时钟拉低,准备下一位
        udelay(1);
    }
    return rx;
}

wandos SPI 的局限:

  • 纯 bitbang 模拟,速度慢(< 1MHz)
  • 尚未实现硬件 SPI 控制器(Intel SPI 等)
  • 没有 DMA 支持

公众号 footer

相关阅读:

  • 《从零写OS内核 | I2C 总线——嵌入式系统的血管》(348)
  • 《从零写OS内核 | USB 协议——从 HID 到高速设备》(345)

动手环节:
如果你有树莓派或嵌入式开发板,运行 ls /dev/spidev* 看有没有 SPI 设备。然后尝试用 spidev_test 读取 Flash ID,或接一个 SPI OLED 屏幕验证通信。

仓库地址:
github.com/golang12306/os-kernel-from-scratch


关注公众号「AI不着急」,回复”资料”获取内核学习路线图。

最后修改: 2024年8月21日

作者

评论

发表评论

您的邮箱地址不会被公开。