You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

671 lines
18 KiB

/**************************************************************************//**
 * @file     SPI_SD.c
 * @brief    SPI Driver for SD Card
 * @author	 Laputa
 * @version  V2.0
 * @date     2023-02-13
 ******************************************************************************/
#include "SPI_SD.h"
#include "uart.h"
#include "delay.h"
#include "sys.h"
#include "spi_drv.h"
#include "eport_drv.h"
#include "dmac_drv.h"
#include "edma_drv.h"
u8  SD_Type=0;//SD卡的类型 
 
#define Speed_High SPI_BaudRatePrescaler_2
#define SD_DMA 1
SPI_InitTypeDef SPI_InitStruct ;
u16 SPIX_ReadWriteByte(u16 TxData)
{		
	u16 retry=0;				 
	while((SPI3->SPISRHW)&(SPISR_TXFFULL_MASK)) //等待发送区空	
	{
		retry++;
		if(retry>=0XFFFE)return 0; 	//超时退出
	}			  
	SPI3->SPIDR=TxData;	 	  		//发送一个byte 
	retry=0;
	while((SPI3->SPISRHW) & (SPISR_RXFEMP_MASK)) 		//等待接收完一个byte  
	{
		retry++;
		if(retry>=0XFFFE)return 0;	//超时退出
	}	  						    
	return SPI3->SPIDR;          		//返回收到的数据					    
}
void SPIX_Init(void)
{
 
	SPI_Cmd(SPI3,DISABLE);
	//////SPI模块配置//////
	SPI_InitStruct.SPI_Direction  	     =  SPI_Direction_2Lines_FullDuplex;
	SPI_InitStruct.SPI_Mode              =  SPI_Mode_Master;
	SPI_InitStruct.SPI_DataSize          =  SPI_DataSize_8b;
	SPI_InitStruct.SPI_CPOL              =  SPI_CPOL_High;
	SPI_InitStruct.SPI_CPHA              =  SPI_CPHA_2Edge;
	SPI_InitStruct.SPI_BaudRatePrescaler =  SPI_BaudRatePrescaler_256;
	SPI_InitStruct.SPI_FirstBit          =  SPI_FirstBit_MSB;
	SPI_InitStruct.SPI_CRCPolynomial     =  7;
	
	SPI_Init(SPI3, &SPI_InitStruct);                                      //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	SPI_Cmd(SPI3,ENABLE);
	SPIX_ReadWriteByte(0xff);
}
void SPIX_SetSpeed(u16 speed)
{
	SPI_Cmd(SPI3,DISABLE);
	SPI3->SPIBR = speed;
	SPI_Cmd(SPI3,ENABLE);
}
void SPI3_DMA_Rx_Init(void)
{
	DMA_Init(DMA2_BASE_ADDR);
	m_dma_control->DMA_CONFIG = 1;
	//rx配置
	m_dma_channel[0]->DMA_SADDR = (UINT32)&SPI3->SPIDR;
	m_dma_channel[0]->DMA_CTRL = SNOCHG|DIEC|P2M_DMA|DWIDTH_B|SWIDTH_B;
	m_dma_channel[0]->DMA_CFG = (HS_SEL_SRC_HARD);
	m_dma_channel[0]->DMA_CFG_HIGH = SRC_PER4;
	m_dma_control->DMA_MASKTFR = CHANNEL_UMASK(0);
	
	//tx
	m_dma_channel[1]->DMA_DADDR = (UINT32)&SPI3->SPIDR;
	m_dma_channel[1]->DMA_CTRL = SNOCHG|DNOCHG|M2P_DMA|DWIDTH_B|SWIDTH_B;
	m_dma_channel[1]->DMA_CFG = (HS_SEL_DST_HARD);
	m_dma_channel[1]->DMA_CFG_HIGH = DST_PER5;
	m_dma_control->DMA_MASKTFR = CHANNEL_UMASK(1);
	
	SPI3->SPIDMATHR = (7<<4)|(0<<0);
}
void SPI3_DMA(UINT8* Rx_Addr, UINT32 length)
{
	uint8_t Tx_buff[1] = {0xFF};
	
	m_dma_channel[0]->DMA_DADDR = (UINT32)Rx_Addr;
	m_dma_channel[0]->DMA_CTRL_HIGH = length ;
	m_dma_control->DMA_CHEN |= CHANNEL_WRITE_ENABLE(0)|CHANNEL_ENABLE(0);
	
	m_dma_channel[1]->DMA_SADDR = (UINT32)Tx_buff;
	m_dma_channel[1]->DMA_CTRL_HIGH = length ;
	m_dma_control->DMA_CHEN |= CHANNEL_WRITE_ENABLE(1)|CHANNEL_ENABLE(1);
	
	SPI3->SPIDMACR |= 0x03;	//使能SPI3 RX的DMA
	while((m_dma_control->DMA_RAWTFR & CHANNEL_STAT(0)) != CHANNEL_STAT(0)){__nop();}
	m_dma_control->DMA_CLRTFR = CHANNEL_STAT(0);
	while((m_dma_control->DMA_RAWTFR & CHANNEL_STAT(1)) != CHANNEL_STAT(1)){__nop();}
	m_dma_control->DMA_CLRTFR = CHANNEL_STAT(1);
	while((SPI3->SPISR & 0x20)  == 0x00){__nop();} //确保SPI传送完成	
	SPI3->SPIDMACR = 0;	//禁止SPI RX的DMA
}
//@brief: SD卡初始化
//@param :
//@retval: 1:错误 r1:命令响应
u8 SD_Init(void)
{
	u16 i;
	u8 r1;
	u16 retry;
	u8 buff[6];
	
	SPIX_Init();                                      //初始化SPI1
	SPIX_SetSpeed(SPI_BaudRatePrescaler_256);         //SD卡初始化时钟不能超过400KHz
	SD_CS = 1;                                        //打开SD卡片选引脚
 
	DelayMS(5);                                      //等待SD卡上电稳定,完成初始化
	for(i=0;i<10;i++)
	{
		SPIX_ReadWriteByte(0xff);                       //产生80个时钟脉冲
	}
//将SD卡复位至idle状态
	
	SD_CS = 0; 
 
	retry = 0;
	do
	{
		r1 = SD_SendCommand(CMD0,0,0X95);               //发送CMD0
		SPIX_ReadWriteByte(0xff);
		retry++;
	}while((r1!=0x01)&&(retry<200));                  //收到正确响应或超时后退出
	if(retry==200)                                    //如果退出原因为超时
	{
		return 1;
	}
	r1= SD_SendCommand(CMD8,0x01aa,0x87);                 //如果退出原因为SD卡复位至idle并收到正确响应,继续发送CMD8获取SD卡版本信息
 //如果是v2.0版本的SD卡
	if(r1==0x01)                                      //接收到CMD8的正确响应接收CMD8响应后续传送的4字节数据
	{
		buff[0] = SPIX_ReadWriteByte(0xff);             //接收CMD8响应后续传送的4字节数据
		buff[1] = SPIX_ReadWriteByte(0xff);
		buff[2] = SPIX_ReadWriteByte(0xff);
		buff[3] = SPIX_ReadWriteByte(0xff);
//////		SD_CS = 1;
//////		SPIX_ReadWriteByte(0xff);                       //多发送8个时钟信号
//// //开始初始化SD卡
		
		SD_CS = 0;
 
		retry = 0;
		do
		{
			r1 = SD_SendCommand(CMD55,0,0xff);               //发送CMD55,通知SD卡下一条命令为应用相关命令而非通用命令
			SPIX_ReadWriteByte(0xff);
			if(r1!=0x01)                                  //如果主机未接收到CMD55的正确响应
				return r1;
			r1 = SD_SendCommand(ACMD41,0x40000000,1);      //否则继续发送ACMD41
			SPIX_ReadWriteByte(0xff);
			retry++;
			if(retry>200)
				return r1;                                  //超时退出
		}while(r1!=0);                                  //接收到ACMD41的正确响应退出
		
		SD_CS = 1;
 
		SPIX_ReadWriteByte(0xff);
		
		SD_CS = 0;
 
 //初始化指令发送完成,继续获取OCR信息
 //识别SD2.00版本的SD卡类型
		r1 = SD_SendCommand_NoDeassert(CMD58,0,0);      //发送CMD58
		if(r1!=0x00)
			return r1;                                    //未接收到CMD58的正确响应退出
		buff[0] = SPIX_ReadWriteByte(0xff);             //否则接收CMD58响应后续传回的4字节COR信息
		buff[1] = SPIX_ReadWriteByte(0xff);
		buff[2] = SPIX_ReadWriteByte(0xff);
		buff[3] = SPIX_ReadWriteByte(0xff);
		SD_CS =1;
 
		SPIX_ReadWriteByte(0xff);
 //分析OCR信息中的bit30(CCS)判断卡的类型
 //CCS=1,SDHC;CCS=0;SD2.0;
		
	if(buff[0]&0x40)
	{
		SD_Type = SD_TYPE_V2HC;                         //类型为SDHC
	}
	else
	{
		SD_Type = SD_TYPE_V2;                           //类型为SD2.0
	}
	SPIX_SetSpeed(Speed_High);           //SPI4时钟频率配置为45MHz
	}
	printf("SD_Type:%d\r\n",SD_Type);
	
	return 0;
	
}
//@brief:向SD卡发送命令(发送结束后关闭片选信号)
//@param:cmd start bit+transmission bit+command index
//@param:arg
//@param :crc CRC7+end bit
//@retval: r1:命令响应
u8 SD_SendCommand(u8 cmd,u32 arg,u8 crc)
{
	unsigned char r1;
	unsigned int Retry = 0;
	SD_CS = 1;
 
	SPIX_ReadWriteByte(0xff);
	SD_CS = 0;
 
	SPIX_ReadWriteByte(cmd|0x40);                                //将CMD命令中的传输位设置为1
	SPIX_ReadWriteByte((u8)(arg>>24));                           //写入CMD命令的32位参数
	SPIX_ReadWriteByte((u8)(arg>>16));
	SPIX_ReadWriteByte((u8)(arg>>8));
	SPIX_ReadWriteByte((u8)(arg));
	SPIX_ReadWriteByte(crc);                                     //写入CRC校验和
	while((r1 = SPIX_ReadWriteByte(0xff))==0xff)                 //接收到相关命令的正确响应后退出
	{
		Retry++;
		if(Retry>800) break;                                       //超时退出
	}
	SD_CS = 1;
 
	SPIX_ReadWriteByte(0xff);                                    //额外发送8个时钟信号,使SD卡完成剩余工作
	return r1;
}
//@brief:向SD卡发送命令(发送结束后持续打开片选信号)
//@param:cmd start bit+transmission bit+command index
//@param:arg
//@param :crc CRC7+end bit
//@retval:r1:命令响应
u8 SD_SendCommand_NoDeassert(u8 cmd,u32 arg,u8 crc)
{
	unsigned char r1;
	unsigned int Retry = 0;
	SD_CS = 1;
 
	SPIX_ReadWriteByte(0xff);
	SD_CS = 0;
 
	SPIX_ReadWriteByte(cmd|0x40);
	SPIX_ReadWriteByte((u8)(arg>>24));
	SPIX_ReadWriteByte((u8)(arg>>16));
	SPIX_ReadWriteByte((u8)(arg>>8));
	SPIX_ReadWriteByte((u8)(arg));
	SPIX_ReadWriteByte(crc);
	while((r1 = SPIX_ReadWriteByte(0xff))==0xff)
	{
		Retry++;
		if(Retry >800) break;
	}
	return r1;
}
//@brief:从SD卡中读取指定长度的数据
//@param:*data 指向存储读回数据的缓冲区的指针
//@param:len 数据长度
//@param:release 读取结束后是否释放总线
//@retval:1:错误 0:正确
u8 SD_ReceiveData(u8 *data,u16 len,u8 release)
{
	u16 retry;
	u8 r1;
	
	SD_CS = 0;
 
	retry = 0;
	do
	{
		r1 = SPIX_ReadWriteByte(0xff);
		retry++;
		if(retry>4000)
		{
			SD_CS  = 1;
 
			return 1;                                      //超时退出
		}
	}while(r1!=0xfe);                                  //主机收到SD卡传回的正确数据读取响应退出
#if !SD_DMA
/*****************************正常模式*************************************/	
//接收到正确响应后,开始接收数据
	while(len--)
	{
		*data = SPIX_ReadWriteByte(0xff);
		data++;
	}	
/*****************************正常模式*************************************/	
#else	
/*****************************DMA模式*************************************/	
	
	SPI3_DMA_Rx_Init();
	SPI3_DMA(data,len);
	
/*****************************DMA模式*************************************/	
#endif
	
	SPIX_ReadWriteByte(0xff);                          //发送2个无效CRC
	SPIX_ReadWriteByte(0xff);
	if(release == RELEASE)                             //是否需要释放总线
	{
		SD_CS = 1;
 
		SPIX_ReadWriteByte(0xff);
	}
	return 0;
}
//@brief:获取CID寄存器数据
//@param:*cid_data 指向存储CID寄存器数据的缓冲区的指针
//@retval:0:正确 r1:命令响应
u8 SD_GetCID(u8 *cid_data)
{
	u8 r1;
	
	r1 = SD_SendCommand(CMD10,0,0xff);                    //发送CMD10命令以获取CID寄存器数据
	if(r1!=0x00)
		return r1;                                      //主机未接收到正确响应退出
		SD_ReceiveData(cid_data,16,RELEASE);            //接收CID寄存器数据并释放总线
		return 0;
}
//@brief:获取CSD寄存器数据
//@param:*csd_data 指向存储CSD寄存器数据的缓冲区的指针
//@retval:0:正确 r1:命令响应
u8 SD_GetCSD(u8 *csd_data)
{
	u8 r1;
	
	r1 = SD_SendCommand(CMD9,0,0xff);                     //发送CMD9命令以获取CSD寄存器数据
	if(r1!=0x00)
		return r1;                                      //主机未接收到正确响应退出
		SD_ReceiveData(csd_data,16,RELEASE);            //接收CSD寄存器数据并释放总线
		return 0;
}
//@brief:获取SD卡容量信息
//@param:
//@retval:Capacity:SD卡容量
u32 SD_GetCapacity(void)
{
    u8 csd[16];
    u32 Capacity;
    u8 r1;
    u16 i;
    u16 temp;
    if(SD_GetCSD(csd)!=0)
    return 0;                                      //未成功获取CSD寄存器数据退出
    if((csd[0]&0xC0)==0x40)                        //如果CSD寄存器版本为v2.0
    {
			Capacity=((u32)csd[8])<<8;
      Capacity+=(u32)csd[9]+1;
			Capacity = ((u32)Capacity)/2;
       //Capacity = (Capacity)*1024;	              //得到扇区数
       //Capacity*=512;	                            //得到字节数
			
    }
    else		                                      //如果CSD寄存器版本是v1.0
    {
       i = csd[6]&0x03;
       i<<=8;
       i += csd[7];
       i<<=2;
       i += ((csd[8]&0xc0)>>6);
       r1 = csd[9]&0x03;
       r1<<=1;
       r1 += ((csd[10]&0x80)>>7);
       r1+=2;
       temp = 1;
       while(r1)
       {
        temp*=2;
         r1--;
       }
       Capacity = ((u32)(i+1))*((u32)temp);
       i = csd[5]&0x0f;
       temp = 1;
   while(i)
   {
      temp*=2;
      i--;
   }
   Capacity *= (u32)temp;                          //得到字节数
   }
   return (u32)Capacity;                           //返回SD卡容量(字节)
}
//@brief:singleblock读取
//@param:sector 数据地址(扇区地址)
//@param:*buffer 指向存储读回数据的缓冲的指针
//@retval:0:正确 r1:命令响应
u8 SD_ReadSingleBlock(u32 sector, u8 *buffer)
{
  u8 r1;
	
  SPIX_SetSpeed(Speed_High);          //SPI4时钟频率配置为45MHz
  if(SD_Type!=SD_TYPE_V2HC)	                        //如果不是SDHC卡
  {
    sector = sector<<9;	                            //512*sector即物理扇区的边界对齐地址
  }
   r1 = SD_SendCommand(CMD17,sector, 1);	          //发送CMD17 读单块命令
   if(r1 != 0x00)	return r1;                        //未接收到CMD17的正确响应退出
   r1 = SD_ReceiveData(buffer, 512, RELEASE);	      //一个扇区为512字节 读取数据并释放总线
   if(r1 != 0)
     return r1;                                     //读取数据失败退出
   else
     return 0; 	                                  	//读取数据成功
}
//@brief:multiblock读取
//@param:sector 数据地址(扇区地址)
//@param:*buff 指向存储读回数据的缓冲的指针
//@param:count 要读取的扇区数量
//@retval:0:正确 count:未处理完扇区数 r1:命令响应
u8 SD_ReadMultiBlock(u8 *buff, u32 sector, u32 count)
{
  u8 r1;
 
  SPIX_SetSpeed(Speed_High);                       //SPI1时钟频率配置为45MHz
  if(SD_Type != SD_TYPE_V2HC)
  {
    	sector = sector<<9;
  }
  r1 = SD_SendCommand(CMD18, sector, 1);                        //发送CMD18 读多块命令
  if(r1 != 0x00)	return r1;
  do	                                                          //开始接收数据
  {
    	if(SD_ReceiveData((u8 *)buff, 512, NO_RELEASE) != 0x00)       //读取数据完成后不释放总线
    	{
       		break;                                                //读取数据失败退出
    	}
    	buff += 512;
  }while(--count);                                              //数据接收全部完成
  SD_SendCommand(CMD12, 0, 1);	                                //全部传输完成,发送停止命令
  SD_CS=1;      	                                            //释放总线
 
  
  SPIX_ReadWriteByte(0xFF);
  if(count != 0)
    return count;                                               //如果数据传输中途因错误退出,返回剩余扇区个数
  else
    return 0;
}
//@brief:SD卡繁忙检测
//@param:
//@retval:0:空闲 1:繁忙
u8 SD_WaitReady(void)
{
  u8 r1;
  u16 retry=0;
  do
  {
    r1 = SPIX_ReadWriteByte(0xFF);              //当SD卡繁忙,数据线会被拉低;当SD卡结束繁忙后,数据线会被拉高
    retry++;
    if(retry==0xfffe)
    	return 1;
  }while(r1!=0xFF);
    	return 0;
}
//@brief:singleblock写入
//@param:sector 数据地址(扇区地址)
//@param:*data 指向存储要写入SD卡的数据的缓冲区的指针
//@retval:0:正确 1:超时 r1:命令响应
u8 SD_WriteSingleBlock(u32 sector, const u8 *data)
{
  	u8 r1;
  	u16 i;
  	u16 retry;
  	SPIX_SetSpeed(SPI_BaudRatePrescaler_256);                       //SPI4时钟频率配置为45MHz
    if(SD_Type!=SD_TYPE_V2HC)                                    	//如果不是SDHC卡,将sector地址转为byte地址
  	{
     		sector = sector<<9;
  	}
        r1 = SD_SendCommand(CMD24, sector, 0x00);	                //发送CMD24 写扇区命令
  	if(r1 != 0x00)
  	{
    		return r1;
  	}
        SD_CS = 0;                                           //开始准备写入数据
 
  	SPIX_ReadWriteByte(0xff);                                      //先发3个空数据,等待SD卡准备好
  	SPIX_ReadWriteByte(0xff);
  	SPIX_ReadWriteByte(0xff);
    SPIX_ReadWriteByte(0xFE);                                      //发送数据发送起始符号
  	for(i=0;i<512;i++)                                            //发送一个sector的数据
  	{
     		SPIX_ReadWriteByte(*data++);
  	}
  	SPIX_ReadWriteByte(0xff);                                      //发送2个无效CRC校验
  	SPIX_ReadWriteByte(0xff);
  	r1 = SPIX_ReadWriteByte(0xff);                                 //等待SD卡的数据响应符号
  	if((r1&0x1F)!=0x05)                                           //如果为r1=0x05则数据写入成功
  	{
     		SD_CS = 1;
 
     		return r1;                                                //否则释放总线并退出
  	}
  	retry = 0;                                                  	//等待操作完成
  	while(!SPIX_ReadWriteByte(0xff))                               //SD卡自编程时,数据线被拉低
  	{
     		retry++;
     		if(retry>65534)                                           //如果超时写入未完成,退出报错
     		{
        		SD_CS = 1;
	 
        		return 1;                                             //写入超时,返回1
     		}
  	}
  	SD_CS = 1;                                               //写入完成,片选置1
 
	SPIX_ReadWriteByte(0xff);
        return 0;
}
//@brief:multiblock写入
//@param:sector 数据地址(扇区地址)
//@param:*data 指向存储要写入SD卡的数据的缓冲区的指针
//@param:count 要写入的扇区数量
//@retval:0:正确 1:超时 count:未处理完扇区数 r1:命令响应
u8 SD_WriteMultiBlock(const u8 *data, u32 sector, u8 count)
{
  	u8 r1;
  	u16 i;
  	SPIX_SetSpeed(SPI_BaudRatePrescaler_256);                       //SPI4时钟频率配置为45MHz
  	if(SD_Type != SD_TYPE_V2HC)
        {
    	    sector = sector<<9;
        }
  	/*if(SD_Type != SD_TYPE_MMC)
        {
    	     r1 = SD_SendCommand(ACMD23, count, 0x01);               //启用ACMD23指令使能预擦除
        }*/
        r1 = SD_SendCommand(CMD25, sector, 0x01);                  //发送CMD25 写多块命令
  	if(r1 != 0x00)	return r1;
  	SD_CS = 0;                                                  //开始准备数据传输
 
		
  	SPIX_ReadWriteByte(0xff);                                       //发送3个空数据让SD卡准备好
  	SPIX_ReadWriteByte(0xff);
    SPIX_ReadWriteByte(0xff);
  	do                                                            //下面是N个sector循环写入的部分
  	{
     	    SPIX_ReadWriteByte(0xFC);                                //发送数据传输起始符号0xFC,表明是多块写入
     	    for(i=0;i<512;i++)                                      //发1个sector的数据
     	    {
        	SPIX_ReadWriteByte(*data++);
     	    }
	   SPIX_ReadWriteByte(0xff);                                     //发2个伪CRC
     SPIX_ReadWriteByte(0xff);
     	    r1 = SPIX_ReadWriteByte(0xff);                           //等待SD卡回应
     	    if((r1&0x1F)!=0x05)                                     //如果r1=0x05则表示数据写入成功
     	    {
        	SD_CS = 1;                                         //写入失败则释放总线退出
         
			return r1;
     	    }
     	    if(SD_WaitReady()==1)                                   //检测SD卡忙信号
     	   {
        	SD_CS = 1;                                           //如果长时间写入未完成,释放总线并退出
         
			return 1;
     	   }
   }while(--count);
       SPIX_ReadWriteByte(0xFD);                                   //发送数据传输结束符号0xFD
       if(SD_WaitReady())                                         //等待准备好
       {
           SD_CS = 1;                                            //超时未退出繁忙则释放总线并退出
     
		   return 1;
        }
       SD_CS = 1;                                           //写入完成,片选置1
      
		SPIX_ReadWriteByte(0xff);
       return count;                                              //返回count值,如果写完,则count=0,否则count=未写完的sector数
}