STM32之SDIO通讯接口和SD卡(九)

发布于:2024-12-08 ⋅ 阅读:(124) ⋅ 点赞:(0)

STM32F407 系列文章 - SDIO-To-SD Card(九)


目录

前言

一、SDIO接口

二、SD卡

三、实现程序

1.SD卡结构体参数说明

2.头文件定义

3.函数sd_init()

4.函数HAL_SD_MspInit()

5.函数get_sd_card_info()

6.函数get_sd_card_state()

7.函数sd_read_disk()

8.函数sd_write_disk()

9.函数show_sdcard_info()

总结


前言

当单片机需要保存大量数据时,靠它自身的容量往往是不能满足需求的,一般采取的措施是外挂一个存储器。目前市面上存储器的种类繁多,根据它们各自特点,选择一款最适合单片机存储器,莫过于SD卡了,它不仅价格便宜、体积小、速度快、而且容量可以做到很大,并支持SPI/SDIO驱动,能满足单片机的要求。只需要少数几个IO口即可外扩一个高达32GB或以上的外部存储器,容量从几十M到几十G选择范围很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。一般MCU都会自带SDIO接口,因此要实现此功能,需准备一块带SD卡接口的开发板,在本章中,将向大家介绍,如何通过SDIO通讯接口实现对Micor SD卡数据的读取。


一、SDIO接口

SDIO(Secure Digital Input and Output,安全数字输入输出),最早由SD协会(SD Association, SDA)于2001年发布,是在SD标准上定义的一种外设接口,不仅支持传统的存储功能,还允许设备通过SD卡接口进行输入输出操作,常见的多媒体卡(MMC卡)、SD存储卡、SDI/O卡和CEATA设备都有SDIO接口。关于SDIO更为详细的介绍可以参考下知乎上面的一篇文章,链接如下SDIO协议从入门到精通 - 知乎 (zhihu.com)

SDIO遵循SD物理标准,因此支持SD卡的设备通常也能够兼容SDIO设备。常见的SDIO卡尺寸和普通的SD卡相同,包括标准尺寸(SD)、迷你尺寸(miniSD)和微型尺寸(microSD)‌,本文将选用microSD作为设计目标,另采用的407芯片使用自带的SDIO接口驱动,使用4位数据总线模式,最高通信速度可达 48Mhz(分频器旁路时),最高每秒可传输数据24M字节,对于一般应用足够了。407芯片的SDIO控制器包含2部分,SDIO适配器模块和APB2总线接口,其功能框图如下所示:

二、SD卡

SD卡(Secure Digital Memory Card,SD存储卡,简称SD卡),SD卡主要有SD、Mini SD和microSD三种类型,Mini SD已经被microSD取代,使用得不多。本文选用microSD作为设计目标,microSD原名Trans-flash Card(TF卡),2004年正式更名为Micro SD Card,由SanDisk闪迪)公司发明,主要应用于嵌入式设备,具体介绍参考Micro SD卡_百度百科 (baidu.com)

市场上卖microSD卡样式如下(示例),及对应SD通讯模式下相应引脚的含义:

与microSD卡匹配的卡座(自锁式)样式如下:

SD卡的驱动方式之一是用SDIO接口通讯,F407芯片自带SDIO接口,MCU单片机与SD卡连接示意图如下所示:

三、实现程序

SD卡的驱动

1.SD卡结构体参数说明

该结构体参数定义来自于STM32的HAL库,主要是完成对SD卡详细信息以参数化定义,具体说明及代码如下:

/** 
 * @brief SD 操作句柄结构体定义 
 */ 
