STM32Cubemx配置SD卡、FATFS和USB

 cubumx版本:6.12.1

   芯片:STM32F407VET6

    为了使我们的的单片机能够存储文件,我们需要一个或多个存储设备以及实现该设备的相关操作驱动如读、写,这里我们通过cubemx能很方便的配置SD卡这种存储设备的驱动;

    而为了方便对存储设备中大量文件的操作管理,我们引入了FATFS文件系统这个概念,而在cubemx中我们也能够很方便的进行配置;

   最后,我们的单片机通过FATFS和存储设备驱动实现了对文件的读、写等操作,但是如何把存储设备中的文件传输到像电脑这类外部设备中呢?或者如何把电脑这类外部设备中的文件传入到存储设备中呢?实现方式很多,例如串口,但是说实话很不方便。这个时候有一个通讯方式能够很简洁的实现单片机和电脑这类外部设备的文件传输,那就是USB。我们通过USB就能让电脑像操作U盘一样对单片机上的SD卡进行操作,从而读出单片机写入到SD卡中的文件,同时也能向SD卡中写入文件,然后单片机通过自身的FATFS去读取。

    那么SD卡驱动、FATFS和USB该如何进行配置呢?我参考了如下几篇文章,以自己的角度进行一下记录:

STM32CubeMX教程28 SDIO - 使用FatFs文件系统读写SD卡 - OSnotes - 博客园

STM32CubeMX学习笔记(26)——SDIO接口使用(读写SD卡)_cubemx sdio-优快云博客

STM32CubeMX学习笔记(49ton)——USB接口使用(MSC基于SD卡模拟U盘)_stm32u盘存储-优快云博客

   这里说一下需要注意的点,就是如果只在cubemx中配置SD卡SDIO驱动,那么SDIO的初始化函数void MX_SDIO_SD_Init(void)中会直接初始化SDIO,而如果你配置了SDIO也配置了FATFS,那么SDIO的初始化函数void MX_SDIO_SD_Init(void)中只会设置SDIO的一些参数而不会对SDIO进行初始化,如下:

Sdio.c:

//只在Cubemx中配置SDIO,没配置FATFS
void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_4B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 7;
  if (HAL_SD_Init(&hsd) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SDIO_Init 2 */
   hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  /* USER CODE END SDIO_Init 2 */

}

//在Cubemx中即配置了SDIO,也配置了FATFS
void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_4B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 7;
  /* USER CODE BEGIN SDIO_Init 2 */
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  /* USER CODE END SDIO_Init 2 */

}

    原因是因为有了FATFS后,SD卡的SDIO驱动就在sd_diskio.c转化封装成为了FATFS的设备驱动,其中有一个关于SD_Driver的绑定代码:

ff_gen_drv.h:

/**
  * @brief  Disk IO Driver structure definition
  */
typedef struct
{
  DSTATUS (*disk_initialize) (BYTE);                     /*!< Initialize Disk Drive                     */
  DSTATUS (*disk_status)     (BYTE);                     /*!< Get Disk Status                           */
  DRESULT (*disk_read)       (BYTE, BYTE*, DWORD, UINT);       /*!< Read Sector(s)                            */
#if _USE_WRITE == 1
  DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT (*disk_ioctl)      (BYTE, BYTE, void*);              /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */

}Diskio_drvTypeDef;


sd_diskio.c:
const Diskio_drvTypeDef  SD_Driver =
{
  SD_initialize,
  SD_status,
  SD_read,
#if  _USE_WRITE == 1
  SD_write,
#endif /* _USE_WRITE == 1 */

#if  _USE_IOCTL == 1
  SD_ioctl,
#endif /* _USE_IOCTL == 1 */
};


/**
  * @brief  Initializes a Drive
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_initialize(BYTE lun)
{
Stat = STA_NOINIT;

  /*
   * check that the kernel has been started before continuing
   * as the osMessage API will fail otherwise
   */
#if (osCMSIS <= 0x20000U)
  if(osKernelRunning())
#else
  if(osKernelGetState() == osKernelRunning)
