niosii spi 外部_NIOS2随笔——SD卡之SPI操作

本文详细介绍了如何在SPI模式下使用NIOSII处理器与SD卡进行通信,包括SD卡的初始化流程、SPI读写操作的步骤,以及具体的SPI驱动程序和读写程序实现。通过示例代码展示了如何读取和写入数据到SD卡,为嵌入式系统的数据存储提供参考。
摘要由CSDN通过智能技术生成

1. 概述

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

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

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

SDIOCD/DAT3CMDVSSVCCCLKVSSDAT0DAT1DAT2

SPICSMOSIVSSVCCCLKVSSMISONCNC

2. SD初始化

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

在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位处理器。

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

5. SPI驱动程序

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

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

驱动代码如下:#include 

#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

}

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 <

}

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<>7);

delay();

SD_SCLK_SET;

delay();

}

SD_SCLK_SET;

return 0;

}

6. SD初始化与读写程序//取消选择,释放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] <

Capacity = (u32)csize <

}else//V1.XX的卡

{

n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) <

csize = (csd[8] >> 6) + ((u16)csd[7] <

Capacity= (u32)csize <

}

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/ MMCV3

{

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个循环递增的字节,并将其读出打印在串口终端:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值