typedef struct { 
    SD_TypeDef *Instance;     /* SD 相关寄存器基地址 */ 
    SD_InitTypeDef Init;      /* SDIO 初始化变量 */ 
    HAL_LockTypeDef Lock;     /* 互斥锁,用于解决外设访问冲突 */ 
    uint8_t *pTxBuffPtr;      /* SD 发送数据指针 */ 
    uint32_t TxXferSize;      /* SD 发送缓存按字节数的大小 */ 
    uint8_t *pRxBuffPtr;      /* SD 接收数据指针 */ 
    uint32_t RxXferSize;      /* SD 接收缓存按字节数的大小 */ 
    __IO uint32_t Context;    /* HAL 库对 SD 卡的操作阶段 */ 
    __IO HAL_SD_StateTypeDef State; /* SD 卡操作状态 */ 
    __IO uint32_t ErrorCode;        /* SD 卡错误代码 */ 
    DMA_HandleTypeDef *hdmatx;      /* SD DMA 数据发送指针 */ 
    DMA_HandleTypeDef *hdmarx;      /* SD DMA 数据接收指针 */ 
    HAL_SD_CardInfoTypeDef SdCard;  /* SD 卡信息的 */ 
    uint32_t CSD[4];                /* 保存 SD 卡 CSD 寄存器信息 */ 
    uint32_t CID[4];                /* 保存 SD 卡 CID 寄存器信息 */ 
} SD_HandleTypeDef;

/** 
 * @brief SD 卡信息结构定义 
 */ 
typedef struct { 
    uint32_t CardType;     /* 存储卡类型标记:标准卡、高速卡 */ 
    uint32_t CardVersion;  /* 存储卡版本 */ 
    uint32_t Class;        /* 卡类型 */ 
    uint32_t RelCardAdd;   /* 卡相对地址 */ 
    uint32_t BlockNbr;     /* 卡存储块数 */ 
    uint32_t BlockSize;    /* SD 卡每个存储块大小 */ 
    uint32_t LogBlockNbr;  /* 以块表示的卡逻辑容量 */ 
    uint32_t LogBlockSize; /* 以字节为单位的逻辑块大小 */ 
} HAL_SD_CardInfoTypeDef;

2.头文件定义

这里完成对IO引脚的定义和操作方式,并定义了SD卡的一些操作函数定义,代码如下(示例):

/* SDIO的信号线: SD_D0 ~ SD_D3/SD_CLK/SD_CMD 引脚 定义 
 * 如果你使用了其他引脚做SDIO的信号线,修改这里写定义即可适配.
 */
#define SD_D0_GPIO_PORT                GPIOC
#define SD_D0_GPIO_PIN                 GPIO_PIN_8
/* 所在IO口时钟使能 */
#define SD_D0_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) 
   
#define SD_D1_GPIO_PORT                GPIOC
#define SD_D1_GPIO_PIN                 GPIO_PIN_9
/* 所在IO口时钟使能 */
#define SD_D1_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)    

#define SD_D2_GPIO_PORT                GPIOC
#define SD_D2_GPIO_PIN                 GPIO_PIN_10
/* 所在IO口时钟使能 */
#define SD_D2_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)    

#define SD_D3_GPIO_PORT                GPIOC
#define SD_D3_GPIO_PIN                 GPIO_PIN_11
/* 所在IO口时钟使能 */
#define SD_D3_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   

#define SD_CLK_GPIO_PORT               GPIOC
#define SD_CLK_GPIO_PIN                GPIO_PIN_12
/* 所在IO口时钟使能 */
#define SD_CLK_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   

#define SD_CMD_GPIO_PORT               GPIOD
#define SD_CMD_GPIO_PIN                GPIO_PIN_2
/* 所在IO口时钟使能 */
#define SD_CMD_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)    

#define SD_TIMEOUT             ((uint32_t)100000000)    /* 超时时间 */
#define SD_TRANSFER_OK         ((uint8_t)0x00)
#define SD_TRANSFER_BUSY       ((uint8_t)0x01)

/* 根据 SD_HandleTypeDef 定义的宏,用于快速计算容量 */
#define SD_TOTAL_SIZE_BYTE(__Handle__)  (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 0)
#define SD_TOTAL_SIZE_KB(__Handle__)    (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 10)
#define SD_TOTAL_SIZE_MB(__Handle__)    (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 20)
#define SD_TOTAL_SIZE_GB(__Handle__)    (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 30)

