NIOS2随笔——SD卡之SPI操作

简介:

1. 概述

SD卡(Secure Digital Memory Card),基于MMC发展而来,被广泛应用于数码产品中。

SD卡可分为3类:SD卡(0~2G)、SDHC卡(2~32G)、SDXC卡(32G~2T)。

SD卡有9个PIN,支持SPI和SDIO模式:

PIN
1 2 3 4 5 6 7 8 9
SDIO CD/DAT3 CMD VSS VCC CLK VSS DAT0 DAT1 DAT2
SPI CS MOSI VSS VCC CLK VSS MISO NC NC

2. SD初始化

这里介绍SD卡在SPI模式下的初始化流程。

wKiom1gTT_Dj3YRIAAHy1OQV1Co091.jpg

在SD卡进入SPI模式后,至少发送74个时钟后才能发送CMD0命令,且时钟周期不能大于400KHz。

SD卡有6类响应:R1/R1b/R2/R3/R6/R7。

在发送ACMD命令前,要先发CMD55命令。

关于具体命令格式和响应内容可参看"SD Specifications Part 1 Physical Layer Simplified Specification"


3. SPI读写操作

SD 单块读 

(1) 发送 CMD17(收到 0x00 表示发送成功) 

(2) 连续读取直到读到 0xFE 

(3) 读一个 BLOCK 

(4) 读 2 字节 CRC 

SD 卡多块读 

(1) 发送 CMD18(收到 0x00 表示发送成功) 

(2) 连续读取直到读到 0xFE 

(3) 读一个 BLOCK 

(4) 读 2 字节 CRC 

(5) 重复(2)-(4) 

(6) 发送 CMD12 停止 

SD 卡单块写 

(1) 发送 CMD24(收到 0x00 表示发送成功) 

(2) 发送若干时钟 

(3) 发送开始标志 0xFE 

(4) 发送一个 BLOCK 

(5) 发送两个字节的 CRC 

(6) 连续读直到 xxx0_0101 表示写入成功 

(7) 连续读忙检测,直到 0xFF 完成 

SD 卡单块写 

(1) 发送 CMD25(收到 0x00 表示发送成功) 

(2) 发送若干时钟 

(3) 发送开始标志 0xFC 

(4) 发送一个 BLOCK 

(5) 发送两个字节的 CRC 

(6) 连续读直到 xxx0_0101 表示写入成功 

(7) 重复(2)-(6) 

(8) 发送 0xFD 停止写操作 

(9) 连续读忙检测,直到 0xFF 完成


4.搭建QSYS系统

选择NIOS2处理器,这里选择快速型的32位处理器。

wKioL1gTbDjjC8t7AAD3Kvd1-I4337.jpg

添加内存、串口、GPIO模块,这里用GPIO模拟SPI接口。

wKiom1gTbLrgLQNRAAFcaoFNfX8278.jpg


5. SPI驱动程序

用GPIO模拟SPI接口,设置SPI_CS、SPI_SCLK、SPI_MOSI为输出,SPI_MISO为输入。

SPI时序采用CPOL=1,CPHA=1(Format B),如下图:

wKiom1gTb2DyjVYKAAC1h7uUSMg793.jpg

驱动代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <io.h>
 
#define u8  unsigned char
#define u32 unsigned int
 
#define SD_CS_SET  (IOWR(SPI_CS_BASE,0,1))
#define SD_CS_CLR  (IOWR(SPI_CS_BASE,0,0))
 
#define SD_SCLK_SET (IOWR(SPI_SCLK_BASE,0,1))
#define SD_SCLK_CLR (IOWR(SPI_SCLK_BASE,0,0))
 
#define SD_SDI_HIGH (IORD(SPI_MISO_BASE,0)==1)
#define SD_SDI_LOW  (IORD(SPI_MISO_BASE,0)==0)
 
#define SD_SDO_SET (IOWR(SPI_MOSI_BASE,0,1))
#define SD_SDO_CLR (IOWR(SPI_MOSI_BASE,0,0))
 
 
u8  SD_Type=0;
u32 cyc=64;
 
//SD卡初始化的时候,需要低速
void  SD_SPI_SpeedLow( void ){
     cyc = 512;
}
//SD卡正常工作的时候,可以高速了
void  SD_SPI_SpeedHigh( void ){
    cyc = 8;
}
 
