STM32F1 HAL库读写SD卡的操作要点

本文采用的HAL库版本为STM32Cube_FW_F1_V1.8.0(带Patch-CubeF1 1.8.4)。

知识点一:SD卡数据线位宽的配置

SD卡可以采用1位数据线模式,也可以采用4位数据线模式。但是必须确保STM32单片机的SDIO设置的数据线位宽,和SD卡上设置的数据线位宽是一致的。
将hsd.Init.BusWide设为SDIO_BUS_WIDE_4B,然后执行HAL_SD_Init函数,只能把STM32单片机的SDIO设置为4位位宽,SD卡上还是用的1位位宽。
所以通常的做法是hsd.Init.BusWide设为SDIO_BUS_WIDE_1B,HAL_SD_Init执行完成后,再调用HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B),这个函数可以将STM32和SD卡同时设为4位模式。

知识点二:SDIO_CK时钟线频率的配置

如图所示,SDIO分为两个部分:AHB interface和SDIO adapter。AHB interface采用的时钟是HCLK/2=36MHz, 是用来访问STM32 SDIO本身的寄存器的。SDIO adapter采用的时钟是SDIOCLK=HCLK=72MHz,SDIO_CK时钟线(PC12脚,单片机给SD卡提供的时钟)输出的时钟就是从这个上面分频得到的,分频公式为SDIOCLK/(CLKDIV+2)。
CLKDIV就是hsd.Init.ClockDiv的值。
当CLKDIV=70时,SDIO_CK输出的频率为72MHz/(70+2)=1MHz。在这个频率下可以不使用DMA收发数据。
当CLKDIV=1时,SDIO_CK输出的频率为72MHz/(1+2)=24MHz。在这个频率下必须使用DMA收发数据。

知识点三:获取SD卡容量

SD卡读写是以扇区为单位的,每个扇区的大小都是512字节。总容量=扇区数×扇区大小。
要获取SD卡的总容量可使用HAL_SD_GetCardInfo(&hsd, &info)函数。函数调用后,info.BlockNbr就是扇区数,info.BlockSize就是扇区大小。两者相乘就是SD卡的总容量,单位为字节。
读写扇区时,扇区号必须是0~info.BlockNbr-1之间的号码。
那info.LogBlockNbr和info.LogBlockSize又是什么呢?假如插入的卡是SDSC卡,有可能info.BlockSize不等于512。而在HAL库里面info.LogBlockSize始终等于512,那么info.LogBlockNbr就等于info.BlockNbr*info.BlockSize/512,也就是说此时info.BlockNbr为实际的扇区数,而info.LogBlockNbr为按512字节扇区大小来算的扇区数

知识点四:SDIO DMA的初始化

STM32F1的主频较低,在SDIO时钟频率很高的情况下(如24MHz)必须要用DMA收发数据。但STM32CubeMX里面,SDIO却只能添加一个DMA Handle,要么选择收,要么选择发,是不是很奇怪呢?实际上SDIO收发数据是可以用同一个DMA Handle的。
C语言全局变量的值默认为全0,所以hdma24.Init.Direction=0,不用管。
设置完hdma24.Init的其他成员后,调用HAL_DMA_Init初始化DMA2_Channel4,然后两次调用__HAL_LINKDMA宏将hdma24同时绑定到hsd的hdmarx和hdmatx上:
__HAL_LINKDMA(&hsd, hdmarx, hdma24);
__HAL_LINKDMA(&hsd, hdmatx, hdma24);
之后调用HAL_SD_ReadBlocks_DMA或者HAL_SD_WriteBlocks_DMA就会自动切换DMA传输方向。但是为了确保DMA传输方向能切换成功,必须在每次传输完成后关闭DMA,也就是在HAL_SD_RxCpltCallback和HAL_SD_TxCpltCallback传输完成回调函数里面调用__HAL_DMA_DISABLE(hsd->hdmaXX)。这一点非常重要。

知识点五:HAL_SD_Erase的参数和HAL_SD_ReadBlocks、HAL_SD_WriteBlocks的区别

