【stm32】【SD】SDIO fatfs

注意事项:跳转SD卡初始化

API:stm32xx_hal_sd.c文件开头有多行注释。

cpp 复制代码
    (#) Configure the SD Card Data transfer frequency. You can change or adapt this
        frequency by adjusting the "ClockDiv" field.
        In transfer mode and according to the SD Card standard, make sure that the
        SDIO_CK frequency doesn't exceed 25MHz and 50MHz in High-speed mode switch.
        To be able to use a frequency higher than 24MHz, you should use the SDIO
        peripheral in bypass mode. Refer to the corresponding reference manual
        for more details.

33. SDHI------SD卡读写测试 --- [野火]瑞萨RA系列FSP库开发实战指南------基于野火启明开发板 文档

SDIO配置

  1. 对于SD卡 :在驱动正常的情况下,系统通常会自动切换 。先以1位模式识别卡,然后通过命令协商切换到4位模式以获得最佳性能。手动选择时,应选 "SD 4 bits"

  2. 对于SDIO外设(如Wi-Fi模块) :同样,为了达到标称的网络速度,必须工作在 "SD 4 bits" 模式。如果强制为1位模式,网速会非常慢。

模式 数据线数量 速度 主要应用
SD 1 bit 1根 (DAT0) 最慢 初始化、兼容模式、低速设备
SD 4 bits 4根 (DAT0-DAT3) SD卡SDIO Wi-Fi/蓝牙模块
MMC 1 bit 1根 (DAT0) 最慢 MMC设备初始化
MMC 4 bits 4根 (DAT0-DAT3) 老式MMC设备
MMC 8 bits 8根 (DAT0-DAT7) 最快 eMMC内置存储器
1. Clock transition on which the ... (Rising transition)数据采样的时钟边沿
  • 详细解释: 这决定了SDIO控制器在时钟信号的哪个边沿(上升沿或下降沿)去读取数据线上的数据。

    • Rising transition:在时钟信号从低电平跳变到高电平的瞬间对数据进行采样。

    • Falling transition:在时钟信号从高电平跳变到低电平的瞬间对数据进行采样。

  • 默认值与建议 : 绝大多数SDIO协议标准规定使用上升沿采样 。除非有特殊的硬件设计或信号完整性问题,否则应保持为 Rising transition

    1. SDIO Clock divider bypassSDIO时钟分频器旁路
  • 详细解释: SDIO控制器通常有一个输入时钟,然后通过一个内部分频器来产生最终给SD卡/设备使用的SDIO_CLK。这个选项可以绕过那个分频器。

    • Enable/Bypass:启用旁路。输入时钟直接作为SDIO_CLK输出,不分频。这通常用于需要非常高时钟频率的场景,或者分频器出现问题时。

    • Disable :禁用旁路。使用下面的"Clock divide factor"进行分频。这是正常模式

  • 默认值与建议 : 通常保持为 Disable(即使用分频器),以便灵活地控制SDIO时钟速度。

3. SDIO Clock output enable ... (Disable the power save for the clock)
  • 中文理解时钟输出使能/时钟功耗节省模式禁用

  • 详细解释: 这是一个与功耗相关的设置。

    • Enable:始终输出SDIO_CLK时钟信号,即使总线空闲时也是如此。

    • Disable:启用功耗节省模式。当总线空闲时,自动停止SDIO_CLK的输出,以降低功耗。

  • 默认值与建议 : 对于移动设备,为了省电,通常建议启用此功能。但在调试某些设备时,如果时钟时有时无会导致设备状态机出错,则可能需要禁用它(即始终输出时钟)。

4. SDIO hardware flow control SDIO硬件流控制
  • 详细解释: 这是一种防止数据丢失的机制。当SDIO主机(CPU)发送数据太快,设备(如Wi-Fi模块)来不及处理时,设备可以通过一个硬件信号线(通常是DAT1线复用为Ready信号)通知主机"暂停发送"。

    • Enable:启用硬件流控制。可以提高高负载下的数据传输可靠性,避免FIFO溢出。

    • Disable:禁用硬件流控制。依赖软件来管理数据流。

  • 默认值与建议 : 对于高速、高吞吐量的设备(如SDIO Wi-Fi),强烈建议启用。对于普通的SD存储卡,通常不需要,可以禁用

5. SDIOCLK clock divide factor SDIO时钟分频系数
  • 详细解释 : 这是最重要的参数之一,直接决定了SDIO总线的速度。
  1. **SDIO 适配器时钟(SDIOCLK)**该时钟用于驱动 SDIO 内部适配器,是生成 SDIO_CK 的基础时钟。在 STM32F4 系列芯片中,它固定来源于 PLL48CK,标准工作频率为 48MHz;而 STM32F1 系列中则来自 HCLK(系统时钟,通常 72MHz)。这个时钟与系统主 PLL 时钟相互独立,能保证 SDIO 模块时钟的稳定性,不受其他模块时钟波动的干扰。
  2. 总线接口时钟其作用是驱动 SDIO 的总线接口,不同型号的 STM32 芯片对应的总线类型和频率不同。比如 STM32F4 系列中为 APB2 总线接口时钟(PCLK2),频率一般为 84MHz;STM32F1 系列中则是 AHB 总线接口时钟,由 HCLK 二分频得到,频率为 36MHz。该时钟仅负责 SDIO 模块与单片机内部总线的数据交互,不参与外部卡的通信时序。

对于F4

SDIO_CK = SDIOCLK / (2 + 0) = 24Mhz(读写失败可以用更高分频)

外部中断:

SD卡往往都有一个检测GPIO(可以设置外部中断,中断不要使用HAL延时函数)

【STM32】检测SD卡是否插入_sd卡插入检测-CSDN博客

基本调试SD卡:

基本了解一下信息,下面两个结构体用两个函数获取。第二个结构体可以不用函数获取。

cpp 复制代码
typedef struct {
  uint8_t  ManufacturerID;  // 制造商ID
  uint16_t OEM_AppliID;     // OEM/应用ID
  uint32_t ProdName1;       // 产品名称部分1
  uint32_t ProdName2;       // 产品名称部分2  
  uint32_t ProdRev;         // 产品版本
  uint32_t ProdSN;          // 产品序列号
  uint8_t  Reserved1;       // 保留
  uint16_t ManufactDate;    // 制造日期
  uint8_t  CID_CRC;         // CID CRC校验
  uint8_t  Reserved2;       // 保留
} HAL_SD_CardCIDTypeDef;
typedef struct {
  SD_TypeDef           CardType;         // 卡片类型
  uint32_t             CardVersion;      // 卡片版本  
  uint32_t             Class;            // 速度等级
  uint32_t             RelCardAdd;       // 相对卡片地址
  uint32_t             BlockNbr;         // 块数量
  uint32_t             BlockSize;        // 块大小
  uint32_t             LogBlockNbr;      // 逻辑块数量
  uint32_t             LogBlockSize;     // 逻辑块大小
下面的f4xx不包含
  uint32_t             CardSpeed;        // 卡片速度
  uint32_t             TransferSpeed;    // 传输速度
  uint32_t             BusWide;          // 总线宽度
  uint32_t             Security;         // 安全特性
  uint32_t             DataTimeOut;      // 数据超时
  uint32_t             EraseSize;        // 擦除大小
  uint32_t             EraseTimeout;     // 擦除超时
  FunctionalState      WriteBlockPaPartial;  // 部分块写入
  uint32_t             CardFlags;        // 卡片标志
} HAL_SD_CardInfoTypeDef;
cpp 复制代码
    volatile uint8_t gpio = 1;
  while (1)
  {
	if(HAL_GPIO_ReadPin(SD_CD_GPIO_Port,SD_CD_Pin) == GPIO_PIN_RESET)
	{
		gpio = 0;
	}
	else gpio = 1;
    if (sd_insert_pending) {
        sd_insert_pending = false;
        printf("SD in\r\n");
        MX_SDIO_SD_Init();
		SD_PrintCardInfo(&hsd);
    }
    
    if (sd_remove_pending) {
        sd_remove_pending = false;
        printf("SD out\r\n");
        HAL_SD_DeInit(&hsd);
    }
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	printf("gpio = %d\r\n",gpio);
	HAL_Delay(1000);
	  
  }

和硬件设备挂钩的设置volatile,我这里设置玩的。

cpp 复制代码
volatile bool sd_insert_pending = false;
volatile bool sd_remove_pending = false;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//	HAL_Delay(30);
	if(GPIO_Pin == SD_CD_Pin)
	{
		if(HAL_GPIO_ReadPin(SD_CD_GPIO_Port,SD_CD_Pin) == GPIO_PIN_RESET)
		{
			sd_insert_pending = true;
		}
		else
		{
			sd_remove_pending = true;
		}
	}
}
复制代码
【stm32】【printf】-CSDN博客
cpp 复制代码
void SD_Getinfo(void)
{
  printf("SD_Getinfo\r\n");
  HAL_SD_CardStateTypeDef state = HAL_SD_GetCardState(&hsd);
  HAL_SD_CardCIDTypeDef SD_CardCID;

  if (state == HAL_SD_CARD_TRANSFER)
  {
    HAL_SD_GetCardCID(&hsd, &SD_CardCID);
    printf("\r\nInitialize SD card sucessfully!\r\n");
    printf("SD card information\r\n");
    printf("CardType           :%ul\r\n", hsd.SdCard.CardType);
    printf("CardVersion        :%ul\r\n", hsd.SdCard.CardVersion);
    printf("Class              :%ul\r\n", hsd.SdCard.Class);
    printf("RelCardAdd         :%ul\r\n", hsd.SdCard.RelCardAdd);
    printf("BlockNbr           :%ul\r\n", hsd.SdCard.BlockNbr);
    printf("BlockSize          :%ul\r\n", hsd.SdCard.BlockSize);
    printf("LogBlockNbr        :%ul\r\n", hsd.SdCard.LogBlockNbr);
    printf("LogBlockSize       :%ul\r\n", hsd.SdCard.LogBlockSize);
    printf("ManufacturerID     :%d\r\n", SD_CardCID.ManufacturerID);
  }
  else
  {
    printf("SD card initialize failed.\r\n");
  }
}
void SD_PrintCardInfo(SD_HandleTypeDef *hsd)
{
  printf("===== SD Card Information =====\r\n");

  HAL_SD_CardStateTypeDef card_state = HAL_SD_GetCardState(hsd);
  if (HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)
  {
    printf("SD Card State: Not Ready (State Code: %d)\r\n", card_state);
    printf("===============================\r\n");
    return;
  }
  printf("SD Card State: Ready\r\n");

  HAL_StatusTypeDef status = HAL_SD_GetCardCID(hsd, &card_cid);
  if (status == HAL_OK)
  {
    printf("Manufacturer ID    : 0x%02X\r\n", card_cid.ManufacturerID);
    printf("OEM/Application ID : %c%c\r\n", 
           (card_cid.OEM_AppliID >> 8) & 0xFF, 
           card_cid.OEM_AppliID & 0xFF);

    printf("Product Name       : ");

	char product_name[9];
	product_name[0] = (char)((card_cid.ProdName1 >> 24) & 0xFF);
	product_name[1] = (char)((card_cid.ProdName1 >> 16) & 0xFF);
	product_name[2] = (char)((card_cid.ProdName1 >> 8) & 0xFF);
	product_name[3] = (char)(card_cid.ProdName1 & 0xFF);
	product_name[4] = (char)((card_cid.ProdName2 >> 24) & 0xFF);
	product_name[5] = (char)((card_cid.ProdName2 >> 16) & 0xFF);
	product_name[6] = (char)((card_cid.ProdName2 >> 8) & 0xFF);
	product_name[7] = (char)(card_cid.ProdName2 & 0xFF);
	product_name[8] = '\0';
	printf("%s\r\n", product_name);

    printf("Product Revision   : %d.%d\r\n", 
           (card_cid.ProdRev >> 4) & 0x0F, 
           card_cid.ProdRev & 0x0F);
    
	printf("Product Serial No. : %u\r\n", card_cid.ProdSN);
	uint16_t year = 2000 + ((card_cid.ManufactDate >> 4) & 0xFF);
	uint8_t month = card_cid.ManufactDate & 0x0F;
	printf("Manufacturing Date : %d/%d\r\n", year, month);
  }
  else
  {
    printf("Failed to read CID register.\r\n");
  }

  printf("\r\nCard Type          : ");
  switch(hsd->SdCard.CardType)
  {
    case CARD_SDSC:
      printf("Standard Capacity SD (SDSC) < 2GB\r\n");
      break;
    case CARD_SDHC_SDXC:
        printf("High Capacity SD (SDHC) < 32GB\r\n");
      break;
    case CARD_SECURED:
      printf("Secured Digital Card (SDIO)\r\n");
      break;
    default:
      printf("Unknown (0x%u)\r\n", hsd->SdCard.CardType);
  }

  printf("Card Version       : ");
  switch(hsd->SdCard.CardVersion)
  {
    case CARD_V1_X:
      printf("SD Specification v1.x\r\n");
      break;
    case CARD_V2_X:
      printf("SD Specification v2.x\r\n");
      break;
    default:
      printf("Unknown (0x%u)\r\n", hsd->SdCard.CardVersion);
  }
	printf("Speed Class        : Class %lu\r\n", (unsigned long)hsd->SdCard.Class);
	printf("Relative Card Addr : 0x%08lX\r\n", (unsigned long)hsd->SdCard.RelCardAdd);
	printf("Block Count        : %lu\r\n", (unsigned long)hsd->SdCard.BlockNbr);
	printf("Block Size			: %lu bytes\r\n", (unsigned long)hsd->SdCard.BlockSize);
	printf("Log Block Count		: %lu\r\n", (unsigned long)hsd->SdCard.LogBlockNbr);
	printf("Log Block Size		: %lu bytes\r\n", (unsigned long)hsd->SdCard.LogBlockSize);
  
	uint64_t card_capacity_bytes = (uint64_t)hsd->SdCard.BlockNbr * hsd->SdCard.BlockSize;
	uint64_t card_capacity_mb = card_capacity_bytes / (1024 * 1024);
	uint64_t card_capacity_gb = card_capacity_bytes / (1024ULL * 1024 * 1024);
	if (card_capacity_gb > 0) {
		printf("Card Capacity		: %llu GB (%llu MB)\r\n", card_capacity_gb, card_capacity_mb);
	} else {
		printf("Card Capacity      : %llu MB\r\n", card_capacity_mb);
	}
	
  printf("===============================\r\n");
}

