1)实验平台:正点原子开拓者
FPGA 开发板
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
在上述代码的always语句块中,我们使用div_clk_180deg(同sd_clk)的上升沿采集SD卡
返回的信号,而其它语句块使用div_clk时钟(sd_clk相位偏差180度的时钟)来操作,这是因
为SD卡的SPI模式下SD卡的上升沿锁存(采集)数据,在下降沿的时候更新(发送)数据,所
以在SD卡的上升沿采集数据是数据保持稳定的时刻,以确保采集的数据不会发送错误。我们知
道,SD卡在初始化过程中共返回三种响应类型,分别为R1、R3和R7,其中返回的R3类型和R7类
型中包含R1类型,R1类型最高位固定为0,而SD_MISO在空闲时是为高电平状态,因此我们可以
通过判断SD_MISO引脚拉低作为开始接收响应信号的条件。
图 39.4.4 和图 39.4.5为SD卡初始化过程中SignalTap抓取的波形图,从图中我们可以
清晰的看到在SD卡初始化过程中,各个状态的跳转。在初始化完成之后,sd_init_done信号由
低电平变为高电平,说明SD卡初始化完成。
图 39.4.4 SD卡初始化SignalTap波形图
图 39.4.5 SD卡初始化完成SignalTap波形图
SD卡写操作模块的代码如下:
1
module sd_write
(
2
input clk_ref
, //时钟信号
3
input clk_ref_180deg
, //时钟信号,与sd_clk相位相差180度
4
input rst_n
, //复位信号,低电平有效
5 //SD卡接口
6
input sd_miso
, //SD卡SPI串行输入数据信号
7
output reg sd_cs
, //SD卡SPI片选信号
8
output reg sd_mosi
, //SD卡SPI串行输出数据信号
9 //用户写接口
10
input wr_start_en
, //开始写SD卡数据信号
11
input [31
:0
] wr_sec_addr
, //写数据扇区地址
12
input [15
:0
] wr_data
, //写数据
13
output reg wr_busy
, //写数据忙信号
14
output reg wr_req //写数据请求信号
15
);
16
17 //parameter define
18
parameter HEAD_BYTE
= 8'hfe
; //数据头
19
20 //reg define
21
reg wr_en_d0
; //wr_start_en信号延时打拍
22
reg wr_en_d1
;
23
reg res_en
; //接收SD卡返回数据有效信号
24
reg [7
:0
] res_data
; //接收SD卡返回数据
25
reg res_flag
; //开始接收返回数据的标志
26
reg [5
:0
] res_bit_cnt
; //接收位数据计数器
27
28
reg [3
:0
] wr_ctrl_cnt
; //写控制计数器
29
reg [47
:0
] cmd_wr
; //写命令
30
reg [5
:0
] cmd_bit_cnt
; //写命令位计数器
31
reg [3
:0
] bit_cnt
; //写数据位计数器
32
reg [8
:0
] data_cnt
; //写入数据数量
33
reg [15
:0
] wr_data_t
; //寄存写入的数据,防止发生改变
34
reg detect_done_flag
; //检测写空闲信号的标志
35
reg [7
:0
] detect_data
; //检测到的数据
36
37 //wire define
38
wire pos_wr_en
; //开始写SD卡数据信号的上升沿
39
40 //*****************************************************
41 //** main code
42 //*****************************************************
43
44
assign pos_wr_en
= (~wr_en_d1
) & wr_en_d0
;
45
46 //wr_start_en信号延时打拍
47
always @(posedge clk_ref
or negedge rst_n
) begin
48
if(!rst_n
) begin
49 wr_en_d0
<= 1'b0
;
50 wr_en_d1
<= 1'b0
;
51
end
52
else begin
53 wr_en_d0
<= wr_start_en
;
54 wr_en_d1
<= wr_en_d0
;
55
end
56
end
57
58 //接收sd卡返回的响应数据
59 //在clk_ref_180deg(sd_clk)的上升沿锁存数据
60
always @(posedge clk_ref_180deg
or negedge rst_n
) begin
61
if(!rst_n
) begin
62 res_en
<= 1'b0
;
63 res_data
<= 8'd0
;
64 res_flag
<= 1'b0
;
65 res_bit_cnt
<= 6'd0
;
66
end
67
else begin
68 //sd_miso = 0 开始接收响应数据
69
if(sd_miso
== 1'b0
&& res_flag
== 1'b0
) begin
70 res_flag
<= 1'b1
;
71 res_data
<= {res_data
[6
:0
],sd_miso
};
72 res_bit_cnt
<= res_bit_cnt
+ 6'd1
;
73 res_en
<= 1'b0
;
74
end
75
else if(res_flag
) begin
76 res_data
<= {res_data
[6
:0
],sd_miso
};
77 res_bit_cnt
<= res_bit_cnt
+ 6'd1
;
78
if(res_bit_cnt
== 6'd7
) begin
79 res_flag
<= 1'b0
;
80 res_bit_cnt
<= 6'd0
;
81 res_en
<= 1'b1
;
82
end
83
end
84
else
85 res_en
<= 1'b0
;
86
end
87
end
88
89 //写完数据后检测SD卡是否空闲
90
always @(posedge clk_ref
or negedge rst_n
) begin
91
if(!rst_n
)
92 detect_data
<= 8'd0
;
93
else if(detect_done_flag
)
94 detect_data
<= {detect_data
[6
:0
],sd_miso
};
95
else
96 detect_data
<= 8'd0
;
97
end
98
99 //SD卡写入数据
100
always @(posedge clk_ref
or negedge rst_n
) begin
101
if(!rst_n
) begin
102 sd_cs
<= 1'b1
;
103 sd_mosi
<= 1'b1
;
104 wr_ctrl_cnt
<= 4'd0
;
105 wr_busy
<= 1'b0
;
106 cmd_wr
<= 48'd0
;
107 cmd_bit_cnt
<= 6'd0
;
108 bit_cnt
<= 4'd0
;
109 wr_data_t
<= 16'd0
;
110 data_cnt
<= 9'd0
;
111 wr_req
<= 1'b0
;
112 detect_done_flag
<= 1'b0
;
113
end
114
else begin
115 wr_req
<= 1'b0
;
116
case(wr_ctrl_cnt
)
117 4'd0
: begin
118 wr_busy
<= 1'b0
; //写空闲
119 sd_cs
<= 1'b1
;
120 sd_mosi
<= 1'b1
;
121
if(pos_wr_en
) begin
122 cmd_wr
<= {8'h58
,wr_sec_addr
,8'hff
}; //写入单个命令块CMD24
123 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
; //控制计数器加1
124 //开始执行写入数据,拉高写忙信号
125 wr_busy
<= 1'b1
;
126
end
127
end
128 4'd1
: begin
129
if(cmd_bit_cnt
<= 6'd47
) begin //开始按位发送写命令
130 cmd_bit_cnt
<= cmd_bit_cnt
+ 6'd1
;
131 sd_cs
<= 1'b0
;
132 sd_mosi
<= cmd_wr
[6'd47
- cmd_bit_cnt
]; //先发送高字节
133
end
134
else begin
135 sd_mosi
<= 1'b1
;
136
if(res_en
) begin //SD卡响应
137 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
; //控制计数器加1
138 cmd_bit_cnt
<= 6'd0
;
139 bit_cnt
<= 4'd1
;
140
end
141
end
142
end
143 4'd2
: begin
144 bit_cnt
<= bit_cnt
+ 4'd1
;
145 //bit_cnt = 0~7 等待8个时钟周期
146 //bit_cnt = 8~15,写入命令头8'hfe
147
if(bit_cnt
>=4'd8
&& bit_cnt
<= 4'd15
) begin
148 sd_mosi
<= HEAD_BYTE
[4'd15
-bit_cnt
]; //先发送高字节
149
if(bit_cnt
== 4'd14
)
150 wr_req
<= 1'b1
; //提前拉高写数据请求信号
151
else if(bit_cnt
== 4'd15
)
152 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
; //控制计数器加1
153
end
154
end
155 4'd3
: begin //写入数据
156 bit_cnt
<= bit_cnt
+ 4'd1
;
157
if(bit_cnt
== 4'd0
) begin
158 sd_mosi
<= wr_data
[4'd15
-bit_cnt
]; //先发送数据高位
159 wr_data_t
<= wr_data
; //寄存数据
160
end
161
else
162 sd_mosi
<= wr_data_t
[4'd15
-bit_cnt
]; //先发送数据高位
163
if((bit_cnt
== 4'd14
) && (data_cnt
<= 9'd255
))
164 wr_req
<= 1'b1
;
165
if(bit_cnt
== 4'd15
) begin
166 data_cnt
<= data_cnt
+ 9'd1
;
167 //写入单个BLOCK共512个字节 = 256 * 16bit
168
if(data_cnt
== 9'd255
) begin
169 data_cnt
<= 9'd0
;
170 //写入数据完成,控制计数器加1
171 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
;
172
end
173
end
174
end
175 //写入2个字节CRC校验,由于SPI模式下不检测校验值,此处写入两个字节的8'hff
176 4'd4
: begin
177 bit_cnt
<= bit_cnt
+ 4'd1
;
178 sd_mosi
<= 1'b1
;
179 //crc写入完成,控制计数器加1
180
if(bit_cnt
== 4'd15
)
181 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
;
182
end
183 4'd5
: begin
184
if(res_en
) //SD卡响应
185 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
;
186
end
187 4'd6
: begin //等待写完成
188 detect_done_flag
<= 1'b1
;
189 //detect_data = 8'hff时,SD卡写入完成,进入空闲状态
190
if(detect_data
== 8'hff
) begin
191 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
;
192 detect_done_flag
<= 1'b0
;
193
end
194
end
195
default : begin
196 //进入空闲状态后,拉高片选信号,等待8个时钟周期
197 sd_cs
<= 1'b1
;
198 wr_ctrl_cnt
<= wr_ctrl_cnt
+ 4'd1
;
199
end
200
endcase
201
end
202
end
203
204
endmodule
SD卡写数据模块主要完成对SD卡的写操作,在程序的第18定义了一个参数HEAD_BYTE(SD
卡写数据头),在开始写入有效数据之前,必须先发送数据头8’hfe。在代码的第60行开始的
always语句块中,同样使用clk_ref_180deg(sd_clk)的上升沿采集数据,其原因同SD初始化
模块一样,clk_ref_180deg的上升沿是数据保持稳定的时刻,采集的数据不会发生错误。
在代码第100行开始的always语句块中,使用写计数控制器(wr_ctrl_cnt)控制写入的流
程。其流程为首先检测开始写入数据信号(wr_start_en)的上升沿,检测到上升沿之后开始
发送写命令(CMD24);写命令发送完成等待SD卡返回响应信号;SD卡返回响应命令后,等待8
个时钟周期,随后写入数据头和数据,注意写数据之前写请求信号要提前拉高,以保证写入数
据时刻数据是有效的;发送完数据之后,再次发送两个字节的CRC校验值,由于SPI模式下不对
数据做校验,这里发送两个字节的8’hff,然后等待SD卡返回响应数据。在接收完SD卡的响应
之后给出标志detect_done_flag,以检测SD卡是否进入空闲状态。当SD卡进入空闲状态后等待8个时钟周期即可重新检测开始写入数据信号(wr_start_en)的上升沿。
图 39.4.6为SD卡写数据过程中SignalTap抓取的波形图。从图中可以看出,在检测到
wr_start_en的上升沿(脉冲信号)后,wr_busy(写忙信号)开始拉高,sd_cs片选信号拉低,
开始对SD卡写命令和数据,wr_req为请求写入的数据,共请求数据256次。在数据及CRC校验写
完后detect_done_flag信号拉高,开始等待SD卡空闲。
图 39.4.6 SD卡写数据SignalTap波形图
图 39.4.7为数据写完后抓取到的SignalTap波形图。从图中可以看出,SD卡返回空闲信号
(sd_miso由低电平变为高电平)后,片选信号开始拉高拉高,detect_done_flag信号拉低,
wr_busy(写忙信号)拉低,此时可以进行下一次写操作。
图 39.4.7 SD卡数据写完SignalTap波形图
SD卡读操作模块的代码如下:
1
module sd_read
(
2
input clk_ref
, //时钟信号
3
input clk_ref_180deg
, //时钟信号,与sd_clk相位相差180度
4
input rst_n
, //复位信号,低电平有效
5 //SD卡接口
6
input sd_miso
, //SD卡SPI串行输入数据信号
7
output reg sd_cs
, //SD卡SPI片选信号
8
output reg sd_mosi
, //SD卡SPI串行输出数据信号
9 //用户读接口
10
input rd_start_en
, //开始读SD卡数据信号
11
input [31
:0
] rd_sec_addr
, //读数据扇区地址
12
output reg rd_busy
, //读数据忙信号
13
output reg rd_val_en
, //读数据有效信号
14
output reg [15
:0
] rd_val_data //读数据
15
);
16
17 //reg define
18
reg rd_en_d0
; //rd_start_en信号延时打拍
19
reg rd_en_d1
;
20
reg res_en
; //接收SD卡返回数据有效信号
21
reg [7
:0
] res_data
; //接收SD卡返回数据
22
reg res_flag
; //开始接收返回数据的标志
23
reg [5
:0
] res_bit_cnt
; //接收位数据计数器
24
25
reg rx_en_t
; //接收SD卡数据使能信号
26
reg [15
:0
] rx_data_t
; //接收SD卡数据
27
reg rx_flag
; //开始接收的标志
28
reg [3
:0
] rx_bit_cnt
; //接收数据位计数器
29
reg [8
:0
] rx_data_cnt
; //接收的数据个数计数器
30
reg rx_finish_en
; //接收完成使能信号
31
32
reg [3
:0
] rd_ctrl_cnt
; //读控制计数器
33
reg [47
:0
] cmd_rd
; //读命令
34
reg [5
:0
] cmd_bit_cnt
; //读命令位计数器
35
reg rd_data_flag
; //准备读取数据的标志
36
37 //wire define
38
wire pos_rd_en
; //开始读SD卡数据信号的上升沿
39
40 //*****************************************************
41 //** main code
42 //*****************************************************
43
44
assign pos_rd_en
= (~rd_en_d1
) & rd_en_d0
;
45
46 //rd_start_en信号延时打拍
47
always @(posedge clk_ref
or negedge rst_n
) begin
48
if(!rst_n
) begin
49 rd_en_d0
<= 1'b0
;
50 rd_en_d1
<= 1'b0
;
51
end
52
else begin
53 rd_en_d0
<= rd_start_en
;
54 rd_en_d1
<= rd_en_d0
;
55
end
56
end
57
58 //接收sd卡返回的响应数据
59 //在clk_ref_180deg(sd_clk)的上升沿锁存数据
60
always @(posedge clk_ref_180deg
or negedge rst_n
) begin
61
if(!rst_n
) begin
62 res_en
<= 1'b0
;
63 res_data
<= 8'd0
;
64 res_flag
<= 1'b0
;
65 res_bit_cnt
<= 6'd0
;
66
end
67
else begin
68 //sd_miso = 0 开始接收响应数据
69
if(sd_miso
== 1'b0
&& res_flag
== 1'b0
) begin
70 res_flag
<= 1'b1
;
71 res_data
<= {res_data
[6
:0
],sd_miso
};
72 res_bit_cnt
<= res_bit_cnt
+ 6'd1
;
73 res_en
<= 1'b0
;
74
end
75
else if(res_flag
) begin
76 res_data
<= {res_data
[6
:0
],sd_miso
};
77 res_bit_cnt
<= res_bit_cnt
+ 6'd1
;
78
if(res_bit_cnt
== 6'd7
) begin
79 res_flag
<= 1'b0
;
80 res_bit_cnt
<= 6'd0
;
81 res_en
<= 1'b1
;
82
end
83
end
84
else
85 res_en
<= 1'b0
;
86
end
87
end
88
89 //接收SD卡有效数据
90 //在clk_ref_180deg(sd_clk)的上升沿锁存数据
91
always @(posedge clk_ref_180deg
or negedge rst_n
) begin
92
if(!rst_n
) begin
93 rx_en_t
<= 1'b0
;
94 rx_data_t
<= 16'd0
;
95 rx_flag
<= 1'b0
;
96 rx_bit_cnt
<= 4'd0
;
97 rx_data_cnt
<= 9'd0
;
98 rx_finish_en
<= 1'b0
;
99
end
100
else begin
101 rx_en_t
<= 1'b0
;
102 rx_finish_en
<= 1'b0
;
103 //数据头0xfe 8'b1111_1110,所以检测0为起始位
104
if(rd_data_flag
&& sd_miso
== 1'b0
&& rx_flag
== 1'b0
)
105 rx_flag
<= 1'b1
;
106
else if(rx_flag
) begin
107 rx_bit_cnt
<= rx_bit_cnt
+ 4'd1
;
108 rx_data_t
<= {rx_data_t
[14
:0
],sd_miso
};
109
if(rx_bit_cnt
== 4'd15
) begin
110 rx_data_cnt
<= rx_data_cnt
+ 9'd1
;
111 //接收单个BLOCK共512个字节 = 256 * 16bit
112
if(rx_data_cnt
<= 9'd255
)
113 rx_en_t
<= 1'b1
;
114
else if(rx_data_cnt
== 9'd257
) begin //接收两个字节的CRC校验值
115 rx_flag
<= 1'b0
;
116 rx_finish_en
<= 1'b1
; //数据接收完成
117 rx_data_cnt
<= 9'd0
;
118 rx_bit_cnt
<= 4'd0
;
119
end
120
end
121
end
122
else
123 rx_data_t
<= 16'd0
;
124
end
125
end
126
127 //寄存输出数据有效信号和数据
128
always @(posedge clk_ref
or negedge rst_n
) begin
129
if(!rst_n
) begin
130 rd_val_en
<= 1'b0
;
131 rd_val_data
<= 16'd0
;
132
end
133
else begin
134
if(rx_en_t
) begin
135 rd_val_en
<= 1'b1
;
136 rd_val_data
<= rx_data_t
;
137
end
138
else
139 rd_val_en
<= 1'b0
;
140
end
141
end
142
143 //读命令
144
always @(posedge clk_ref
or negedge rst_n
) begin
145
if(!rst_n
) begin
146 sd_cs
<= 1'b1
;
147 sd_mosi
<= 1'b1
;
148 rd_ctrl_cnt
<= 4'd0
;
149 cmd_rd
<= 48'd0
;
150 cmd_bit_cnt
<= 6'd0
;
151 rd_busy
<= 1'b0
;
152 rd_data_flag
<= 1'b0
;
153
end
154
else begin
155
case(rd_ctrl_cnt
)
156 4'd0
: begin
157 rd_busy
<= 1'b0
;
158 sd_cs
<= 1'b1
;
159 sd_mosi
<= 1'b1
;
160
if(pos_rd_en
) begin
161 cmd_rd
<= {8'h51
,rd_sec_addr
,8'hff
}; //写入单个命令块CMD17
162 rd_ctrl_cnt
<= rd_ctrl_cnt
+ 4'd1
; //控制计数器加1
163 //开始执行读取数据,拉高读忙信号
164 rd_busy
<= 1'b1
;
165
end
166
end
167 4'd1
: begin
168
if(cmd_bit_cnt
<= 6'd47
) begin //开始按位发送读命令
169 cmd_bit_cnt
<= cmd_bit_cnt
+ 6'd1
;
170 sd_cs
<= 1'b0
;
171 sd_mosi
<= cmd_rd
[6'd47
- cmd_bit_cnt
]; //先发送高字节
172
end
173
else begin
174 sd_mosi
<= 1'b1
;
175
if(res_en
) begin //SD卡响应
176 rd_ctrl_cnt
<= rd_ctrl_cnt
+ 4'd1
; //控制计数器加1
177 cmd_bit_cnt
<= 6'd0
;
178
end
179
end
180
end
181 4'd2
: begin
182 //拉高rd_data_flag信号,准备接收数据
183 rd_data_flag
<= 1'b1
;
184
if(rx_finish_en
) begin //数据接收完成
185 rd_ctrl_cnt
<= rd_ctrl_cnt
+ 4'd1
;
186 rd_data_flag
<= 1'b0
;
187 sd_cs
<= 1'b1
;
188
end
189
end
190
default : begin
191 //进入空闲状态后,拉高片选信号,等待8个时钟周期
192 sd_cs
<= 1'b1
;
193 rd_ctrl_cnt
<= rd_ctrl_cnt
+ 4'd1
;
194
end
195
endcase
196
end
197
end
198
199
endmodule
SD卡读数据模块主要完成对SD卡的读操作。在代码的第60行开始的always语句块和第91行
开始的always语句块中,同样采用clk_ref_180deg的上升沿采集数据,其原因同SD初始化模块
一样,clk_ref_180deg的上升沿是数据保持稳定的时刻,采集的数据不会发生错误。
在代码第144行开始的always语句块中,使用读计数控制器(rd_ctrl_cnt)控制读取数据
的流程。其流程为检测开始读取数据信号(rd_start_en)的上升沿,检测到上升沿之后开始
发送读命令(CMD17);读命令发送完成之后等待SD卡返回响应信号;SD卡返回响应命令后,
准备接收SD卡的数据头,因为SD卡的数据头为8’hfe = 8’b1111_1110,所以我们只需要检测
SD_MISO输入引脚的第一个低电平即可检测到数据头;检测到数据头之后,紧跟后面的就是256
个16位数据和两个字节的CRC校验值,我们只需接收有效数据,CRC的校验值可不用关心;CRC
校验接收完成后等待8个时钟周期即可检测开始读取数据信号(rd_start_en)的上升沿,再次
对SD卡进行读操作。
图 39.4.8为SD卡读数据过程中SignalTap抓取的波形图。从图中可以看出,在检测到rd_start_en的上升沿(脉冲信号)后,rd_busy(读忙信号)开始拉高,sd_cs片选信号拉低,
开始对SD卡发送读命令;在SD卡返回读命令响应信号后,拉高rd_data_flag信号,开始检测数
据头8’hfe;在检测到数据头8’hfe后,开始接收SD卡返回的有效数据,共返回256个16位数
据;数据接收完成后产生rx_finish_en(接收完成信号)脉冲信号,拉低rd_data_flag和sd_cs
(SD片选信号),等待8个时钟周期后拉低rd_busy信号,此时可以进行下一次读操作。
图 39.4.8 SD卡读数据过程中SignalTap波形图
SD卡测试数据产生模块的代码如下:
1
module data_gen
(
2
input clk
, //时钟信号
3
input rst_n
, //复位信号,低电平有效
4
input sd_init_done
, //SD卡初始化完成信号
5 //写SD卡接口
6
input wr_busy
, //写数据忙信号
7
input wr_req
, //写数据请求信号
8
output reg wr_start_en
, //开始写SD卡数据信号
9
output reg [31
:0
] wr_sec_addr
, //写数据扇区地址
10
output [15
:0
] wr_data
, //写数据
11 //读SD卡接口
12
input rd_val_en
, //读数据有效信号
13
input [15
:0
] rd_val_data
, //读数据
14
output reg rd_start_en
, //开始写SD卡数据信号
15
output reg [31
:0
] rd_sec_addr
, //读数据扇区地址
16
17
output error_flag //SD卡读写错误的标志
18
);
19
20 //reg define
21
reg sd_init_done_d0
; //sd_init_done信号延时打拍
22
reg sd_init_done_d1
;
23
reg wr_busy_d0
; //wr_busy信号延时打拍
24
reg wr_busy_d1
;
25
reg [15
:0
] wr_data_t
;
26
reg [15
:0
] rd_comp_data
; //用于对读出数据作比较的正确数据
27
reg [8
:0
] rd_right_cnt
; //读出正确数据的个数
28
29 //wire define
30
wire pos_init_done
; //sd_init_done信号的上升沿,用于启动写入信号
31
wire neg_wr_busy
; //wr_busy信号的下降沿,用于判断数据写入完成
32
33 //*****************************************************
34 //** main code
35 //*****************************************************
36
37
assign pos_init_done
= (~sd_init_done_d1
) & sd_init_done_d0
;
38
assign neg_wr_busy
= wr_busy_d1
& (~wr_busy_d0
);
39 //wr_data_t变化范围0~256;wr_data范围:0~255
40
assign wr_data
= (wr_data_t
> 16'd0
) ? (wr_data_t
- 1'b1
) : 16'd0
;
41 //读256次正确的数据,说明读写测试成功,error_flag = 0
42
assign error_flag
= (rd_right_cnt
== (9'd256
)) ? 1'b0
: 1'b1
;
43
44 //sd_init_done信号延时打拍
45
always @(posedge clk
or negedge rst_n
) begin
46
if(!rst_n
) begin
47 sd_init_done_d0
<= 1'b0
;
48 sd_init_done_d1
<= 1'b0
;
49
end
50
else begin
51 sd_init_done_d0
<= sd_init_done
;
52 sd_init_done_d1
<= sd_init_done_d0
;
53
end
54
end
55
56 //SD卡写入信号控制
57
always @(posedge clk
or negedge rst_n
) begin
58
if(!rst_n
) begin
59 wr_start_en
<= 1'b0
;
60 wr_sec_addr
<= 32'd0
;
61
end
62
else begin
63
if(pos_init_done
) begin
64 wr_start_en
<= 1'b1
;
65 wr_sec_addr
<= 32'd2000
; //任意指定一块扇区地址
66
end
67
else
68 wr_start_en
<= 1'b0
;
69
end
70
end
71
72 //SD卡写数据
73
always @(posedge clk
or negedge rst_n
) begin
74
if(!rst_n
)
75 wr_data_t
<= 16'b0
;
76
else if(wr_req
)
77 wr_data_t
<= wr_data_t
+ 16'b1
;
78
end
79
80 //wr_busy信号延时打拍
81
always @(posedge clk
or negedge rst_n
) begin
82
if(!rst_n
) begin
83 wr_busy_d0
<= 1'b0
;
84 wr_busy_d1
<= 1'b0
;
85
end
86
else begin
87 wr_busy_d0
<= wr_busy
;
88 wr_busy_d1
<= wr_busy_d0
;
89
end
90
end
91
92 //SD卡读出信号控制
93
always @(posedge clk
or negedge rst_n
) begin
94
if(!rst_n
) begin
95 rd_start_en
<= 1'b0
;
96 rd_sec_addr
<= 32'd0
;
97
end
98
else begin
99
if(neg_wr_busy
) begin
100 rd_start_en
<= 1'b1
;
101 rd_sec_addr
<= 32'd2000
;
102
end
103
else
104 rd_start_en
<= 1'b0
;
105
end
106
end
107
108 //读数据错误时给出标志
109
always @(posedge clk
or negedge rst_n
) begin
110
if(!rst_n
) begin
111 rd_comp_data
<= 16'd0
;
112 rd_right_cnt
<= 9'd0
;
113
end
114
else begin
115
if(rd_val_en
) begin
116 rd_comp_data
<= rd_comp_data
+ 16'b1
;
117
if(rd_val_data
== rd_comp_data
)
118 rd_right_cnt
<= rd_right_cnt
+ 9'd1
;
119
end
120
end
121
end
122
123
endmodule
在代码的第45行开始的always语句块中,对SD卡控制器的初始化完成信号(sd_init_done)
打两拍,以检测SD卡的上升沿,用于发起SD卡的写入操作。在代码的第57行开始的always块中,
当检测到SD卡的上升沿之后,开始发起写入操作,即wr_start_en(开始写SD卡数据信号)和
wr_sec_addr(wr_sec_addr)。这里需要注意的是,写扇区地址并没有从0开始写,0扇区地址
为SD卡的分区引导记录区,如果从0扇区地址开始写入测试数据会破坏SD卡的FAT文件系统,因
此我们随意指定SD卡的中间数据区域开始测试,这里是从扇区地址2000开始写入,读出扇区地
址和写扇区地址保持一致。
测试数据写完后(wr_busy写忙信号的下降沿代表写完数据),就开始从SD卡中读出数据。
需要注意的是代码中的error_flag默认是高电平,即错误状态,正确读取到256次(单个扇区
为512个字节,因为接口封装成16位,因此需要读取256次)时才会变成低电平,目的是在未插
入SD卡时,SD卡无法完成初始化,也就无法对SD卡进行读写测试,如果此时默认低电平的话,
会误以为读写测试正确。
下载验证
首先我们打开SD卡读写测试实验工程,在工程所在的路径下打开top_sd_rw/par文件夹,
在里面找到“top_sd_rw.qpf”并双击打开。注意工程所在的路径名只能由字母、数字以及下
划线组成,不能出现中文、空格以及特殊字符等。工程打开后如图 39.5.1所示:
图 39.5.1 以太网
通信实验工程
然后将SD卡适配器(用于插入MicroSD卡)或者SD卡插入开发板的SD卡插槽,注意带有金
属引脚的一面朝上。接下来将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连
接
电源线并打开电源开关。
注意:本次实验是基于市面上常用的SD2.0版本协议的SDHC卡或者SD2.0版本协议的
MicroSD卡,存储容量为2GB至32GB。
开拓者开发板实物图如下图所示:
图 39.5.2 开拓者开发板实物图
接下来我们下载程序,验证SD卡读写测试功能。工程打开后通过点击工具栏中的
“Programmer”图标打开下载界面,通过“Add File”按钮选择top_sd_rw/par/output_files
目录下的“top_sd_rw.sof”文件。开发板电源打开后,在程序下载界面点击“Hardware
Setup”,在弹出的对话框中选择当前的硬件连接为“USB-Blaster[USB-0]”。然后点击
“Start”将工程编译完成后得到的sof文件下载到开发板中,如图 39.5.3所示:
图 39.5.3 程序下载完成界面
下载完成后开发板上最右侧的LED灯常亮,说明从SD卡读出的512个字节(256个16位数据)
与写入的数据相同,SD卡读写测试程序下载验证成功。