void  delay( void ){
     u32 i;
     for (i=0;i<cyc;i++);
}
 
u8 SpiRead( void ){
     u8 i;
     u8 data=0;
 
     SD_SCLK_SET;
     //read data
     for (i=0;i<8;i++){
         SD_SCLK_CLR;
         delay();
         SD_SCLK_SET;
         if (SD_SDI_HIGH)
             data = data | 0x0001;
         delay();
         if (i<7)
             data = data << 0x0001;
     }
     SD_SCLK_SET;
     return  data;
 
}
 
u8 SpiWrite(u8 data){
     u8 i;
 
     SD_SCLK_SET;
     //send data
     for (i=0;i<8;i++){
         SD_SCLK_CLR;
         IOWR(SPI_MOSI_BASE,0,(data<<i)>>7);
         delay();
         SD_SCLK_SET;
         delay();
     }
     SD_SCLK_SET;
     return  0;
}


6. SD初始化与读写程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//取消选择,释放SPI总线
void  SD_DisSelect( void )
{
     SD_CS_SET;
     SpiWrite(0xff); //提供额外的8个时钟
}
//选择sd卡,并且等待卡准备OK
//返回值:0,成功;1,失败;
u8 SD_Select( void )
{
     SD_CS_CLR;
     if (SD_WaitReady()==0) return  0; //等待成功
     SD_DisSelect();
     return  1; //等待失败
}
//等待卡准备好
//返回值:0,准备好了;其他,错误代码
u8 SD_WaitReady( void )
{
     u32 t=0;
     do
     {
         if (SpiRead()==0XFF) return  0; //OK
         t++;
     } while (t<0XFFFFFF); //等待
     return  1;
}
//等待SD卡回应
//Response:要得到的回应值
//返回值:0,成功得到了该回应值
//    其他,得到回应值失败
u8 SD_GetResponse(u8 Response)
{
     u16 Count=0xFFFF; //等待次数
     while  ((SpiRead()!=Response)&&Count)
         Count--; //等待得到准确的回应
     if  (Count==0)
         return  MSD_RESPONSE_FAILURE; //得到回应失败
     else
         return  MSD_RESPONSE_NO_ERROR; //正确回应
}
//从sd卡读取一个数据包的内容
//buf:数据缓存区
//len:要读取的数据长度.
//返回值:0,成功;其他,失败;
u8 SD_RecvData(u8*buf,u16 len)
{
     if (SD_GetResponse(0xFE)) return  1; //等待SD卡发回数据起始令牌0xFE
     while (len--) //开始接收数据
     {
         *buf=SpiRead();
         buf++;
     }
     //下面是2个伪CRC(dummy CRC)
     SpiWrite(0xFF);
     SpiWrite(0xFF);
     return  0; //读取成功
}
//向sd卡写入一个数据包的内容 512字节
//buf:数据缓存区
//cmd:指令
//返回值:0,成功;其他,失败;
u8 SD_SendBlock(u8*buf,u8 cmd)
{
     u16 t;
     if (SD_WaitReady()) return  1; //等待准备失效
     SpiWrite(cmd);
     if (cmd!=0XFD) //不是结束指令
     {
         for (t=0;t<512;t++)
             SpiWrite(buf[t]); //提高速度,减少函数传参时间
         SpiWrite(0xFF); //忽略crc
         SpiWrite(0xFF);
         t=SpiRead(); //接收响应
         if ((t&0x1F)!=0x05) return  2; //响应错误
     }
     return  0; //写入成功
}
//向SD卡发送一个命令
//输入: u8 cmd   命令
//      u32 arg  命令参数
//      u8 crc   crc校验值
//返回值:SD卡返回的响应
u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
{
     u8 r1;
     u8 Retry=0;
     SD_DisSelect(); //取消上次片选
     if (SD_Select()) return  0XFF; //片选失效
     //发送
     SpiWrite(cmd | 0x40); //分别写入命令
     SpiWrite(arg >> 24);
     SpiWrite(arg >> 16);
     SpiWrite(arg >> 8);
     SpiWrite(arg);
     SpiWrite(crc);
     if (cmd==CMD12)SpiWrite(0xff); //Skip a stuff byte when stop reading
     //等待响应,或超时退出
     Retry=0X1F;
     do
     {
         r1=SpiRead();
     } while ((r1&0X80) && Retry--);
     //返回状态值
     return  r1;
}
//获取SD卡的CID信息,包括制造商信息
//输入: u8 *cid_data(存放CID的内存,至少16Byte)
//返回值:0:NO_ERR
//       1:错误
u8 SD_GetCID(u8 *cid_data)
{
     u8 r1;
     //发CMD10命令,读CID
     r1=SD_SendCmd(CMD10,0,0x01);
     if (r1==0x00)
     {
         r1=SD_RecvData(cid_data,16); //接收16个字节的数据
     }
     SD_DisSelect(); //取消片选
     if (r1)
         return  1;
     else
         return  0;
}
//获取SD卡的CSD信息,包括容量和速度信息
//输入:u8 *cid_data(存放CID的内存,至少16Byte)
//返回值:0:NO_ERR
//       1:错误
u8 SD_GetCSD(u8 *csd_data)
{
     u8 r1;
     r1=SD_SendCmd(CMD9,0,0x01); //发CMD9命令,读CSD
     if (r1==0)
     {
         r1=SD_RecvData(csd_data, 16); //接收16个字节的数据
     }
     SD_DisSelect(); //取消片选
     if (r1)
         return  1;
     else
         return  0;
}
//获取SD卡的总扇区数(扇区数)
//返回值:0: 取容量出错
//       其他:SD卡的容量(扇区数/512字节)
//每扇区的字节数必为512,因为如果不是512,则初始化不能通过.
u32 SD_GetSectorCount( void )
{
     u8 csd[16];
     u32 Capacity;
     u8 n;
     u16 csize;
     //取CSD信息,如果期间出错,返回0
     if (SD_GetCSD(csd)!=0)  return  0;
     //如果为SDHC卡,按照下面方式计算
     if ((csd[0]&0xC0)==0x40)   //V2.00的卡
     {
         csize = csd[9] + ((u16)csd[8] << 8) + 1;
         Capacity = (u32)csize << 10; //得到扇区数
     } else //V1.XX的卡
     {
         n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
         csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
         Capacity= (u32)csize << (n - 9); //得到扇区数
     }
     return  Capacity;
}
//初始化SD卡
u8 SD_Initialize( void )
{
     u8 r1;       // 存放SD卡的返回值
     u16 retry;   // 用来进行超时计数
     u8 buf[4];
     u16 i;
 
     SD_SDO_SET;
     SD_SCLK_SET;
     SD_CS_SET;
 
    SD_SPI_SpeedLow();   //设置到低速模式
    for (i=0;i<10;i++)
        SpiWrite(0xFF); //发送最少74个脉冲
 
     retry=20;
     do
     {
         r1=SD_SendCmd(CMD0,0,0x95); //进入IDLE状态
     } while ((r1!=0x01) && retry--);
 
     //log
 
 
    SD_Type=0; //默认无卡
 
     if (r1==0X01)
     {
         if (SD_SendCmd(CMD8,0x1AA,0x87)==1) //SD V2.0
         {
             for (i=0;i<4;i++)
             {
                 buf[i]=SpiRead();    //Get trailing return value of R7 resp
             }
             if (buf[2]==0X01&&buf[3]==0XAA) //2.7~3.6V
             {
                 retry=0XFFFE;
                 do
                 {
                     SD_SendCmd(CMD55,0,0X01);    //
                     r1=SD_SendCmd(CMD41,0x40000000,0X01); //
                 } while (r1&&retry--);
                 if (retry&&SD_SendCmd(CMD58,0,0X01)==0) //
                 {
                     for (i=0;i<4;i++)
                         buf[i]=SpiRead(); //
                     if (buf[0]&0x40)
                         SD_Type=SD_TYPE_V2HC;     //
                     else
                         SD_Type=SD_TYPE_V2;
                 }
             }
         }
         else //SD V1.x/ MMC    V3
         {
             SD_SendCmd(CMD55,0,0X01);        //发送CMD55
             r1=SD_SendCmd(CMD41,0,0X01);     //发送CMD41
             if (r1<=1)
             {
                 SD_Type=SD_TYPE_V1;
                 retry=0XFFFE;
                 do  //等待退出IDLE模式
                 {
                     SD_SendCmd(CMD55,0,0X01);    //发送CMD55
                     r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
                 } while (r1&&retry--);
             } else
             {
                 SD_Type=SD_TYPE_MMC; //MMC V3
                 retry=0XFFFE;
                 do  //等待退出IDLE模式
                 {
                     r1=SD_SendCmd(CMD1,0,0X01); //发送CMD1
                 } while (r1&&retry--);
             }
             if (retry==0||SD_SendCmd(CMD16,512,0X01)!=0)
                 SD_Type=SD_TYPE_ERR; //错误的卡
         }
     }
 
     SD_DisSelect(); //取消片选
     SD_SPI_SpeedHigh(); //高速
 
     if (SD_Type)
         return  0;
     else  if (r1)
         return  r1;
     return  0xaa; //其他错误
}
//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
     u8 r1;
     if (SD_Type!=SD_TYPE_V2HC)sector <<= 9; //转换为字节地址
     if (cnt==1)
     {
         r1=SD_SendCmd(CMD17,sector,0X01); //读命令
         if (r1==0) //指令发送成功
         {
             r1=SD_RecvData(buf,512); //接收512个字节
         }
     } else
     {
         r1=SD_SendCmd(CMD18,sector,0X01); //连续读命令
         do
         {
             r1=SD_RecvData(buf,512); //接收512个字节
             buf+=512;
         } while (--cnt && r1==0);
         SD_SendCmd(CMD12,0,0X01);    //发送停止命令
     }
     SD_DisSelect(); //取消片选
     return  r1; //
}
//写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
     u8 r1;
     if (SD_Type!=SD_TYPE_V2HC)sector *= 512; //转换为字节地址
     if (cnt==1)
     {
         r1=SD_SendCmd(CMD24,sector,0X01); //读命令
         if (r1==0) //指令发送成功
         {
             r1=SD_SendBlock(buf,0xFE); //写512个字节
         }
     } else
     {
         if (SD_Type!=SD_TYPE_MMC)
         {
             SD_SendCmd(CMD55,0,0X01);
             SD_SendCmd(CMD23,cnt,0X01); //发送指令
         }
        r1=SD_SendCmd(CMD25,sector,0X01); //连续读命令
         if (r1==0)
         {
             do
             {
                 r1=SD_SendBlock(buf,0xFC); //接收512个字节
                 buf+=512;
             } while (--cnt && r1==0);
             r1=SD_SendBlock(0,0xFD); //接收512个字节
         }
     }
     SD_DisSelect(); //取消片选
     return  r1; //
}