#endif
  {
#if !defined(DISABLE_SD_INIT)

    if(BSP_SD_Init() == MSD_OK)
    {
      Stat = SD_CheckStatus(lun);
    }

#else
    Stat = SD_CheckStatus(lun);
#endif

    /*
    * if the SD is correctly initialized, create the operation queue
    * if not already created
    */

    if (Stat != STA_NOINIT)
    {
      if (SDQueueID == NULL)
      {
 #if (osCMSIS <= 0x20000U)
      osMessageQDef(SD_Queue, QUEUE_SIZE, uint16_t);
      SDQueueID = osMessageCreate (osMessageQ(SD_Queue), NULL);
#else
      SDQueueID = osMessageQueueNew(QUEUE_SIZE, 2, NULL);
#endif
      }

      if (SDQueueID == NULL)
      {
        Stat |= STA_NOINIT;
      }
    }
  }

  return Stat;
}

bsp_driver_sd.c:
/* USER CODE BEGIN BeforeInitSection */
/* can be used to modify / undefine following code or add code */
/* USER CODE END BeforeInitSection */
/**
  * @brief  Initializes the SD card device.
  * @retval SD status
  */
__weak uint8_t BSP_SD_Init(void)
{
  uint8_t sd_state = MSD_OK;
  /* Check if the SD card is plugged in the slot */
  if (BSP_SD_IsDetected() != SD_PRESENT)
  {
    return MSD_ERROR;
  }
  /* HAL SD initialization */
  sd_state = HAL_SD_Init(&hsd);
  /* Configure SD Bus width (4 bits mode selected) */
  if (sd_state == MSD_OK)
  {
    /* Enable wide operation */
    if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }
  }

  return sd_state;
}

     而关于SDIO的初始化操作则被放在了SD_initialize函数中,我们通过FATFS的设备驱动去调用这个初始化函数,手动调用方法如下:

SD_Driver.disk_initialize(0);  //这里函数输入参数无效,0,1,2,3...都可以

    不过需要注意的是,手动调用该函数时一定要开启Freertos,并且在freertos初始化完成后调用该函数,一般手动调用该函数我们放在任务中调用。原因是SD_initialize函数中个判断RTOS是否运行的判断 if(osKernelGetState() == osKernelRunning),当操作系统运行后才执行后面的SDIO初始化函数,并创建一个和SD卡通讯相关的消息队列。这也从侧面说明了我们在Cubemx中配置了FAFTS和SD卡之后,一定也要配置一下FreeRTOS,这里也要注意Freertos除去任务用的堆栈外,空闲堆栈要足够大,差不多100字节以上,保证 SDQueueID = osMessageQueueNew(QUEUE_SIZE, 2, NULL);能够创建成功。

   该初始化函数在对SD卡设备进行FATFS挂载f_mount(fs, path, opt)时也会被自动调用,即挂载SD卡设备时就会自动对SD卡进行初始化,并不需要我们手动进行初始化。

   在配置SD卡驱动时有两个一定需要注意的点:

1、第一个就是很多人说的,不管是只配置了SDIO还是SDIO和FATFS都配置了,都要先将SDIO设置成一线制,不然初始化SDIO设备时就会出错,一般会在BSP_SD_Init(void)函数中的下面这个SDIO四线制使能函数中出错;

bsp_driver_sd.c:
__weak uint8_t BSP_SD_Init(void)
{
......
    if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }
......
}

   我在void MX_SDIO_SD_Init(void)函数的末尾加上了将SDIO设置成一线制的代码,成功初始化SDIO。

Sdio.c:
void MX_SDIO_SD_Init(void)
{

 ......
  /* USER CODE BEGIN SDIO_Init 2 */
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  /* USER CODE END SDIO_Init 2 */

}

2.SDIO中断和SDIO的DMA中断都必须在Freertos的管理下,加入freertos管理的中断优先级为5-15,那么SDIO中断和SDIO的DMA中断就应该在5-15之间,不然通讯时会等待超时并失败,有时也会卡死,参见下面这篇文章;

笔记---FreeRtos系统中外设中断优先级设置的要点 - STM32团队 ST意法半导体中文论坛