/*  
 *  SD传输时钟分频,由于HAL库运行效率低,很容易产生上溢(读SD卡时)/下溢错误(写SD卡时)
 *  使用4bit模式时,需降低SDIO时钟频率,将该宏改为 1,SDIO时钟频率:48/( SDIO_TRANSF_CLK_DIV + 2 ) = 16M * 4bit = 64Mbps 
 *  使用1bit模式时,该宏SDIO_TRANSF_CLK_DIV改为 0,SDIO时钟频率:48/( SDIO_TRANSF_CLK_DIV + 2 ) = 24M * 1bit = 24Mbps 
 */
#define  SDIO_TRANSF_CLK_DIV        1   

extern SD_HandleTypeDef        g_sdcard_handler;        /* SD卡句柄 */
extern HAL_SD_CardInfoTypeDef  g_sd_card_info_handle;   /* SD卡信息结构体 */

/* 函数声明 */
uint8_t sd_init(void);                                              /* 初始化SD卡 */
uint8_t get_sd_card_info(HAL_SD_CardInfoTypeDef *cardinfo);         /* 获取卡信息函数 */
uint8_t get_sd_card_state(void);                                    /* 获取卡的状态 */
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt);  /* 读SD卡 */
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt); /* 写SD卡 */
void show_sdcard_info(void);  

3.函数sd_init()

SD卡初始化函数,被main函数调用,主要是完成对SDIO结构体的控制句柄填充,即将该结构体类型指针变量初始化,然后使用HAL库的HAL_SD_Init初始化函数即可,在此过程中HAL_SD_Init会调用函数 HAL_SD_MspInit回调函数,根据外设的情况,我们可以设置数据总线宽度为4位,代码如下(示例):

SD_HandleTypeDef g_sdcard_handler;            /* SD卡句柄 */
HAL_SD_CardInfoTypeDef g_sd_card_info_handle; /* SD卡信息结构体 */

/**
 * @brief       初始化SD卡
 * @param       无
 * @retval      返回值:0 初始化正确;其他值,初始化错误
 */
uint8_t sd_init(void)
{
    uint8_t SD_Error;
    /* 初始化时的时钟不能大于400KHZ */
    g_sdcard_handler.Instance = SDIO;
    g_sdcard_handler.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;  /* 上升沿 */
    /* 不使用bypass模式,直接用HCLK进行分频得到SDIO_CK */
    g_sdcard_handler.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
    /* 空闲时不关闭时钟电源 */  
    g_sdcard_handler.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;           
    g_sdcard_handler.Init.BusWide = SDIO_BUS_WIDE_1B;      /* 1位数据线 */
    /* 关闭硬件流控 */
    g_sdcard_handler.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; 
    g_sdcard_handler.Init.ClockDiv = SDIO_TRANSF_CLK_DIV;  /* SD传输时钟频率最大25MHZ */

    SD_Error = HAL_SD_Init(&g_sdcard_handler);
    if (SD_Error != HAL_OK)
        return 1;
    HAL_SD_GetCardInfo(&g_sdcard_handler, &g_sd_card_info_handle);  /* 获取SD卡信息 */
    /* 使能4bit宽总线模式 */
    SD_Error = HAL_SD_ConfigWideBusOperation(&g_sdcard_handler, SDIO_BUS_WIDE_4B);  
    if (SD_Error != HAL_OK)
        return 2;
    return 0;
}

4.函数HAL_SD_MspInit()

完成SDIO底层驱动,时钟使能,引脚配置,被sd_init()函数中的HAL_SD_Init函数调用,代码如下(示例):

/**
 * @brief       SDIO底层驱动,时钟使能,引脚配置
                此函数会被HAL_SD_Init()调用
 * @param       hsd:SD卡句柄
 * @retval      无
 */
