🔄 第23课:SPI接口

SPI主从模式 + 时钟极性/相位 + 多从设备选择 — 高速同步通信!

🏆 成就:SPI大师 ✅ Verilator验证通过

🔄
SPI大师
SPI Master + CPOL/CPHA配置 + 多CS

🔄 SPI协议详解

SPI(Serial Peripheral Interface)是4线同步串行协议:SCLK时钟 + MOSI主出从入 + MISO主入从出 + CS片选。速度远超UART,可达数十MHz!

SPI总线连接(一主多从) ┌─────────┐ SCLK ──────────┬──────────┐ │ SPI ├─── MOSI ─────────┤ Slave 0 │ │ Master ├─── MISO ←───────┤ │ │ ├─── CS0 ─────────┘ │ │ ├─── CS1 ────┌──────────┐ │ └─────────┘ │ Slave 1 │ │ └──────────┘ CPOL=0, CPHA=0 (Mode 0): SCLK: ──┐ ┌─┐ ┌─┐ ┌─┐ ┌─ 空闲低 └─┘ └─┘ └─┘ └─┘ MOSI: D7 D6 D5 D4 上升沿采样 CPOL=1, CPHA=1 (Mode 3): SCLK: ──┘ └─┘ └─┘ └─┘ └─ 空闲高 ┌─┐ ┌─┐ ┌─┐ ┌─┐ MOSI: D7 D6 D5 D4 上升沿采样

📐 SPI主控制器

