以下是 SD NAND芯片读写操作的详细实现步骤,涵盖从硬件连接到代码实现的完整流程,适用于MCU(如STM32、ESP32)开发场景:
SD NAND引脚 | MCU引脚 | 备注 |
---|---|---|
CLK | SPI_SCK | 时钟线,需10kΩ上拉电阻 |
CMD/DI | SPI_MOSI | 命令/数据输入线 |
DAT0/DO | SPI_MISO | 数据输出线,需10kΩ上拉电阻 |
CS | GPIO(如PA4) | 片选信号,低电平有效 |
VCC | 3.3V | 不可接5V! |
GND | GND | 共地 |
注意事项:
若使用高速模式(25MHz+),需缩短走线长度,避免信号反射。
电源需并联100nF滤波电容。
// STM32 HAL库示例(SPI1)void SPI_Init() {
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制CS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 初始化阶段低速
HAL_SPI_Init(&hspi1);}
// 步骤分解uint8_t SD_Init() {
// 1. 发送至少74个时钟脉冲(初始化同步)
CS_HIGH();
for(int i=0; i<10; i++) SPI_Transfer(0xFF);
// 2. 发送CMD0(复位到空闲状态)
uint8_t cmd0[] = {0x40, 0x00, 0x00, 0x00, 0x00, 0x95};
if(SD_SendCmd(cmd0, 0x01) != 0x01) return SD_ERROR;
// 3. 发送CMD8(检查电压兼容性)
uint8_t cmd8[] = {0x48, 0x00, 0x00, 0x01, 0xAA, 0x87};
if(SD_SendCmd(cmd8, 0x01) != 0x01) return SD_ERROR;
// 4. 循环发送CMD55 + ACMD41(初始化完成)
uint32_t timeout = 0;
do {
SD_SendCmd((uint8_t[]){0x77,0,0,0,0,0x65}, 0x01); // CMD55
if(SD_SendCmd((uint8_t[]){0x69,0x40,0,0,0,0x77}, 0x01) == 0x00) break; // ACMD41
HAL_Delay(10);
} while(timeout++ < 100); // 超时1秒
// 5. 切换到高速模式
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 25MHz(若支持)
HAL_SPI_Init(&hspi1);
return SD_OK;}
uint8_t SD_ReadBlock(uint32_t sector, uint8_t *buffer) {
// 1. 发送CMD17(读取单块)
uint8_t cmd17[] = {0x51,
(sector >> 24) & 0xFF,
(sector >> 16) & 0xFF,
(sector >> 8) & 0xFF,
sector & 0xFF,
0xFF};
if(SD_SendCmd(cmd17, 0x00) != 0x00) return SD_ERROR;
// 2. 等待数据起始令牌(0xFE)
uint8_t token;
uint32_t retry = 0;
do {
token = SPI_Transfer(0xFF);
if(retry++ > 0xFFFF) return SD_TIMEOUT;
} while(token != 0xFE);
// 3. 读取512字节数据
HAL_SPI_Receive(&hspi1, buffer, 512, 1000);
// 4. 丢弃2字节CRC
SPI_Transfer(0xFF);
SPI_Transfer(0xFF);
return SD_OK;}
void SD_ReadMultiBlocks(uint32_t start_sector, uint32_t num, uint8_t *buf) {
// 发送CMD18(连续读)
uint8_t cmd18[] = {0x52,
(start_sector >> 24) & 0xFF,
(start_sector >> 16) & 0xFF,
(start_sector >> 8) & 0xFF,
start_sector & 0xFF,
0xFF};
SD_SendCmd(cmd18, 0x00);
for(uint32_t i=0; i<num; i++) {
// 等待0xFE令牌并读取数据
while(SPI_Transfer(0xFF) != 0xFE);
HAL_SPI_Receive(&hspi1, buf + i*512, 512, 1000);
SPI_Transfer(0xFF); // 跳过CRC
SPI_Transfer(0xFF);
}
// 发送CMD12终止传输
SD_SendCmd((uint8_t[]){0x4C,0,0,0,0,0xFF}, 0x00);}
uint8_t SD_WriteBlock(uint32_t sector, const uint8_t *data) {
// 1. 发送CMD24(写单块)
uint8_t cmd24[] = {0x58,
(sector >> 24) & 0xFF,
(sector >> 16) & 0xFF,
(sector >> 8) & 0xFF,
sector & 0xFF,
0xFF};
if(SD_SendCmd(cmd24, 0x00) != 0x00) return SD_ERROR;
// 2. 发送数据起始令牌(0xFE)
SPI_Transfer(0xFE);
// 3. 发送512字节数据
HAL_SPI_Transmit(&hspi1, data, 512, 1000);
// 4. 发送伪CRC(0xFF 0xFF)
SPI_Transfer(0xFF);
SPI_Transfer(0xFF);
// 5. 等待写入完成(数据响应令牌)
uint8_t status;
do {
status = SPI_Transfer(0xFF);
} while(status == 0xFF);
// 检查响应是否成功(低4位为0100)
if((status & 0x1F) != 0x04) return SD_ERROR;
return SD_OK;}
void SD_WriteMultiBlocks(uint32_t start_sector, uint32_t num, const uint8_t *buf) {
// 发送CMD25(连续写)
uint8_t cmd25[] = {0x59,
(start_sector >> 24) & 0xFF,
(start_sector >> 16) & 0xFF,
(start_sector >> 8) & 0xFF,
start_sector & 0xFF,
0xFF};
SD_SendCmd(cmd25, 0x00);
for(uint32_t i=0; i<num; i++) {
// 发送块起始令牌(0xFC)
SPI_Transfer(0xFC);
// 发送数据
HAL_SPI_Transmit(&hspi1, buf + i*512, 512, 1000);
// 发送伪CRC
SPI_Transfer(0xFF);
SPI_Transfer(0xFF);
// 等待写入完成
while(SPI_Transfer(0xFF) != 0xFF);
}
// 发送停止令牌(0xFD)
SPI_Transfer(0xFD);}
uint8_t SD_SendCmd(uint8_t *cmd, uint8_t expected_response) {
CS_LOW();
// 发送命令(6字节)
HAL_SPI_Transmit(&hspi1, cmd, 6, 100);
// 等待响应(超时检测)
uint8_t response, retry = 0;
do {
response = SPI_Transfer(0xFF);
if(retry++ > 200) { // 超时约2ms
CS_HIGH();
return 0xFF;
}
} while(response == 0xFF);
CS_HIGH();
return (response == expected_response) ? SD_OK : SD_ERROR;}
错误码 | 含义 | 解决方法 |
---|---|---|
0x01 | 卡处于空闲状态 | 继续发送ACMD41初始化 |
0x05 | 命令参数错误 | 检查地址是否对齐到扇区边界 |
0x0B | 擦除序列错误 | 确认CMD32-CMD38顺序 |
0x0D | 写保护锁定 | 检查WP引脚是否接地 |
逻辑分析仪抓包:
监控SPI的CLK/MOSI/MISO信号,验证CMD序列和数据传输。
HAL库错误回调:
重写HAL_SPI_ErrorCallback()
函数捕获SPI通信错误。
SD NAND状态查询(CMD13):
发送CMD13 + 0x00000000
获取当前状态寄存器值。
启用4线SDIO模式(若MCU支持):
修改初始化流程,发送CMD55 + ACMD6切换总线宽度。
读写速度可提升至50MB/s(对比SPI模式的12MB/s)。
DMA传输:
// STM32 DMA配置示例HAL_SPI_Transmit_DMA(&hspi1, data, length);while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
缓存机制:
实现LRU缓存算法,减少物理读写次数。
通过以上步骤,即可完成SD NAND芯片的完整读写操作。