HAL_SD_Erase的第二、第三个参数名称为BlockStartAdd和BlockEndAdd,两个参数都是扇区号。
HAL_SD_Erase(&hsd, 2, 7)意思是擦除2、3、4、5、6、7这6个扇区,包括扇区2和扇区7。
HAL_SD_ReadBlocks、HAL_SD_WriteBlocks的第三、第四个参数名称是BlockAdd和NumberOfBlocks。是起始扇区号和扇区个数的意思。
所以HAL_SD_ReadBlocks(&hsd, (uint8_t *)0x60000000, 4096, 2048, HAL_MAX_DELAY)意思是从第4096扇区开始,读2048个扇区,读出来的数据放到0x60000000内存地址处。也就是说读的是扇区4096~6143。

知识点六:等待擦除、写入数据完毕

读数据时,DMA传输完毕就是读操作完毕了。
但擦除和写入数据,DMA传输完毕并不代表擦除和写操作完毕。必须要等到HAL_SD_GetCardState(&hsd)的返回值从HAL_SD_CARD_PROGRAMMING(=7)变为HAL_SD_CARD_TRANSFER(=4)后,才算完毕了,才能执行新的操作(比如读操作)。
提示:读操作未完毕时,返回值是HAL_SD_CARD_SENDING(=5),完毕后是HAL_SD_CARD_TRANSFER(=4)。

电路图

SD卡部分有两个卡检测引脚(CD)。
第2脚CD/DAT3是SD卡上的引脚,低电平是没有插卡,高电平是插了卡。由于SD卡内部是一个几十kΩ的上拉电阻,所以卡外的下拉电阻必须要很大,这里选择的是470kΩ。
第9脚CD是SD卡槽上的引脚,不是SD卡上的引脚,插卡后9脚和外壳(PAD)短路,外壳是接地的。所以高电平是没插卡,低电平是插了卡。

示例代码

在Keil MDK 5里面直接创建使用HAL库的STM32工程的方法(不使用STM32CubeMX)_ZLK1214的专栏-CSDN博客https://blog.csdn.net/ZLK1214/article/details/103224868

#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"

#define WRITE_ENABLE 0

DMA_HandleTypeDef hdma24;
SD_HandleTypeDef hsd;
SRAM_HandleTypeDef hsram;
static volatile uint8_t sd_rx_complete, sd_tx_complete;

/* 初始化外部SRAM内存 */
// 型号:IS62WV51216BLL
// 容量:1MB
// 地址:0x60000000~0x600fffff
static void sram_init(void)
{
  FSMC_NORSRAM_TimingTypeDef timing = {0};
  GPIO_InitTypeDef gpio;
  
  __HAL_RCC_FSMC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  
  // FSMC_D2~3: PD0~1, NOE: PD4, NWE: PD5, FSMC_NE1: PD7, FSMC_D13~15: PD8~10, FSMC_A16~18: PD11~13, FSMC_D0~1: PD14~15
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOD, &gpio);
  
  // FSMC_NBL0~1: PE0~1, FSMC_D4~12: PE7~15
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  HAL_GPIO_Init(GPIOE, &gpio);
  
  // FSMC_A0~5: PF0~5, FSMC_A6~9: PF12~15
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  HAL_GPIO_Init(GPIOF, &gpio);
  
  // FSMC_A10~15: PG0~PG5
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
  HAL_GPIO_Init(GPIOG, &gpio);
  
  hsram.Instance = FSMC_NORSRAM_DEVICE;
  hsram.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
  hsram.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
  hsram.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
  hsram.Init.NSBank = FSMC_NORSRAM_BANK1;
  hsram.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
  timing.AddressHoldTime = 1;
  timing.AddressSetupTime = 1;
  timing.BusTurnAroundDuration = 0;
  timing.DataSetupTime = 1;
  
  // 以下参数与SRAM无关, 仅仅为了避免assert_param报错
  timing.CLKDivision = 2;
  timing.DataLatency = 2;
  
  HAL_SRAM_Init(&hsram, &timing, NULL);
}

