写在前面:



目录

  • 硬件接口及电路
  • 外设信号引脚
  • 时钟控制线路
  • 外设框图架构
  • 数据缓冲及 DMA技术
  • 驱动移植
  • 代码例程
  • 最后




硬件接口及电路

MIMXRT1050 EVKB评估板上有一个SD卡插槽(J20)。J20是用于 USDHC1接口的 Micro SD插槽。

SD 卡一般都支持 SDIO 和 SPI 这两种接口,Micro SD卡接口模式引脚定义如下:

内置存储emmc通信接口_内置存储emmc通信接口

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

内置存储emmc通信接口_sdcard_02

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

内置存储emmc通信接口_sdcard_03



外设信号引脚

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

内置存储emmc通信接口_sdio_04


内置存储emmc通信接口_sdcard_05

总的概括如下:

内置存储emmc通信接口_sdcard_06

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



时钟控制线路

内置存储emmc通信接口_rt1052_07

从上图可知,uSDHC 根时钟有 2 个可选输入来源(以 uSDHC1为例,红色线跟蓝色线部分):

  • PLL2 PFD0
  • PLL2 PFD2

最后配置出来的时钟一般是 198 MHz,这也是最大时钟频率。在官方例程中,利用的时钟线就是上面的蓝色线路部分。



外设框图架构

内置存储emmc通信接口_sdcard_08



数据缓冲及 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这一类的驱动文件了,所以我们只需要把相应的文件添加到自己的工程文件里就好了,如下图:

内置存储emmc通信接口_sdio_09


接着,还需要一个驱动上层的配置文件(sdmmc_config.c),这个文件在官方SDK包的 …\boards\evkbimxrt1050\sdmmc_examples\sdcard_polling 路径下找到,如下图:

内置存储emmc通信接口_sdio_10

然后把上面用到的全部提取到个人的工程文件夹里面,放到下图的路径文件夹中;其中 port文件夹里存放的是上图的 sdmmc_config两个文件:

内置存储emmc通信接口_内置存储emmc通信接口_11

对于 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的支持文件;然后添加到的工程架构如下图:

内置存储emmc通信接口_sdcard_12


最后,在原有的预处理宏上,还需要再添加以下宏去使能相应的代码:

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.
*/