当前位置:网站首页>i.MX - RT1052 SDCard操作(SDIO接口)
i.MX - RT1052 SDCard操作(SDIO接口)
2022-06-21 11:59:00 【夏沫の浅雨】
写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
硬件接口及电路
MIMXRT1050 EVKB评估板上有一个SD卡插槽(J20)。J20是用于 USDHC1接口的 Micro SD插槽。
SD 卡一般都支持 SDIO 和 SPI 这两种接口,Micro SD卡接口模式引脚定义如下:

Micro SD卡与 SD卡相比,Micro SD卡只有 8个引脚是因为比SD卡少了一个 Vss,区别如下图:

在评估板上,是使用了 SDIO 接口来操作 Micro SD卡的,(下文统一以 SD卡称呼),其硬件电路如下:

外设信号引脚
在 RT1052上,提供了 uSDHC这个外设功能,它的信号引脚定义如下:


总的概括如下:

其中,CD,WP,LCTL,RST和 VSELECT对于系统实现都是可选的。 如果 uSDHC需要支持 4位数据传输,则 DAT7〜DAT4 也可以是可选的并绑定为高电平;如果 uSDHC不支持 HS400模式,则 STROBE也可以是可选的并绑定为低电平。
时钟控制线路

从上图可知,uSDHC 根时钟有 2 个可选输入来源(以 uSDHC1为例,红色线跟蓝色线部分):
- PLL2 PFD0
- PLL2 PFD2
最后配置出来的时钟一般是 198 MHz,这也是最大时钟频率。在官方例程中,利用的时钟线就是上面的蓝色线路部分。
外设框图架构

