在嵌入式系统开发中,外部存储扩展是常见需求。SPI(Serial Peripheral Interface)作为一种同步串行通信协议,因其简单高效的特性,成为连接微控制器与SD卡的首选方案。SPI总线仅需四根信号线即可实现全双工通信:
SD卡支持两种通信模式:原生SD总线模式和SPI模式。后者虽然传输速率较低(最高25MHz),但硬件实现简单,特别适合资源受限的嵌入式场景。当CS引脚在GO_IDLE_STATE(CMD0)命令期间保持低电平时,SD卡自动切换至SPI模式。
关键提示:SPI模式下,初始化阶段时钟频率必须限制在400kHz以内,识别完成后可提升至25MHz。这是SD卡规范中的硬性要求,违反可能导致初始化失败。
CRC校验本质上是一种基于多项式除法的错误检测编码。发送方对原始数据执行特定多项式运算,生成校验码附加在数据后;接收方重复相同计算,通过比对校验码判断传输是否出错。SD卡通信使用两种CRC:
CRC-7的硬件实现可采用7位移位寄存器(如图2所示)。计算流程如下:
c复制// CRC-7软件实现示例
uint8_t crc7(const uint8_t* data, uint16_t length) {
uint8_t crc = 0;
for(uint16_t i=0; i<length; i++) {
crc ^= data[i];
for(uint8_t j=0; j<8; j++) {
if(crc & 0x80) crc = (crc << 1) ^ 0x09;
else crc <<= 1;
}
}
return crc >> 1; // 取高7位
}
虽然SD卡SPI模式下CRC校验是可选的,但强烈建议通过CMD59(CRC_ON_OFF)命令启用:
bash复制# 发送启用CRC的命令帧格式
0x7B 0x00 0x00 0x00 0x01 0xXX
# 其中0x7B是CMD59的SPI编码(0x3B|0x40)
# 最低位参数1表示启用CRC
# 最后字节为CRC-7校验码(启用前可设为0)
SPI模式下所有命令均为6字节固定格式:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 0x40 + 命令号 | 命令号范围0-63 |
| 1-4 | 32位参数 | 大端格式 |
| 5 | CRC-7 | 前5字节的校验码 |
重要命令示例:
SD卡响应分为几种类型:
R1响应(1字节):bit7为0表示正常,各bit含义如下:
code复制bit0: 卡处于空闲状态
bit1: 擦除复位
bit2: 非法命令
bit3: 通信CRC错误
bit4: 擦除序列错误
bit5: 地址错误
bit6: 参数错误
bit7: 固定为0
数据响应令牌(读操作):
数据响应令牌(写操作):
code复制0bXXX0AAA1
│││ └─固定1
│└└───操作状态(000=接受,其他=拒绝)
└────固定0
正确的初始化是通信成功的前提:
经验之谈:初始化阶段若卡长时间无响应,可尝试降低时钟频率或检查电源电压(SD卡要求2.7-3.6V)。
c复制// 伪代码示例
uint8_t readBlock(uint32_t blockAddr, uint8_t* buffer) {
sendCommand(CMD17, blockAddr);
if(waitResponse() != 0x00) return ERROR;
while(SPI_Receive() != 0xFE); // 等待数据令牌
for(int i=0; i<512; i++) buffer[i] = SPI_Receive();
uint16_t crc = (SPI_Receive() << 8) | SPI_Receive();
if(calc_crc16(buffer, 512) != crc) return CRC_ERROR;
return SUCCESS;
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| CMD0无响应 | CS信号问题/电压不足 | 检查硬件连接,测量供电电压 |
| ACMD41循环超时 | 卡不支持电压范围 | 尝试其他品牌SD卡 |
| 读数据CRC错误 | 时钟速率过高/信号干扰 | 降低时钟频率,缩短走线长度 |
| 写操作频繁失败 | 卡写保护/文件系统损坏 | 检查物理写保护开关,重新格式化 |
| 随机数据错误 | 未启用CRC校验 | 通过CMD59启用CRC功能 |
逻辑分析仪捕获:使用Saleae等工具抓取SPI波形,重点观察:
分阶段验证:
电源噪声处理:
我在实际项目中曾遇到一个典型问题:某批次SD卡在高温环境下频繁出现CRC错误。最终发现是PCB走线过长(>10cm)导致信号完整性下降。解决方案包括:
双缓冲技术:在RAM允许的情况下,采用乒乓缓冲区实现:
c复制uint8_t bufferA[512], bufferB[512];
// 当CPU处理bufferA时,DMA正在填充bufferB
块预读取:根据访问模式预测下一个可能读取的块,提前加载到缓存
写延迟隐藏:利用SD卡允许CS释放的特性,在卡编程期间处理其他任务
时钟动态调整:
c复制void setSpiSpeed(uint32_t kHz) {
if(kHz <= 400) SPIBR = (CPU_CLK / (2 * 400000)) - 1;
else SPIBR = (CPU_CLK / (2 * kHz * 1000)) - 1;
}
对于MAXQ2000这类资源受限的MCU,建议将频繁使用的SD卡操作(如读扇区)封装为汇编语言函数,可提升约30%的执行效率。以下是CRC16计算的汇编优化示例:
assembly复制; MAXQ2000 CRC16实现
CRC16_Calc:
MOV DP[0], #0xFFFF ; 初始化CRC寄存器
MOV ACC, #data_ptr ; 数据指针
MOV LC[0], #length ; 数据长度
Loop:
MOV A[0], @ACC++ ; 取数据字节
XOR DP[0], A[0] ; 异或操作
...
DBNZ LC[0], Loop ; 循环处理
RET
通过合理运用这些技术,即使在8MHz主频的MAXQ2000上,也能实现超过500KB/s的稳定读取速度,完全满足大多数嵌入式存储需求。