void HAL_SD_MspInit(SD_HandleTypeDef *hsd)
{
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_SDIO_CLK_ENABLE();    /* 使能SDIO时钟 */
    SD_D0_GPIO_CLK_ENABLE();        /* D0引脚IO时钟使能 */
    SD_D1_GPIO_CLK_ENABLE();        /* D1引脚IO时钟使能 */
    SD_D2_GPIO_CLK_ENABLE();        /* D2引脚IO时钟使能 */
    SD_D3_GPIO_CLK_ENABLE();        /* D3引脚IO时钟使能 */
    SD_CLK_GPIO_CLK_ENABLE();       /* CLK引脚IO时钟使能 */
    SD_CMD_GPIO_CLK_ENABLE();       /* CMD引脚IO时钟使能 */

    gpio_init_struct.Pin = SD_D0_GPIO_PIN;              /* SD_D0引脚模式设置 */
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 推挽复用 */
    gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
    gpio_init_struct.Alternate = GPIO_AF12_SDIO;        /* 复用为SDIO */
    HAL_GPIO_Init(SD_D0_GPIO_PORT, &gpio_init_struct);  /* 初始化 */
    gpio_init_struct.Pin = SD_D1_GPIO_PIN;              /* SD_D1引脚模式设置 */
    HAL_GPIO_Init(SD_D1_GPIO_PORT, &gpio_init_struct);  /* 初始化 */

    gpio_init_struct.Pin = SD_D2_GPIO_PIN;              /* SD_D2引脚模式设置 */
    HAL_GPIO_Init(SD_D2_GPIO_PORT, &gpio_init_struct);  /* 初始化 */

    gpio_init_struct.Pin = SD_D3_GPIO_PIN;              /* SD_D3引脚模式设置 */
    HAL_GPIO_Init(SD_D3_GPIO_PORT, &gpio_init_struct);  /* 初始化 */

    gpio_init_struct.Pin = SD_CLK_GPIO_PIN;             /* SD_CLK引脚模式设置 */
    HAL_GPIO_Init(SD_CLK_GPIO_PORT, &gpio_init_struct); /* 初始化 */

    gpio_init_struct.Pin = SD_CMD_GPIO_PIN;             /* SD_CMD引脚模式设置 */
    HAL_GPIO_Init(SD_CMD_GPIO_PORT, &gpio_init_struct); /* 初始化 */
}

5.函数get_sd_card_info()

获取卡信息函数,代码如下(示例):

/**
 * @brief       获取卡信息函数
 * @param       cardinfo:SD卡信息句柄
 * @retval      返回值:读取卡信息状态值
 */
uint8_t get_sd_card_info(HAL_SD_CardInfoTypeDef *cardinfo)
{
    uint8_t sta;
    sta = HAL_SD_GetCardInfo(&g_sdcard_handler, cardinfo);
    return sta;
}

6.函数get_sd_card_state()

获取卡状态函数,判断SD卡是否可以传输(读写)数据,代码如下(示例):

/**
 * @brief       判断SD卡是否可以传输(读写)数据
 * @param       无
 * @retval      返回值:SD_TRANSFER_OK      传输完成,可以继续下一次传输
                       SD_TRANSFER_BUSY SD 卡正忙,不可以进行下一次传输
 */
uint8_t get_sd_card_state(void)
{
    return ((HAL_SD_GetCardState(&g_sdcard_handler) == HAL_SD_CARD_TRANSFER) ?
         SD_TRANSFER_OK : SD_TRANSFER_BUSY);
}

7.函数sd_read_disk()

完成对SD卡中数据信息读取,代码如下(示例):