数据缓冲及 DMA技术
从上图可知,RT1052的 uSDHC外设支持 DMA传输技术的。
直接内存访问(Direct Memory Access,DMA)是计算机科学中的一种内存访问技术。它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载;否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方;在这个时间中,CPU 对于其他的工作来说就无法使用。
在 uSDHC 中使用了一个可配置的数据缓冲区以优化的方式在系统总线(IP总线或 AHB总线)和 SD卡之间传输数据,从而最大化两个时钟域(IP外设时钟和主时钟)之间的吞吐量。
该缓冲区用作在主机系统和卡之间传输数据的临时存储。 支持配置读写水印值(1~128 个字)以及配置读写突发长度(1~31 个字)。
写入操作序列:
当用户将数据传输到卡时,有两种将数据写入缓冲区的方法:
- 处理器内核通过中断状态寄存器中的 BWR 位进行轮询(中断或轮询)
- 内部直接存储器存取(利用 DMA技术)
读取操作序列:
当用户将数据传输到卡时,有两种从缓冲区读取数据的方法:
- 处理器内核通过中断状态寄存器中的 BRR 位进行轮询(中断或轮询)
- 内部直接存储器存取(利用 DMA技术)
驱动移植
在官方的工程里面,已经有包含移植了 SDMMC这一类的驱动文件了,所以我们只需要把相应的文件添加到自己的工程文件里就好了,如下图:
接着,还需要一个驱动上层的配置文件(sdmmc_config.c),这个文件在官方SDK包的 …\boards\evkbimxrt1050\sdmmc_examples\sdcard_polling 路径下找到,如下图:
然后把上面用到的全部提取到个人的工程文件夹里面,放到下图的路径文件夹中;其中 port文件夹里存放的是上图的 sdmmc_config两个文件:
对于 SDCard所需的文件有以上框选的几个,其中 osa文件夹值得注意一下,这个文件夹里其实存放一个引用了 OS处理的上级 API文件,这是因为在官方提供的 SDCard驱动中,是需要一个类似于 OS操作的处理;同时,还需要把组件里面的 fsl_os_abstraction_bm.c文件(路径:…\components\osa)添加到工程,这个文件就是上面的 osa文件夹里所依赖的模拟 OS处理文件,并且可以在这个文件的开头得到 /* This is the source file for the OS Abstraction layer for MQXLite. */ 这个信息,若是使用 FreeRTOS,则需要选取另外一个文件;另外,后缀带 “ _bm ” 的,其实就是不外带 OS的支持文件;然后添加到的工程架构如下图:
最后,在原有的预处理宏上,还需要再添加以下宏去使能相应的代码:
FSL_SDK_ENABLE_DRIVER_CACHE_CONTROL=1, SD_ENABLED
代码例程
底层驱动有了,那么,我们还需要一个例程来校验,添加以下代码:
sd_card.c 源文件
#include "sd_card.h"
#include "sdmmc_config.h"
#include "pin_mux.h"
#include "board.h"
#include "fsl_debug_console.h"
/* SD Card硬件检测使能 */
#define SD_DETECT_ENABLE 1
#define BOARD_USDHC1_CLK_FREQ (CLOCK_GetSysPfdFreq(kCLOCK_Pfd2) / (CLOCK_GetDiv(kCLOCK_Usdhc1Div) + 1U))
#define BOARD_SD_HOST_CLK_FREQ BOARD_USDHC1_CLK_FREQ
/*! @brief Data block count accessed in card */
#define DATA_BLOCK_COUNT (5U)
/*! @brief Start data block number accessed in card */
#define DATA_BLOCK_START (2U)
/*! @brief Data buffer size. */
#define DATA_BUFFER_SIZE (FSL_SDMMC_DEFAULT_BLOCK_SIZE * DATA_BLOCK_COUNT)
/*! @brief Card descriptor. */
sd_card_t g_sd;
/*! @brief sdmmc dma buffer */
AT_NONCACHEABLE_SECTION_ALIGN(static uint32_t s_sdmmcHostDmaBuffer[BOARD_SDMMC_HOST_DMA_DESCRIPTOR_BUFFER_SIZE],
SDMMCHOST_DMA_DESCRIPTOR_BUFFER_ALIGN_SIZE);
/*! @brief Data written to the card */
SDK_ALIGN(uint8_t g_dataWrite[DATA_BUFFER_SIZE], BOARD_SDMMC_DATA_BUFFER_ALIGN_SIZE);
/*! @brief Data read from the card */
SDK_ALIGN(uint8_t g_dataRead[DATA_BUFFER_SIZE], BOARD_SDMMC_DATA_BUFFER_ALIGN_SIZE);
#if (0 == SD_DETECT_ENABLE)
static sd_detect_card_t s_cd;
static sdmmchost_t s_host;
OSA_EVENT_HANDLE_DEFINE(host_event);
#endif /* SD_DETECT_ENABLE */
static bool s_isReadOnly;
static void SDCard_ClockConfig(void);
static status_t AccessCard(sd_card_t *card, bool isReadOnly);
static void CardInformationLog(sd_card_t *card);
/************************************************ 函数名称 : SDCard_Test 功 能 : SD Card测试 参 数 : 无 返 回 值 : -1 / 0 *************************************************/
int SDCard_Test(void)
{
sd_card_t *card = &g_sd;
char ch = '0';
while (ch != 'q') {
PRINTF("\r\nRead/Write/Erase the card continuously until encounter error......\r\n");
for (;;) {
if (kStatus_Success != AccessCard(card, s_isReadOnly)) {
/* access card fail, due to card remove. */
if (SD_IsCardPresent(card) == false) {
SD_HostDoReset(card);
PRINTF("\r\nCard removed\r\n");
PRINTF(
"\r\nInput 'q' to quit read/write/erase process.\ \r\nInput other char to wait card re-insert.\r\n");
ch = GETCHAR();
PUTCHAR(ch);
}
/* access card fail, due to transfer error */
else {
ch = 'q';
}
break;
}
else {
PRINTF("\r\nInput 'q' to quit read/write/erase process.\ \r\nInput other char to read/write/erase data blocks again.\r\n");
ch = GETCHAR();
PUTCHAR(ch);
if (ch == 'q') {
break;
}
}
}
}
PRINTF("\r\nThe example will not read/write data blocks again.\r\n");
SD_Deinit(card);
return 0;
}
/************************************************ 函数名称 : SDCard_Init 功 能 : SD Card用户初始化 参 数 : 无 返 回 值 : 无 *************************************************/
void SDCard_Init(void)
{
sd_card_t *card = &g_sd;
#if SD_DETECT_ENABLE
BOARD_SD_Config(card, NULL, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY, NULL);
#else
SDCard_ClockConfig();
card->host = &s_host;
card->host->dmaDesBuffer = s_sdmmcHostDmaBuffer; // dma缓冲区
card->host->dmaDesBufferWordsNum = BOARD_SDMMC_HOST_DMA_DESCRIPTOR_BUFFER_SIZE;// dma缓冲区大小
card->host->hostController.base = BOARD_SDMMC_SD_HOST_BASEADDR; // 主机外设地址
card->host->hostController.sourceClock_Hz = BOARD_SD_HOST_CLK_FREQ; // 时钟频率
card->host->hostEvent = &host_event; // 事件处理程序指针
card->usrParam.cd = &s_cd; // 卡检测回调函数指针(这里主要去除断言警告)
NVIC_SetPriority(BOARD_SDMMC_SD_HOST_IRQ, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY);
#endif /* SD_DETECT_ENABLE */
/* SD host init function */
if (SD_HostInit(card) != kStatus_Success)
{
PRINTF("\r\nSD host init fail\r\n");
}
PRINTF("\r\nPlease insert a card into board.\r\n");
#if SD_DETECT_ENABLE
/* power off card */
SD_SetCardPower(card, false);
/* wait card insert */
SD_PollingCardInsert(card, kSD_Inserted);
/* power on the card */
SD_SetCardPower(card, true);
PRINTF("\r\nCard inserted.\r\n");
#else
PRINTF("\r\nWait Card initialization......\r\n");
/* power on the card */
SD_SetCardPower(card, true);
#endif /* SD_DETECT_ENABLE */
/* Init card. */
if (SD_CardInit(card)){
PRINTF("\r\nSD card init failed.\r\n");
}
else{
PRINTF("\r\nSD card init succeed.\r\n");
}
/* card information log */
CardInformationLog(card);
/* Check if card is readonly. */
s_isReadOnly = SD_CheckReadOnly(card);
}
/************************************************ 函数名称 : SDCard_ClockConfig 功 能 : SD Card时钟配置 参 数 : 无 返 回 值 : 无 *************************************************/
static void SDCard_ClockConfig(void)
{
/* 检查是否预定义了宏 SKIP_SYSCLK_INIT, 一般是默认定义了的,若是定义了, 那么在 clock_config.c中 sys pll是停止 关闭配置的交由用户自己配置选择。 */
CLOCK_InitSysPll(&sysPllConfig_BOARD_BootClockRUN);
/*configure system pll PFD2 fractional divider to 24, output clock is 528MHZ * 18 / 24 = 396 MHZ*/
CLOCK_InitSysPfd(kCLOCK_Pfd2, 24U);
/* Configure USDHC clock source and divider */
CLOCK_SetDiv(kCLOCK_Usdhc1Div, 1U); /* USDHC clock root frequency maximum: 198MHZ */
CLOCK_SetMux(kCLOCK_Usdhc1Mux, 0U);
}
/************************************************ 函数名称 : AccessCard 功 能 : 存取卡数据 参 数 : card ---- sd卡结构体指针 isReadOnly ---- 只读操作 返 回 值 : status_t ---- 状态 *************************************************/
static status_t AccessCard(sd_card_t *card, bool isReadOnly)
{
if (isReadOnly) {
PRINTF("\r\nRead one data block......\r\n");
if (kStatus_Success != SD_ReadBlocks(card, g_dataRead, DATA_BLOCK_START, 1U)) {
PRINTF("Read one data block failed.\r\n");
return kStatus_Fail;
}
PRINTF("Read multiple data blocks......\r\n");
if (kStatus_Success != SD_ReadBlocks(card, g_dataRead, DATA_BLOCK_START, DATA_BLOCK_COUNT)) {
PRINTF("Read multiple data blocks failed.\r\n");
return kStatus_Fail;
}
}
else {
memset(g_dataWrite, 0x67U, sizeof(g_dataWrite));
PRINTF("\r\nWrite/read one data block......\r\n");
if (kStatus_Success != SD_WriteBlocks(card, g_dataWrite, DATA_BLOCK_START, 1U)) {
PRINTF("Write one data block failed.\r\n");
return kStatus_Fail;
}
memset(g_dataRead, 0U, sizeof(g_dataRead));
if (kStatus_Success != SD_ReadBlocks(card, g_dataRead, DATA_BLOCK_START, 1U)) {
PRINTF("Read one data block failed.\r\n");
return kStatus_Fail;
}
PRINTF("Compare the read/write content......\r\n");
if (memcmp(g_dataRead, g_dataWrite, FSL_SDMMC_DEFAULT_BLOCK_SIZE)) {
PRINTF("The read/write content isn't consistent.\r\n");
return kStatus_Fail;
}
PRINTF("The read/write content is consistent.\r\n");
PRINTF("Write/read multiple data blocks......\r\n");
if (kStatus_Success != SD_WriteBlocks(card, g_dataWrite, DATA_BLOCK_START, DATA_BLOCK_COUNT)) {
PRINTF("Write multiple data blocks failed.\r\n");
return kStatus_Fail;
}
memset(g_dataRead, 0U, sizeof(g_dataRead));
if (kStatus_Success != SD_ReadBlocks(card, g_dataRead, DATA_BLOCK_START, DATA_BLOCK_COUNT)) {
PRINTF("Read multiple data blocks failed.\r\n");
return kStatus_Fail;
}
PRINTF("Compare the read/write content......\r\n");
if (memcmp(g_dataRead, g_dataWrite, FSL_SDMMC_DEFAULT_BLOCK_SIZE)) {
PRINTF("The read/write content isn't consistent.\r\n");
return kStatus_Fail;
}
PRINTF("The read/write content is consistent.\r\n");
PRINTF("Erase multiple data blocks......\r\n");
if (kStatus_Success != SD_EraseBlocks(card, DATA_BLOCK_START, DATA_BLOCK_COUNT)) {
PRINTF("Erase multiple data blocks failed.\r\n");
return kStatus_Fail;
}
}
return kStatus_Success;
}
/************************************************ 函数名称 : CardInformationLog 功 能 : 卡信息打印 参 数 : card ---- sd卡结构体指针 返 回 值 : 无 *************************************************/
static void CardInformationLog(sd_card_t *card)
{
assert(card);
PRINTF("\r\nCard size %d * %d bytes\r\n", card->blockCount, card->blockSize); // sd card内存大小
PRINTF("\r\nWorking condition:\r\n");
/* 工作电压 */
if (card->operationVoltage == kSDMMC_OperationVoltage330V) {
PRINTF("\r\n Voltage : 3.3V\r\n"); // 3.3V
}
else if (card->operationVoltage == kSDMMC_OperationVoltage180V) {
PRINTF("\r\n Voltage : 1.8V\r\n"); // 1.8V
}
/* 时序模式 */
if (card->currentTiming == kSD_TimingSDR12DefaultMode) {
if (card->operationVoltage == kSDMMC_OperationVoltage330V) {
PRINTF("\r\n Timing mode: Default mode\r\n"); // 常规模式
}
else if (card->operationVoltage == kSDMMC_OperationVoltage180V) {
PRINTF("\r\n Timing mode: SDR12 mode\r\n"); // SDR12 模式
}
}
else if (card->currentTiming == kSD_TimingSDR25HighSpeedMode) {
if (card->operationVoltage == kSDMMC_OperationVoltage180V) {
PRINTF("\r\n Timing mode: SDR25\r\n"); // SDR25 模式
}
else {
PRINTF("\r\n Timing mode: High Speed\r\n"); // 高速模式
}
}
else if (card->currentTiming == kSD_TimingSDR50Mode) {
PRINTF("\r\n Timing mode: SDR50\r\n"); // SDR50 模式
}
else if (card->currentTiming == kSD_TimingSDR104Mode) {
PRINTF("\r\n Timing mode: SDR104\r\n"); // SDR104 模式
}
else if (card->currentTiming == kSD_TimingDDR50Mode) {
PRINTF("\r\n Timing mode: DDR50\r\n"); // DDR50 模式
}
PRINTF("\r\n Freq : %d HZ\r\n", card->busClock_Hz); // 频率
}
/*---------------------------- END ----------------------------*/
sd_card.h 头文件
#ifndef __SD_CARD_H
#define __SD_CARD_H
#include "MIMXRT1052.h"
#include "fsl_usdhc.h"
int SDCard_Test(void);
void SDCard_Init(void);
#endif /* __SD_CARD_H */
/*---------------------------- END ----------------------------*/
从上面的引脚说明那里知道,CD,WP,LCTL,RST和 VSELECT的引脚都是可选的。
所以有时候因为成本或者引脚紧凑之类,往往不使用硬件检测功能,所以这里可以像上面的代码一样利用宏 SD_DETECT_ENABLE 来确定是否启用硬件检测功能,但一旦取消检测功能,那么就要在设备上电运行之前插好 SDCard,否则就会出现死机卡死。
其中,BOARD_SD_Config();函数主要就是用来配置硬件检测功能引脚的处理函数;另外,值得注意的是,BOARD_SD_Config();函数里面的时钟配置用的是 sys pll pfd0的来源时钟,而当取消硬件检测功能时,可以看到执行的 SDCard_ClockConfig();函数是使用 sys pll pfd2的来源时钟。
最后
sd_card.c 头注:
/* * ReadMe: * 下面例程只是演示 SD Card操作的演示; * 引脚配置在 sdmmc_config.h中更改; * SD Card插入检测是使用阻塞轮训的,非中断处理; * 中断处理可以参考官网 SDK包里的以下工程路径: * ...\boards\evkbimxrt1050\sdmmc_examples\sdcard_interrupt * * 注意:如果将 DATA3用作卡检测 PIN, * 请确保 DATA3被下拉,无论是内部还是外部, * 同时确保卡可以拉 DATA3,然后主机可以通过 DATA3检测卡。 * 而 SDHC不支持主机通过 CD检测卡,可以通过 DATA3或 GPIO检测卡。 * 无论通过主机还是gpio检测卡,都要确保 pinmux配置正确。 * 需要添加以下宏: * (FSL_SDK_ENABLE_DRIVER_CACHE_CONTROL=1, SD_ENABLED) * * 当 SD Card硬件检测使能时,时钟用的是 sys pll pfd0, * 不使用时,时钟用的是 sys pll pfd2。 */
/* 打印示例: [2020-08-17 17:13:52.118]# RECV ASCII> Please insert a card into board. [2020-08-17 17:13:58.809]# RECV ASCII> Card inserted. [2020-08-17 17:13:59.076]# RECV ASCII> Card size 247808 * 512 bytes Working condition: Voltage : 3.3V Timing mode: High Speed Freq : 49500000 HZ [2020-08-17 17:14:10.909]# RECV ASCII> Read/Write/Erase the card continuously until encounter error...... Write/read one data block...... Compare the read/write content...... The read/write content is consistent. Write/read multiple data blocks...... Compare the read/write content...... The read/write content is consistent. Erase multiple data blocks...... Input 'q' to quit read/write/erase process. Input other char to read/write/erase data blocks again. [2020-08-17 17:14:20.992]# SEND ASCII> q [2020-08-17 17:14:21.041]# RECV ASCII> q The example will not read/write data blocks again. */
边栏推荐
猜你喜欢