SD卡的通讯测试如下:通讯成功打印sd write result: 0和sd read result: 0以及写入的数据;

  
void SDCard_Test(void)
{
	BYTE sd_buf[1024];
	SD_Driver.disk_initialize(0);
  printf_sdcard_info();
	
	DSTATUS	sd_datus = SD_Driver.disk_write(0,(BYTE*)"President will take part in the Global Health Summit on Friday via video link from Beijing,\
 joining leaders of the Group of 20 members and heads of international and regional organizations in efforts to boost global cooperation in\
 the fight against the COVID-19 pandemic.Xi will deliver a speech at the event, which will take place at Villa Pamphilj in Rome. The summit\
 was organized by Italy,the holder of the G20 presidency, in partnership with the European Commission.Speaking at a news conference on Thursday,\
 Foreign Ministry spokesman Zhao Lijian said China expects the summit to send a strong message to uphold multilateralism and enhance solidarity\
 and cooperation in the global fight against the pandemic.As COVID-19 is making a resurgence and spreading globally, Zhao said international\
 cooperation in combating the virus is at a critical juncture.",20,2);
 
	printf("sd write result:%d\r\n",sd_datus);
	sd_datus = SD_Driver.disk_read(0,sd_buf,20,2);
	printf("sd read result:%d\r\n",sd_datus);
	printf("sd read content:\r\n%s\r\n",sd_buf);
}