关于SD卡初始化:

真正的SD卡初始化函数为HAL_SD_InitCard(),进入该函数发现实际初始化SD卡时用到的并不是用户配置的参数,而是使用的默认初始化参数,这里时钟分频因子被设置为了0x76,也即118,根据上面提到的公式计算可知48MHz / (118 + 2) = 400KHz,满足SD卡的初始化频率。

但是在初始化SDIO的时候初始化失败会阻塞程序,修改error_handler以免基础程序卡住。

中断配置(fatfs配置)

我们使用fatfs时,一定要有DMA+中断的形式,以免fatfs的api被打断。不适用fatfs的时候可以使用轮询方式。

关键注意点(FatFS 与 SDIO 中断 / DMA 适配)

FatFS 的核心是「磁盘 I/O 接口」(diskio.c 中的 5 个函数),中断 / DMA 逻辑必须封装在这些函数内部,不能让 FatFS 直接感知中断(FatFS 本身是同步操作,需等待读写完成后才返回)。

Memory - Increment Address

读写 SD 卡的文件数据时,数据在内存中是连续存储的(如连续的扇区缓冲区),使能内存地址递增可让 DMA 自动将数据连续写入或读取到内存的连续区域。

Peripheral - Increment Address

SDIO 的 FIFO 地址是固定的,在 DMA 传输过程中不需要地址递增,因此不使能。

  • FatFS 适配无影响

    • FatFS 仅关注扇区级读写(512 字节 / 扇区),底层 DMA 数据宽度不影响上层文件操作;
    • 只需确保 diskio.c 中缓冲区地址对齐,且总传输字节数为 512 的整数倍(Half Word×4=8 字节,512 是 8 的整数倍,无问题)。