/* 初始化SD卡 */
static int sd_init(void)
{
  GPIO_InitTypeDef gpio;
  HAL_SD_CardInfoTypeDef info;
  HAL_StatusTypeDef status;
  
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_SDIO_CLK_ENABLE();
  
  // SDIO_D0~3: PC8~11, SDIO_CK: PC12
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOC, &gpio);
  
  // SDIO_CMD: PD2
  gpio.Pin = GPIO_PIN_2;
  HAL_GPIO_Init(GPIOD, &gpio);
  
  hsd.Instance = SDIO;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B; // 这里必须设置为1位宽度, 不能是4位, 想要4位宽度必须使用HAL_SD_ConfigWideBusOperation函数
  hsd.Init.ClockDiv = 70; // 分频系数
  status = HAL_SD_Init(&hsd);
  if (status == HAL_OK)
    printf("SD init OK! frequency=%lgMHz\n", (double)SystemCoreClock / (2 + hsd.Init.ClockDiv) / 1000000);
  else
  {
    // 没有插卡
    printf("SD init failed! status=%d\n", status);
    return -1;
  }
  
  // 发送和接收共用1个DMA Handle, 不用填写Init.Direction成员
  // alignment必须为word(每次传输4字节数据)
  hdma24.Instance = DMA2_Channel4;
  hdma24.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma24.Init.MemInc = DMA_MINC_ENABLE;
  hdma24.Init.Mode = DMA_NORMAL;
  hdma24.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma24.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma24.Init.Priority = DMA_PRIORITY_VERY_HIGH;
  HAL_DMA_Init(&hdma24);
  __HAL_LINKDMA(&hsd, hdmarx, hdma24);
  __HAL_LINKDMA(&hsd, hdmatx, hdma24);
  HAL_NVIC_EnableIRQ(DMA2_Channel4_5_IRQn);
  HAL_NVIC_EnableIRQ(SDIO_IRQn);
  
  // 现在可以设置为4位宽度
  status = HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B);
  if (status != HAL_OK)
  {
    printf("Failed to configure wide bus! status=%d\n", status);
    return -1;
  }
  
  // 查看卡的类型、版本和容量
  status = HAL_SD_GetCardInfo(&hsd, &info);
  if (status == HAL_OK)
  {
    if (info.CardType == CARD_SDSC)
      printf("SDCard type: SDSC\n");
    else if (info.CardType == CARD_SDHC_SDXC)
      printf("SDCard type: SDHC/SDXC\n");
    if (info.CardVersion == CARD_V1_X)
      printf("SDCard version: V1\n");
    else if (info.CardVersion == CARD_V2_X)
      printf("SDCard version: V2\n");
    printf("SDCard block number: %u\n", info.BlockNbr);
    printf("SDCard block size: %u\n", info.BlockSize);
    printf("SDCard capacity: %lgMB\n", (double)info.BlockNbr * info.BlockSize / 1048576);
  }
  
  return 0;
}

/* 测试读写SD卡 */
static int sd_test(void)
{
  int i, n;
  uint32_t addr;
  HAL_StatusTypeDef status;
  
  /* 普通模式读SD卡 */
  // 从SD卡扇区0开始读, 读1个扇区到0x60000000内存地址处
  status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)0x60000000, 0, 1, HAL_MAX_DELAY);
  if (status == HAL_OK)
  {
    printf("[SDCard Block 0]\n");
    dump_data((void *)0x60000000, 512); // 显示数据内容
  }
  else
  {
    printf("Failed to read sdcard block 0! status=%d\n", status);
    return -1;
  }
  
  // 从SD卡扇区4096开始读, 读2048个扇区到0x60000000内存地址处, 填满整个1MB内存
  status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)0x60000000, 4096, 2048, HAL_MAX_DELAY);
  if (status == HAL_OK)
    printf("Read 2048 sdcard blocks!\n");
  else
  {
    printf("Failed to read 2048 sdcard blocks! status=%d\n", status);
    return -1;
  }
  
  /* 普通模式写SD卡 */
