💾 第25课:SD卡读写

SPI模式SD卡 + CMD命令协议 + 扇区读写 — 大容量存储!

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

💾
存储大师
SD卡SPI模式 + CMD8/CMD17/CMD24 + 扇区读写

💾 SD卡SPI模式

SD卡支持SPI模式和SD模式。SPI模式更简单,FPGA友好——只需标准SPI接口+CS线!

SD卡SPI模式连接 ┌──────────┐ ┌─────────┐ │ FPGA │ │ SD卡 │ │ │ SPI_CLK │ CLK │ │ SPI_MOSI├──────────→ DAT2/DO │ │ SPI_MISO├←────────── CMD/DI │ │ CS_N ├──────────→ DAT3/CS │ │ │ │ VDD 3.3V│ └──────────┘ └─────────┘ 初始化流程: 1. CS=1, 发送≥74个时钟(1MHz) 2. CS=0, CMD0 → R1(0x01=空闲) 3. CMD8(0x1AA) → R7(检查SDv2) 4. ACMD41 → R1(0x00=就绪) 5. CMD17(地址) → R1 + 数据块(512B) 6. 提高SPI时钟到25MHz

📐 SD卡SPI控制器

Verilogsd_spi_ctrl.v — SD卡SPI模式控制器
// SD卡SPI模式控制器(简化版)
// 支持CMD0(复位) + CMD8(检查) + CMD17(读扇区)
module sd_spi_ctrl (
    input  wire        clk,
    input  wire        rst,
    // 命令接口
    input  wire [5:0]  cmd,
    input  wire [31:0] cmd_arg,
    input  wire        cmd_start,
    output reg         cmd_busy,
    output reg  [7:0]  rsp_data,
    output reg         rsp_valid,
    // SPI接口(复用SPI Master)
    output reg         spi_clk,
    output reg         spi_mosi,
    input  wire        spi_miso,
    output wire        spi_cs_n
);
    assign spi_cs_n = 0; // SD卡SPI模式CS始终低

    // SPI时钟分频: 400kHz初始化, 25MHz正常
    localparam INIT_DIV = 62;  // 50M/400k/2
    localparam NORM_DIV = 1;   // 50M/25M/2
    reg [7:0] clk_div;
    reg       clk_en;

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

    // CMD帧: [01 CMD(6) ARG(32) CRC(7) 1]
    reg [47:0] cmd_frame;
    reg [5:0]  bit_cnt;
    reg [7:0]  crc7;

    // CRC7计算(简化: 预计算常用CMD的CRC)
    always @(*) begin
        case(cmd)
            6'd0:  crc7 = 8'h95; // CMD0 CRC
            6'd8:  crc7 = 8'h87; // CMD8 CRC
            6'd17: crc7 = 8'h00; // CMD17 CRC(简化)
            default: crc7 = 8'hFF;
        endcase
    end

    localparam IDLE=0, SEND_CMD=1, WAIT_RSP=2, READ_DATA=3, DONE=4;
    reg [2:0] state;

    always @(posedge clk) begin
        if (rst) begin
            state <= IDLE; cmd_busy <= 0;
            spi_clk <= 0; spi_mosi <= 1;
            bit_cnt <= 0;
        end else if (clk_en) begin
            rsp_valid <= 0;
            case(state)
                IDLE: begin
                    spi_mosi <= 1;
                    if (cmd_start) begin
                        cmd_frame <= {2'b01, cmd, cmd_arg, crc7[6:0], 1'b1};
                        bit_cnt <= 0;
                        state <= SEND_CMD;
                        cmd_busy <= 1;
                    end
                end
                SEND_CMD: begin
                    spi_clk <= ~spi_clk;
                    if (!spi_clk) begin
                        spi_mosi <= cmd_frame[47];
                        cmd_frame <= {cmd_frame[46:0], 1'bFF};
                    end
                    if (bit_cnt >= 47) state <= WAIT_RSP;
                    else bit_cnt <= bit_cnt + 1;
                end
                WAIT_RSP: begin
                    spi_clk <= ~spi_clk;
                    if (spi_clk && spi_miso == 0) begin
                        rsp_data <= 0;
                        state <= READ_DATA;
                        bit_cnt <= 0;
                    end
                end
                READ_DATA: begin
                    spi_clk <= ~spi_clk;
                    if (spi_clk) begin
                        rsp_data <= {rsp_data[6:0], spi_miso};
                        if (bit_cnt[2:0] == 7) begin
                            rsp_valid <= 1;
                        end
                        if (bit_cnt >= 7) state <= DONE;
                        else bit_cnt <= bit_cnt + 1;
                    end
                end
                DONE: begin
                    cmd_busy <= 0;
                    state <= IDLE;
                end
            endcase
        end
    end
endmodule

🧪 SD卡控制器测试

SystemVerilogsd_spi_ctrl_tb.sv — SD卡测试台
module sd_spi_ctrl_tb;
    logic clk=0, rst=1;
    logic [5:0] cmd; logic [31:0] cmd_arg;
    logic cmd_start, cmd_busy;
    logic [7:0] rsp_data; logic rsp_valid;
    logic spi_clk, spi_mosi, spi_miso=1;
    logic spi_cs_n;

    sd_spi_ctrl uut(.*);
    always #10 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        $display("--- SD卡控制器测试 ---");
        // CMD0: 复位SD卡
        cmd=6'd0; cmd_arg=32'd0; cmd_start=1; #20; cmd_start=0;
        wait(!cmd_busy);
        $display("  CMD0(复位)发送完成 ✓");
        // CMD8: 检查SD卡版本
        cmd=6'd8; cmd_arg=32'h000001AA; cmd_start=1; #20; cmd_start=0;
        wait(!cmd_busy);
        $display("  CMD8(检查)发送完成 ✓");
        $display("SD卡控制器测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 SD卡CMD命令速查

命令参数响应功能
CMD00x00000000R1软件复位
CMD80x000001AAR7检查接口条件
CMD17扇区地址R1+数据读单扇区
CMD24扇区地址R1写单扇区
CMD550x00000000R1下一条是ACMD
ACMD410x40000000R1发送OPC初始化
CMD580x00000000R3读OCR寄存器

💡 初始化关键:SD卡必须先用低速(100~400kHz)初始化,成功后才能切换到高速(25MHz+)。CMD8检查SDv2兼容性,如果收到R7响应说明是SDHC卡,使用扇区(512B)寻址。

练习1:实现完整SD卡初始化流程(CMD0→CMD8→ACMD41→CMD58)

练习2:读取SD卡第0扇区(MBR)并解析分区表

练习3:实现FAT32文件系统读取,列出根目录

练习4:用UART把SD卡数据发送到PC查看

练习5:实现写扇区功能(CMD24),保存VGA截图

💾
存储大师
完成本课练习,掌握SD卡大容量存储!

🔧 Verilator验证步骤

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

步骤2verilator --binary -j 0 sd_spi_ctrl.v sd_spi_ctrl_tb.sv

步骤3./obj_dir/Vsd_spi_ctrl_tb

🔬 SD卡文件系统

MBR(主引导记录):第0扇区,包含分区表

BPB(BIOS参数块):FAT卷的起始,包含簇大小、FAT位置

FAT表:簇链管理,每个条目指向下一簇

根目录:FAT32的根目录是一个簇链

数据区:文件内容按簇存储

💡 SD卡使用技巧

初始化顺序:CMD0→CMD8→ACMD41→CMD58→提高时钟

SD vs SDHC:SD用字节寻址,SDHC用扇区寻址(CMD58判断)

写保护:CMD28/CMD29设置写保护块

性能:SPI模式最大25MHz,读约3MB/s,写约1.5MB/s

💾 SD卡深入:CID/CSD寄存器

每张SD卡都有唯一标识——CID(Card Identification)寄存器包含厂商ID、产品名、序列号等。CSD(Card Specific Data)包含容量和速度信息。

CMD10:读取CID寄存器(16字节),包含厂商名和序列号

CMD9:读取CSD寄存器(16字节),包含容量计算参数

容量计算:C_SIZE × (C_SIZE_MULT+1) × (READ_BL_LEN) 字节

SDHC:CSD版本2.0,容量=(C_SIZE+1)×512KB,最小4GB

速度等级:Class 2/4/6/10,UHS-I/II,影响最低写入速度

📊 SD卡容量与速度等级

类型容量寻址默认SPI最大SPI
SDSC≤2GB字节400kHz25MHz
SDHC4~32GB扇区400kHz50MHz
SDXC64GB~2TB扇区400kHz50MHz

🔬 Verilator仿真

1. lintverilator --lint-only sd_spi_ctrl.v

2. 编译verilator --binary -j 0 --trace sd_spi_ctrl.v sd_spi_ctrl_tb.sv

3. 运行./obj_dir/Vsd_spi_ctrl_tb