对于STM32操作SD卡来说,最重要的就算初始化、写操作、读操作、擦除这几个操作了。
对于初始化部分上一篇文章已经分析,本篇就主要分析写、读、擦除操作。
本篇函数来自于STM32提供的例程。参考野火的程序进行了解释,与野火函数有些不同。
这几种函数完成之后,就是开始实现对SD卡进行操作了。
SD卡擦除函数
SD卡擦除函数比较简单,只用到CMD32、CMD33、CMD38指令。
最后需要确保SD卡擦除完成才能退出SD_Erase函数。通过IsCardProgramming函数实现。
/**
* @brief 控制 SD 卡擦除指定的数据区域
* @param startaddr: 擦除的开始地址
* @param endaddr: 擦除的结束地址
* @retval SD_Error: SD 返回的错误代码
*/
SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr)
{
SD_Error errorstatus = SD_OK;
uint32_t delay = 0;
__IO uint32_t maxdelay = 0;
uint8_t cardstate = 0;
/*!< 检查SD卡是否支持擦除操作 */
if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2); //延时,根据时钟分频设计来计算
if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) //卡上锁
{
errorstatus = SD_LOCK_UNLOCK_FAILED;
return(errorstatus);
}
/* SDHC卡,地址参数为块地址,每块512字节,SDSC卡地址为字节地址 */
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
startaddr /= 512;
endaddr /= 512;
}
/*!< ERASE_GROUP_START (CMD32) and SD_CMD_SD_ERASE_GRP_END(CMD33) */
if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) ||
(SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) ||
(SDIO_HIGH_CAPACITY_SD_CARD == CardType))
{
/*!< Send CMD32 SD_ERASE_GRP_START with startaddr */
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 CMD33 SD_ERASE_GRP_END with endaddr */
SDIO_CmdInitStructure.SDIO_Argument = endaddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
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_END);
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);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
for (delay = 0; delay < maxdelay; delay++){}
/*!< 等待SD卡的内部时序操作完成 */
errorstatus = IsCardProgramming(&cardstate);
while ((errorstatus == SD_OK) &&
((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
//SD卡编程 || SD卡接收
{
errorstatus = IsCardProgramming(&cardstate); //检测SD卡是否在读写操作
}
return(errorstatus);
}
数据写入操作(单块写入、DMA传输方式)
因为分析DMA传输模式,故有关轮询的函数删掉了。
数据写入函数
/**
* @brief 向 sd 卡写入一个 BLOCK 的数据(512 字节)
* @note 本函数使用后需要调用如下两个函数来等待数据传输完成
* - SD_WaitWriteOperation(): 确认 DMA 已把数据传输到 SDIO 接口
* - SD_GetStatus(): 确认 SD 卡内部已经把数据写入完毕
* @param writebuff: 指向要写入的数据
* @param WriteAddr: 要把数据写入到 sd 卡的地址
* @param BlockSize: 块大小,sdhc 卡为 512 字节
* @retval SD_Error: 返回的 sd 错误代码
*/
SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, 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;
WriteAddr /= 512;
}
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
/*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
/* Set Block Size for Card,cmd16,
* 若是sdsc卡,可以用来设置块大小,
* 若是sdhc卡,块大小为512字节,不受cmd16影响
*/
/*!< Send CMD 16 */
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);
}
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
/*!< Send CMD24 WRITE_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = WriteAddr; //写入地址
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_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_WRITE_SINGLE_BLOCK);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/*!< 配置SDIO的写数据寄存器 */
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; //可用此参数代替SDIO_DataBlockSize_512b
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard; //写数据
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; //开启数据通道状态机
SDIO_DataConfig(&SDIO_DataInitStructure);
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE); //数据传输结束中断
SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize); //配置DMA
SDIO_DMACmd(ENABLE); //使能SDIO的DMA请求
return(errorstatus);
}
首先,设置SDIO->DCTRL清零,清除之前的传输设置。
发送CMD16指定块的大小。对于标准卡,要写入 BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。
发送CMD24通知 SD 卡要进行数据写入操作,并指定待写入数据的目标地址。
通过结构体配置数据传输的超时、块数量、块大小、传输方向等参数。
调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到 SDIO 的 中断服务函数运行。
调用前面讲解的 SD_LowLevel_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的 DMA 请求。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数使能。
SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。
写入DMA配置
/*
* 函数名:SD_DMA_RxConfig
* 描述 :为SDIO发送数据配置DMA2的通道4的请求
* 输入 :BufferDST:装载了数据的变量指针
* BufferSize: 缓冲区大小
* 输出 :无
*/
void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
/*!< DMA2 Channel4 disable */
DMA_Cmd(DMA2_Channel4, DISABLE);
/*!< DMA2 Channel4 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设为写入目标
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);
/*!< DMA2 Channel4 enable */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
写入操作等待函数
/**
* @brief 本函数会一直等待到 DMA 传输结束
* 在 SDIO_WriteBlock() 和 SDIO_WriteMultiBlocks() 函数后必须被调用以确保DMA 数据传输完成
* @param None.
* @retval SD_Error: 返回的 sd 错误代码.
*/
SD_Error SD_WaitWriteOperation(void)
{
SD_Error errorstatus = SD_OK;
//等待 DMA 是否传输结束
while ((SD_DMAEndOfTransferStatus() == RESET)
&& (TransferEnd == 0) && (TransferError == SD_OK))
{}
if (TransferError != SD_OK)
{
return(TransferError);
}
/*!< 清除标志 */
SDIO_ClearFlag(SDIO_STATIC_FLAGS);
return(errorstatus);
}
调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,当 DMA 传输 结束时,该函数会返回 SET 值。另外,while 循环中的判断条件使用的 TransferEnd 和 TransferError是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被设置,传输结束后,根据TransferError 的值来确认是否正确传输,若不正确则直接返回错误代码。SD_WaitWriteOperation 函数最后是清 除相关标志位并返回错误。由于这个函数里的 while 循环的存在,它会确保 DMA 的传输结束。
数据读取操作(单块读取、DMA传输方式)
数据读取函数
/**
* @brief 向 sd 卡写入一个 BLOCK 的数据(512 字节)
* @note 本函数使用后需要调用如下两个函数来等待数据传输完成
* - SD_WaitWriteOperation(): 确认 DMA 已把数据传输到 SDIO 接口
* - SD_GetStatus(): 确认 SD 卡内部已经把数据写入完毕
* @param writebuff: 指向要写入的数据
* @param WriteAddr: 要把数据写入到 sd 卡的地址
* @param BlockSize: 块大小,sdhc 卡为 512 字节
* @retval SD_Error: 返回的 sd 错误代码
*/
SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK;
TransferError = SD_OK;
TransferEnd = 0; //传输结束标志位,在中断服务中置1
StopCondition = 0;
SDIO->DCTRL = 0x0;
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
ReadAddr /= 512;
}
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
/*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
/* Set Block Size for Card,cmd16,
* 若是sdsc卡,可以用来设置块大小,
* 若是sdhc卡,块大小为512字节,不受cmd16影响
*/
/*!< Send CMD 16 */
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);
}
//+++++++++++++++++++++++++++++++++++野火添加的函数++++++++++++++++++++++++++++++++++++
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_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
/*!< Send CMD17 READ_SINGLE_BLOCK */
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);
}
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE); //启用或禁用SDIO中断,第一个为指定禁用值
SDIO_DMACmd(ENABLE); //启用或禁用SDIO DMA请求。
SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
return(errorstatus);
}
数据读操作和写操作类似!
首先,设置SDIO->DCTRL清零,清除之前的传输设置。
发送CMD16指定块的大小。对于标准卡,要写入 BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。
通过结构体配置数据传输的超时、块数量、块大小、传输方向等参数。
发送CMD17,SD卡在接收到命令后就会通过数据线把书记传输到SDIO的数据FIFO中。
调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到 SDIO 的 中断服务函数运行。
调用前面讲解的 SD_LowLevel_DMA_RxConfig 函数,配置使能 SDIO 从 SD 卡读取数据的 DMA 请求。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数使能。
SD 卡发出的数据将会传输到 STM32 的 SDIO 外设,而 SDIO 外设激发 DMA 请求,把 数据搬运到内存中。
读取DMA配置
/*
* 函数名:SD_DMA_RxConfig
* 描述 :为SDIO接收数据配置DMA2的通道4的请求
* 输入 :BufferDST:用于装载数据的变量指针
* : BufferSize: 缓冲区大小
* 输出 :无
*/
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标志位
/*!< DMA2 Channel4 disable */
DMA_Cmd(DMA2_Channel4, DISABLE); //SDIO为第四通道
/*!< DMA2 Channel4 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS; //外设地址,fifo
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST; //目标地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设为源地址
DMA_InitStructure.DMA_BufferSize = BufferSize / 4; //除以4,把字转成字节单位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //使能外设地址不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //使能存储目标地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据大小为字,32位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //外设数据大小为字,32位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //不循环,循环模式主要用在adc上
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //通道优先级高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非 存储器至存储器模式
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
/*!< DMA2 Channel4 enable */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
读取操作等待函数
/**
* @brief 本函数会一直等待到 DMA 传输结束
* SDIO_ReadMultiBlocks() 函数后必须被调用以确保 DMA 数据传输完成
* @param None.
* @retval SD_Error: 返回的 sd 错误代码.
*/
SD_Error SD_WaitWriteOperation(void)
{
SD_Error errorstatus = SD_OK;
//等待 DMA 传输结束
while ((SD_DMAEndOfTransferStatus() == RESET)
&& (TransferEnd == 0) && (TransferError == SD_OK))
{}
if (TransferError != SD_OK)
{
return(TransferError);
}
/*!< Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS);
return(errorstatus);
}
其中,写入读取操作分为单块和多块,但是相差不大,故本篇文章仅仅分析了单块读取操作!
SDIO的中断服务操作
中断服务操作在 stm32f10x_it.c 文件中。需要注意!!!!!
在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。
中断服务接口
在 stm32f10x_it.c 文件中需要设置:(其实设置到sdio用户自定文件中也可!)
void SDIO_IRQHandler(void)
{
/* Process All SDIO Interrupt Sources */
SD_ProcessIRQSrc();
}
中断服务函数
SD_Error SD_ProcessIRQSrc(void)
{
if (StopCondition == 1) //发送读、写命令时设置为1
{
SDIO->ARG = 0x0; //命令参数寄存器
SDIO->CMD = 0x44C; //命令寄存器 0100-0100-1100
/* CPSMEN[10] WAITRESP[7:6] CMDINDEX[5:0]
* 0100 01 001100
* 开启命令状态机 短响应 命令索引:CMD12
*/
TransferError = CmdResp1Error(SD_CMD_STOP_TRANSMISSION);
}
else
{
TransferError = SD_OK;
}
SDIO_ClearITPendingBit(SDIO_IT_DATAEND); //清除中断
SDIO_ITConfig(SDIO_IT_DATAEND, DISABLE); //关闭SDIO中断使能
TransferEnd = 1;
return(TransferError);
}
函数首先判断 StopCondition !多块读写中被置为1,单块读写中是置为0 。SD卡要求多块读写命令有 CMD12 结束,SD卡收到 CMD12 时才停止多块的传输!发送命令直接采用向寄存器写入命令和参数的方式。此外,根据传输情况设置全局变量 TransferError、TransferEnd 。
至此,大部分的STM32 的 SDIO 功能就基本上看到了。可根据此执行一些简单的读写操作了。
标签:DMA,SDIO,STM32,CmdInitStructure,errorstatus,InitStructure,SD From: https://blog.51cto.com/u_15784394/8500677