写在前面:
目录
- 硬件接口及电路
- 外设信号引脚
- 时钟控制线路
- 外设框图架构
- 数据缓冲及 DMA技术
- 驱动移植
- 代码例程
- 最后
硬件接口及电路
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.
*/