void printf_sdcard_info(void)
{
	uint64_t CardCap;      	//SD卡容量
	HAL_SD_CardCIDTypeDef SDCard_CID; 
	HAL_SD_CardInfoTypeDef  SDCardInfo; 

	HAL_SD_GetCardCID(&hsd,&SDCard_CID);	//获取CID
	HAL_SD_GetCardInfo(&hsd,&SDCardInfo);                    //获取SD卡信息
	CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize);	//计算SD卡容量
	switch(SDCardInfo.CardType)
	{
		case CARD_SDSC:
		{
			if(SDCardInfo.CardVersion == CARD_V1_X)
				printf("Card Type:SDSC V1\r\n");
			else if(SDCardInfo.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;
	}	
		
   printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID);				//制造商ID	
 	printf("CardVersion:         %d \r\n",(uint32_t)(SDCardInfo.CardVersion));		//卡版本号
	printf("Class:               %d \r\n",(uint32_t)(SDCardInfo.Class));		    //
 	printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd);					//卡相对地址
	printf("Card BlockNbr:       %d \r\n",SDCardInfo.BlockNbr);						//块数量
 	printf("Card BlockSize:      %d \r\n",SDCardInfo.BlockSize);					//块大小
	printf("LogBlockNbr:         %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr));		//逻辑块数量
	printf("LogBlockSize:        %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize));		//逻辑块大小
	printf("Card Capacity:       %d MB\r\n",(uint32_t)(CardCap>>20));				//卡容量
}

    这里我们的SDIO驱动就已经配置和测试好了,接下来开始测试FATFS文件系统,测试函数如下:注意如果你的SD卡是exFAT文件系统的话,需要在cumemx中将宏定义_FS_EXFAT配置为1、宏定义_USE_LFN配置为 >= 1;


void FATFS_Test(void)
{
   Mount_FatFs(&User_FatFs, User_SDPath, 1 ,User_FatType ,workBuffer);  //挂载时会自动调用相关存储设备初始化函数
   FatFs_GetDiskInfo(User_SDPath);
	 /*----------------------- 文件系统测试:写测试 -----------------------------*/
  FatFs_WriteTXTFile(User_SDPath,"test.txt",2025,1,14); 
   /*------------------- 文件系统测试:读测试 ------------------------------------*/
  FatFs_ReadTXTFile(User_SDPath,"test.txt"); 
	/* 不再读写,关闭文件 */
	f_close(&SDFile);
  /*------------------- 文件系统测试:删除测试 ------------------------------------*/
	FatFs_DeleteFile(User_SDPath,"test.txt");
}


File_Operate.c:
#include "File_Operate.h"
 
//定义用于格式化的工作区缓存

BYTE FATFS_Init_workBuffer[FATFS_Init_workBuffer_SIZE];

 
/*挂载FatFs文件系统*/

/**
 *挂载文件系统
 *@param fs    Pointer to the file system object (NULL:unmount)
 *@param path,	 Logical drive number to be mounted/unmounted 
 *@param opt   Mode option 0:Do not mount (delayed mount), 1:Mount immediately
 *@param Format_opt   Format option 
 *@param work			 Pointer to working buffer ,为NULL就选择malloc动态申请内存
 *@return 
 */
FRESULT Mount_FatFs(FATFS* fs,const TCHAR* path,BYTE opt,BYTE Format_opt, void* workBuffer 
                 ,uint32_t workBuffer_Size)
{
	//挂载文件系统
	FRESULT retFATFS = f_mount(fs, path, opt);
	//发生错误
	if(retFATFS != FR_OK)
	{
		//没有文件系统,需要格式化
		if(retFATFS == FR_NO_FILESYSTEM)
		{
			printf("The Device does not yet have a file system and is about to be formatted...\r\n");
			//创建文件系统
			if(workBuffer == NULL)  //动态申请内存
			{
                workBuffer_Size = _MAX_SS;
				workBuffer = malloc(_MAX_SS);
				if(workBuffer == NULL)
				{
					printf("malloc workBuffer failed\r\n");
					return retFATFS;
				}
               retFATFS = f_mkfs(path, Format_opt, 0, workBuffer, workBuffer_Size);
				free(workBuffer);
			}
			else
			   retFATFS = f_mkfs(path, Format_opt, 0, workBuffer, workBuffer_Size);

			//格式化失败
			if(retFATFS != FR_OK)
			{
				printf("The format failed , Error code = %d\r\n", retFATFS);
			}
			else
			{
				printf("The Device successfully formatted the file system\r\n");
				 /* 格式化后,先取消挂载 */
        retFATFS = f_mount(NULL,path,1);
				//有文件系统后重新挂载
				retFATFS = f_mount(fs, path, 1);
				//挂载失败
				if(retFATFS != FR_OK)
				{
					printf("mount failed , Error code = %d\r\n", retFATFS);
				}
				//挂载成功
				else
				{
					 printf("file system mount sucess!!! \r\n");
				}
			}
		}
		//不是没有文件系统,而是发生其他错误
		else
		{
			printf("other failed , Error code = %d\r\n", retFATFS);
		}
	}
	//有文件系统直接挂在成功
	else
	{
		printf("file system mount sucess!!! \r\n");
	}
	return retFATFS;
}
 

/**
 *通过要操作的逻辑驱动器的路径path,获取对应文件系统对象设备的磁盘信息
 *@param path,	Path name of the logical drive number 
 *@return 
 */
/*获取磁盘信息并通过串口打印*/
void FatFs_GetDiskInfo(const TCHAR* path)
{
    FATFS *fs;
    // 定义剩余簇个数变量
    DWORD fre_clust; 
    // 获取剩余簇个数
    FRESULT res = f_getfree(path, &fre_clust, &fs); 
    // 获取失败
    if (res != FR_OK)
    {
        printf("f_getfree() error\r\n");
        return;
    }
    printf("\r\n*** FAT disk info ***\r\n");
        
    // 总的扇区个数
    DWORD tot_sect = (fs->n_fatent - 2) * fs->csize;  
        
    // 剩余的扇区个数 = 剩余簇个数 * 每个簇的扇区个数
    DWORD fre_sect = fre_clust * fs->csize;    

    // 使用 uint64_t 进行计算以避免溢出
		#if _MAX_SS == _MIN_SS
		   uint64_t totalSpaceBytes = (uint64_t)tot_sect * _MIN_SS;
       uint64_t freeSpaceBytes = (uint64_t)fre_sect * _MIN_SS;
		#else
		   uint64_t totalSpaceBytes = (uint64_t)tot_sect * fs->ssize;
       uint64_t freeSpaceBytes = (uint64_t)fre_sect * fs->ssize;
		#endif
   

    double totalSpaceKB = (double)totalSpaceBytes / 1024;
    double freeSpaceKB = (double)freeSpaceBytes / 1024;

    // FAT类型
    printf("FAT type = %d\r\n", fs->fs_type);
    printf("[1=FAT12,2=FAT16,3=FAT32,4=exFAT]\r\n");
        
    // 扇区大小,单位字节
		#if _MAX_SS == _MIN_SS
		  WORD  Sector_size = _MIN_SS;
      printf("Sector size(bytes) = %d\r\n", Sector_size);
		#else
		  printf("Sector size(bytes) = %d\r\n", fs->ssize);
		#endif
    printf("Cluster size(sectors) = %d\r\n", fs->csize);
    printf("Total cluster count = %ld\r\n", fs->n_fatent - 2);
    printf("Total sector count = %ld\r\n", tot_sect);
        
    // 总空间
    if (totalSpaceKB >= 1024 * 1024) // 如果总空间大于等于 1GB,用 GB 表示
    {
        printf("Total space(GB) = %.1f\r\n", totalSpaceKB / (1024 * 1024));
    }
    else if (totalSpaceKB >= 1024) // 如果总空间大于等于 1MB,用 MB 表示
    {
        printf("Total space(MB) = %.1f\r\n", totalSpaceKB / 1024);
    }
    else // 否则用 KB 表示
    {
        printf("Total space(KB) = %.1f\r\n", totalSpaceKB);
    }
        
    // 空闲簇数量
    printf("Free cluster count = %ld\r\n", fre_clust);
    // 空闲扇区数量
    printf("Free sector count = %ld\r\n", fre_sect);
        
    // 空闲空间
    if (freeSpaceKB >= 1024 * 1024) // 如果空闲空间大于等于 1GB,用 GB 表示
    {
        printf("Free space(GB) = %.1f\r\n", freeSpaceKB / (1024 * 1024));
    }
    else if (freeSpaceKB >= 1024) // 如果空闲空间大于等于 1MB,用 MB 表示
    {
        printf("Free space(MB) = %.1f\r\n", freeSpaceKB / 1024);
    }
    else // 否则用 KB 表示
    {
        printf("Free space(KB) = %.1f\r\n", freeSpaceKB);
    }

    printf("Get FAT disk info OK\r\n");
}

 /**
 * 向特定逻辑驱动器创建、写入文本文件
 *@param path  逻辑驱动器号
 *@param filename  定义的文件名
 *@return 
 */
void FatFs_WriteTXTFile(const TCHAR* path , TCHAR *filename,FIL	file,uint16_t year, uint8_t month, uint8_t day)
{
	TCHAR fullPath[256];  // 用于存储组合后的完整路径
    if(sizeof(fullPath) <= sizeof(path) + sizeof(filename))
	{
		printf("Filename is too long! WriteTXTFile failed ");
		return;
	}
        
    snprintf(fullPath, sizeof(fullPath), "%s%s", path, filename); // 拼接路径和文件名
	printf("\r\n*** Creating TXT file: %s ***\r\n", fullPath);
	
	FRESULT res = f_open(&file, fullPath, FA_CREATE_ALWAYS | FA_WRITE);
	//打开/创建文件成功
	if(res == FR_OK)
	{
		//字符串必须有换行符"\n"
		TCHAR str[]="Line1: Hello, FatFs***\n";  
		//不会写入结束符"\0"
		f_puts(str, &file); 
		
		printf("Write file OK: %s\r\n", fullPath);
	}
	else
	{
		printf("Open file error , error code: %d\r\n", res);
	}
	//使用完毕关闭文件
	f_close(&file);
}
 

 /**
 * 向特定逻辑驱动器读取一个文本文件的内容
 *@param path  逻辑驱动器号
 *@param filename  定义的文件名
 *@return 
 */
void FatFs_ReadTXTFile(const TCHAR* path , TCHAR *filename,FIL	file)
{
	TCHAR fullPath[256];  // 用于存储组合后的完整路径
    if(sizeof(fullPath) <= sizeof(path) + sizeof(filename))
	{
		printf("Filename is too long! WriteTXTFile failed ");
		return;
	}
	snprintf(fullPath, sizeof(fullPath), "%s%s", path, filename); // 拼接路径和文件名

	printf("\r\n*** Reading TXT file: %s ***\r\n", fullPath);
 

	//以只读方式打开文件
	FRESULT res = f_open(&file, fullPath, FA_READ);  
	//打开成功
	if(res == FR_OK)
	{
		//读取缓存
		TCHAR str[100];
		//没有读到文件内容末尾
		while(!f_eof(&file))
		{
			//读取1个字符串,自动加上结束符”\0”
			f_gets(str,100, &file);	
			printf("%s", str);
		}
		printf("\r\n");
	}
	//如果没有该文件
	else if(res == FR_NO_FILE)
		printf("File does not exist\r\n");
	//打开失败
	else
		printf("f_open() error , error code: %d\r\n", res);
	//关闭文件
	f_close(&file);
}
 
/*扫描和显示指定目录下的文件和目录*/
void FatFs_ScanDir(const TCHAR* PathName)
{
	DIR dir;					//目录对象
	FILINFO fno;				//文件信息
	//打开目录
	FRESULT res = f_opendir(&dir, PathName);
	//打开失败
	if(res != FR_OK)
	{
		//关闭目录,直接退出函数
		f_closedir(&dir);
		printf("\r\nf_opendir() error , error code: %d\r\n", res);
		return;
	}
	
	printf("\r\n*** All entries in dir: %s ***\r\n", PathName);
	//顺序读取目录中的文件
	while(1)
	{
		//读取目录下的一个项
		res = f_readdir(&dir, &fno);    
		//文件名为空表示没有多的项可读了
		if(res != FR_OK || fno.fname[0] == 0)
			break;  
		//如果是一个目录
		if(fno.fattrib & AM_DIR)  		
		{
			printf("DIR: %s\r\n", fno.fname);
		}
		//如果是一个文件
		else  		
		{
			printf("FILE: %s\r\n",fno.fname);
		}
	}
	//扫描完毕,关闭目录
	printf("Scan dir OK\r\n");
	f_closedir(&dir);
}
 

 /**
 * 向特定逻辑驱动器获取一个文件的文件信息
 *@param path  逻辑驱动器号
 *@param filename  定义的文件名
 *@return 
 */
void FatFs_GetFileInfo(const TCHAR* path , TCHAR *filename)
{
    TCHAR fullPath[256];  // 用于存储组合后的完整路径
    if(sizeof(fullPath) <= sizeof(path) + sizeof(filename))
	{
		printf("Filename is too long! WriteTXTFile failed ");
		return;
	}
	snprintf(fullPath, sizeof(fullPath), "%s%s", path, filename); // 拼接路径和文件名

	printf("\r\n*** File info of: %s ***\r\n", fullPath);
 
	FILINFO fno;
	//检查文件或子目录是否存在
	FRESULT fr = f_stat(fullPath, &fno);
	//如果存在从fno中读取文件信息
	if(fr == FR_OK)
	{
		printf("File size(bytes) = %lld\r\n", fno.fsize);
		printf("File attribute = 0x%x\r\n", fno.fattrib);
		printf("File Name = %s\r\n", fno.fname);
		//输出创建/修改文件时的时间戳
		FatFs_PrintfFileDate(fno.fdate, fno.ftime);
	}
	//如果没有该文件
	else if (fr == FR_NO_FILE)
		printf("File does not exist\r\n");
	//发生其他错误
	else
		printf("f_stat() error , error code: %d\r\n", fr);
}
 

 /**
 * 向特定逻辑驱动器删除文件
 *@param path  逻辑驱动器号
 *@param filename  定义的文件名
 *@return 
 */
void FatFs_DeleteFile(const TCHAR* path , TCHAR *filename,FIL	file)
{
	TCHAR fullPath[256];  // 用于存储组合后的完整路径
    if(sizeof(fullPath) <= sizeof(path) + sizeof(filename))
	{
		printf("Filename is too long! WriteTXTFile failed ");
		return;
	}
	snprintf(fullPath, sizeof(fullPath), "%s%s", path, filename); // 拼接路径和文件名

	printf("\r\n*** Delete File: %s ***\r\n", fullPath);

	//打开文件
	FRESULT res = f_open(&file, fullPath, FA_OPEN_EXISTING);  
	if(res == FR_OK)
	{
		//关闭文件
		f_close(&file);
		printf("open successfully!\r\n");
	}
	//删除文件
	res = f_unlink(fullPath);
	//删除成功
	if(res == FR_OK)
	{
		printf("The file was deleted successfully!\r\n");
	}
	//删除失败
	else
	{
		printf("File deletion failed, error code:%d\r\n", res);
	}
}
 
/*打印输出文件日期*/
void FatFs_PrintfFileDate(WORD date, WORD time)
{
	printf("File data = %d/%d/%d\r\n", ((date>>9)&0x7F)+1980, (date>>5)&0xF, date&0x1F);
	printf("File time = %d:%d:%d\r\n", (time>>11)&0x1F, (time>>5)&0x3F, time&0x1F);
}
 

File_Operate.h:
#ifndef FILE_OPERATE_H
#define FILE_OPERATE_H


#define FATFS_Init_workBuffer_SIZE  _MAX_SS
extern BYTE FATFS_Init_workBuffer[FATFS_Init_workBuffer_SIZE];
 
/*函数声明*/
FRESULT Mount_FatFs(FATFS* fs,const TCHAR* path,BYTE opt,BYTE Format_opt, void* workBuffer 
                 ,uint32_t workBuffer_Size);
void FatFs_GetDiskInfo(const TCHAR* path);
void FatFs_ScanDir(const TCHAR* PathName);
void FatFs_ReadTXTFile(const TCHAR* path , TCHAR *filename,FIL file);
void FatFs_WriteTXTFile(const TCHAR* path , TCHAR *filename,FIL	file,uint16_t year, uint8_t month, uint8_t day);
void FatFs_GetFileInfo(const TCHAR* path , TCHAR *filename);
void FatFs_DeleteFile(const TCHAR* path , TCHAR *filename,FIL	file);
void FatFs_PrintfFileDate(WORD date, WORD time);
 
#endif

        在SD卡的SDIO驱动和FATFS都验证完毕后,我们需要编写USB的驱动,主要就是修改usbd_storage_if.c中的函数,把SD卡的相关操作填入usbd_storage_if.c中对应的函数中,然后将单片机和电脑用USB线连接,电脑中识别出有U盘插入,且能读里面的文件并向里面写入文件即可;参见该文章:STM32CubeMX学习笔记(49)——USB接口使用(MSC基于SD卡模拟U盘)_stm32u盘存储-优快云博客

    在使用USB读写SD卡和FATFS读写SD卡时有一个需要注意的点:FATFS和USB不能同时读写SD卡(插入USB时也会对SD卡进行读取),否则容易出现读写错误。USB操作SD卡驱动如下:

usbd_storage_if.c:

/** @defgroup USBD_STORAGE_Exported_Variables
  * @brief Public variables.
  * @{
  */

extern USBD_HandleTypeDef hUsbDeviceFS;

/* USER CODE BEGIN EXPORTED_VARIABLES */
extern SD_HandleTypeDef hsd;
/* USER CODE END EXPORTED_VARIABLES */


/**
  * @brief  Returns the medium capacity.
  * @param  lun: Logical unit number.
  * @param  block_num: Number of total block number.
  * @param  block_size: Block size.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
    HAL_SD_CardInfoTypeDef info;
	if(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER)
	{
		HAL_SD_GetCardInfo(&hsd, &info);
		*block_num = info.LogBlockNbr;
		*block_size = info.LogBlockSize;
 
		return  USBD_OK;
	}
	return  USBD_FAIL;
  /* USER CODE END 3 */
}


/**
  * @brief  Reads data from the medium.
  * @param  lun: Logical unit number.
  * @param  buf: data buffer.
  * @param  blk_addr: Logical block address.
  * @param  blk_len: Blocks number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
  int8_t ret = USBD_FAIL;
    if(HAL_SD_ReadBlocks(&hsd, buf, blk_addr,  blk_len, HAL_MAX_DELAY) == HAL_OK)
    {
    	ret = USBD_OK;
 
        while(HAL_SD_GetState(&hsd) == HAL_SD_STATE_BUSY);
        while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
    }
 
    return ret;
  /* USER CODE END 6 */
}

/**
  * @brief  Writes data into the medium.
  * @param  lun: Logical unit number.
  * @param  buf: data buffer.
  * @param  blk_addr: Logical block address.
  * @param  blk_len: Blocks number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
 int8_t ret = USBD_FAIL;
    if(HAL_SD_WriteBlocks(&hsd, buf, blk_addr, blk_len, HAL_MAX_DELAY) == HAL_OK)
    {
    	ret = USBD_OK;
 
        while(HAL_SD_GetState(&hsd) == HAL_SD_STATE_BUSY);
        while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
    }
 
    return ret;
  /* USER CODE END 7 */
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值