/**
 * @brief       读SD卡(fatfs/usb调用)
 * @param       pbuf  : 数据缓存区
 * @param       saddr : 扇区地址
 * @param       cnt   : 扇区个数
 * @retval      0, 正常;  其他, 错误代码(详见SD_Error定义);
 */
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
    uint8_t sta = HAL_OK;
    uint32_t timeout = SD_TIMEOUT;
    long long lsector = saddr;
    
    __disable_irq();   /* 关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!) */                                                                 
    sta = HAL_SD_ReadBlocks(&g_sdcard_handler, (uint8_t *)pbuf, lsector, cnt, 
            SD_TIMEOUT); /* 多个sector的读操作 */
    /* 等待SD卡读完 */
    while (get_sd_card_state() != SD_TRANSFER_OK)
    {
        if (timeout-- == 0)
            sta = SD_TRANSFER_BUSY;
    }
    __enable_irq();    /* 开启总中断 */

    return sta;
}

8.函数sd_write_disk()

完成对SD卡中数据信息写入,代码如下(示例):

/**
 * @brief       写SD卡(fatfs/usb调用)
 * @param       pbuf  : 数据缓存区
 * @param       saddr : 扇区地址
 * @param       cnt   : 扇区个数
 * @retval      0, 正常;  其他, 错误代码(详见SD_Error定义);
 */
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
    uint8_t sta = HAL_OK;
    uint32_t timeout = SD_TIMEOUT;
    long long lsector = saddr;
    
    __disable_irq();  /* 关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!) */
    sta = HAL_SD_WriteBlocks(&g_sdcard_handler, (uint8_t *)pbuf, lsector, cnt, 
            SD_TIMEOUT); /* 多个sector的写操作 */
    /* 等待SD卡写完 */
    while (get_sd_card_state() != SD_TRANSFER_OK)
    {
        if (timeout-- == 0)
            sta = SD_TRANSFER_BUSY;
    }
    __enable_irq();     /* 开启总中断 */
    
    return sta;
}

9.函数show_sdcard_info()

打印SD卡相关信息,代码如下(示例):

/**
 * @brief       打印SD卡相关信息
 * @param       无
 * @retval      无
 */
void show_sdcard_info(void)
{
    HAL_SD_CardCIDTypeDef sd_card_cid;
    HAL_SD_GetCardCID(&g_sdcard_handler, &sd_card_cid); /* 获取CID */
    get_sd_card_info(&g_sd_card_info_handle);           /* 获取SD卡信息 */

    switch (g_sd_card_info_handle.CardType)
    {
        case CARD_SDSC:
        {
            if (g_sd_card_info_handle.CardVersion == CARD_V1_X)
                printf("Card Type:SDSC V1\r\n");
            else if (g_sd_card_info_handle.CardVersion == CARD_V2_X)
                printf("Card Type:SDSC V2\r\n");
        }
        break;

        case CARD_SDHC_SDXC:
            printf("Card Type:SDHC\r\n");
            break;
        default: break;
    }
    /* 制造商ID */
    printf("Card ManufacturerID:%d\r\n", sd_card_cid.ManufacturerID); 
    /* 卡相对地址 */                       
    printf("Card RCA:%d\r\n", g_sd_card_info_handle.RelCardAdd);          
    /* 显示逻辑块数量 */
    printf("LogBlockNbr:%d \r\n", (uint32_t)(g_sd_card_info_handle.LogBlockNbr));
    /* 显示逻辑块大小 */       
    printf("LogBlockSize:%d \r\n", (uint32_t)(g_sd_card_info_handle.LogBlockSize));
    /* 显示容量 */
    printf("Card Capacity:%d MB\r\n", (uint32_t)SD_TOTAL_SIZE_MB(&g_sdcard_handler));
    /* 显示块大小 */
    printf("Card BlockSize:%d\r\n\r\n", g_sd_card_info_handle.BlockSize); 
}

总结

这章完成是STM32的SDIO通讯接口和SD卡介绍,为下一章FATFS文件系统做铺垫。

下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。

相应的代码链接:代码程序


网站公告

今日签到

点亮在社区的每一天
去签到