本文主要是介绍STM32F4X SDIO(八) 例程讲解 设置SD卡 SDIO 总线宽度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
STM32F4X SDIO(八) 例程讲解 设置SD卡 & SDIO 总线宽度
- 设置SD卡 & SDIO 总线宽度
- 设置SDIO_CK时钟
- CMD7:SELECT/DESELECT_CARD
- 命令发送程序
- 命令响应程序
- 设置SD卡总线宽度
- CMD55:APP_CMD
- ACMD51:SEND_SCR
- 命令发送
- 命令响应
- 设置SDIO控制器数据传输模式
- 读取SCR寄存器
- SCR寄存器数据解析
- 设置SD卡总线宽度
- CMD55:APP_CMD
- ACMD6:SET_BUS_WIDTH
- 命令响应
- 步骤
设置SD卡 & SDIO 总线宽度
在上一节中我们已经初始化了SD卡,并获取了SD卡的CID、CSD和RCA地址,接下来就可以对SD卡进行读写操作了,但是在进行读写操作时,还需要提高SD卡的总线宽度和SDIO_CK时钟,加快传输速度。
设置SDIO_CK时钟
在SD卡刚上电的时候,SD卡默认的时钟是400KHz,总线宽度是1位,所以在进行SD卡读写操作前,需要提高SD卡的时钟速率。首先需要先设置SDIO_CK时钟。
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV; // 时钟分频系数0SDIO_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);
根据STM32F4X的SDIO_CK时钟计算公式
SDIO_CK 频率 = SDIOCLK / [CLKDIV + 2]
SDIO_CK = 48MHz / (0 + 2) = 24MHz
上面的代码就将SDIO_CK的频率设置成24MHz
CMD7:SELECT/DESELECT_CARD
CMD7的作用是通常传入RCA地址,将对应的RCA地址的SD卡进入传输模式,同一时刻只能有一张卡处于传输状态。
命令发送程序
SDIO_CmdInitStructure.SDIO_Argument = rca << 16; // SD卡的RCA地址SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEL_DESEL_CARD; // 命令索引 CMD7SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; // 短响应SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; // 不等待SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; // 使能CPSM状态机SDIO_SendCommand(&SDIO_CmdInitStructure);
命令响应程序
CMD7的响应类型是R1b,R1b会返回一个32位的卡状态给主机,主机需要通过卡状态判断SD卡是否准备完毕。
static SD_Error CmdResp1Error(uint8_t cmd)
{SD_Error errorstatus = SD_OK;uint32_t status;uint32_t response_r1;status = SDIO->STA;while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))){status = SDIO->STA;}if (status & SDIO_FLAG_CTIMEOUT){errorstatus = SD_CMD_RSP_TIMEOUT;SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT);return(errorstatus);}else if (status & SDIO_FLAG_CCRCFAIL){errorstatus = SD_CMD_CRC_FAIL;SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL);return(errorstatus);}/*!< Check response received is of desired command */if (SDIO_GetCommandResponse() != cmd){errorstatus = SD_ILLEGAL_CMD;return(errorstatus);}/*!< Clear all the static flags */SDIO_ClearFlag(SDIO_STATIC_FLAGS);/*!< We have received response, retrieve it for analysis */response_r1 = SDIO_GetResponse(SDIO_RESP1);if ((response_r1 & SD_OCR_ERRORBITS) == SD_ALLZERO){return(errorstatus);}if (response_r1 & SD_OCR_ADDR_OUT_OF_RANGE){return(SD_ADDR_OUT_OF_RANGE);}if (response_r1 & SD_OCR_ADDR_MISALIGNED){return(SD_ADDR_MISALIGNED);}if (response_r1 & SD_OCR_BLOCK_LEN_ERR){return(SD_BLOCK_LEN_ERR);}if (response_r1 & SD_OCR_ERASE_SEQ_ERR){return(SD_ERASE_SEQ_ERR);}if (response_r1 & SD_OCR_BAD_ERASE_PARAM){return(SD_BAD_ERASE_PARAM);}if (response_r1 & SD_OCR_WRITE_PROT_VIOLATION){return(SD_WRITE_PROT_VIOLATION);}if (response_r1 & SD_OCR_LOCK_UNLOCK_FAILED){return(SD_LOCK_UNLOCK_FAILED);}if (response_r1 & SD_OCR_COM_CRC_FAILED){return(SD_COM_CRC_FAILED);}if (response_r1 & SD_OCR_ILLEGAL_CMD){return(SD_ILLEGAL_CMD);}if (response_r1 & SD_OCR_CARD_ECC_FAILED){return(SD_CARD_ECC_FAILED);}if (response_r1 & SD_OCR_CC_ERROR){return(SD_CC_ERROR);}if (response_r1 & SD_OCR_GENERAL_UNKNOWN_ERROR){return(SD_GENERAL_UNKNOWN_ERROR);}if (response_r1 & SD_OCR_STREAM_READ_UNDERRUN){return(SD_STREAM_READ_UNDERRUN);}if (response_r1 & SD_OCR_STREAM_WRITE_OVERRUN){return(SD_STREAM_WRITE_OVERRUN);}if (response_r1 & SD_OCR_CID_CSD_OVERWRIETE){return(SD_CID_CSD_OVERWRITE);}if (response_r1 & SD_OCR_WP_ERASE_SKIP){return(SD_WP_ERASE_SKIP);}if (response_r1 & SD_OCR_CARD_ECC_DISABLED){return(SD_CARD_ECC_DISABLED);}if (response_r1 & SD_OCR_ERASE_RESET){return(SD_ERASE_RESET);}if (response_r1 & SD_OCR_AKE_SEQ_ERROR){return(SD_AKE_SEQ_ERROR);}return(errorstatus);
}
上面的逻辑分析仪解析出来的CMD7响应不知道外什么会解析成R6,在这里我们忽略逻辑分析仪的解析结果。通过上述的波形可以看到,SD卡返回的卡状态数据位0x700,通过SD卡状态表分析可知
当前SD卡已经处于standby状态,并且已经准备就绪。
设置SD卡总线宽度
程序经过上面的步骤之后,SD卡就进入了准备状态,此时我们需要将SD卡设置成4位总线的模式,但是并不是所有的SD卡都支持4位总线模式,所以我们需要通过查询SD卡的SCR寄存器,查看SD卡是否支持4线模式。
CMD55:APP_CMD
查询SD卡的SCR寄存器的命令是ACMD51,该命令是特殊应用命令,所以在发送ACMD51前需要先发送CMD55。有关CMD55命令的使用在前面的章节已经讲过了,这里就不细讲了。不过需要注意的是,这里的CMD55命令需要带上查询的SD卡的RCA地址。
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; // SD卡的RCASDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; // CMD55SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; // 短响应SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; // 不等待SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; // 使能CPSM状态机SDIO_SendCommand(&SDIO_CmdInitStructure);errorstatus = CmdResp1Error(SD_CMD_APP_CMD); // 判断命令是否发送成功
ACMD51:SEND_SCR
CMD51的作用是读取SCR寄存器,查看需要操作的SD卡是否支持4线模式。
命令发送
SDIO_CmdInitStruct.SDIO_Argument = 0x0; // 不带参数
SDIO_CmdInitStruct.SDIO_CmdIndex = SD_CMD_SEND_SCR; // 命令索引 ACMD51
SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable; // 使能CPSM状态机
SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //短响应
SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No; // 不等待
SDIO_SendCommand(&SDIO_CmdInitStruct);
命令响应
ACMD51的响应类型是R1,主机需要判断SD卡的状态是否准备就绪。
static SD_Error CmdResp1Error(uint8_t cmd)
{SD_Error errorstatus = SD_OK;uint32_t status;uint32_t response_r1;status = SDIO->STA;while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))){status = SDIO->STA;}if (status & SDIO_FLAG_CTIMEOUT){errorstatus = SD_CMD_RSP_TIMEOUT;SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT);return(errorstatus);}else if (status & SDIO_FLAG_CCRCFAIL){errorstatus = SD_CMD_CRC_FAIL;SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL);return(errorstatus);}/*!< Check response received is of desired command */if (SDIO_GetCommandResponse() != cmd){errorstatus = SD_ILLEGAL_CMD;return(errorstatus);}/*!< Clear all the static flags */SDIO_ClearFlag(SDIO_STATIC_FLAGS);/*!< We have received response, retrieve it for analysis */response_r1 = SDIO_GetResponse(SDIO_RESP1);if ((response_r1 & SD_OCR_ERRORBITS) == SD_ALLZERO){return(errorstatus);}if (response_r1 & SD_OCR_ADDR_OUT_OF_RANGE){return(SD_ADDR_OUT_OF_RANGE);}if (response_r1 & SD_OCR_ADDR_MISALIGNED){return(SD_ADDR_MISALIGNED);}if (response_r1 & SD_OCR_BLOCK_LEN_ERR){return(SD_BLOCK_LEN_ERR);}if (response_r1 & SD_OCR_ERASE_SEQ_ERR){return(SD_ERASE_SEQ_ERR);}if (response_r1 & SD_OCR_BAD_ERASE_PARAM){return(SD_BAD_ERASE_PARAM);}if (response_r1 & SD_OCR_WRITE_PROT_VIOLATION){return(SD_WRITE_PROT_VIOLATION);}if (response_r1 & SD_OCR_LOCK_UNLOCK_FAILED){return(SD_LOCK_UNLOCK_FAILED);}if (response_r1 & SD_OCR_COM_CRC_FAILED){return(SD_COM_CRC_FAILED);}if (response_r1 & SD_OCR_ILLEGAL_CMD){return(SD_ILLEGAL_CMD);}if (response_r1 & SD_OCR_CARD_ECC_FAILED){return(SD_CARD_ECC_FAILED);}if (response_r1 & SD_OCR_CC_ERROR){return(SD_CC_ERROR);}if (response_r1 & SD_OCR_GENERAL_UNKNOWN_ERROR){return(SD_GENERAL_UNKNOWN_ERROR);}if (response_r1 & SD_OCR_STREAM_READ_UNDERRUN){return(SD_STREAM_READ_UNDERRUN);}if (response_r1 & SD_OCR_STREAM_WRITE_OVERRUN){return(SD_STREAM_WRITE_OVERRUN);}if (response_r1 & SD_OCR_CID_CSD_OVERWRIETE){return(SD_CID_CSD_OVERWRITE);}if (response_r1 & SD_OCR_WP_ERASE_SKIP){return(SD_WP_ERASE_SKIP);}if (response_r1 & SD_OCR_CARD_ECC_DISABLED){return(SD_CARD_ECC_DISABLED);}if (response_r1 & SD_OCR_ERASE_RESET){return(SD_ERASE_RESET);}if (response_r1 & SD_OCR_AKE_SEQ_ERROR){return(SD_AKE_SEQ_ERROR);}return(errorstatus);
}
从波形图上可知,SD卡返回的卡状态是0x920,根据SD卡状态表可知,当前SD卡已经接收到ACMD命令并解析成功,总线上可以接收数据,并且已经进入传输模式。
设置SDIO控制器数据传输模式
当主机发送ACMD51命令并且SD卡响应正常时,下一步就可以开始在数据线上读取SCR寄存器数据了,但是在读取数据之前,我们需要将配置SDIO控制器的数据传输模式。
/* 因为SCR寄存器是64位,所以这里的数据长度设置为8字节
*/
SDIO_DataInitStruct.SDIO_DataBlockSize = SDIO_DataBlockSize_8b; // 数据块8字节
SDIO_DataInitStruct.SDIO_DataLength = 8; // 数据长度8字节
SDIO_DataInitStruct.SDIO_DataTimeOut = SD_DATATIMEOUT; // 超时时间
SDIO_DataInitStruct.SDIO_DPSM = SDIO_DPSM_Enable; // 使能DPSM状态机
SDIO_DataInitStruct.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; // 数据传输方向为SD卡到SDIO控制器
SDIO_DataInitStruct.SDIO_TransferMode = SDIO_TransferMode_Block; // 传输模式为块传输
SDIO_DataConfig(&SDIO_DataInitStruct);
读取SCR寄存器
设置好SDIO的数据传输模式之后,下一步就可以开始读取SD卡的SCR寄存器了。
SDIO_ReadData这个函数是用来读取SD卡数据线上的数据,返回的一个32位的数据,因为SCR寄存器是64位的寄存器,所以我们需要连续读取两次。
while (!(SDIO->STA & (SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR))){if (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET){tempscr[index] = SDIO_ReadData(); // 连续读取两次index++;if(index >= 2)break;}}
读取到的数据还需要对其进行处理
pscr[0] = ((tempscr[0] & 0xFF) << 24) | (((tempscr[0] >> 8) & 0xFF) << 16) | (((tempscr[0] >> 16) & 0xFF) << 8) | (((tempscr[0] >> 24) & 0xFF));
pscr[1] = ((tempscr[1] & 0xFF) << 24) | (((tempscr[1] >> 8) & 0xFF) << 16) | (((tempscr[1] >> 16) & 0xFF) << 8) | (((tempscr[1] >> 24) & 0xFF));
最后从SD卡读取到的SCR寄存器数据是0x0205808301000000
SCR寄存器数据解析
SCR寄存器的数据解析如下
Name | Field | Width | CID-slice | Value |
---|---|---|---|---|
SCR Structure | SCR_STRUCTURE | 4 | [63:60] | 0x0 |
SD Memory Card - Spec. Version | SD_SPEC | 4 | [59:56] | 0x2 |
data_status_after erases | DATA_STAT_AFTER_ERASE | 1 | [55:55] | 0x0 |
CPRM Security Support | SD_SECURITY | 3 | [54:52] | 0x0 |
DAT Bus widths supported | SD_BUS_WIDTHS | 4 | [51:48] | 0x5 |
Spec. Version 3.00 or higher | SD_SPEC3 | 1 | [47:47] | 1 |
Extended Security Support | EX_ SECURITY | 4 | [46:43] | 0x0 |
Spec. Version 4.00 or higher | SD_SPEC4 | 1 | [42:42] | 0x0 |
Spec. Version 5.00 or higher | SD_SPECX | 4 | [41:38] | 0x2 |
Reserved | X | 2 | [37:36] | 0x0 |
Command Support bits | CMD_SUPPORT | 4 | [35:32] | 0x3 |
reserved for manufacturer usage | X | 32 | [31:0] | 0x01000000 |
从上面表格的SD_BUS_WIDTHS可知,读出来的SD_BUS_WIDTHS为0x5,也就是bit2为1,说明该SD卡支持4位总线模式。
设置SD卡总线宽度
通过上述的操作,我们知道操作的SD卡支持4线模式,所以下一步我们就需要将SD卡设置成4线模式
CMD55:APP_CMD
因为设置SD卡总线宽度的命令是特殊应用命令,所以在发送设置总线宽度命令前,需要发送CMD55命令
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
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_APP_CMD);
ACMD6:SET_BUS_WIDTH
ACMD6命令的作用是设置SD卡总线宽度,支持设置1位总线宽度和4位总线宽度。当传入参数为0时设置1位总线,传入参数为2时设置4位总线
SDIO_CmdInitStructure.SDIO_Argument = 0x2; // 参数为2,设置4位总线宽度
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_SD_SET_BUSWIDTH; // 命令索引,ACMD16
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);
命令响应
ACMD16的响应类型是R1响应,同样需要判断响应是否正常
static SD_Error CmdResp1Error(uint8_t cmd)
{SD_Error errorstatus = SD_OK;uint32_t status;uint32_t response_r1;status = SDIO->STA;while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))){status = SDIO->STA;}if (status & SDIO_FLAG_CTIMEOUT){errorstatus = SD_CMD_RSP_TIMEOUT;SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT);return(errorstatus);}else if (status & SDIO_FLAG_CCRCFAIL){errorstatus = SD_CMD_CRC_FAIL;SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL);return(errorstatus);}/*!< Check response received is of desired command */if (SDIO_GetCommandResponse() != cmd){errorstatus = SD_ILLEGAL_CMD;return(errorstatus);}/*!< Clear all the static flags */SDIO_ClearFlag(SDIO_STATIC_FLAGS);/*!< We have received response, retrieve it for analysis */response_r1 = SDIO_GetResponse(SDIO_RESP1);if ((response_r1 & SD_OCR_ERRORBITS) == SD_ALLZERO){return(errorstatus);}if (response_r1 & SD_OCR_ADDR_OUT_OF_RANGE){return(SD_ADDR_OUT_OF_RANGE);}if (response_r1 & SD_OCR_ADDR_MISALIGNED){return(SD_ADDR_MISALIGNED);}if (response_r1 & SD_OCR_BLOCK_LEN_ERR){return(SD_BLOCK_LEN_ERR);}if (response_r1 & SD_OCR_ERASE_SEQ_ERR){return(SD_ERASE_SEQ_ERR);}if (response_r1 & SD_OCR_BAD_ERASE_PARAM){return(SD_BAD_ERASE_PARAM);}if (response_r1 & SD_OCR_WRITE_PROT_VIOLATION){return(SD_WRITE_PROT_VIOLATION);}if (response_r1 & SD_OCR_LOCK_UNLOCK_FAILED){return(SD_LOCK_UNLOCK_FAILED);}if (response_r1 & SD_OCR_COM_CRC_FAILED){return(SD_COM_CRC_FAILED);}if (response_r1 & SD_OCR_ILLEGAL_CMD){return(SD_ILLEGAL_CMD);}if (response_r1 & SD_OCR_CARD_ECC_FAILED){return(SD_CARD_ECC_FAILED);}if (response_r1 & SD_OCR_CC_ERROR){return(SD_CC_ERROR);}if (response_r1 & SD_OCR_GENERAL_UNKNOWN_ERROR){return(SD_GENERAL_UNKNOWN_ERROR);}if (response_r1 & SD_OCR_STREAM_READ_UNDERRUN){return(SD_STREAM_READ_UNDERRUN);}if (response_r1 & SD_OCR_STREAM_WRITE_OVERRUN){return(SD_STREAM_WRITE_OVERRUN);}if (response_r1 & SD_OCR_CID_CSD_OVERWRIETE){return(SD_CID_CSD_OVERWRITE);}if (response_r1 & SD_OCR_WP_ERASE_SKIP){return(SD_WP_ERASE_SKIP);}if (response_r1 & SD_OCR_CARD_ECC_DISABLED){return(SD_CARD_ECC_DISABLED);}if (response_r1 & SD_OCR_ERASE_RESET){return(SD_ERASE_RESET);}if (response_r1 & SD_OCR_AKE_SEQ_ERROR){return(SD_AKE_SEQ_ERROR);}return(errorstatus);
}
当到了这一步之后,SD的初始化就算是成功了,后面就可以对SD卡进行擦除和读写测试了。
步骤
- 设置SDIO_CK时钟为24MHz
- 选中需要操作的SD卡
- 设置SDIO控制器的数据传输模式,准备读取SCR寄存器
- 读取SCR寄存器,判断是否支持4位总线模式
- 如果SD卡支持4位总线模式,则设置SD卡为4位总线模式,否则设置1位总线模式
- SD卡初始化完成,准备擦除、读写测试
这篇关于STM32F4X SDIO(八) 例程讲解 设置SD卡 SDIO 总线宽度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!