(呕心沥血,写了接近三个小时。我觉得如果耐心看的话,真的会有所帮助哦哈哈哈。看在博主这么kindhearted的份上,点个赞吧!!!!)
学到后面发现例程文件越来越多,代码越来越恶心了。SD卡,I2C_EEPROM,SPI_Flash这种芯片的各种时序看得头晕,数据手册冗长啰嗦,搞不好还是100多页的英文版。。。不是人看的。。所以我觉得更加要把自己的心得写出来,让更多爱学习的人提高自己的效率。虽说看完博客不会给你特别大的帮助,但起码会给你入个门,有个感性的了解。
OK,讲了那么多,下面开始我们今天的小任务:解剖FPGA黑金开发板AX301SD卡的例程:
在研究代码之前,应该要事先对SD卡的操作指令和时序了解:
可以去看看这篇博客:https://blog.csdn.net/c602273091/article/details/41593955
(我浏览了下这个作者的主页,真的是太大佬了,任重而道远啊,要学的东西还很多,希望我将来也可以成为这样的大佬。)
请牢记刚刚那篇博客所讲的东西,然后我开始结合代码讲解。PS:代码要剖开讲,完整的代码就是我剖开的顺序。
首先分析sd_card_cmd.v这个文件
module sd_card_cmd(
input sys_clk,
input rst,
input[15:0] spi_clk_div, //SPI module clock division parameter
input cmd_req, //SD card command request
output cmd_req_ack, //SD card command request response
output reg cmd_req_error, //SD card command request error
input[47:0] cmd, //SD card command
input[7:0] cmd_r1, //SD card expect response
input[15:0] cmd_data_len, //SD card command read data length
input block_read_req, //SD card sector data read request
output reg block_read_valid, //SD card sector data read data valid
output reg[7:0] block_read_data, //SD card sector data read data
output block_read_req_ack, //SD card sector data read response
input block_write_req, //SD card sector data write request
input[7:0] block_write_data, //SD card sector data write data next clock is valid
output block_write_data_rd, //SD card sector data write data
output block_write_req_ack, //SD card sector data write response
output nCS_ctrl, //SPI module chip select control
output reg[15:0] clk_div,
output reg spi_wr_req, //SPI module data sending request
input spi_wr_ack, //SPI module data request response
output[7:0] spi_data_in, //SPI module send data
input[7:0] spi_data_out //SPI module data returned
);
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;
这些参数的设定比较多,大家一定要有个初步的印象。我自己学习的时候,特地用一张白纸记下一些寄存器的状态,例如spi_wr_ack什么时候为1,只有这样在if条件判断的时候才会理解得更加清楚。而且大家要记住,只要是input的寄存器,就一定是另一个文件的output,一一对应好,把条件理清楚,再开始看接下来的模块化代码。
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
//send 11 bytes on power(at least 74 SPI clocks)
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
这段代码对应的原理是:SD卡的初始化,需要先给予至少74个CLK后。那么为什么要74个CLK呢?因为在上电初期,电压的上升过程据SD卡组织的计算约合64个CLK周期才能到达SD卡的正常工作电压他们管这个叫做Supply ramp up time,其后的10个CLK是为了与SD卡同步,之后开始CMD0的操作,严格按照此项操作,一定没有问题。因此,在S_INIT状态下,要发送10个字节,才进入等待状态。我一开始也很懵逼,为什么是10,然后看到初始化这个状态应该就是等待的那个原理,转念一想10*8=80(一个字节8位2333),刚好大于74个CLK。
S_WAIT:
begin
cmd_req_error <= 1'b0;
wr_data_cnt <= 10'd0;
//wait for instruction
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
然后就是等待状态了。假想一下SD卡是一个工人,这个时候它有三个任务,发送命令,读操作,写操作,这个时候就是在等待有没有人让它去做那样的事情。
S_CMD_PRE:
begin
//before sending a command, send an byte 'ff',provide some clocks
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
begin
state <= S_END;
byte_cnt <= 16'd0;
end
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
我把命令的两段代码都放在一起写。我一开始也混淆了,为什么这个spi_wr_ack老是出现,把我完全弄糊涂了。后来一想,发送命令也是write操作啊,,只不过我是在写 命 令!而已啊。所以,这段代码的理解是:当写命令应答完成,就进入下一个状态S_CMD,写命令清0,字节计数器清零,否则(应答没完成),写请求,CS保持,然后发送校验码0xff。
对应的原理,那篇博客也有讲到:写入一个CMD命令时,首先将CS拉低,在SD卡的DIN引脚输入命令及相应的参数和校验码。
接着就是S_CMD指令:如果应答完成,需要校验。发送一个写命令和地址参数,后跟校验码,不采用校验码,发送0xff即可,SD卡会以R1回应。这里就是校验到底与R1返回的数据对不对。如果不对,则进入错误,如果正确,当命令的数据长度不为0时,进入S_CMD_DATA状态,否则结束。否则计数器开始计时等待响应。如果应答没有完成,这才是S_CMD真正要做的事情,把CS拉低,然后开始发送命令。然后,我相信你会对这段代码感到懵逼,没关系,我来给你解释下:每一个命令的长度都是固定的6个字节,前1个字节的值=命令号+0x40;中间4个字节为参数,不同的命令参数格式都不相同,但参数最多为4个字节;最后1个字节是CRC校验码和1位固定结束位‘1’。所以你会看到cmd的命令在sd_card_sec_read_write.v中,每一个cmd都是由6个字节组成,如:8'd0,8'h00,8'h00,8'h00,8'h00,8'h95。
然后进入了一个叫S_CMD_DATA的状态。
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
这个又是什么东西。真的恶心。后来顺着思路,我去找cmd_data_len到底是个什么东西,发现在sd_card_sec_read_write.v中,只有CMD8有4个字节的长度,其他均为0!这其中一定有什么骚规则,没错,后来百度了资料,果然:原来CMD8会返回5个字节的数据,首先接收到第一个字节格式为R1的数据,这个数据只要判断是否为0X01即可,如果为0X01,表示SD卡响应了CMD8命令,如果不为0X01(一般会是0X09或0X05,不用关心),则表示SD卡不支持CMD8命令。在接收到0X01之后,随后需要接收4字节数据,其中31-28位为command version,即命令的类型,此处为CMD8;然后27-12位是保留的数据位,通常为0;然后11-8位是SD卡支持的电压范围,此处得到的应该是‘0001’;最后一个字节是我们在CMD8的命令中发送给SD卡的数据,SD卡又原模原样的返回来了,在命令中我们发送的是0XAA,此处得到的也应该是0XAA。
命令就此讲完了,然后就开始了读和写的等待状态:
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
首先是读:SD卡读一个块: 发送一个读命令,后面的参数是读取的块的首地址和0xff,SD会以R1回应,如果地址正确,回应应该为0。接着是SD卡发送数据,以令牌0xfe为起始,后面是要读取的数据,最后是2个字节的校验码
首先,在读的等待状态时,要等待写应答,为什么呢?我自己的理解是,读也需要先写地址,才能够读到地址所对应的数据。然后就是0xfe,如上所说。如果写没有应答,则发送写请求。
当进入读状体时,这个时候就要读正式数据512Bytes了,读完时,,则进入了S_READ_ACK的读应答状态。否则,字节计数器+1,等待读完。如果没有写应答信号,则发送写请求。
然后进入写状态:
S_WRITE_TOKEN:
if(spi_wr_ack == 1'b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
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 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
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_WRITE_TOKEN状态下,发送0xfe,发送写请求(对,应该是从else if开始理解),当写应答时,进入S_WRITE_DATA_0状态,此时计数器加1,进入S_WRITE_DATA_1状态,对SD卡进行写操作。这段代码我怎么感觉有点多余,后面还没应答的时候一直返回S_WRITE_DATA_0,其实就是计数计到513,把开始的0xfe也算上,到达513之后,开始进入CRC检验。然后就是上面的第三条,读到00101才说明写成功。然后进入第四条。当接收到0xff时则说明写操作完成,发送一个写成功的应答信号,否则继续写。。。
然后就是错误警告和应答状态又回到空闲状态:
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;
最后还有两段代码,就是对读操作的补充,因为读操作其实就是读SD卡的内容,当读有效时,把spi通信接收到的data_recv发送到主机。
吐血。。。把sd_card_cmd的思路讲清楚了,细节上的理解需要你们自己去写代码,然后才能够透彻地理解清楚。
接下来我们来看看另一个文件:sd_card_sec_read_write.v
这里无非就是一些命令,我们来看看。
鉴于篇幅太长,输入输出以及寄存器的代码还有RESET的代码我就不发上来了。直接进入状态机:
case(state)
S_IDLE:
begin
state <= S_CMD0;
sd_init_done <= 1'b0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
end
空闲状态下,发送CMD0,
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
大家不要意外,8'd0,8'h00,8'h00,8'h00,8'h00,8'h95是CMD0的序列,CMD0时,发送命令请求,且没有返回长度(这在前面的CMD8时详细讲过,r1的返回值应该是0x01。没有错误时,进入CMD8.
后面的CMDx大同小异,我就忽略带过,接着随着CMD8进入CMD55,CMD41,CMD16。具体为什么请看上面的图片,初始化时的步骤。这些步骤就是为了确定SD卡的型号。发送指令看它返回什么。
接着
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
然后就是写和读的等待命令。当写时,发送地址,同时进入CMD24,读时,发送地址,进入CMD17
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};
当进入CMD24时,发送cmd:命令加地址加0xff,然后进入S_WRITE,真正开始写了。
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
这里只是给了给寄存器赋了1,让它执行命令,具体的写前面已经讲过了。
然后就是读了,一样,CMD17和CMD24有异曲同工之处,然后同样读也是发送一个读指令。
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;
天呐。终于讲完了。博主本来也是半懂的状态开始写,写着写着思路越来越清晰,现在自己也完全理清楚了。希望能帮到大家。也期待自己的博文一篇篇变多,然后成为大佬哈哈哈哈。
----------------------------分割线-------------------------------------------
新建了一个群,希望和大家一起交流FPGA。