#if WRITE_ENABLE
  for (i = 0; i < 500 * 512; i += 4)
    *(uint32_t *)(0x60000000 + i) = 0x60000000 + i;
  
  // 写之前先擦除, 擦除后扇区的内容为全0
  // 请注意HAL_SD_Erase(&hsd, x, y)的意思是擦除扇区x到扇区y, 共计y-x+1个扇区, 也就是说要包括扇区x和扇区y
  // 擦除扇区1~500, 共计500-1+1=500个扇区
  status = HAL_SD_Erase(&hsd, 1, 500);
  if (status == HAL_OK)
  {
    printf("Erasing 500 sdcard blocks. state=%d\n", HAL_SD_GetCardState(&hsd));
    while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
    printf("Erase complete! state=%d\n", HAL_SD_GetCardState(&hsd));
  }
  else
  {
    printf("Failed to erase 500 sdcard blocks! status=%d\n", status);
    return -1;
  }
  
  // 将0x60000000内存里面的数据写到SD卡的扇区1~500(共500个扇区)
  status = HAL_SD_WriteBlocks(&hsd, (uint8_t *)0x60000000, 1, 500, HAL_MAX_DELAY);
  if (status == HAL_OK)
  {
    printf("Writing 500 sdcard blocks. state=%d\n", HAL_SD_GetCardState(&hsd));
    while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
    printf("Write complete! state=%d\n", HAL_SD_GetCardState(&hsd));
  }
  else
  {
    printf("Failed to write 500 sdcard blocks! status=%d\n", status);
    return -1;
  }
#endif
  
  /* DMA模式读SD卡 */
  // 提速
  hsd.Init.ClockDiv = 1;
  status = HAL_SD_Init(&hsd);
  if (status == HAL_OK)
    printf("Frequency is changed to %lgMHz.\n", (double)SystemCoreClock / (2 + hsd.Init.ClockDiv) / 1000000);
  else
  {
    printf("Failed to change frequency! status=%d\n", status);
    return -1;
  }
  
  // 从SD卡扇区1开始读, 读1个扇区到0x60000000内存地址处
  status = HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t *)0x60000000, 1, 1);
  if (status == HAL_OK)
  {
    while (!sd_rx_complete);
    sd_rx_complete = 0;
    printf("[SDCard Block 1 with DMA]\n");
    dump_data((void *)0x60000000, 512);
  }
  else
  {
    printf("Failed to read sdcard block 4096 with DMA! status=%d\n", status);
    return -1;
  }
  
  // 从SD卡扇区4096开始读, 读2048个扇区到0x60000000内存地址处, 填满整个1MB内存
  // 注意DMA模式下每次最多只能读127个扇区
  for (i = 0; i < 2048; i += n)
  {
    n = 127;
    if (i + n > 2048)
      n = 2048 - i;
    addr = 0x60000000 + i * 512;
    status = HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t *)addr, 4096 + i, n);
    if (status == HAL_OK)
    {
      while (!sd_rx_complete);
      sd_rx_complete = 0;
      printf("Read %d sdcard blocks with DMA! memaddr=0x%08x\n", n, addr);
    }
    else
    {
      printf("Failed to read %d sdcard blocks with DMA! status=%d\n", n, status);
      return -1;
    }
  }
  
  /* DMA模式写SD卡 */
#if WRITE_ENABLE
  // 擦除扇区501~627, 共计627-501+1=127个扇区
  status = HAL_SD_Erase(&hsd, 501, 627);
  if (status == HAL_OK)
  {
    printf("Erasing 127 sdcard blocks. state=%d\n", HAL_SD_GetCardState(&hsd));
    while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
    printf("Erase complete! state=%d\n", HAL_SD_GetCardState(&hsd));
  }
  else
  {
    printf("Failed to erase 127 sdcard blocks! status=%d\n", status);
    return -1;
  }
  
  // 检查第627、628扇区是否为全0
  status = HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t *)0x600e0000, 627, 2);
  if (status == HAL_OK)
  {
    while (!sd_rx_complete);
    sd_rx_complete = 0;
    for (i = 0; i < 512; i++)
    {
      if (*(uint8_t *)(0x600e0000 + i) != 0)
        break;
    }
    if (i == 512)
      printf("Sector 627 is empty.\n");
    else
    {
      printf("Sector 627 is NOT empty!!!! pos=%d\n", i);
      return -1;
    }
    
    for (i = 512; i < 1024; i++)
    {
      if (*(uint8_t *)(0x600e0000 + i) != 0)
        break;
    }
    if (i == 1024)
      printf("Sector 628 is empty.\n");
    else
      printf("Sector 628 is not empty. pos=%d\n", i - 512);
  }
  else
  {
    printf("Failed to check block 627~628!\n");
    return -1;
  }
  
  // 将0x60000000内存里面的数据写到SD卡的扇区501~627
  status = HAL_SD_WriteBlocks_DMA(&hsd, (uint8_t *)0x60000000, 501, 127);
  if (status == HAL_OK)
  {
    while (!sd_tx_complete);
    sd_tx_complete = 0;
    printf("Writing 127 sdcard blocks with DMA. state=%d\n", HAL_SD_GetCardState(&hsd));
    while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
    printf("Write complete! state=%d\n", HAL_SD_GetCardState(&hsd));
  }
  else
  {
    printf("Failed to write 127 sdcard blocks with DMA! status=%d\n", status);
    return -1;
  }
