初始化和去初始化SD卡
SD_Init
- 该函数首先将SDIO模块设置为SPI模式SD_LowLevel_Init,在初始化结束以后,再将数据总线设置为4位数据模式。
- 然后调用SD_PowerON对SD卡进行上电,进行时钟设置和相对位置的设置,以及卡的类型判断。
- 第三步调用SD_InitializeCards初始化,读出CID和CSD,这些是卡的身份证号码。
- 身份证验证通过以后,我们将读取的CID和CSD赋值给结构体,用于后面的程序调用和输出显示。
- 选中该卡以后,我们配置数据总线为4位,SPI完成了任务,1位模式退出。
SD_Error SD_Init(void)
{
SD_Error errorstatus = SD_OK;
//外设的端口设置,打开各引脚的时钟,配置为SPI,CLK,MO,MI和CS引脚,以及一个检测引脚。
//初始化SPI以及打开SPI模块。
SD_LowLevel_Init();
//SD上电,配置SD卡正常工作的所有参数,主要是时钟和电压检测
//第一个调用的函数SDIO_Init,其中总线为1位宽度,并配置好时钟。
//然后调用 SDIO_SetPowerState给SDIO模块上电,然后 SDIO_ClockCmd打开时钟开关,此时可以正常工作了。
//发送CMD0,把卡进入闲置模式,发送的是 SDIO_SendCommand命令,先将结构体进行填充
//用CmdError获取一下SD的状态,判断返回为SD_OK后,发送下一条命令
//CMD8用来获取卡的一些基本信息,返回的是SDIO_Response_Short响应。
//用CmdResp7Error来获取SD的状态信息,如果是OK,则将卡的版本和容量类型进行读取。
//发送CMD55表示下面要发送的是APP命令,然后发送ACMD41带RCA=0的应用命令,将SD的RCA进行配置。
//判断一些状态以后,此函数完成上电操作。
SDIO_DeInit();
errorstatus = SD_PowerON();
if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
//第三个重要的操作,初始化卡,具体可以参考手册的20.4.4卡识别过程
//通知所有的激活卡卡发送CID:ALL_SEND_CID,然后等待R2的响应,响应会发回到SDIO的寄存器SDIO_RESP1
//我们将寄存器的值进行读出后放到 CID_Tab中
//然后将激活的卡赋予一个地址,用CMD3给此卡分配CRA地址
//如果卡设置了保护区域,则还需要配置CSD,用CMD9发送以后等待响应,响应的格式还是R2
//这样,我们就获取了CSD,同CID存入 CSD_Tab中SD_InitializeCards执行完毕。
errorstatus = SD_InitializeCards();
if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
//再调用一次SDIO的初始化配置,前面已经设置过一次了,SPI模式。
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
//获取卡信息,包括SD_cid和SD_csd,具体来说,就是收到的127位CID和CSD中获取数据,并赋值给CID和CSD的结构体。
if (errorstatus == SD_OK)
{
errorstatus = SD_GetCardInfo(&SDCardInfo);
}
//然后发送CMD7命令选择该卡
if (errorstatus == SD_OK)
{
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
}
//用SPI配置完成以后,再使能总线宽度为4位,里面对 SDIO_Init进行了重新的初始化。
if (errorstatus == SD_OK)
{
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
}
return(errorstatus);
}
SD卡读操作
读单块ReadBlock
- 读单块我们必须设置为512字节,这是SD卡决定的,不能修改。
- 入口参数包括:从那个地址开始,连续读512字节ReadAddr,以及读取的数据存放的地址readbuff
- 在进行任何步骤的时候,我们都要双向的查询SD卡和SDIO模块的状态,是否可以进行下一步
SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0;
SDIO->DCTRL = 0x0;
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
ReadAddr /= 512;
}
//SDIO的数据这里是第一次用,进行一次配置,用BLOCK模式,将SD卡中的数据读入SDIO中。
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
//发送CMD17进行连续一块的命令,CMD18为连续多块的操作,入口参数当然是块的首地址
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
//然后获取响应,响应为短响应
errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
//确认接收的各个状态位都没有错误后,获取FIFO的状态,如果也没有错误,则将8个FIFO中的数据全部读出以后,保存到tempbuff,并且将tempbuff的位置+8。
while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
{
for (count = 0; count < 8; count++)
{
*(tempbuff + count) = SDIO_ReadData();
}
tempbuff += 8;
}
}
if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
errorstatus = SD_RX_OVERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
//最后一次操作,如果FIFO中还有数据,也继续读出来,一般8位读完以后,当然就没数据啦。
while (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)
{
*tempbuff = SDIO_ReadData();
tempbuff++;
}
//成功读取以后,将所有的标志都清空,等待下一次传输。
SDIO_ClearFlag(SDIO_STATIC_FLAGS);
//如果配置为DMA模式,则512数据收到以后,将自动存入到readbuff中。
#elif defined (SD_DMA_MODE)
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
SDIO_DMACmd(ENABLE);
SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
#endif
return(errorstatus);
}
读多块ReadMultiBlocks
和读单块稍微有不同的是,单块可以用轮询方式,多块我们用DMA方式更好,程序如下:
SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)
{
SD_Error errorstatus = SD_OK;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 1;
SDIO->DCTRL = 0x0;
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
ReadAddr /= 512;
}
//上面的参数基本上和单块是一样的,这里我们先发送读多块的命令,块大小还是512.
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
//将数据结构体进行配置,这里配置的数据长度为NumberOfBlocks * BlockSize;
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
//如上文,读多块用的是CMD18命令
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_MULT_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_READ_MULT_BLOCK);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
//中断配置,所有的数据传输完成后进行中断,开启DMA配置
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
SDIO_DMACmd(ENABLE);
SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize));
return(errorstatus);
}
DMA的配置如下:在中断函数中,我们设置了清SDIO_IT_DATAEND标志和中断配置关闭,并返回卡的状态SD_OK;
void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
DMA_Cmd(DMA2_Channel4, DISABLE);
//确认SDIO对应的DMA通道为DMA2_Channel4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
DMA_Cmd(DMA2_Channel4, ENABLE);
}
写单块和写多块
和读单块与多块的流程差不多,不再介绍
SD擦除
先检查CSD中的擦除命令,是否可以擦除
if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
然后获取响应寄存器中的值
if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)
如果SD卡为大容量卡,则按照512一块进行操作,然后发送开始地址和截止地址
然后用CMD32和CMD33发送收尾地址,首地址发送的代码如下:
/*!< Send CMD32 SD_ERASE_GRP_START with argument as addr */
SDIO_CmdInitStructure.SDIO_Argument = startaddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
然后发送一个擦除的命令,如下:
/*!< Send CMD38 ERASE */
SDIO_CmdInitStructure.SDIO_Argument = 0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_ERASE);
然后进入等待状态,我们这里有一个延时函数,如果不用此延时,直接用
errorstatus = IsCardProgramming(&cardstate);
不知道是否OK,延时的计算如下:
maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);
for (delay = 0; delay < maxdelay; delay++)
{}
IsCardProgramming用来检查看是否在编程状态,也就是在进行擦除运算中。
此时发送CMD13,并发送CRA后,等待响应后,用respR1 = SDIO_GetResponse(SDIO_RESP1);获取状态,然后我们根据SDIO_RESP1的内容进行判断SD卡的运行状态。