Rectangular coverage area

Illustrated with pictures and texts -- wechat applet to obtain the user's geographic location information and call Tencent map API to obtain the user's specific location

南京大学 静态软件分析(static program analyzes)-- Intermediate Representation 学习笔记

STM32開發之 VS Code + gcc環境編譯

清除交换机配置、配置镜像端口以及Wireshark抓包(以Huawei S5720为例)

记一次Vmware虚拟机升级GLIBC导致系统瘫痪的恢复解决方法

harmonyos培訓一

Apache ShardingSphere 5.1.2 发布|全新驱动 API + 云原生部署,打造高性能数据网关

Chapter VIII web project testing

使用Huggingface在矩池云快速加载预训练模型和数据集
随机推荐
Interesting research on mouse pointer interaction
Codeworks round 797 (Div. 3) F. shifting string problem solution
100w的数据表比1000w的数据表查询更快吗?
Shell process control - 35. Multi branch case conditional statements
Operation and maintenance security, not so simple
一文搞懂 Flink OperatorChain 对象重用
Introduction to the upper computer software ns-scope of Tektronix oscilloscope
Understand Flink operatorchain object reuse
【综合笔试题】剑指 Offer II 114. 外星文字典
HMS core machine learning service ID card identification function to achieve efficient information entry
使用赞美提高绩效
Harmonyos training I
知识点:PCB电路板的几种特殊布线方法
Citus 11 for Postgres is completely open source and can be queried from any node (citus official blog)
Tensorflower uses the specified GPU and GPU video memory
Rectangular coverage area
Devsecops: s-sdlc enterprise best practices
Apache ShardingSphere 5.1.2 发布|全新驱动 API + 云原生部署,打造高性能数据网关
Tensorflower使用指定的GPU和GPU显存
泰克Tektronix示波器上位机软件NS-Scope介绍