#endif
  return 0;
}

int main(void)
{
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  printf("STM32F103ZE SDCard\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  sram_init();
  if (sd_init() == 0)
    sd_test();
  
  while (1)
  {
  }
}

void DMA2_Channel4_5_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma24);
}

void SDIO_IRQHandler(void)
{
  HAL_SD_IRQHandler(&hsd);
}

void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
  __HAL_DMA_DISABLE(hsd->hdmarx);
  sd_rx_complete = 1;
}

void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
  __HAL_DMA_DISABLE(hsd->hdmatx);
  sd_tx_complete = 1;
}

程序运行结果(WRITE_ENABLE=1):

STM32F103ZE SDCard
SystemCoreClock=72000000
SD init OK! frequency=1MHz
SDCard type: SDHC/SDXC
SDCard version: V2
SDCard block number: 60506112
SDCard block size: 512
SDCard capacity: 29544MB
[SDCard Block 0]
FAB800108ED0BC00B0B800008ED88EC0FBBE007CBF0006B90002F3A4EA21060000BEBE073804750B83C61081FEFE0775F3EB16B402B001BB007CB2808A74018B4C02CD13EA007C0000EBFE0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B35E8A8400000004010483FEC2FF0008000000B82C0000FEC2FF83FEC2FF00C02C000040A10100FEC2FF0CFEC2FF0000CE010040CD010000000000000000000000000000000055AA
Read 2048 sdcard blocks!
Erasing 500 sdcard blocks. state=7
Erase complete! state=4
Writing 500 sdcard blocks. state=7
Write complete! state=4
Frequency is changed to 24MHz.
[SDCard Block 1 with DMA]
0000006004000060080000600C0000601000006014000060180000601C0000602000006024000060280000602C0000603000006034000060380000603C0000604000006044000060480000604C0000605000006054000060580000605C0000606000006064000060680000606C0000607000006074000060780000607C0000608000006084000060880000608C0000609000006094000060980000609C000060A0000060A4000060A8000060AC000060B0000060B4000060B8000060BC000060C0000060C4000060C8000060CC000060D0000060D4000060D8000060DC000060E0000060E4000060E8000060EC000060F0000060F4000060F8000060FC0000600001006004010060080100600C0100601001006014010060180100601C0100602001006024010060280100602C0100603001006034010060380100603C0100604001006044010060480100604C0100605001006054010060580100605C0100606001006064010060680100606C0100607001006074010060780100607C0100608001006084010060880100608C0100609001006094010060980100609C010060A0010060A4010060A8010060AC010060B0010060B4010060B8010060BC010060C0010060C4010060C8010060CC010060D0010060D4010060D8010060DC010060E0010060E4010060E8010060EC010060F0010060F4010060F8010060FC010060
Read 127 sdcard blocks with DMA! memaddr=0x60000000
Read 127 sdcard blocks with DMA! memaddr=0x6000fe00
Read 127 sdcard blocks with DMA! memaddr=0x6001fc00
Read 127 sdcard blocks with DMA! memaddr=0x6002fa00
Read 127 sdcard blocks with DMA! memaddr=0x6003f800
Read 127 sdcard blocks with DMA! memaddr=0x6004f600
Read 127 sdcard blocks with DMA! memaddr=0x6005f400
Read 127 sdcard blocks with DMA! memaddr=0x6006f200
Read 127 sdcard blocks with DMA! memaddr=0x6007f000
Read 127 sdcard blocks with DMA! memaddr=0x6008ee00
Read 127 sdcard blocks with DMA! memaddr=0x6009ec00
Read 127 sdcard blocks with DMA! memaddr=0x600aea00
Read 127 sdcard blocks with DMA! memaddr=0x600be800
Read 127 sdcard blocks with DMA! memaddr=0x600ce600
Read 127 sdcard blocks with DMA! memaddr=0x600de400
Read 127 sdcard blocks with DMA! memaddr=0x600ee200
Read 16 sdcard blocks with DMA! memaddr=0x600fe000
Erasing 127 sdcard blocks. state=7
Erase complete! state=4
Sector 627 is empty.
Sector 628 is not empty. pos=0
Writing 127 sdcard blocks with DMA. state=7
Write complete! state=4

