/**************************************************************************//** * @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数 }