最后加大堆栈大小,使用了DMA和提前配置都要使用到。

未完待续

相关推荐
就是蠢啊1 小时前
51单片机——独立按钮、矩阵按键
单片机·嵌入式硬件·51单片机
云山工作室2 小时前
多传感器融合的办公室智能门禁系统(论文+源码)
stm32·单片机·嵌入式硬件·物联网·毕业设计·课程设计
天天爱吃肉82183 小时前
智能网联汽车信息安全深度解析:从UN-R155与GB44495标准到OBD/UDS技术实践
网络·嵌入式硬件·汽车
小曹要微笑6 小时前
STM32H7系列全面解析:嵌入式性能的巅峰之作
c语言·stm32·单片机·嵌入式硬件·算法
小曹要微笑6 小时前
STM32F103ZET6 全面详解
单片机·嵌入式硬件
Python小老六13 小时前
STM32 Flash:扇区、页、块
stm32·单片机·嵌入式硬件
普中科技15 小时前
【普中DSP28335开发攻略】-- 第 16 章 定时器中断实验
单片机·嵌入式硬件·定时器·dsp28335·普中科技
雅欣鱼子酱15 小时前
电流检测的电路设计与选型——分流电阻法
stm32·单片机·嵌入式硬件·芯片·电子元器件·电流检测芯片
DIY机器人工房17 小时前
嵌入式面试题:看你学习了自动控制原理这门课,讲一下欠驱动系统?
stm32·单片机·学习·嵌入式·自动控制原理