实现FATFS读写接口

很遗憾,STM32CubeMX没有给出DMA模式下fatfs的diskio.c的实现,SDIO_CK频率很高时又必须用DMA模式,我们只能自己来实现了。
SDIO的DMA只支持32位传输模式,所以内存地址必须按4字节对齐,而disk_read和disk_write函数传入的buff地址是有可能没有对齐的。
为了解决这个问题,我们必须创建一个uint32型的512字节全局数组:static uint32_t sd_buffer[128];
在Keil中,uint32_t数组变量的首地址一定是四字节对齐的,所以sd_buffer的地址一定能被4整除。
读数据和写数据都要一块一块地写。
读数据时,先通过DMA读到sd_buffer中,然后再memcpy到buff中。
写数据时,先把buff中的数据memcpy到sd_buffer中,再通过DMA写入SD卡。
请看代码:(fatfs版本为ff13c)

/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs     (C)ChaN, 2016        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stm32f1xx.h>
#include <string.h>
#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */

/* Definitions of physical drive number for each drive */
#define DEV_SDCARD 0

DMA_HandleTypeDef hdma24;
SD_HandleTypeDef hsd;
static uint32_t sd_buffer[128];
static DSTATUS sd_status = STA_NOINIT;
static HAL_SD_CardInfoTypeDef sd_info;

static int sd_init(void)
{
  GPIO_InitTypeDef gpio;
  HAL_StatusTypeDef status;
  
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_SDIO_CLK_ENABLE();
  
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOC, &gpio);
  
  gpio.Pin = GPIO_PIN_2;
  HAL_GPIO_Init(GPIOD, &gpio);
  
  hsd.Instance = SDIO;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.ClockDiv = 1;
  status = HAL_SD_Init(&hsd);
  
  hdma24.Instance = DMA2_Channel4;
  hdma24.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma24.Init.MemInc = DMA_MINC_ENABLE;
  hdma24.Init.Mode = DMA_NORMAL;
  hdma24.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma24.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma24.Init.Priority = DMA_PRIORITY_VERY_HIGH;
  HAL_DMA_Init(&hdma24);
  __HAL_LINKDMA(&hsd, hdmarx, hdma24);
  __HAL_LINKDMA(&hsd, hdmatx, hdma24);
  HAL_NVIC_EnableIRQ(DMA2_Channel4_5_IRQn);
  HAL_NVIC_EnableIRQ(SDIO_IRQn);
  
  if (status == HAL_OK)
    printf("SD init OK! frequency=%lgMHz\n", (double)SystemCoreClock / (2 + hsd.Init.ClockDiv) / 1000000);
  else
  {
    printf("SD init failed! status=%d\n", status);
    return -1;
  }
  
  status = HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B);
  if (status != HAL_OK)
  {
    printf("Failed to configure wide bus! status=%d\n", status);
    return -1;
  }
  
  status = HAL_SD_GetCardInfo(&hsd, &sd_info);
  if (status == HAL_OK)
  {
    if (sd_info.CardType == CARD_SDSC)
      printf("SDCard type: SDSC\n");
    else if (sd_info.CardType == CARD_SDHC_SDXC)
      printf("SDCard type: SDHC/SDXC\n");
    if (sd_info.CardVersion == CARD_V1_X)
      printf("SDCard version: V1\n");
    else if (sd_info.CardVersion == CARD_V2_X)
      printf("SDCard version: V2\n");
    printf("SDCard block number: %u\n", sd_info.BlockNbr);
    printf("SDCard block size: %u\n", sd_info.BlockSize);
    printf("SDCard capacity: %lgMB\n", (double)sd_info.BlockNbr * sd_info.BlockSize / 1048576);
  }
  
  return 0;
}

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
  switch (pdrv)
  {
    case DEV_SDCARD:
      return sd_status;
  }
  return STA_NOINIT;
}