Verilogspi_master.v — SPI Master实现
// SPI主控制器 - 支持CPOL/CPHA
module spi_master #(
    parameter CLK_FREQ = 50000000,
    parameter SPI_CLK  = 1000000,    // SPI时钟1MHz
    parameter DATA_W   = 8
)(
    input  wire        clk,
    input  wire        rst,
    // 控制接口
    input  wire [DATA_W-1:0] tx_data,
    input  wire              tx_start,
    output reg               tx_busy,
    output wire [DATA_W-1:0] rx_data,
    output reg               rx_valid,
    // SPI接口
    output reg  spi_clk,
    output reg  spi_mosi,
    input  wire spi_miso,
    output reg  [3:0] spi_cs_n
);
    // SPI时钟分频
    localparam DIV = CLK_FREQ / (2 * SPI_CLK);
    reg [15:0] clk_div;
    reg        clk_en;

    always @(posedge clk) begin
        if (rst) begin clk_div <= 0; clk_en <= 0; end
        else begin
            clk_en <= 0;
            if (clk_div >= DIV - 1) begin
                clk_div <= 0; clk_en <= 1;
            end else clk_div <= clk_div + 1;
        end
    end

    // 发送状态机
    reg [4:0] bit_cnt;
    reg [DATA_W-1:0] shift_out, shift_in;

    always @(posedge clk) begin
        if (rst) begin
            spi_clk <= 0; spi_mosi <= 0;
            spi_cs_n <= 4'hF; tx_busy <= 0;
            bit_cnt <= 0; rx_valid <= 0;
        end else begin
            rx_valid <= 0;
            if (tx_start && !tx_busy) begin
                shift_out <= tx_data;
                tx_busy <= 1; bit_cnt <= 0;
                spi_cs_n <= 4'hE; // CS0选中
            end else if (tx_busy && clk_en) begin
                spi_clk <= ~spi_clk;
                if (spi_clk) begin
                    // 上升沿: 采样MISO
                    shift_in <= {shift_in[DATA_W-2:0], spi_miso};
                end else begin
                    // 下降沿: 输出MOSI
                    spi_mosi <= shift_out[DATA_W-1];
                    shift_out <= {shift_out[DATA_W-2:0], 1'b0};
                end
                if (bit_cnt >= DATA_W * 2 - 1) begin
                    tx_busy <= 0;
                    rx_valid <= 1;
                    spi_cs_n <= 4'hF;
                end else bit_cnt <= bit_cnt + 1;
            end
        end
    end

    assign rx_data = shift_in;
endmodule

🧪 SPI Master测试

SystemVerilogspi_master_tb.sv — SPI测试台
module spi_master_tb;
    logic clk=0, rst=1;
    logic [7:0] tx_data;
    logic tx_start, tx_busy;
    logic [7:0] rx_data;
    logic rx_valid;
    logic spi_clk, spi_mosi, spi_miso=0;
    logic [3:0] spi_cs_n;

    spi_master #(.CLK_FREQ(1000), .SPI_CLK(100)) uut(.*);
    always #5 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        $display("--- SPI Master测试 ---");
        tx_data=8'hA5; tx_start=1; #10; tx_start=0;
        wait(rx_valid); #10;
        $display("  发送0xA5, 接收0x%h ✓", rx_data);
        tx_data=8'h3C; tx_start=1; #10; tx_start=0;
        wait(rx_valid); #10;
        $display("  发送0x3C, 接收0x%h ✓", rx_data);
        $display("SPI Master测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 SPI vs UART vs I2C

特性SPIUARTI2C
线数4+22
速度50MHz+1Mbps3.4Mbps
距离短(<1m)长(15m+)中(几m)
从设备数CS线决定点对点127(7位地址)
双工全双工全双工半双工
复杂度

练习1:实现SPI回环测试:MOSI→MISO

练习2:连接SPI Flash(如W25Q)读取JEDEC ID

练习3:实现CPOL/CPHA可配置的SPI Master

练习4:设计SPI从设备模拟器(回复固定数据)

练习5:用SPI驱动ADC芯片(如MCP3008)读取模拟值

🔄
SPI大师
完成本课练习,掌握SPI高速通信!

🔧 Verilator验证步骤

步骤1verilator --lint-only spi_master.v

步骤2verilator --binary -j 0 spi_master.v spi_master_tb.sv

步骤3./obj_dir/Vspi_master_tb

🔬 SPI高级应用

1. SPI Flash读写:W25Q系列,支持页编程(256B)和扇区擦除(4KB)

2. SPI ADC:MCP3008(8ch 10位),3字节命令→2字节数据

3. SPI DAC:MCP4922(12位双通道),2字节命令写入

4. SPI OLED:SSD1351(128×128彩色),命令/数据模式切换

5. SPI陀螺仪:MPU6000(6轴IMU),支持DMA读取

💡 SPI时序优化

全双工:MOSI和MISO同时传输,效率翻倍

DMA:FIFO+自动CS控制,批量传输零CPU

流水线:当前字节传输时准备下一字节

多I/O:Quad SPI用4根数据线,速度4倍

🔄 SPI深入:时序与信号完整性

高速SPI的关键挑战是信号完整性。长线+高速→反射、串扰、振铃。以下是实用设计规则:

1. 线长:10MHz以下20cm内没问题,25MHz以上建议<10cm

2. 阻抗匹配:PCB走线50Ω,末端不加电阻(SPI是点对点)

3. CS时序:CS拉低→等≥tSU→开始SCLK,防止误读

4. 时钟极性:CPOL=0时SCLK空闲低,大多数设备默认

5. 多从设备布线:星形拓扑(从FPGA分别走线),避免菊花链

📊 SPI设备兼容性表

设备最大时钟模式CS有效字节序
W25Q Flash104MHz0/3MSB
MCP3008 ADC3.2MHz0MSB
MCP4922 DAC20MHz0MSB
SSD1351 OLED10MHz0MSB
ADXL3455MHz0/3MSB
SD卡(SPI)25MHz0MSB

🔬 Verilator完整仿真流程

1. lint检查verilator --lint-only spi_master.v

2. 编译verilator --binary -j 0 --trace spi_master.v spi_master_tb.sv

3. 运行./obj_dir/Vspi_master_tb

4. 查看波形gtkwave spi_master.vcd

📊 SPI模式详解:CPOL/CPHA组合

模式CPOLCPHASCL空闲采样边沿常见设备
000上升沿SD卡/Flash
101下降沿少数ADC
210下降沿少数传感器
311上升沿ADXL345

🔬 Verilator仿真流程

1. lintverilator --lint-only spi_master.v

2. 编译verilator --binary -j 0 --trace spi_master.v spi_master_tb.sv

3. 运行./obj_dir/Vspi_master_tb

4. 波形gtkwave spi_master.vcd