第一次接触SD卡SPI模式时,我对着开发板上那个小小的卡槽发呆了半小时——这么小的接口怎么传输海量数据?后来才明白,SPI协议就像是用四根电线搭建的高速公路。MOSI(主机输出从机输入)和MISO(主机输入从机输出)是双向车道,CLK是交通信号灯,而CS就是收费站起落杆。这种设计比SDIO模式节省了3根数据线,特别适合FPGA这种引脚资源紧张的场合。
SD卡内部其实是个精密的闪存管理系统。以常见的512字节扇区为例,相当于把存储空间划分成无数个小格子。CMD17/CMD24指令就像快递单号,精确指向某个格子进行存取。实测发现,Class10的SD卡在SPI模式下写入速度能达到1.2MB/s,读取速度约2MB/s,完全能满足大多数嵌入式系统的需求。
在Artix7开发板上连接MicroSD卡时,引脚对应关系要特别注意:SPI的MOSI接DI引脚(引脚3),MISO接DO引脚(引脚7),CLK接CLK引脚(引脚5),CS接CS引脚(引脚2)。有次调试时我把MOSI和MISO接反了,结果读出来的全是乱码,排查了半天才发现这个低级错误。
信号稳定性方面有三大经验:
特别提醒:热插拔时一定要先拉高CS信号,否则可能损坏SD卡控制器。我就曾因此烧毁过一张32GB的卡。
SD卡初始化就像是在对暗号,错一个步骤就前功尽弃。根据SD2.0规范,完整的初始化序列应该是:
最坑的是时钟频率控制。初始化阶段必须用400kHz低速时钟,等收到ACMD41返回0x00后才能切换到50MHz。我在Verilog里是这样实现的:
verilog复制always @(posedge clk) begin
if(init_phase)
spi_clk <= div_clk(400_000); // 分频产生400kHz
else
spi_clk <= div_clk(50_000_000); // 切换为50MHz
end
写扇区时最容易卡在"写忙碌"状态。正确的CMD24操作流程应该是:
读扇区有个隐藏陷阱:CMD17之后要跳过填充字节。我的处理方法是:
verilog复制parameter WAIT_FE = 3'd0, READ_DATA = 3'd1;
always @(state) begin
case(state)
WAIT_FE: if(rx_data==8'hFE) state <= READ_DATA;
READ_DATA: if(byte_cnt==511) state <= IDLE;
endcase
end
实测发现,连续读写时插入8个时钟间隔能提升稳定性。对于大文件传输,建议预先擦除多扇区(CMD32+CMD33+CMD38),速度能提升3倍以上。
稳定的SD卡驱动离不开严谨的状态机设计。我的方案采用三级状态机:
特别要注意超时处理。比如等待响应时应加入计数器:
verilog复制if(wait_cnt > 200_000) begin // 约10ms超时
cmd_retry <= cmd_retry + 1;
if(cmd_retry>3) error <= 1'b1;
end
调试时用SignalTap抓取的波形显示,从发送CMD17到收到第一个数据字节通常需要300个时钟周期。这个延迟在实时系统中必须考虑。
提升SPI模式吞吐量有三大诀窍:
通过DMA加速的测试数据显示,512字节传输时间可从2.3ms压缩到1.1ms。对于图像采集系统,这种优化直接决定了能否实现30fps的存储速率。
遇到初始化失败时,建议按这个顺序排查:
有个隐蔽的坑是某些品牌SD卡对ACMD41响应较慢。解决方案是延长超时时间到500ms,或者改用CMD1尝试初始化。