特别说明:以下所涉及代码参考网络、相关开发板厂家。只用于自己学习、理解和感悟。
SD存储卡( Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD 卡是现在嵌入式设备重要的存储模块。通常SD卡中都有文件系统,可以安装文件名和目录路径来读写文件。SD卡结构如下图所示:

- SD卡协议简介

数明:SD卡命令是6字节组成的命令包。
a) 命令码:
命令码由1个字节组成,第7位和第6位固定为“01”。
b) 校验与结束码:
校验与结束码由7位CRC校验和1位结束码组成。
c) 命令参数码:
2. 响应:
SD卡对每个命令多有响应,在SPI模式下有三种响应模式:R1、R2、R3。



-
SD卡2.0版的初始化步骤:
a.上电后延时至少 74clock,等待 SD 卡内部操作完成。
b. 片选 CS 低电平选中 SD 卡。
c. 发送 CMD0,需要返回 0x01,进入 Idle 状态。
d. 为了区别 SD 卡是 2.0 还是 1.0,或是 MMC 卡,这里根据协议向上兼容的,首先发送只有 SD2.0 才有的命令 CMD8,如果 CMD8 返回无错误,则初步判断为 2.0 卡,进一步循环发送命令CMD55+ACMD41,直到返回 0x00,确定 SD2.0 卡。
e. 如果 CMD8 返回错误则判断为 1.0 卡还是 MMC 卡,循环发送 CMD55+ACMD41,返回无错误,
则为 SD1.0 卡,到此 SD1.0 卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发
送 CMD1 进行初始化,如果返回无错误,则确定为 MMC 卡,如果在一定的次数下,返回为错
误,则不能识别该卡,初始化结束。(通过 CMD16 可以改变 SD 卡一次性读写的长度)。
f. CS 拉高。 -
SD卡的读步骤:
a. 发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00。
b. 接收数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes 默认正式传输的数据长度是 512Bytes。 -
SD 卡的写步骤:
a 发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00。
b. 发送数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes。
相关代码如下:
- spi_master.v
module spi_master(
input sys_clk,
input rst,
output nCS,
output DCLK,
output MOSI,
input MISO,
input CPOL,
input CPHA,
input nCS_ctrl,
input[15:0] clk_div,
input wr_req,
output wr_ack,
input[7:0] data_in,
output[7:0] data_out
);
localparam IDLE = 0;
localparam DCLK_EDGE = 1;
localparam DCLK_IDLE = 2;
localparam ACK = 3;
localparam LAST_HALF_CYCLE = 4;
localparam ACK_WAIT = 5;
reg DCLK_reg;
reg[7:0] MOSI_shift;
reg[7:0] MISO_shift;
reg[2:0] state;
reg[2:0] next_state;
reg[15:0] clk_cnt;
reg[4:0] clk_edge_cnt;
assign MOSI = MOSI_shift[7];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl;
always @(posedge sys_clk or posedge rst)
begin
if(rst)
state <= IDLE;
else
state <= next_state;
end
always @(*)
begin
case(state)
IDLE:
if(wr_req == 1’b1)
next_state <= DCLK_IDLE;
else
next_state <= IDLE;
DCLK_IDLE:
if(clk_cnt == clk_div)
next_state <= DCLK_EDGE;
else
next_state <= DCLK_IDLE;
DCLK_EDGE:
if(clk_edge_cnt == 5’d15)
next_state <= LAST_HALF_CYCLE;
else
next_state <= DCLK_IDLE;
LAST_HALF_CYCLE:
if(clk_cnt == clk_div)
next_state <= ACK;
else
next_state <= LAST_HALF_CYCLE;
ACK:
next_state <= ACK_WAIT;
ACK_WAIT:
next_state <= IDLE;
default:
next_state <= IDLE;
endcase
end
always @(posedge sys_clk or posedge rst)
begin
if(rst)
DCLK_reg <= 1’b0;
else if(state == IDLE)
DCLK_reg <= CPOL;
else if(state == DCLK_EDGE)
DCLK_reg <= ~DCLK_reg;
end
always @(posedge sys_clk or posedge rst)
begin
if(rst)
clk_cnt <= 16’d0;
else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
clk_cnt <= clk_cnt + 16’d1;
else
clk_cnt <= 16’d0;
end
//SPI时钟边沿计数
always @(posedge sys_clk or posedge rst)
begin
if(rst)
clk_edge_cnt <= 5’d0;
else if(state == DCLK_EDGE)
clk_edge_cnt <= clk_edge_cnt + 5’d1;
else if(state == IDLE)
clk_edge_cnt <= 5’d0;
end
//SPI数据输出
always @(posedge sys_clk or posedge rst)
begin
if(rst)
MOSI_shift <= 8’d0;
else if(state == IDLE && wr_req)
MOSI_shift <= data_in;
else if(state == DCLK_EDGE)
if(CPHA == 1’b0 && clk_edge_cnt[0] == 1’b1)
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
else if(CPHA == 1’b1 && (clk_edge_cnt != 5’d0 && clk_edge_cnt[0] == 1’b0))
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end
//SPI数据输入
always @(posedge sys_clk or posedge rst)
begin
if(rst)
MISO_shift <= 8’d0;
else if(state == IDLE && wr_req)
MISO_shift <= 8’h00;
else if(state == DCLK_EDGE)
if(CPHA == 1’b0 && clk_edge_cnt[0] == 1’b0)
MISO_shift <= {MISO_shift[6:0],MISO};
else if(CPHA == 1’b1 && (clk_edge_cnt[0] == 1’b1))
MISO_shift <= {MISO_shift[6:0],MISO};
end
endmodule
7.sd_card_cmd.v
module sd_card_cmd(
input sys_clk, //时钟输入
input rst, //异步复位输入,高复位
input[15:0] spi_clk_div, //SPI时钟分频,SPI时钟频率=系统时钟/((spi_clk_div+2)*2)
input cmd_req, //SD卡命令请求
output cmd_req_ack, //SD卡命令请求应答
output reg cmd_req_error, //SD卡命令请求错误
input[47:0] cmd, //SD卡命令,命令+参数+CRC,一共 48bit
input[7:0] cmd_r1, //sd卡命令期待的 R1响应
input[15:0] cmd_data_len, //sd卡命令后读取的数据长度,大部分命令没有读取数据
input block_read_req, //块数据读取请求
output reg block_read_valid, //块数据读取数据有效
output reg[7:0] block_read_data, //块数据读取数据
output block_read_req_ack, //块数据读取请求应答
input block_write_req, //块数据写请求
input[7:0] block_write_data, //块数据写数据
output block_write_data_rd, //块数据写数据请求,提前 block_write_data一个时钟周期
output block_write_req_ack, //块数据写请求应答
output nCS_ctrl, //到 SPI master控制器,cs片选控制
output reg[15:0] clk_div, //到 SPI Master控制器,时钟分频参数
output reg spi_wr_req, //到 SPI Master控制器,写一个字节请求
input spi_wr_ack, //来自 SPI Master控制器,写请求应答
output[7:0] spi_data_in, //到 SPI Master控制器,写数据
input[7:0] spi_data_out //来自 SPI Master控制器,读数据
);
parameter S_IDLE = 0;
parameter S_WAIT = 1;
parameter S_INIT = 2;
parameter S_CMD_PRE = 3;
parameter S_CMD = 4;
parameter S_CMD_DATA = 5;
parameter S_READ_WAIT = 6;
parameter S_READ = 7;
parameter S_READ_ACK = 8;
parameter S_WRITE_TOKEN = 9;
parameter S_WRITE_DATA_0 = 10;
parameter S_WRITE_DATA_1 = 11;
parameter S_WRITE_CRC = 12;
parameter S_WRITE_SUC = 13;
parameter S_WRITE_BUSY = 14;
parameter S_WRITE_ACK = 15;
parameter S_ERR = 16;
parameter S_END = 17;
reg[4:0] state;
reg CS_reg;
reg[15:0] byte_cnt;
reg[7:0] send_data;
wire[7:0] data_recv;
reg[9:0] wr_data_cnt;
assign cmd_req_ack = (state == S_END);
assign block_read_req_ack = (state == S_READ_ACK);
assign block_write_req_ack = (state == S_WRITE_ACK);
assign block_write_data_rd = (state == S_WRITE_DATA_0);
assign spi_data_in = send_data;
assign data_recv = spi_data_out;
assign nCS_ctrl = CS_reg;
always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
begin
CS_reg <= 1’b1;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
clk_div <= 16’d0;
send_data <= 8’hff;
state <= S_IDLE;
cmd_req_error <= 1’b0;
wr_data_cnt <= 10’d0;
end
else
case(state)
S_IDLE:
begin
state <= S_INIT;
clk_div <= spi_clk_div;
CS_reg <= 1’b1;
end
S_INIT:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt >= 16’d10)
begin
byte_cnt <= 16’d0;
spi_wr_req <= 1’b0;
state <= S_WAIT;
end
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_WAIT:
begin
cmd_req_error <= 1’b0;
wr_data_cnt <= 10’d0;
if(cmd_req == 1’b1)
state <= S_CMD_PRE;
else if(block_read_req == 1’b1)
state <= S_READ_WAIT;
else if(block_write_req == 1’b1)
state <= S_WRITE_TOKEN;
clk_div <= spi_clk_div;
end
S_CMD_PRE:
begin
if(spi_wr_ack == 1’b1)
begin
state <= S_CMD;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
spi_wr_req <= 1’b1;
CS_reg <= 1’b1;
send_data <= 8’hff;
end
end
S_CMD:
begin
if(spi_wr_ack == 1’b1)
begin
if((byte_cnt == 16’hffff) || (data_recv != cmd_r1 && data_recv[7] == 1’b0))
begin
state <= S_ERR;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else if(data_recv == cmd_r1)
begin
spi_wr_req <= 1’b0;
if(cmd_data_len != 16’d0)
begin
state <= S_CMD_DATA;
byte_cnt <= 16’d0;
end
else
state <= S_END;
byte_cnt <= 16’d0;
end
else
byte_cnt <= byte_cnt + 16’d1;
end
else
begin
spi_wr_req <= 1’b1;
CS_reg <= 1’b0;
if(byte_cnt == 16’d0)
send_data <= (cmd[47:40] | 8’h40);
else if(byte_cnt == 16’d1)
send_data <= cmd[39:32];
else if(byte_cnt == 16’d2)
send_data <= cmd[31:24];
else if(byte_cnt == 16’d3)
send_data <= cmd[23:16];
else if(byte_cnt == 16’d4)
send_data <= cmd[15:8];
else if(byte_cnt == 16’d5)
send_data <= cmd[7:0];
else
send_data <= 8’hff;
end
end
S_CMD_DATA:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt == cmd_data_len - 16’d1)
begin
state <= S_END;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_READ_WAIT:
begin
if(spi_wr_ack == 1’b1 && data_recv == 8’hfe)
begin
spi_wr_req <= 1’b0;
state <= S_READ;
byte_cnt <= 16’d0;
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_READ:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt == 16’d513)
begin
state <= S_READ_ACK;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_WRITE_TOKEN:
if(spi_wr_ack == 1’b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1’b1;
send_data <= 8’hfe;
end
S_WRITE_DATA_0:
begin
state <= S_WRITE_DATA_1;
wr_data_cnt <= wr_data_cnt + 10’d1;
end
S_WRITE_DATA_1:
begin
if(spi_wr_ack == 1’b1 && wr_data_cnt == 10’d512)
begin
state <= S_WRITE_CRC;
spi_wr_req <= 1’b0;
end
else
begin
if(spi_wr_ack == 1’b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1’b0;
end
else
begin
spi_wr_req <= 1’b1;
send_data <= block_write_data;
end
end
end
S_WRITE_CRC:
begin
if(spi_wr_ack == 1'b1)
begin
if(byte_cnt <= 16'd1)
begin
state <= S_WRITE_SUC;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
byte_cnt <= byte_cnt + 16'd1;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_WRITE_SUC:
begin
if(spi_wr_ack == 1'b1)
begin
if(data_recv[4:0] == 5'b00101)
begin
state <= S_WRITE_BUSY;
spi_wr_req <= 1'b0;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_WRITE_BUSY:
begin
if(spi_wr_ack == 1'b1)
begin
if(data_recv == 8'hff)
begin
state <= S_WRITE_ACK;
spi_wr_req <= 1'b0;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_ERR:
begin
state <= S_END;
cmd_req_error <= 1'b1;
end
S_READ_ACK,S_WRITE_ACK,S_END:
begin
state <= S_WAIT;
end
default:
state <= S_IDLE;
endcase
end
always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
block_read_valid <= 1’b0;
else if(state == S_READ && byte_cnt <= 16’d512)
block_read_valid <= spi_wr_ack;
else
block_read_valid <= 1’b0;
end
always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
block_read_data <= 8’d0;
else if(state == S_READ && spi_wr_ack ==1’b1)
block_read_data <= data_recv;
end
endmodule
8.sd_card_sec_read_write.v
module sd_card_sec_read_write
#(
parameter SPI_LOW_SPEED_DIV = 248,
parameter SPI_HIGH_SPEED_DIV = 0
)
(
input clk, //时钟输入
input rst, //异步复位输入,高复位
output reg sd_init_done, //SD卡初始化完成
input sd_sec_read, //SD卡扇区读请求
input [31:0] sd_sec_read_addr, //SD卡扇区读地址
output [7:0] sd_sec_read_data, //SD卡扇区读数据
output sd_sec_read_data_valid, //SD卡读出的数据有效
output sd_sec_read_end, //SD卡数据读完成
input sd_sec_write, //SD卡数据写请求
input [31:0] sd_sec_write_addr, //SD卡扇区写请求应答
input [7:0] sd_sec_write_data, //SD卡扇区写请求数据
output sd_sec_write_data_req, //SD卡扇区写请求数据读取,提前一个sd_sec_write_data一个时钟周期
output sd_sec_write_end, //SD卡扇区写请求完成
output reg [15:0] spi_clk_div, //SPI时钟分频,SPI时钟频率=系统时钟/((spi_clk_div + 2)*2)
output reg cmd_req, //SD卡命令请求
input cmd_req_ack, //SD卡命令请求应答
input cmd_req_error, //SD卡命令请求错误
output reg[47:0] cmd, //SD卡命令,
output reg[7:0] cmd_r1, //SD卡命令期待的R1响应
output reg[15:0] cmd_data_len, //SD卡所读取的数据长度,大部分命令没有读取数据
output reg block_read_req, //块数据读取请求
input block_read_valid, //块数据读取数据有效
input [7:0] block_read_data, //块读取数据
input block_read_req_ack, //块数据读取请求应答
output reg block_write_req, //块写请求
output[7:0] block_write_data, //块写数据
input block_write_data_rd, //块写数据请求,提前block_write_data一个时钟周期
input block_write_req_ack //块写请求应答
);
reg[7:0] read_data;
reg[31:0] timer;
localparam S_IDLE = 0;
localparam S_CMD0 = 1;
localparam S_CMD8 = 2;
localparam S_CMD55 = 3;
localparam S_CMD41 = 4;
localparam S_CMD17 = 5;
localparam S_READ = 6;
localparam S_CMD24 = 7;
localparam S_WRITE = 8;
localparam S_ERR = 14;
localparam S_WRITE_END = 15;
localparam S_READ_END = 16;
localparam S_WAIT_READ_WRITE = 17;
localparam S_CMD16 = 18;
reg[4:0] state;
reg[31:0] sec_addr;
assign sd_sec_read_data_valid = (state == S_READ) && block_read_valid;
assign sd_sec_read_data = block_read_data;
assign sd_sec_read_end = (state == S_READ_END);
assign sd_sec_write_data_req = (state == S_WRITE) && block_write_data_rd;
assign block_write_data = sd_sec_write_data;
assign sd_sec_write_end = (state == S_WRITE_END);
always @(posedge clk or posedge rst)
begin
if(rst)
begin
state <= S_IDLE;
cmd_req <= 1’b0;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’d0;
cmd <= 48’d0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
block_write_req <= 1’b0;
block_read_req <= 1’b0;
sec_addr <= 32’d0;
sd_init_done <= 1’b0;
end
else
case(state)
S_IDLE:
begin
state <= S_CMD0;
sd_init_done <= 1’b0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
end
S_CMD0:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD8;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h01;
cmd <= {8’d0,8’h00,8’h00,8’h00,8’h00,8’h95};
end
end
S_CMD8:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD55;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d4;
cmd_r1 <= 8’h0;
cmd <= {8’d8,8’h00,8’h00,8’h01,8’haa,8’h87};
end
end
S_CMD55:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD41;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h01;
cmd <= {8’d55,8’h00,8’h00,8’h00,8’h00,8’hff};
end
end
S_CMD41:
begin
if(cmd_req_ack & ~ cmd_req_error)
begin
state <= S_CMD16;
cmd_req <= 1’b0;
sd_init_done <= 1’b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d41,8’h40,8’h00,8’h00,8’h00,8’hff};
end
end
S_CMD16:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WAIT_READ_WRITE;
cmd_req <= 1’b0;
sd_init_done <= 1’b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d16,32’d512,8’hff};
end
end
S_WAIT_READ_WRITE:
begin
if(sd_sec_write == 1’b1)
begin
state <= S_CMD24;
sec_addr <= sd_sec_write_addr;
end
else if(sd_sec_read == 1’b1)
begin
state <= S_CMD17;
sec_addr <= sd_sec_read_addr;
end
spi_clk_div <= 16’d0;
end
S_CMD24:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WRITE;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d24,sec_addr,8’hff};
end
end
S_WRITE:
begin
if(block_write_req_ack == 1'b1)
begin
block_write_req <= 1'b0;
state <= S_WRITE_END;
end
else
block_write_req <= 1'b1;
end
S_CMD17:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_READ;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h00;
cmd <= {8'd17,sec_addr,8'hff};
end
end
S_READ:
begin
if(block_read_req_ack)
begin
state <= S_READ_END;
block_read_req <= 1'b0;
end
else
begin
block_read_req <= 1'b1;
end
end
S_WRITE_END:
begin
state <= S_WAIT_READ_WRITE;
end
S_READ_END:
begin
state <= S_WAIT_READ_WRITE;
end
default:
state <= S_IDLE;
endcase
end
endmodule
本文详细介绍了SD卡的协议,包括命令结构、响应模式和初始化步骤,并提供了FPGA实现SD卡读写操作的Verilog代码,涵盖了读写命令、数据传输等关键过程。
4619