7. 测试结果

写入512个循环递增的字节,并将其读出打印在串口终端:

wKioL1gTbr-Rc8NkAAEN7KlQr2Q582.jpg


本文转自 shugenyin 51CTO博客,原文链接:http://blog.51cto.com/shugenyin/1867032


相关文章
|
7月前
|
编解码 Linux
Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码(二)
Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码(二)
568 0
|
存储 芯片 内存技术
Jlink使用技巧之读写SPI Flash存储芯片
Jlink使用技巧之读写SPI Flash存储芯片
620 0
Jlink使用技巧之读写SPI Flash存储芯片
|
20天前
NUC980修改内核支持spi-nand
NUC980修改内核支持spi-nand
9 2
|
11月前
|
存储 芯片 UED
【STM32】单片机模式配置&FlyMcu串口下载固件&STLINK Utility
【STM32】单片机模式配置&FlyMcu串口下载固件&STLINK Utility
322 0
|
XML 开发工具 数据格式
Hi3516开发笔记(十一):通过HiTools使用网口将uboot、kernel、roofts烧写进eMMC
前面烧写一直时烧写进入flush,是按照分区烧写。定制的板子挂的是eMMC,前面的烧写步骤一致,但是在烧写目标则时烧写eMMC了。重新走一遍从无到有通过网口刷定制板卡的uboot、kernel、rootfs。
Hi3516开发笔记(十一):通过HiTools使用网口将uboot、kernel、roofts烧写进eMMC
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十三)普适的GPIO引脚操作方法
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十三)普适的GPIO引脚操作方法
154 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十三)普适的GPIO引脚操作方法
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十四)具体单板的GPIO操作方法
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十四)具体单板的GPIO操作方法
113 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十四)具体单板的GPIO操作方法
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十二)LED模板驱动程序的改造:设备树
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十二)LED模板驱动程序的改造:设备树
273 1
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十二)LED模板驱动程序的改造:设备树
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
202 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
|
Ubuntu Linux 开发工具
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十二)文件IO
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十二)文件IO
121 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十二)文件IO