/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
  int ret;
  
  switch (pdrv)
  {
    case DEV_SDCARD:
      // 初始化SD卡
      if (sd_status != 0)
      {
        ret = sd_init();
        if (ret == 0)
          sd_status = 0; // 插了SD卡
        else
          sd_status = STA_NODISK; // 没有插SD卡
      }
      return 0;
  }
  return STA_NOINIT;
}



/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	DWORD sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
  HAL_StatusTypeDef status;
  
  switch (pdrv)
  {
    case DEV_SDCARD:
      while (count != 0) // 一个扇区一个扇区地读
      {
        status = HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t *)sd_buffer, sector, 1); // 通过DMA方式读取SD卡, 暂存入4字节对齐的数组中
        if (status != HAL_OK)
        {
          printf("Failed to read sector %d! status=%u\n", sector, status);
          return RES_ERROR;
        }
        while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待读取完毕
        memcpy(buff, sd_buffer, sd_info.LogBlockSize); // 将读出的数据复制到最终的buff数组中
        
        // 跳转到下一个扇区
        buff += sd_info.LogBlockSize;
        sector++;
        count--;
      }
      return RES_OK;
  }

  return RES_PARERR;
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	DWORD sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
  HAL_StatusTypeDef status;
  
  printf("W%d,%d\n", sector, count);
  switch (pdrv)
  {
    case DEV_SDCARD:
      // 可以不用擦除, 直接写数据
      while (count != 0) // 一个扇区一个扇区地写
      {
        memcpy(sd_buffer, buff, sd_info.LogBlockSize); // 先将要写入的数据复制到4字节对齐的数组中
        status = HAL_SD_WriteBlocks_DMA(&hsd, (uint8_t *)sd_buffer, sector, 1); // 通过DMA方式写入SD卡
        if (status != HAL_OK)
        {
          printf("Failed to write sector %d! status=%u\n", sector, status);
          return RES_ERROR;
        }
        while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待写入完毕
        
        // 跳转到下一个扇区
        buff += sd_info.LogBlockSize;
        sector++;
        count--;
      }
      return RES_OK;
  }

  return RES_PARERR;
}

#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
  DWORD *p = buff;
  
  switch (pdrv)
  {
    case DEV_SDCARD:
      if (sd_status != 0)
        return RES_ERROR;
      switch (cmd)
      {
        case CTRL_SYNC:
          return RES_OK;
        case GET_SECTOR_COUNT:
          *p = sd_info.LogBlockNbr; // 扇区数 (假定每个扇区都是512字节)
          return RES_OK;
        case GET_BLOCK_SIZE:
        case GET_SECTOR_SIZE:
          *p = sd_info.LogBlockSize; // 扇区大小
          return RES_OK;
      }
      break;
  }

  return RES_PARERR;
}

void DMA2_Channel4_5_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma24);
}

void SDIO_IRQHandler(void)
{
  HAL_SD_IRQHandler(&hsd);
}

void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
  __HAL_DMA_DISABLE(hsd->hdmarx);
}

void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
  __HAL_DMA_DISABLE(hsd->hdmatx);
}

版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZLK1214/article/details/121388735

生成海报
点赞 0

巨大八爪鱼

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

RT-Thread Studio联合STM32CubeMX进行开发

RT-Thread Studio联合STM32CubeMX进行开发 一、准备内容 1.1硬件平台 使用正点原子STM32F4探索者 使用到板载LED灯,原理图如下: 1.2软件环境 STM32CubeMX软件